Skip to content

Commit c8a8cf5

Browse files
author
Fabrice Bellard
committed
faster appending of elements in arrays
1 parent 79f3ae2 commit c8a8cf5

File tree

1 file changed

+69
-34
lines changed

1 file changed

+69
-34
lines changed

quickjs.c

Lines changed: 69 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,8 @@ typedef enum {
353353
struct JSGCObjectHeader {
354354
int ref_count; /* must come first, 32-bit */
355355
JSGCObjectTypeEnum gc_obj_type : 4;
356-
uint8_t mark : 4; /* used by the GC */
356+
uint8_t mark : 1; /* used by the GC */
357+
uint8_t dummy0: 3;
357358
uint8_t dummy1; /* not used by the GC */
358359
uint16_t dummy2; /* not used by the GC */
359360
struct list_head link;
@@ -446,7 +447,14 @@ struct JSContext {
446447

447448
uint16_t binary_object_count;
448449
int binary_object_size;
449-
450+
/* TRUE if the array prototype is "normal":
451+
- no small index properties which are get/set or non writable
452+
- its prototype is Object.prototype
453+
- Object.prototype has no small index properties which are get/set or non writable
454+
- the prototype of Object.prototype is null (always true as it is immutable)
455+
*/
456+
uint8_t std_array_prototype;
457+
450458
JSShape *array_shape; /* initial shape for Array objects */
451459

452460
JSValue *class_proto;
@@ -904,10 +912,6 @@ struct JSShape {
904912
/* true if the shape is inserted in the shape hash table. If not,
905913
JSShape.hash is not valid */
906914
uint8_t is_hashed;
907-
/* If true, the shape may have small array index properties 'n' with 0
908-
<= n <= 2^31-1. If false, the shape is guaranteed not to have
909-
small array index properties */
910-
uint8_t has_small_array_index;
911915
uint32_t hash; /* current hash value */
912916
uint32_t prop_hash_mask;
913917
int prop_size; /* allocated properties */
@@ -923,7 +927,8 @@ struct JSObject {
923927
JSGCObjectHeader header;
924928
struct {
925929
int __gc_ref_count; /* corresponds to header.ref_count */
926-
uint8_t __gc_mark; /* corresponds to header.mark/gc_obj_type */
930+
uint8_t __gc_mark : 7; /* corresponds to header.mark/gc_obj_type */
931+
uint8_t is_prototype : 1; /* object may be used as prototype */
927932

928933
uint8_t extensible : 1;
929934
uint8_t free_mark : 1; /* only used when freeing objects with cycles */
@@ -4765,7 +4770,6 @@ static no_inline JSShape *js_new_shape2(JSContext *ctx, JSObject *proto,
47654770
/* insert in the hash table */
47664771
sh->hash = shape_initial_hash(proto);
47674772
sh->is_hashed = TRUE;
4768-
sh->has_small_array_index = FALSE;
47694773
js_shape_hash_link(ctx->rt, sh);
47704774
return sh;
47714775
}
@@ -5010,7 +5014,6 @@ static int add_shape_property(JSContext *ctx, JSShape **psh,
50105014
pr = &prop[sh->prop_count++];
50115015
pr->atom = JS_DupAtom(ctx, atom);
50125016
pr->flags = prop_flags;
5013-
sh->has_small_array_index |= __JS_AtomIsTaggedInt(atom);
50145017
/* add in hash table */
50155018
hash_mask = sh->prop_hash_mask;
50165019
h = atom & hash_mask;
@@ -5125,6 +5128,7 @@ static JSValue JS_NewObjectFromShape(JSContext *ctx, JSShape *sh, JSClassID clas
51255128
if (unlikely(!p))
51265129
goto fail;
51275130
p->class_id = class_id;
5131+
p->is_prototype = 0;
51285132
p->extensible = TRUE;
51295133
p->free_mark = 0;
51305134
p->is_exotic = 0;
@@ -7401,6 +7405,14 @@ static int JS_SetPrototypeInternal(JSContext *ctx, JSValueConst obj,
74017405
if (sh->proto)
74027406
JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, sh->proto));
74037407
sh->proto = proto;
7408+
if (proto)
7409+
proto->is_prototype = TRUE;
7410+
if (p->is_prototype) {
7411+
/* track modification of Array.prototype */
7412+
if (unlikely(p == JS_VALUE_GET_OBJ(ctx->class_proto[JS_CLASS_ARRAY]))) {
7413+
ctx->std_array_prototype = FALSE;
7414+
}
7415+
}
74047416
return TRUE;
74057417
}
74067418

@@ -8587,6 +8599,14 @@ static JSProperty *add_property(JSContext *ctx,
85878599
{
85888600
JSShape *sh, *new_sh;
85898601

8602+
if (unlikely(p->is_prototype)) {
8603+
/* track addition of small integer properties to Array.prototype and Object.prototype */
8604+
if (unlikely((p == JS_VALUE_GET_OBJ(ctx->class_proto[JS_CLASS_ARRAY]) ||
8605+
p == JS_VALUE_GET_OBJ(ctx->class_proto[JS_CLASS_OBJECT])) &&
8606+
__JS_AtomIsTaggedInt(prop))) {
8607+
ctx->std_array_prototype = FALSE;
8608+
}
8609+
}
85908610
sh = p->shape;
85918611
if (sh->is_hashed) {
85928612
/* try to find an existing shape */
@@ -8656,6 +8676,11 @@ static no_inline __exception int convert_fast_array_to_array(JSContext *ctx,
86568676
p->u.array.u.values = NULL; /* fail safe */
86578677
p->u.array.u1.size = 0;
86588678
p->fast_array = 0;
8679+
8680+
/* track modification of Array.prototype */
8681+
if (unlikely(p == JS_VALUE_GET_OBJ(ctx->class_proto[JS_CLASS_ARRAY]))) {
8682+
ctx->std_array_prototype = FALSE;
8683+
}
86598684
return 0;
86608685
}
86618686

@@ -8880,8 +8905,8 @@ static int expand_fast_array(JSContext *ctx, JSObject *p, uint32_t new_len)
88808905

88818906
/* Preconditions: 'p' must be of class JS_CLASS_ARRAY, p->fast_array =
88828907
TRUE and p->extensible = TRUE */
8883-
static int add_fast_array_element(JSContext *ctx, JSObject *p,
8884-
JSValue val, int flags)
8908+
static inline int add_fast_array_element(JSContext *ctx, JSObject *p,
8909+
JSValue val, int flags)
88858910
{
88868911
uint32_t new_len, array_len;
88878912
/* extend the array by one */
@@ -9283,27 +9308,13 @@ static int JS_SetPropertyValue(JSContext *ctx, JSValueConst this_obj,
92839308
switch(p->class_id) {
92849309
case JS_CLASS_ARRAY:
92859310
if (unlikely(idx >= (uint32_t)p->u.array.count)) {
9286-
JSObject *p1;
9287-
JSShape *sh1;
9288-
92899311
/* fast path to add an element to the array */
9290-
if (idx != (uint32_t)p->u.array.count ||
9291-
!p->fast_array || !p->extensible)
9312+
if (unlikely(idx != (uint32_t)p->u.array.count ||
9313+
!p->fast_array ||
9314+
!p->extensible ||
9315+
p->shape->proto != JS_VALUE_GET_OBJ(ctx->class_proto[JS_CLASS_ARRAY]) ||
9316+
!ctx->std_array_prototype)) {
92929317
goto slow_path;
9293-
/* check if prototype chain has a numeric property */
9294-
p1 = p->shape->proto;
9295-
while (p1 != NULL) {
9296-
sh1 = p1->shape;
9297-
if (p1->class_id == JS_CLASS_ARRAY) {
9298-
if (unlikely(!p1->fast_array))
9299-
goto slow_path;
9300-
} else if (p1->class_id == JS_CLASS_OBJECT) {
9301-
if (unlikely(sh1->has_small_array_index))
9302-
goto slow_path;
9303-
} else {
9304-
goto slow_path;
9305-
}
9306-
p1 = sh1->proto;
93079318
}
93089319
/* add element */
93099320
return add_fast_array_element(ctx, p, val, flags);
@@ -18689,9 +18700,32 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj,
1868918700
idx = JS_VALUE_GET_INT(sp[-2]);
1869018701
if (unlikely(p->class_id != JS_CLASS_ARRAY))
1869118702
goto put_array_el_slow_path;
18692-
if (unlikely(idx >= (uint32_t)p->u.array.count))
18693-
goto put_array_el_slow_path;
18694-
set_value(ctx, &p->u.array.u.values[idx], sp[-1]);
18703+
if (unlikely(idx >= (uint32_t)p->u.array.count)) {
18704+
uint32_t new_len, array_len;
18705+
if (unlikely(idx != (uint32_t)p->u.array.count ||
18706+
!p->fast_array ||
18707+
!p->extensible ||
18708+
p->shape->proto != JS_VALUE_GET_OBJ(ctx->class_proto[JS_CLASS_ARRAY]) ||
18709+
!ctx->std_array_prototype)) {
18710+
goto put_array_el_slow_path;
18711+
}
18712+
if (likely(JS_VALUE_GET_TAG(p->prop[0].u.value) != JS_TAG_INT))
18713+
goto put_array_el_slow_path;
18714+
/* cannot overflow otherwise the length would not be an integer */
18715+
new_len = idx + 1;
18716+
if (unlikely(new_len > p->u.array.u1.size))
18717+
goto put_array_el_slow_path;
18718+
array_len = JS_VALUE_GET_INT(p->prop[0].u.value);
18719+
if (new_len > array_len) {
18720+
if (unlikely(!(get_shape_prop(p->shape)->flags & JS_PROP_WRITABLE)))
18721+
goto put_array_el_slow_path;
18722+
p->prop[0].u.value = JS_NewInt32(ctx, new_len);
18723+
}
18724+
p->u.array.count = new_len;
18725+
p->u.array.u.values[idx] = sp[-1];
18726+
} else {
18727+
set_value(ctx, &p->u.array.u.values[idx], sp[-1]);
18728+
}
1869518729
JS_FreeValue(ctx, sp[-3]);
1869618730
sp -= 3;
1869718731
} else {
@@ -54343,7 +54377,8 @@ static void JS_AddIntrinsicBasicObjects(JSContext *ctx)
5434354377
JS_PROP_INITIAL_HASH_SIZE, 1);
5434454378
add_shape_property(ctx, &ctx->array_shape, NULL,
5434554379
JS_ATOM_length, JS_PROP_WRITABLE | JS_PROP_LENGTH);
54346-
54380+
ctx->std_array_prototype = TRUE;
54381+
5434754382
/* XXX: could test it on first context creation to ensure that no
5434854383
new atoms are created in JS_AddIntrinsicBasicObjects(). It is
5434954384
necessary to avoid useless renumbering of atoms after

0 commit comments

Comments
 (0)