]> git.kaiwu.me - njs.git/commitdiff
Parser: compact array literal elements
authorDmitry Volyntsev <xeioex@nginx.com>
Thu, 3 Sep 2026 00:39:41 +0000 (17:39 -0700)
committerDmitry Volyntsev <xeioexception@gmail.com>
Thu, 10 Sep 2026 03:11:04 +0000 (20:11 -0700)
The generic object-property lowering used six parser nodes and several
generator continuations for each array element.  Store array values and indexes
in compact records and emit a dedicated immediate-index initializer instead.

This also avoids interning numeric element indexes and generates elements in
source order with constant continuation depth.  Keep template backing arrays
flat because template concatenation requires contiguous storage.

src/njs_disassembler.c
src/njs_generator.c
src/njs_parser.c
src/njs_parser.h
src/njs_vmcode.c
src/njs_vmcode.h
src/test/njs_unit_test.c

index e7de5ecd7f4b9175d9c6069cdfdfc637993224b6..84834c16dc5e4b98833ef0f7cbf78d30cdb482eb 100644 (file)
@@ -202,6 +202,7 @@ njs_disassemble(u_char *start, u_char *end, njs_int_t count, njs_arr_t *lines)
     njs_vmcode_2addr_t           *code2;
     njs_vmcode_3addr_t           *code3;
     njs_vmcode_array_t           *array;
+    njs_vmcode_array_init_t      *array_init;
     njs_vmcode_catch_t           *catch;
     njs_vmcode_import_t          *import;
     njs_vmcode_finally_t         *finally;
@@ -242,6 +243,18 @@ njs_disassemble(u_char *start, u_char *end, njs_int_t count, njs_arr_t *lines)
             continue;
         }
 
