From: Dmitry Volyntsev Date: Thu, 3 Sep 2026 00:39:41 +0000 (-0700) Subject: Parser: compact array literal elements X-Git-Tag: 1.0.2~69 X-Git-Url: https://git.kaiwu.me/?a=commitdiff_plain;h=019bfd90fcf25490b6522f0ce6bf415892f41646;p=njs.git Parser: compact array literal elements 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. --- diff --git a/src/njs_disassembler.c b/src/njs_disassembler.c index e7de5ecd..84834c16 100644 --- a/src/njs_disassembler.c +++ b/src/njs_disassembler.c @@ -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; diff --git a/src/njs_generator.c b/src/njs_generator.c index 37ecea08..54e3eca4 100644 --- a/src/njs_generator.c +++ b/src/njs_generator.c @@ -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); } diff --git a/src/njs_parser.c b/src/njs_parser.c index 661728ee..66d105e9 100644 --- a/src/njs_parser.c +++ b/src/njs_parser.c @@ -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); diff --git a/src/njs_parser.h b/src/njs_parser.h index 1347fb33..1a5cd013 100644 --- a/src/njs_parser.h +++ b/src/njs_parser.h @@ -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; diff --git a/src/njs_vmcode.c b/src/njs_vmcode.c index 6d51ab67..3d453f3e 100644 --- a/src/njs_vmcode.c +++ b/src/njs_vmcode.c @@ -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) { diff --git a/src/njs_vmcode.h b/src/njs_vmcode.h index 077eb745..bd81185e 100644 --- a/src/njs_vmcode.h +++ b/src/njs_vmcode.h @@ -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; diff --git a/src/test/njs_unit_test.c b/src/test/njs_unit_test.c index c30604f4..fd9dd150 100644 --- a/src/test/njs_unit_test.c +++ b/src/test/njs_unit_test.c @@ -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 \";\"") },