+        if (operation == NJS_VMCODE_ARRAY_INIT) {
+            array_init = (njs_vmcode_array_init_t *) p;
+
+            njs_printf("%5uD | %05uz ARRAY INIT        %04Xz %04Xz %uD\n",
+                       line, p - start, (size_t) array_init->array,
+                       (size_t) array_init->value, array_init->index);
+
+            p += sizeof(njs_vmcode_array_init_t);
+
+            continue;
+        }
+
         if (operation == NJS_VMCODE_IF_TRUE_JUMP) {
             cond_jump = (njs_vmcode_cond_jump_t *) p;
 
index 37ecea0834625bd548a065ee3edb49d73004add8..54e3eca4a91d8e172ec9c96ec0c88e1a314b751c 100644 (file)
@@ -71,6 +71,12 @@ typedef struct {
 } njs_generator_stack_entry_t;
 
 
+typedef struct {
+    njs_parser_node_t           *array;
+    uint32_t                    item;
+} njs_generator_array_ctx_t;
+
+
 typedef struct {
     njs_generator_patch_t       *patch;
     njs_generator_patch_t       **last;
@@ -401,6 +407,8 @@ static njs_int_t njs_generate_property_accessor_end(njs_vm_t *vm,
     njs_generator_t *generator, njs_parser_node_t *node);
 static njs_int_t njs_generate_array(njs_vm_t *vm, njs_generator_t *generator,
     njs_parser_node_t *node);
+static njs_int_t njs_generate_array_item(njs_vm_t *vm,
+    njs_generator_t *generator, njs_parser_node_t *node);
 static njs_int_t njs_generate_function_expression(njs_vm_t *vm,
     njs_generator_t *generator, njs_parser_node_t *node);
 static njs_int_t njs_generate_function(njs_vm_t *vm, njs_generator_t *generator,
@@ -3972,9 +3980,18 @@ static njs_int_t
 njs_generate_array(njs_vm_t *vm, njs_generator_t *generator,
     njs_parser_node_t *node)
 {
-    njs_vmcode_array_t  *array;
+    njs_int_t                  ret;
+    njs_parser_array_item_t    *item;
+    njs_vmcode_array_t         *array;
+    njs_generator_array_ctx_t  ctx;
+
+    if (node->u.array.items == NULL) {
+        node->index = njs_generate_object_dest_index(vm, generator, node);
+
+    } else {
+        node->index = njs_generate_node_temp_index_get(vm, generator, node);
+    }
 
-    node->index = njs_generate_object_dest_index(vm, generator, node);
     if (njs_slow_path(node->index == NJS_INDEX_ERROR)) {
         return NJS_ERROR;
     }
@@ -3982,16 +3999,66 @@ njs_generate_array(njs_vm_t *vm, njs_generator_t *generator,
     njs_generate_code(generator, njs_vmcode_array_t, array,
                       NJS_VMCODE_ARRAY, node);
     array->ctor = node->ctor;
+    array->flat = node->array_flat;
     array->retval = node->index;
-    array->length = node->u.length;
+    array->length = node->u.array.length;
 
-    /* Initialize array. */
+    if (node->u.array.items == NULL) {
+        return njs_generator_stack_pop(vm, generator, NULL);
+    }
 
-    njs_generator_next(generator, njs_generate, node->left);
+    ctx.array = node;
+    ctx.item = 0;
+    item = njs_arr_item(node->u.array.items, 0);
+
+    njs_generator_next(generator, njs_generate, item->value);
+
+    ret = njs_generator_after(vm, generator,
+                              njs_queue_first(&generator->stack), node,
+                              njs_generate_array_item, &ctx, sizeof(ctx));
+    if (njs_slow_path(ret != NJS_OK)) {
+        return ret;
+    }
+
+    return NJS_OK;
+}
+
+
+static njs_int_t
+njs_generate_array_item(njs_vm_t *vm, njs_generator_t *generator,
+    njs_parser_node_t *node)
+{
+    njs_int_t                  ret;
+    njs_parser_array_item_t    *item;
+    njs_vmcode_array_init_t    *init;
+    njs_generator_array_ctx_t  *ctx;
+
+    ctx = generator->context;
+    item = njs_arr_item(ctx->array->u.array.items, ctx->item);
+
+    njs_generate_code(generator, njs_vmcode_array_init_t, init,
+                      NJS_VMCODE_ARRAY_INIT, item->value);
+    init->value = item->value->index;
+    init->array = ctx->array->index;
+    init->index = item->index;
+
+    ret = njs_generate_node_index_release(vm, generator, item->value);
+    if (njs_slow_path(ret != NJS_OK)) {
+        return ret;
+    }
+
+    ctx->item++;
+
+    if (ctx->item == ctx->array->u.array.items->items) {
+        return njs_generator_stack_pop(vm, generator, ctx);
+    }
+
+    item = njs_arr_item(ctx->array->u.array.items, ctx->item);
+    njs_generator_next(generator, njs_generate, item->value);
 
     return njs_generator_after(vm, generator,
                                njs_queue_first(&generator->stack),
-                               NULL, njs_generator_pop, NULL, 0);
+                               node, njs_generate_array_item, ctx, 0);
 }
 
 
index 661728eef5f22fb9d3e13eeecb6de7484d3d248c..66d105e9cbfb4adba27bcead86b618bf7bfee2a9 100644 (file)
@@ -1336,6 +1336,7 @@ njs_parser_template_literal(njs_parser_t *parser, njs_lexer_token_t *token,
     }
 
     array->token_line = token->line;
+    array->array_flat = 1;
 
     template = parser->node;
 
@@ -1517,7 +1518,7 @@ njs_parser_array_element_list(njs_parser_t *parser, njs_lexer_token_t *token,
         njs_lexer_consume_token(parser->lexer, 1);
 
         array->ctor = 1;
-        array->u.length++;
+        array->u.array.length++;
 
         return NJS_OK;
 
@@ -8250,8 +8251,7 @@ njs_parser_object_value(njs_parser_t *parser, njs_parser_node_t *parent,
 {
     njs_parser_node_t  *object;
 
-    njs_assert(parent->token_type == NJS_TOKEN_OBJECT
-               || parent->token_type == NJS_TOKEN_ARRAY);
+    njs_assert(parent->token_type == NJS_TOKEN_OBJECT);
 
     object = njs_parser_node_new(parser, NJS_TOKEN_OBJECT_VALUE);
     if (njs_slow_path(object == NULL)) {
@@ -8379,25 +8379,31 @@ static njs_int_t
 njs_parser_array_item(njs_parser_t *parser, njs_parser_node_t *array,
     njs_parser_node_t *value)
 {
-    njs_int_t          ret;
-    njs_parser_node_t  *number;
+    njs_arr_t                *items;
+    njs_parser_array_item_t  *item;
 
-    number = njs_parser_node_new(parser, NJS_TOKEN_NUMBER);
-    if (njs_slow_path(number == NULL)) {
-        return NJS_ERROR;
-    }
+    items = array->u.array.items;
 
-    njs_set_number(&number->u.value, array->u.length);
+    if (items == NULL) {
+        items = njs_arr_create(parser->vm->mem_pool, 4,
+                               sizeof(njs_parser_array_item_t));
+        if (njs_slow_path(items == NULL)) {
+            return NJS_ERROR;
+        }
 
-    number->token_line = value->token_line;
+        array->u.array.items = items;
+    }
 
-    ret = njs_parser_object_property(parser, array, number, value, 0);
-    if (njs_slow_path(ret != NJS_OK)) {
+    item = njs_arr_add(items);
+    if (njs_slow_path(item == NULL)) {
         return NJS_ERROR;
     }
 
+    item->value = value;
+    item->index = array->u.array.length;
+
     array->ctor = 0;
-    array->u.length++;
+    array->u.array.length++;
 
     return NJS_OK;
 }
@@ -8927,7 +8933,9 @@ invalid:
 njs_bool_t
 njs_parser_has_side_effect(njs_parser_node_t *node)
 {
-    njs_bool_t  side_effect;
+    uint32_t                 i;
+    njs_bool_t               side_effect;
+    njs_parser_array_item_t  *item;
 
     if (node == NULL) {
         return 0;
@@ -8945,6 +8953,18 @@ njs_parser_has_side_effect(njs_parser_node_t *node)
         return 1;
     }
 
+    if (node->token_type == NJS_TOKEN_ARRAY && node->u.array.items != NULL) {
+        for (i = 0; i < node->u.array.items->items; i++) {
+            item = njs_arr_item(node->u.array.items, i);
+
+            if (njs_parser_has_side_effect(item->value)) {
+                return 1;
+            }
+        }
+
+        return 0;
+    }
+
     side_effect = njs_parser_has_side_effect(node->left);
 
     if (njs_fast_path(!side_effect)) {
@@ -9065,9 +9085,11 @@ njs_int_t
 njs_parser_traverse(njs_vm_t *vm, njs_parser_node_t *root, void *ctx,
     njs_parser_traverse_cb_t cb)
 {
-    njs_int_t          ret;
-    njs_arr_t          *stack;
-    njs_parser_node_t  *node, **ref;
+    uint32_t                 i;
+    njs_int_t                ret;
+    njs_arr_t                *stack;
+    njs_parser_node_t        *node, **ref;
+    njs_parser_array_item_t  *item;
 
     if (root == NULL) {
         return NJS_OK;
@@ -9099,6 +9121,22 @@ njs_parser_traverse(njs_vm_t *vm, njs_parser_node_t *root, void *ctx,
             goto failed;
         }
 
+        if (node->token_type == NJS_TOKEN_ARRAY
+            && node->u.array.items != NULL)
+        {
+            i = node->u.array.items->items;
+
+            while (i != 0) {
+                item = njs_arr_item(node->u.array.items, --i);
+                ref = njs_arr_add(stack);
+                if (njs_slow_path(ref == NULL)) {
+                    goto failed;
+                }
+
+                *ref = item->value;
+            }
+        }
+
         if (node->left != NULL) {
             ref = njs_arr_add(stack);
             if (njs_slow_path(ref == NULL)) {
@@ -9159,7 +9197,9 @@ static void
 njs_parser_serialize_tree(njs_chb_t *chain, njs_parser_node_t *node,
     njs_int_t *ret, size_t indent)
 {
-    njs_str_t  str;
+    uint32_t                 i;
+    njs_str_t                str;
+    njs_parser_array_item_t  *item;
 
     njs_chb_append_literal(chain, "{\"name\": \"");
 
@@ -9207,6 +9247,24 @@ njs_parser_serialize_tree(njs_chb_t *chain, njs_parser_node_t *node,
         break;
     }
 
+    if (node->token_type == NJS_TOKEN_ARRAY && node->u.array.items != NULL) {
+        njs_chb_append_literal(chain, ",\n");
+        njs_parser_serialize_indent(chain, indent);
+        njs_chb_append_literal(chain, " \"items\": [");
+
+        for (i = 0; i < node->u.array.items->items; i++) {
+            item = njs_arr_item(node->u.array.items, i);
+
+            if (i != 0) {
+                njs_chb_append_literal(chain, ", ");
+            }
+
+            njs_parser_serialize_tree(chain, item->value, ret, indent + 1);
+        }
+
+        njs_chb_append_literal(chain, "]");
+    }
+
     if (node->left != NULL) {
         njs_chb_append_literal(chain, ",\n");
         njs_parser_serialize_indent(chain, indent);
index 1347fb33fb0b0d048f4969723fe8a00124b1d730..1a5cd013530dc8a912d4ca2941a92e4b93572226 100644 (file)
@@ -29,15 +29,25 @@ struct njs_parser_scope_s {
 };
 
 
+typedef struct {
+    njs_parser_node_t               *value;
+    uint32_t                        index;
+} njs_parser_array_item_t;
+
+
 struct njs_parser_node_s {
     njs_token_type_t                token_type:16;
     uint8_t                         ctor:1;
     uint8_t                         hoist:1;
+    uint8_t                         array_flat:1;
     uint8_t                         temporary;    /* 1 bit  */
     uint32_t                        token_line;
 
     union {
-        uint32_t                    length;
+        struct {
+            njs_arr_t               *items;
+            uint32_t                length;
+        } array;
         njs_variable_reference_t    reference;
         njs_value_t                 value;
         njs_vmcode_t                operation;
index 6d51ab67cc99564ab4c9112d9b3a666bbaa50c70..3d453f3e2f648d230d36cbf9c08562e207d6a7c6 100644 (file)
@@ -25,6 +25,8 @@ static njs_jump_off_t njs_vmcode_template_literal(njs_vm_t *vm,
 
 static njs_jump_off_t njs_vmcode_property_init(njs_vm_t *vm, njs_value_t *value,
     njs_value_t *key, njs_value_t *retval);
+static njs_jump_off_t njs_vmcode_array_init(njs_vm_t *vm, u_char *pc,
+    njs_value_t *array, njs_value_t *value);
 static njs_jump_off_t njs_vmcode_proto_init(njs_vm_t *vm, njs_value_t *value,
     njs_value_t *key, njs_value_t *retval);
 static njs_jump_off_t njs_vmcode_property_in(njs_vm_t *vm,
@@ -99,6 +101,7 @@ njs_vmcode_interpreter(njs_vm_t *vm, u_char *pc, njs_value_t *rval,
     njs_native_frame_t           *previous, *native;
     njs_property_next_t          *next;
     njs_vmcode_import_t          *import;
+    njs_vmcode_array_init_t      *array_init;
     njs_vmcode_generic_t         *vmcode;
     njs_vmcode_variable_t        *var;
     njs_vmcode_prop_get_t        *get;
@@ -150,6 +153,7 @@ njs_vmcode_interpreter(njs_vm_t *vm, u_char *pc, njs_value_t *rval,
         NJS_GOTO_ROW(NJS_VMCODE_IF_FALSE_JUMP),
         NJS_GOTO_ROW(NJS_VMCODE_IF_EQUAL_JUMP),
         NJS_GOTO_ROW(NJS_VMCODE_PROPERTY_INIT),
+        NJS_GOTO_ROW(NJS_VMCODE_ARRAY_INIT),
         NJS_GOTO_ROW(NJS_VMCODE_RETURN),
         NJS_GOTO_ROW(NJS_VMCODE_FUNCTION_FRAME),
         NJS_GOTO_ROW(NJS_VMCODE_METHOD_FRAME),
@@ -1468,6 +1472,21 @@ NEXT_LBL;
 
         BREAK;
 
+    CASE (NJS_VMCODE_ARRAY_INIT):
+        njs_vmcode_debug_opcode();
+
+        array_init = (njs_vmcode_array_init_t *) pc;
+
+        njs_vmcode_operand(vm, array_init->array, value1);
+        njs_vmcode_operand(vm, array_init->value, value2);
+
+        ret = njs_vmcode_array_init(vm, pc, value1, value2);
+        if (njs_slow_path(ret < 0 && ret >= NJS_PREEMPT)) {
+            goto error;
+        }
+
+        BREAK;
+
     CASE (NJS_VMCODE_RETURN):
         njs_vmcode_debug_opcode();
 
@@ -1913,7 +1932,7 @@ njs_vmcode_array(njs_vm_t *vm, u_char *pc, njs_value_t *retval)
 
     code = (njs_vmcode_array_t *) pc;
 
-    array = njs_array_alloc(vm, 0, code->length, NJS_ARRAY_SPARE);
+    array = njs_array_alloc(vm, code->flat, code->length, NJS_ARRAY_SPARE);
 
     if (njs_fast_path(array != NULL)) {
 
@@ -2052,63 +2071,66 @@ njs_vmcode_template_literal(njs_vm_t *vm, njs_value_t *retval)
 
 
 static njs_jump_off_t
-njs_vmcode_property_init(njs_vm_t *vm, njs_value_t *value, njs_value_t *key,
+njs_vmcode_array_init(njs_vm_t *vm, u_char *pc, njs_value_t *value,
     njs_value_t *init)
 {
-    double               num;
-    uint32_t             index, size;
-    njs_int_t            ret;
-    njs_array_t          *array;
-    njs_value_t          *val, name;
-    njs_object_prop_t    *prop;
-    njs_flathsh_query_t  fhq;
+    uint32_t                 index, size;
+    njs_array_t              *array;
+    njs_value_t              *entry;
+    njs_object_prop_t        *prop;
+    njs_vmcode_array_init_t  *code;
 
-    switch (value->type) {
-    case NJS_ARRAY:
-        num = njs_key_to_index(key);
-        if (njs_slow_path(!njs_key_is_integer_index(num, key))) {
-            njs_internal_error(vm,
-                               "invalid index while property initialization");
-            return NJS_ERROR;
-        }
+    code = (njs_vmcode_array_init_t *) pc;
+    index = code->index;
 
-        index = (uint32_t) num;
-        array = value->data.u.array;
+    if (njs_slow_path(!njs_is_array(value))) {
+        njs_internal_error(vm, "array initialization target is not an array");
+        return NJS_ERROR;
+    }
 
-        if (njs_slow_path(!array->object.fast_array)) {
-            prop = njs_object_property_add(vm, value, njs_number_atom(index),
-                                           0);
-            if (njs_slow_path(prop == NULL)) {
-                return NJS_ERROR;
-            }
+    array = njs_array(value);
 
-            njs_value_assign(njs_prop_value(prop), init);
-            break;
-        }
+    if (njs_fast_path(array->object.fast_array)) {
+        njs_assert(index < array->size);
 
         if (index >= array->length) {
+            entry = &array->start[array->length];
             size = index - array->length;
 
-            ret = njs_array_expand(vm, array, 0, size + 1);
-            if (njs_slow_path(ret != NJS_OK)) {
-                return ret;
-            }
-
-            val = &array->start[array->length];
-
             while (size != 0) {
-                njs_set_invalid(val);
-                val++;
+                njs_set_invalid(entry);
+                entry++;
                 size--;
             }
 
             array->length = index + 1;
         }
 
-        array->start[index] = *init;
+        njs_value_assign(&array->start[index], init);
 
-        break;
+    } else {
+        prop = njs_object_property_add(vm, value, njs_number_atom(index), 0);
+        if (njs_slow_path(prop == NULL)) {
+            return NJS_ERROR;
+        }
 
+        njs_value_assign(njs_prop_value(prop), init);
+    }
+
+    return sizeof(njs_vmcode_array_init_t);
+}
+
+
+static njs_jump_off_t
+njs_vmcode_property_init(njs_vm_t *vm, njs_value_t *value, njs_value_t *key,
+    njs_value_t *init)
+{
+    njs_int_t            ret;
+    njs_value_t          name;
+    njs_object_prop_t    *prop;
+    njs_flathsh_query_t  fhq;
+
+    switch (value->type) {
     case NJS_OBJECT:
         if (key->type == NJS_STRING) {
             if (key->atom_id == NJS_ATOM_STRING_unknown) {
index 077eb745f41dc2fc16256f94991bbcab0f64ce9e..bd81185e18bce07c3a2bc9f71f3a7badeb16703d 100644 (file)
@@ -37,6 +37,7 @@ enum {
     NJS_VMCODE_IF_FALSE_JUMP,
     NJS_VMCODE_IF_EQUAL_JUMP,
     NJS_VMCODE_PROPERTY_INIT,
+    NJS_VMCODE_ARRAY_INIT,
     NJS_VMCODE_RETURN,
     NJS_VMCODE_FUNCTION_FRAME,
     NJS_VMCODE_METHOD_FRAME,
@@ -175,6 +176,7 @@ typedef struct {
     njs_index_t                retval;
     uintptr_t                  length;
     uint8_t                    ctor;       /* 1 bit  */
+    uint8_t                    flat;       /* 1 bit  */
 } njs_vmcode_array_t;
 
 
@@ -251,6 +253,14 @@ typedef struct {
 } njs_vmcode_prop_set_t;
 
 
+typedef struct {
+    njs_vmcode_t               code;
+    uint32_t                   index;
+    njs_index_t                value;
+    njs_index_t                array;
+} njs_vmcode_array_init_t;
+
+
 typedef struct {
     njs_vmcode_t               code;
     njs_index_t                value;
index c30604f4b58d88ad044dba0357668da1708ff782..fd9dd150d8168526bba1132350ae59e61bea8fd6 100644 (file)
@@ -4902,6 +4902,25 @@ static njs_unit_test_t  njs_test[] =
     { njs_str("var n = 1, a = [ n += 1 ]; a"),
       njs_str("2") },
 
+    { njs_str("var a = 1; a = [a, 2]"),
+      njs_str("1,2") },
+
+    { njs_str("var a; a = [(a = 1, 2)]"),
+      njs_str("2") },
+
+    { njs_str("var i = 0; [i++, i++, i++]"),
+      njs_str("0,1,2") },
+
+    { njs_str("var a = [, 1, , 2, ,];"
+              "[a.length, Object.keys(a).length, 0 in a, 4 in a]"),
+      njs_str("5,2,false,false") },
+
+    { njs_str("njs.dump([1, [2, [3, 4]], 5])"),
+      njs_str("[1,[2,[3,4]],5]") },
+
+    { njs_str("var a = 1; a += [(a = 5, 1)][0]; a"),
+      njs_str("2") },
+
     { njs_str("var a = [ 1, 2; 3 ]; a[0] + a[1] + a[2]"),
       njs_str("SyntaxError: Unexpected token \";\"") },