From: Alexander Borisov Date: Fri, 30 Apr 2021 13:02:08 +0000 (+0300) Subject: Scopes refactoring. X-Git-Tag: 0.6.0~32 X-Git-Url: http://www.kaiwu.me/postgresql/commit/static/gitweb.js?a=commitdiff_plain;h=a2c357ba339e15dd4cc23a21a6b8f294628878e2;p=njs.git Scopes refactoring. --- diff --git a/auto/sources b/auto/sources index 9391a01e..e2756186 100644 --- a/auto/sources +++ b/auto/sources @@ -59,6 +59,7 @@ NJS_LIB_SRCS=" \ src/njs_encoding.c \ src/njs_buffer.c \ src/njs_iterator.c \ + src/njs_scope.c \ " NJS_LIB_TEST_SRCS=" \ diff --git a/nginx/ngx_js.c b/nginx/ngx_js.c index 64db0478..9365596a 100644 --- a/nginx/ngx_js.c +++ b/nginx/ngx_js.c @@ -261,7 +261,8 @@ ngx_js_ext_log(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, handler = c->log->handler; c->log->handler = NULL; - ngx_log_error(level, c->log, 0, "js: %*s", msg.length, msg.start); + ngx_log_error((ngx_uint_t) level, c->log, 0, "js: %*s", + msg.length, msg.start); c->log->handler = handler; diff --git a/src/njs.h b/src/njs.h index bac7d117..adb7b90a 100644 --- a/src/njs.h +++ b/src/njs.h @@ -210,7 +210,7 @@ typedef struct { #define NJS_VM_OPT_UNHANDLED_REJECTION_THROW 1 /* - * accumulative - enables "accumulative" mode to support incremental compiling. + * interactive - enables "interactive" mode. * (REPL). Allows starting parent VM without cloning. * disassemble - enables disassemble. * backtrace - enables backtraces. @@ -225,10 +225,9 @@ typedef struct { * - throwing inside a Promise without a catch block. * - throwing inside in a finally or catch block. */ - + uint8_t interactive; /* 1 bit */ uint8_t trailer; /* 1 bit */ uint8_t init; /* 1 bit */ - uint8_t accumulative; /* 1 bit */ uint8_t disassemble; /* 1 bit */ uint8_t backtrace; /* 1 bit */ uint8_t quiet; /* 1 bit */ @@ -277,7 +276,7 @@ NJS_EXPORT njs_int_t njs_vm_posted(njs_vm_t *vm); NJS_EXPORT njs_int_t njs_vm_call(njs_vm_t *vm, njs_function_t *function, const njs_value_t *args, njs_uint_t nargs); NJS_EXPORT njs_int_t njs_vm_invoke(njs_vm_t *vm, njs_function_t *function, - const njs_value_t *args, njs_uint_t nargs, njs_index_t retval); + const njs_value_t *args, njs_uint_t nargs, njs_value_t *retval); /* * Runs posted events. diff --git a/src/njs_builtin.c b/src/njs_builtin.c index 6a06052e..dae0710f 100644 --- a/src/njs_builtin.c +++ b/src/njs_builtin.c @@ -320,6 +320,8 @@ njs_builtin_objects_create(njs_vm_t *vm) vm->global_object = shared->objects[0]; vm->global_object.shared = 0; + njs_set_object(&vm->global_value, &vm->global_object); + string_object = &shared->string_object; njs_lvlhsh_init(&string_object->hash); string_object->shared_hash = shared->string_instance_hash; @@ -637,7 +639,7 @@ njs_vm_expression_completions(njs_vm_t *vm, njs_str_t *expression) } var = ((njs_variable_node_t *) node)->variable; - value = njs_vmcode_operand(vm, var->index); + value = njs_scope_valid_value(vm, var->index); if (!njs_is_object(value)) { return NULL; @@ -939,6 +941,8 @@ njs_global_this_prop_handler(njs_vm_t *vm, njs_object_prop_t *prop, { njs_int_t ret; njs_value_t *value; + njs_variable_t *var; + njs_function_t *function; njs_rbtree_node_t *rb_node; njs_lvlhsh_query_t lhq; njs_variable_node_t *node, var_node; @@ -965,7 +969,19 @@ njs_global_this_prop_handler(njs_vm_t *vm, njs_object_prop_t *prop, } node = (njs_variable_node_t *) rb_node; - value = njs_vmcode_operand(vm, node->variable->index); + + var = node->variable; + + value = njs_scope_valid_value(vm, var->index); + + if (var->type == NJS_VARIABLE_FUNCTION && njs_is_undefined(value)) { + *value = var->value; + + function = njs_function_value_copy(vm, value); + if (njs_slow_path(function == NULL)) { + return NJS_ERROR; + } + } if (setval != NULL) { *value = *setval; diff --git a/src/njs_disassembler.c b/src/njs_disassembler.c index dd67fa61..87d6fdda 100644 --- a/src/njs_disassembler.c +++ b/src/njs_disassembler.c @@ -35,6 +35,9 @@ static njs_code_name_t code_names[] = { { NJS_VMCODE_OBJECT_COPY, sizeof(njs_vmcode_object_copy_t), njs_str("OBJECT COPY ") }, + { NJS_VMCODE_FUNCTION_COPY, sizeof(njs_vmcode_function_copy_t), + njs_str("FUNCTION COPY ") }, + { NJS_VMCODE_PROPERTY_GET, sizeof(njs_vmcode_prop_get_t), njs_str("PROP GET ") }, { NJS_VMCODE_GLOBAL_GET, sizeof(njs_vmcode_prop_get_t), @@ -186,6 +189,7 @@ njs_disassemble(njs_vm_code_t *code) njs_vmcode_catch_t *catch; njs_vmcode_finally_t *finally; njs_vmcode_try_end_t *try_end; + njs_vmcode_move_arg_t *move_arg; njs_vmcode_try_start_t *try_start; njs_vmcode_operation_t operation; njs_vmcode_cond_jump_t *cond_jump; @@ -485,6 +489,17 @@ njs_disassemble(njs_vm_code_t *code) continue; } + if (operation == NJS_VMCODE_MOVE_ARG) { + move_arg = (njs_vmcode_move_arg_t *) p; + + njs_printf("%5uD | %05uz MOVE ARGUMENT %uD %04Xz\n", + line, p - start, move_arg->dst, (size_t) move_arg->src); + + p += sizeof(njs_vmcode_move_arg_t); + + continue; + } + code_name = code_names; n = njs_nitems(code_names); diff --git a/src/njs_function.c b/src/njs_function.c index c8679c42..087765d4 100644 --- a/src/njs_function.c +++ b/src/njs_function.c @@ -8,21 +8,13 @@ #include -static njs_function_t *njs_function_copy(njs_vm_t *vm, - njs_function_t *function); -static njs_native_frame_t *njs_function_frame_alloc(njs_vm_t *vm, size_t size); - - njs_function_t * -njs_function_alloc(njs_vm_t *vm, njs_function_lambda_t *lambda, - njs_closure_t *closures[], njs_bool_t shared) +njs_function_alloc(njs_vm_t *vm, njs_function_lambda_t *lambda) { size_t size; - njs_uint_t n, nesting; njs_function_t *function; - nesting = lambda->nesting; - size = sizeof(njs_function_t) + nesting * sizeof(njs_closure_t *); + size = sizeof(njs_function_t) + lambda->nclosures * sizeof(njs_value_t *); function = njs_mp_zalloc(vm->mem_pool, size); if (njs_slow_path(function == NULL)) { @@ -48,21 +40,8 @@ njs_function_alloc(njs_vm_t *vm, njs_function_lambda_t *lambda, function->object.__proto__ = &vm->prototypes[NJS_OBJ_TYPE_FUNCTION].object; function->object.type = NJS_FUNCTION; - function->object.shared = shared; function->object.extensible = 1; - if (nesting != 0 && closures != NULL) { - function->closure = 1; - - n = 0; - - do { - /* GC: retain closure. */ - njs_function_closures(function)[n] = closures[n]; - n++; - } while (n < nesting); - } - return function; fail: @@ -121,14 +100,6 @@ njs_function_value_copy(njs_vm_t *vm, njs_value_t *value) } -njs_inline njs_closure_t ** -njs_function_active_closures(njs_vm_t *vm, njs_function_t *function) -{ - return (function->closure) ? njs_function_closures(function) - : njs_frame_closures(vm->active_frame); -} - - njs_int_t njs_function_name_set(njs_vm_t *vm, njs_function_t *function, njs_value_t *name, const char *prefix) @@ -199,17 +170,16 @@ njs_function_name_set(njs_vm_t *vm, njs_function_t *function, } -static njs_function_t * +njs_function_t * njs_function_copy(njs_vm_t *vm, njs_function_t *function) { - size_t size; - njs_uint_t n, nesting; - njs_closure_t **closures; + size_t size, n; + njs_value_t **from, **to; njs_function_t *copy; - nesting = (function->native) ? 0 : function->u.lambda->nesting; + n = (function->native) ? 0 : function->u.lambda->nclosures; - size = sizeof(njs_function_t) + nesting * sizeof(njs_closure_t *); + size = sizeof(njs_function_t) + n * sizeof(njs_value_t *); copy = njs_mp_alloc(vm->mem_pool, size); if (njs_slow_path(copy == NULL)) { @@ -220,21 +190,19 @@ njs_function_copy(njs_vm_t *vm, njs_function_t *function) copy->object.__proto__ = &vm->prototypes[NJS_OBJ_TYPE_FUNCTION].object; copy->object.shared = 0; - if (nesting == 0) { + if (n == 0) { return copy; } - copy->closure = 1; + from = njs_function_closures(function); + to = njs_function_closures(copy); - closures = njs_function_active_closures(vm, function); + do { + n--; - n = 0; + to[n] = from[n]; - do { - /* GC: retain closure. */ - njs_function_closures(copy)[n] = closures[n]; - n++; - } while (n < nesting); + } while (n != 0); return copy; } @@ -287,7 +255,7 @@ njs_function_arguments_object_init(njs_vm_t *vm, njs_native_frame_t *frame) for (n = 0; n < nargs; n++) { njs_uint32_to_string(&value, n); - prop = njs_object_prop_alloc(vm, &value, &frame->arguments[n + 1], 1); + prop = njs_object_prop_alloc(vm, &value, &frame->arguments[n], 1); if (njs_slow_path(prop == NULL)) { return NJS_ERROR; } @@ -326,19 +294,20 @@ njs_function_rest_parameters_init(njs_vm_t *vm, njs_native_frame_t *frame) return NJS_ERROR; } - if (n <= nargs) { - i = 0; - do { - /* GC: retain. */ - array->start[i++] = frame->arguments[n++]; - } while (n <= nargs); + for (i = 0; i < length; i++) { + array->start[i] = frame->arguments[i + n - 1]; } - rest_arguments = &frame->arguments[frame->function->u.lambda->nargs]; + rest_arguments = njs_mp_alloc(vm->mem_pool, sizeof(njs_value_t)); + if (njs_slow_path(rest_arguments == NULL)) { + return NJS_ERROR; + } /* GC: retain. */ njs_set_array(rest_arguments, array); + vm->top_frame->local[n] = rest_arguments; + return NJS_OK; } @@ -397,7 +366,9 @@ njs_function_native_frame(njs_vm_t *vm, njs_function_t *function, frame->pc = NULL; value = (njs_value_t *) ((u_char *) frame + NJS_NATIVE_FRAME_SIZE); + frame->arguments = value; + frame->arguments_offset = value + function->args_offset; bound = function->bound; @@ -415,8 +386,6 @@ njs_function_native_frame(njs_vm_t *vm, njs_function_t *function, } while (n != 0); } - vm->scopes[NJS_SCOPE_CALLEE_ARGUMENTS] = value; - if (args != NULL) { memcpy(value, args, nargs * sizeof(njs_value_t)); } @@ -430,9 +399,9 @@ njs_function_lambda_frame(njs_vm_t *vm, njs_function_t *function, const njs_value_t *this, const njs_value_t *args, njs_uint_t nargs, njs_bool_t ctor) { - size_t size; - njs_uint_t n, max_args, closures; - njs_value_t *value, *bound; + size_t n, frame_size; + uint32_t args_count, value_count, value_size, temp_size; + njs_value_t *value, *bound, **new, **temp; njs_frame_t *frame; njs_function_t *target; njs_native_frame_t *native_frame; @@ -461,76 +430,86 @@ njs_function_lambda_frame(njs_vm_t *vm, njs_function_t *function, lambda = target->u.lambda; } - max_args = njs_max(nargs, lambda->nargs); + args_count = function->args_offset + njs_max(nargs, lambda->nargs); + value_count = args_count + njs_max(args_count, lambda->nlocal); - closures = lambda->nesting + lambda->block_closures; + value_size = value_count * sizeof(njs_value_t *); + temp_size = lambda->temp * sizeof(njs_value_t *); - size = njs_frame_size(closures) - + (function->args_offset + max_args) * sizeof(njs_value_t) - + lambda->local_size; + frame_size = value_size + temp_size + + ((value_count + lambda->temp) * sizeof(njs_value_t)); - native_frame = njs_function_frame_alloc(vm, size); + native_frame = njs_function_frame_alloc(vm, NJS_FRAME_SIZE + frame_size); if (njs_slow_path(native_frame == NULL)) { return NJS_ERROR; } + /* Local */ + + new = (njs_value_t **) ((u_char *) native_frame + NJS_FRAME_SIZE); + value = (njs_value_t *) ((u_char *) new + value_size + temp_size); + + n = value_count + lambda->temp; + + while (n != 0) { + n--; + new[n] = &value[n]; + njs_set_invalid(new[n]); + } + + /* Temp */ + + temp = (njs_value_t **) ((u_char *) native_frame + NJS_FRAME_SIZE + + value_size); + + native_frame->arguments = value; + native_frame->arguments_offset = value + (function->args_offset - 1); + native_frame->local = new + args_count; + native_frame->temp = temp; native_frame->function = target; native_frame->nargs = nargs; native_frame->ctor = ctor; native_frame->native = 0; native_frame->pc = NULL; - /* Function arguments. */ + /* Set this and bound arguments. */ + *native_frame->local[0] = *this; - value = (njs_value_t *) ((u_char *) native_frame + - njs_frame_size(closures)); - native_frame->arguments = value; - - if (bound == NULL) { - *value = *this; - - if (njs_slow_path(function->global_this - && njs_is_null_or_undefined(this))) { - njs_set_object(value, &vm->global_object); - } - - value++; + if (njs_slow_path(function->global_this + && njs_is_null_or_undefined(this))) + { + njs_set_object(native_frame->local[0], &vm->global_object); + } - } else { + if (bound != NULL) { n = function->args_offset; native_frame->nargs += n - 1; - if (ctor) { - *value++ = *this; - bound++; - n--; + if (!ctor) { + *native_frame->local[0] = *bound; } + bound++; + n--; + while (n != 0) { *value++ = *bound++; n--; }; } - vm->scopes[NJS_SCOPE_CALLEE_ARGUMENTS] = value; + /* Copy arguments. */ if (args != NULL) { while (nargs != 0) { *value++ = *args++; - max_args--; nargs--; } } - while (max_args != 0) { - njs_set_undefined(value++); - max_args--; - } - frame = (njs_frame_t *) native_frame; frame->exception.catch = NULL; frame->exception.next = NULL; - frame->local = value; frame->previous_active_frame = vm->active_frame; return NJS_OK; @@ -543,15 +522,7 @@ njs_function_frame_alloc(njs_vm_t *vm, size_t size) size_t spare_size, chunk_size; njs_native_frame_t *frame; - /* - * The size value must be aligned to njs_value_t because vm->top_frame - * may point to frame->free and vm->top_frame is used as a base pointer - * in njs_vm_continuation() which is expected to return pointers aligned - * to njs_value_t. - */ - size = njs_align_size(size, sizeof(njs_value_t)); - - spare_size = vm->top_frame->free_size; + spare_size = vm->top_frame ? vm->top_frame->free_size : 0; if (njs_fast_path(size <= spare_size)) { frame = (njs_native_frame_t *) vm->top_frame->free; @@ -602,7 +573,7 @@ njs_function_call2(njs_vm_t *vm, njs_function_t *function, return ret; } - ret = njs_function_frame_invoke(vm, (njs_index_t) &dst); + ret = njs_function_frame_invoke(vm, &dst); if (ret == NJS_OK) { *retval = dst; @@ -615,89 +586,93 @@ njs_function_call2(njs_vm_t *vm, njs_function_t *function, njs_int_t njs_function_lambda_call(njs_vm_t *vm) { - size_t size; + uint32_t n; njs_int_t ret; - njs_uint_t n, nesting; njs_frame_t *frame; - njs_value_t *dst, *src; - njs_closure_t *closure, **closures; + njs_value_t *args, **local, *value; + njs_value_t **cur_local, **cur_closures, **cur_temp; njs_function_t *function; njs_function_lambda_t *lambda; frame = (njs_frame_t *) vm->top_frame; function = frame->native.function; - lambda = function->u.lambda; + if (function->global && !function->closure_copied) { + ret = njs_function_capture_global_closures(vm, function); + if (njs_slow_path(ret != NJS_OK)) { + return NJS_ERROR; + } + } -#if (NJS_DEBUG) - vm->scopes[NJS_SCOPE_CALLEE_ARGUMENTS] = NULL; -#endif + lambda = function->u.lambda; - vm->scopes[NJS_SCOPE_ARGUMENTS] = frame->native.arguments; + args = vm->top_frame->arguments; + local = vm->top_frame->local + function->args_offset; - /* Function local variables and temporary values. */ + /* Move all arguments. */ - vm->scopes[NJS_SCOPE_LOCAL] = frame->local; + for (n = 0; n < function->args_count; n++) { + if (!njs_is_valid(args)) { + njs_set_undefined(args); + } - memcpy(frame->local, lambda->local_scope, lambda->local_size); + *local++ = args++; + } - /* Parent closures values. */ + /* Store current level. */ - n = 0; - nesting = lambda->nesting; + cur_local = vm->levels[NJS_LEVEL_LOCAL]; + cur_closures = vm->levels[NJS_LEVEL_CLOSURE]; + cur_temp = vm->levels[NJS_LEVEL_TEMP]; - if (nesting != 0) { - closures = njs_function_active_closures(vm, function); - do { - closure = *closures++; + /* Replace current level. */ - njs_frame_closures(frame)[n] = closure; - vm->scopes[NJS_SCOPE_CLOSURE + n] = &closure->u.values; + vm->levels[NJS_LEVEL_LOCAL] = vm->top_frame->local; + vm->levels[NJS_LEVEL_CLOSURE] = njs_function_closures(function); + vm->levels[NJS_LEVEL_TEMP] = frame->native.temp; - n++; - } while (n < nesting); + if (lambda->rest_parameters) { + ret = njs_function_rest_parameters_init(vm, &frame->native); + if (njs_slow_path(ret != NJS_OK)) { + return NJS_ERROR; + } } - /* Function closure values. */ + /* Self */ - if (lambda->block_closures > 0) { - closure = NULL; + if (lambda->self != NJS_INDEX_NONE) { + value = njs_scope_value(vm, lambda->self); - size = lambda->closure_size; + if (!njs_is_valid(value)) { + njs_set_function(value, function); + } + } - if (size != 0) { - closure = njs_mp_align(vm->mem_pool, sizeof(njs_value_t), size); - if (njs_slow_path(closure == NULL)) { - njs_memory_error(vm); - return NJS_ERROR; - } + vm->active_frame = frame; - size -= sizeof(njs_value_t); - closure->u.count = 0; - dst = closure->values; + /* Closures */ - src = lambda->closure_scope; + n = lambda->ndeclarations; - do { - *dst++ = *src++; - size -= sizeof(njs_value_t); - } while (size != 0); - } + while (n != 0) { + n--; - njs_frame_closures(frame)[n] = closure; - vm->scopes[NJS_SCOPE_CLOSURE + n] = &closure->u.values; - } + function = njs_function(lambda->declarations[n]); - if (lambda->rest_parameters) { - ret = njs_function_rest_parameters_init(vm, &frame->native); + ret = njs_function_capture_closure(vm, function, function->u.lambda); if (njs_slow_path(ret != NJS_OK)) { - return NJS_ERROR; + return ret; } } - vm->active_frame = frame; + ret = njs_vmcode_interpreter(vm, lambda->start); + + /* Restore current level. */ + vm->levels[NJS_LEVEL_LOCAL] = cur_local; + vm->levels[NJS_LEVEL_CLOSURE] = cur_closures; + vm->levels[NJS_LEVEL_TEMP] = cur_temp; - return njs_vmcode_interpreter(vm, lambda->start); + return ret; } @@ -705,7 +680,6 @@ njs_int_t njs_function_native_call(njs_vm_t *vm) { njs_int_t ret; - njs_value_t *value; njs_function_t *function, *target; njs_native_frame_t *native, *previous; njs_function_native_t call; @@ -741,12 +715,7 @@ njs_function_native_call(njs_vm_t *vm) njs_vm_scopes_restore(vm, native, previous); if (!native->skip) { - value = njs_vmcode_operand(vm, native->retval); - /* - * GC: value external/internal++ depending - * on vm->retval and retval type - */ - *value = vm->retval; + *native->retval = vm->retval; } njs_function_frame_free(vm, native); @@ -775,6 +744,142 @@ njs_function_frame_free(njs_vm_t *vm, njs_native_frame_t *native) } +njs_int_t +njs_function_capture_closure(njs_vm_t *vm, njs_function_t *function, + njs_function_lambda_t *lambda) +{ + void *start, *end; + uint32_t n; + njs_value_t *value, **closure; + njs_native_frame_t *frame; + + if (lambda->nclosures == 0) { + return NJS_OK; + } + + frame = &vm->active_frame->native; + + while (frame->native) { + frame = frame->previous; + } + + start = frame; + end = frame->free; + + closure = njs_function_closures(function); + n = lambda->nclosures; + + do { + n--; + + value = njs_scope_value(vm, lambda->closures[n]); + + if (start <= (void *) value && (void *) value < end) { + value = njs_scope_value_clone(vm, lambda->closures[n], value); + if (njs_slow_path(value == NULL)) { + return NJS_ERROR; + } + } + + closure[n] = value; + + } while (n != 0); + + return NJS_OK; +} + + +njs_inline njs_value_t * +njs_function_closure_value(njs_vm_t *vm, njs_value_t **scope, njs_index_t index, + void *start, void *end) +{ + njs_value_t *value, *newval; + + value = scope[njs_scope_index_value(index)]; + + if (start <= (void *) value && end > (void *) value) { + newval = njs_mp_alloc(vm->mem_pool, sizeof(njs_value_t)); + if (njs_slow_path(newval == NULL)) { + njs_memory_error(vm); + return NULL; + } + + *newval = *value; + value = newval; + } + + scope[njs_scope_index_value(index)] = value; + + return value; +} + + +njs_int_t +njs_function_capture_global_closures(njs_vm_t *vm, njs_function_t *function) +{ + void *start, *end; + uint32_t n; + njs_value_t *value, **refs, **global; + njs_index_t *indexes, index; + njs_native_frame_t *native; + njs_function_lambda_t *lambda; + + lambda = function->u.lambda; + + if (lambda->nclosures == 0) { + return NJS_OK; + } + + native = vm->top_frame; + + while (native->previous->function != NULL) { + native = native->previous; + } + + start = native; + end = native->free; + + indexes = lambda->closures; + refs = njs_function_closures(function); + + global = vm->levels[NJS_LEVEL_GLOBAL]; + + n = lambda->nclosures; + + while (n > 0) { + n--; + + index = indexes[n]; + + switch (njs_scope_index_type(index)) { + case NJS_LEVEL_LOCAL: + value = njs_function_closure_value(vm, native->local, index, + start, end); + break; + + case NJS_LEVEL_GLOBAL: + value = njs_function_closure_value(vm, global, index, start, end); + break; + + default: + njs_type_error(vm, "unexpected value type for closure \"%uD\"", + njs_scope_index_type(index)); + return NJS_ERROR; + } + + if (njs_slow_path(value == NULL)) { + return NJS_ERROR; + } + + refs[n] = value; + } + + function->closure_copied = 1; + + return NJS_OK; +} + + static njs_value_t * njs_function_property_prototype_set(njs_vm_t *vm, njs_lvlhsh_t *hash, njs_value_t *prototype) @@ -937,7 +1042,7 @@ njs_function_constructor(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, parser.lexer = &lexer; - ret = njs_parser(vm, &parser, NULL); + ret = njs_parser(vm, &parser); if (njs_slow_path(ret != NJS_OK)) { return ret; } @@ -953,7 +1058,14 @@ njs_function_constructor(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, type = &safe_ast[0]; for (; *type != NJS_TOKEN_ILLEGAL; type++, node = node->right) { - if (node == NULL || node->left != NULL) { + if (node == NULL) { + goto fail; + } + + if (node->left != NULL + && node->token_type != NJS_TOKEN_FUNCTION_EXPRESSION + && node->left->token_type != NJS_TOKEN_NAME) + { goto fail; } @@ -970,11 +1082,6 @@ njs_function_constructor(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, return ret; } - ret = njs_variables_scope_reference(vm, scope); - if (njs_slow_path(ret != NJS_OK)) { - return ret; - } - njs_memzero(&generator, sizeof(njs_generator_t)); code = njs_generate_scope(vm, &generator, scope, &njs_entry_anonymous); @@ -990,11 +1097,12 @@ njs_function_constructor(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, lambda = ((njs_vmcode_function_t *) generator.code_start)->lambda; - function = njs_function_alloc(vm, lambda, NULL, 0); + function = njs_function_alloc(vm, lambda); if (njs_slow_path(function == NULL)) { return NJS_ERROR; } + function->global = 1; function->global_this = 1; function->args_count = lambda->nargs - lambda->rest_parameters; diff --git a/src/njs_function.h b/src/njs_function.h index 20cda079..7f4b814f 100644 --- a/src/njs_function.h +++ b/src/njs_function.h @@ -9,23 +9,21 @@ struct njs_function_lambda_s { - uint32_t nargs; - uint32_t local_size; - uint32_t closure_size; + njs_index_t *closures; + uint32_t nclosures; + uint32_t nlocal; + uint32_t temp; + + njs_value_t **declarations; + uint32_t ndeclarations; - /* Function nesting level. */ - uint8_t nesting; /* 4 bits */ + njs_index_t self; - /* Function internal block closures levels. */ - uint8_t block_closures; /* 4 bits */ + uint32_t nargs; uint8_t ctor; /* 1 bit */ uint8_t rest_parameters; /* 1 bit */ - /* Initial values of local scope. */ - njs_value_t *local_scope; - njs_value_t *closure_scope; - u_char *start; }; @@ -35,11 +33,10 @@ struct njs_function_lambda_s { njs_align_size(sizeof(njs_native_frame_t), sizeof(njs_value_t)) /* The frame size must be aligned to njs_value_t. */ -#define njs_frame_size(closures) \ - njs_align_size(sizeof(njs_frame_t) + closures * sizeof(njs_closure_t *), \ - sizeof(njs_value_t)) +#define NJS_FRAME_SIZE \ + njs_align_size(sizeof(njs_frame_t), sizeof(njs_value_t)) -#define NJS_FRAME_SPARE_SIZE 512 +#define NJS_FRAME_SPARE_SIZE (4 * 1024) struct njs_native_frame_s { @@ -51,11 +48,15 @@ struct njs_native_frame_s { njs_value_t *arguments; njs_object_t *arguments_object; - - njs_index_t retval; + njs_value_t *arguments_offset; + njs_value_t **local; + njs_value_t **temp; uint32_t size; uint32_t free_size; + + njs_value_t *retval; + uint32_t nargs; uint8_t native; /* 1 bit */ @@ -81,19 +82,14 @@ struct njs_frame_s { njs_exception_t exception; njs_frame_t *previous_active_frame; - - njs_value_t *local; - -#define njs_frame_closures(frame) \ - ((njs_closure_t **) ((u_char *) frame + sizeof(njs_frame_t))) }; -njs_function_t *njs_function_alloc(njs_vm_t *vm, njs_function_lambda_t *lambda, - njs_closure_t *closures[], njs_bool_t shared); +njs_function_t *njs_function_alloc(njs_vm_t *vm, njs_function_lambda_t *lambda); njs_function_t *njs_function_value_copy(njs_vm_t *vm, njs_value_t *value); njs_int_t njs_function_name_set(njs_vm_t *vm, njs_function_t *function, njs_value_t *name, const char *prefix); +njs_function_t *njs_function_copy(njs_vm_t *vm, njs_function_t *function); njs_int_t njs_function_arguments_object_init(njs_vm_t *vm, njs_native_frame_t *frame); njs_int_t njs_function_rest_parameters_init(njs_vm_t *vm, @@ -113,7 +109,12 @@ njs_int_t njs_function_call2(njs_vm_t *vm, njs_function_t *function, njs_uint_t nargs, njs_value_t *retval, njs_bool_t ctor); njs_int_t njs_function_lambda_call(njs_vm_t *vm); njs_int_t njs_function_native_call(njs_vm_t *vm); +njs_native_frame_t *njs_function_frame_alloc(njs_vm_t *vm, size_t size); void njs_function_frame_free(njs_vm_t *vm, njs_native_frame_t *frame); +njs_int_t njs_function_capture_closure(njs_vm_t *vm, njs_function_t *function, + njs_function_lambda_t *lambda); +njs_int_t njs_function_capture_global_closures(njs_vm_t *vm, + njs_function_t *function); njs_inline njs_function_lambda_t * @@ -161,7 +162,7 @@ njs_function_previous_frame(njs_native_frame_t *frame) njs_inline njs_int_t -njs_function_frame_invoke(njs_vm_t *vm, njs_index_t retval) +njs_function_frame_invoke(njs_vm_t *vm, njs_value_t *retval) { njs_native_frame_t *frame; @@ -202,6 +203,13 @@ njs_native_function_same(const njs_function_t *f1, const njs_function_t *f2) } +njs_inline njs_value_t ** +njs_function_closures(const njs_function_t *func) +{ + return (njs_value_t **) ((u_char *) func + sizeof(njs_function_t)); +} + + extern const njs_object_type_init_t njs_function_type_init; extern const njs_object_init_t njs_function_instance_init; extern const njs_object_init_t njs_arrow_instance_init; diff --git a/src/njs_generator.c b/src/njs_generator.c index 1730986e..4b1e7e30 100644 --- a/src/njs_generator.c +++ b/src/njs_generator.c @@ -2,6 +2,7 @@ /* * Copyright (C) Igor Sysoev * Copyright (C) Dmitry Volyntsev + * Copyright (C) Alexander Borisov * Copyright (C) NGINX, Inc. */ @@ -18,7 +19,7 @@ struct njs_generator_patch_s { * because pointer to u_char accesses only one byte so this does not * work on big endian platforms. */ - njs_jump_off_t jump_offset; + njs_jump_off_t jump_offset; njs_generator_patch_t *next; njs_str_t label; @@ -62,7 +63,8 @@ static njs_int_t njs_generate_code_map(njs_vm_t *vm, njs_generator_t *generator, static njs_int_t njs_generate_name(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node); static njs_int_t njs_generate_variable(njs_vm_t *vm, njs_generator_t *generator, - njs_parser_node_t *node, njs_reference_type_t type); + njs_parser_node_t *node, njs_reference_type_t type, + njs_variable_t **retvar); static njs_int_t njs_generate_var_statement(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node); static njs_int_t njs_generate_if_statement(njs_vm_t *vm, @@ -123,6 +125,8 @@ static njs_int_t njs_generate_property_accessor(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_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, njs_parser_node_t *node); static njs_int_t njs_generate_regexp(njs_vm_t *vm, njs_generator_t *generator, @@ -144,7 +148,7 @@ static njs_int_t njs_generate_function_declaration(njs_vm_t *vm, static njs_int_t njs_generate_function_scope(njs_vm_t *vm, njs_function_lambda_t *lambda, njs_parser_node_t *node, const njs_str_t *name); -static njs_int_t njs_generate_lambda_variables(njs_vm_t *vm, +static int64_t njs_generate_lambda_variables(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node); static njs_int_t njs_generate_return_statement(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node); @@ -154,6 +158,8 @@ static njs_int_t njs_generate_method_call(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node); static njs_int_t njs_generate_call(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node); +static njs_int_t njs_generate_move_arguments(njs_vm_t *vm, + njs_generator_t *generator, njs_parser_node_t *node); static njs_int_t njs_generate_try_statement(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node); static njs_int_t njs_generate_throw_statement(njs_vm_t *vm, @@ -392,12 +398,13 @@ njs_generate(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node) case NJS_TOKEN_FALSE: case NJS_TOKEN_NUMBER: case NJS_TOKEN_STRING: - node->index = njs_value_index(vm, &node->u.value, generator->runtime); - if (njs_fast_path(node->index != NJS_INDEX_NONE)) { - return NJS_OK; + node->index = njs_scope_global_index(vm, &node->u.value, + generator->runtime); + if (njs_slow_path(node->index == NJS_INDEX_ERROR)) { + return NJS_ERROR; } - return NJS_ERROR; + return NJS_OK; case NJS_TOKEN_OBJECT_VALUE: node->index = node->u.object->index; @@ -414,6 +421,9 @@ njs_generate(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node) return njs_generate_array(vm, generator, node); case NJS_TOKEN_FUNCTION_EXPRESSION: + return njs_generate_function_expression(vm, generator, node); + + case NJS_TOKEN_FUNCTION: return njs_generate_function(vm, generator, node); case NJS_TOKEN_REGEXP: @@ -422,32 +432,16 @@ njs_generate(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node) case NJS_TOKEN_TEMPLATE_LITERAL: return njs_generate_template_literal(vm, generator, node); - case NJS_TOKEN_THIS: case NJS_TOKEN_EXTERNAL: return NJS_OK; case NJS_TOKEN_NAME: case NJS_TOKEN_ARGUMENTS: case NJS_TOKEN_EVAL: - case NJS_TOKEN_NON_LOCAL_THIS: + case NJS_TOKEN_THIS: return njs_generate_name(vm, generator, node); - case NJS_TOKEN_GLOBAL_OBJECT: - if (vm->options.module) { - node->index = njs_value_index(vm, &njs_value_undefined, - generator->runtime); - if (njs_fast_path(node->index != NJS_INDEX_NONE)) { - return NJS_OK; - } - - return NJS_ERROR; - } - - node->index = NJS_INDEX_GLOBAL_OBJECT; - - return NJS_OK; - - case NJS_TOKEN_FUNCTION: + case NJS_TOKEN_FUNCTION_DECLARATION: return njs_generate_function_declaration(vm, generator, node); case NJS_TOKEN_FUNCTION_CALL: @@ -596,39 +590,39 @@ static njs_int_t njs_generate_name(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node) { - njs_variable_t *var; - njs_vmcode_object_copy_t *copy; - - var = njs_variable_resolve(vm, node); - - if (var != NULL && var->type == NJS_VARIABLE_FUNCTION) { + njs_variable_t *var; + njs_vmcode_function_copy_t *copy; - node->index = njs_generate_dest_index(vm, generator, node); - if (njs_slow_path(node->index == NJS_INDEX_ERROR)) { - return node->index; - } + var = njs_variable_reference(vm, node); + if (njs_slow_path(var == NULL)) { + return njs_generate_global_reference(vm, generator, node, 1); + } - njs_generate_code(generator, njs_vmcode_object_copy_t, copy, - NJS_VMCODE_OBJECT_COPY, 2, node); + if (var->function && var->type == NJS_VARIABLE_FUNCTION) { + njs_generate_code(generator, njs_vmcode_function_copy_t, copy, + NJS_VMCODE_FUNCTION_COPY, 0, node); + copy->function = &var->value; copy->retval = node->index; - copy->object = var->index; - - return NJS_OK; } - return njs_generate_variable(vm, generator, node, NJS_REFERENCE); + return NJS_OK; } static njs_int_t njs_generate_variable(njs_vm_t *vm, njs_generator_t *generator, - njs_parser_node_t *node, njs_reference_type_t type) + njs_parser_node_t *node, njs_reference_type_t type, njs_variable_t **retvar) { - njs_index_t index; + njs_variable_t *var; + njs_vmcode_function_copy_t *copy; - index = njs_variable_index(vm, node); - if (njs_slow_path(index == NJS_INDEX_NONE)) { + var = njs_variable_reference(vm, node); + if (retvar != NULL) { + *retvar = var; + } + + if (njs_slow_path(var == NULL)) { switch (type) { case NJS_DECLARATION: return njs_generate_reference_error(vm, generator, node); @@ -640,7 +634,12 @@ njs_generate_variable(njs_vm_t *vm, njs_generator_t *generator, } } - node->index = index; + if (var->function && var->type == NJS_VARIABLE_FUNCTION) { + njs_generate_code(generator, njs_vmcode_function_copy_t, copy, + NJS_VMCODE_FUNCTION_COPY, 0, node); + copy->function = &var->value; + copy->retval = node->index; + } return NJS_OK; } @@ -651,19 +650,18 @@ njs_generate_var_statement(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node) { njs_int_t ret; - njs_index_t index; + njs_variable_t *var; njs_parser_node_t *lvalue, *expr; njs_vmcode_move_t *move; lvalue = node->left; - index = njs_variable_index(vm, lvalue); - if (njs_slow_path(index == NJS_INDEX_NONE)) { + ret = njs_generate_variable(vm, generator, lvalue, NJS_DECLARATION, &var); + if (njs_slow_path(ret != NJS_OK)) { return NJS_ERROR; } - lvalue->index = index; - + lvalue->index = var->index; expr = node->right; if (expr == NULL) { @@ -1208,6 +1206,11 @@ njs_generate_for_in_statement(njs_vm_t *vm, njs_generator_t *generator, foreach = node->left; + ret = njs_generator(vm, generator, foreach->left); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + ret = njs_generator(vm, generator, foreach->right); if (njs_slow_path(ret != NJS_OK)) { return ret; @@ -1240,11 +1243,6 @@ njs_generate_for_in_statement(njs_vm_t *vm, njs_generator_t *generator, njs_code_set_jump_offset(generator, njs_vmcode_prop_foreach_t, prop_offset); - ret = njs_generator(vm, generator, node->left->left); - if (njs_slow_path(ret != NJS_OK)) { - return ret; - } - njs_generate_code(generator, njs_vmcode_prop_next_t, prop_next, NJS_VMCODE_PROPERTY_NEXT, 3, node->left->left); prop_offset = njs_code_offset(generator, prop_next); @@ -1635,16 +1633,16 @@ njs_generate_stop_statement(njs_vm_t *vm, njs_generator_t *generator, njs_generate_code(generator, njs_vmcode_stop_t, stop, NJS_VMCODE_STOP, 1, node); - index = NJS_INDEX_NONE; + index = njs_scope_undefined_index(vm, 0); node = node->right; - if (node != NULL && node->token_type != NJS_TOKEN_FUNCTION) { - index = node->index; - } - - if (index == NJS_INDEX_NONE) { - index = njs_value_index(vm, &njs_value_undefined, - generator->runtime); + if (node != NULL) { + if ((node->index != NJS_INDEX_NONE + && node->token_type != NJS_TOKEN_FUNCTION_DECLARATION) + || node->token_type == NJS_TOKEN_THIS) + { + index = node->index; + } } stop->retval = index; @@ -1686,7 +1684,8 @@ njs_generate_assignment(njs_vm_t *vm, njs_generator_t *generator, if (lvalue->token_type == NJS_TOKEN_NAME) { - ret = njs_generate_variable(vm, generator, lvalue, NJS_DECLARATION); + ret = njs_generate_variable(vm, generator, lvalue, NJS_DECLARATION, + NULL); if (njs_slow_path(ret != NJS_OK)) { return ret; } @@ -1810,7 +1809,8 @@ njs_generate_operation_assignment(njs_vm_t *vm, njs_generator_t *generator, if (lvalue->token_type == NJS_TOKEN_NAME) { - ret = njs_generate_variable(vm, generator, lvalue, NJS_DECLARATION); + ret = njs_generate_variable(vm, generator, lvalue, NJS_DECLARATION, + NULL); if (njs_slow_path(ret != NJS_OK)) { return ret; } @@ -2028,6 +2028,48 @@ njs_generate_array(njs_vm_t *vm, njs_generator_t *generator, } +static njs_int_t +njs_generate_function_expression(njs_vm_t *vm, njs_generator_t *generator, + njs_parser_node_t *node) +{ + njs_int_t ret; + njs_variable_t *var; + njs_function_lambda_t *lambda; + njs_vmcode_function_t *function; + const njs_lexer_entry_t *lex_entry; + + var = njs_variable_reference(vm, node->left); + if (njs_slow_path(var == NULL)) { + return njs_generate_reference_error(vm, generator, node->left); + } + + lambda = node->u.value.data.u.lambda; + + lex_entry = njs_lexer_entry(var->unique_id); + if (njs_slow_path(lex_entry == NULL)) { + return NJS_ERROR; + } + + ret = njs_generate_function_scope(vm, lambda, node, &lex_entry->name); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + njs_generate_code(generator, njs_vmcode_function_t, function, + NJS_VMCODE_FUNCTION, 1, node); + function->lambda = lambda; + + node->index = njs_generate_object_dest_index(vm, generator, node); + if (njs_slow_path(node->index == NJS_INDEX_ERROR)) { + return NJS_ERROR; + } + + function->retval = node->index; + + return NJS_OK; +} + + static njs_int_t njs_generate_function(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node) @@ -2263,7 +2305,7 @@ njs_generate_typeof_operation(njs_vm_t *vm, njs_generator_t *generator, expr = node->left; if (expr->token_type == NJS_TOKEN_NAME) { - ret = njs_generate_variable(vm, generator, expr, NJS_TYPEOF); + ret = njs_generate_variable(vm, generator, expr, NJS_TYPEOF, NULL); if (njs_slow_path(ret != NJS_OK)) { return NJS_ERROR; } @@ -2307,7 +2349,8 @@ njs_generate_inc_dec_operation(njs_vm_t *vm, njs_generator_t *generator, if (lvalue->token_type == NJS_TOKEN_NAME) { - ret = njs_generate_variable(vm, generator, lvalue, NJS_DECLARATION); + ret = njs_generate_variable(vm, generator, lvalue, NJS_DECLARATION, + NULL); if (njs_slow_path(ret != NJS_OK)) { return ret; } @@ -2402,21 +2445,21 @@ njs_generate_function_declaration(njs_vm_t *vm, njs_generator_t *generator, { njs_int_t ret; njs_variable_t *var; + njs_function_t *function; njs_function_lambda_t *lambda; const njs_lexer_entry_t *lex_entry; - var = njs_variable_resolve(vm, node); + var = njs_variable_reference(vm, node); if (njs_slow_path(var == NULL)) { return njs_generate_reference_error(vm, generator, node); } - if (!njs_is_function(&var->value)) { - /* A variable was declared with the same name. */ - return NJS_OK; + if (njs_is_function(&var->value)) { + lambda = njs_function(&var->value)->u.lambda; + } else { + lambda = var->value.data.u.lambda; } - lambda = njs_function_lambda(&var->value); - lex_entry = njs_lexer_entry(node->u.reference.unique_id); if (njs_slow_path(lex_entry == NULL)) { return NJS_ERROR; @@ -2427,6 +2470,17 @@ njs_generate_function_declaration(njs_vm_t *vm, njs_generator_t *generator, return ret; } + function = njs_function_alloc(vm, lambda); + if (njs_slow_path(function == NULL)) { + return NJS_ERROR; + } + + function->global = njs_function_scope(var->scope)->type == NJS_SCOPE_GLOBAL; + function->object.shared = 1; + function->args_count = lambda->nargs - lambda->rest_parameters; + + njs_set_function(&var->value, function); + return ret; } @@ -2435,8 +2489,7 @@ static njs_int_t njs_generate_function_scope(njs_vm_t *vm, njs_function_lambda_t *lambda, njs_parser_node_t *node, const njs_str_t *name) { - size_t size; - njs_arr_t *closure; + njs_arr_t *arr; njs_bool_t module; njs_vm_code_t *code; njs_generator_t generator; @@ -2444,9 +2497,6 @@ njs_generate_function_scope(njs_vm_t *vm, njs_function_lambda_t *lambda, njs_memzero(&generator, sizeof(njs_generator_t)); - module = node->right->scope->module; - file_node = module ? node->right : node; - node = node->right; code = njs_generate_scope(vm, &generator, node->scope, name); @@ -2458,25 +2508,23 @@ njs_generate_function_scope(njs_vm_t *vm, njs_function_lambda_t *lambda, return NJS_ERROR; } + module = node->right->scope->module; + file_node = module ? node->right : node; + code->file = file_node->scope->file; - size = 0; - closure = node->scope->values[1]; + lambda->start = generator.code_start; + lambda->closures = generator.closures->start; + lambda->nclosures = generator.closures->items; + lambda->nlocal = node->scope->items; + lambda->temp = node->scope->temp; - if (closure != NULL) { - lambda->block_closures = 1; - lambda->closure_scope = closure->start; - size = (1 + closure->items) * sizeof(njs_value_t); + if (node->scope->declarations != NULL) { + arr = node->scope->declarations; + lambda->declarations = arr->start; + lambda->ndeclarations = arr->items; } - lambda->closure_size = size; - - lambda->nesting = node->scope->nesting; - - lambda->start = generator.code_start; - lambda->local_size = generator.scope_size; - lambda->local_scope = generator.local_scope; - return NJS_OK; } @@ -2486,11 +2534,8 @@ njs_generate_scope(njs_vm_t *vm, njs_generator_t *generator, njs_parser_scope_t *scope, const njs_str_t *name) { u_char *p; - size_t size; - uintptr_t scope_size; - njs_int_t ret; - njs_uint_t n, index; - njs_value_t *value; + int64_t nargs; + njs_uint_t index; njs_vm_code_t *code; generator->code_size = 128; @@ -2504,8 +2549,8 @@ njs_generate_scope(njs_vm_t *vm, njs_generator_t *generator, generator->code_start = p; generator->code_end = p; - ret = njs_generate_lambda_variables(vm, generator, scope->top); - if (njs_slow_path(ret != NJS_OK)) { + nargs = njs_generate_lambda_variables(vm, generator, scope->top); + if (njs_slow_path(nargs < NJS_OK)) { return NULL; } @@ -2536,6 +2581,13 @@ njs_generate_scope(njs_vm_t *vm, njs_generator_t *generator, generator->lines = code->lines; } + generator->closures = njs_arr_create(vm->mem_pool, 4, sizeof(njs_index_t)); + if (njs_slow_path(generator->closures == NULL)) { + return NULL; + } + + scope->closures = generator->closures; + if (njs_slow_path(njs_generator(vm, generator, scope->top) != NJS_OK)) { return NULL; } @@ -2548,46 +2600,22 @@ njs_generate_scope(njs_vm_t *vm, njs_generator_t *generator, generator->code_size = generator->code_end - generator->code_start; - scope_size = njs_scope_offset(scope->next_index[0]); - - if (scope->type == NJS_SCOPE_GLOBAL) { - scope_size -= NJS_INDEX_GLOBAL_OFFSET; - } - - generator->local_scope = njs_mp_alloc(vm->mem_pool, scope_size); - if (njs_slow_path(generator->local_scope == NULL)) { - return NULL; - } - - generator->scope_size = scope_size; - - size = scope->values[0]->items * sizeof(njs_value_t); - - njs_thread_log_debug("SCOPE SIZE: %uz %uz", size, scope_size); - - p = memcpy(generator->local_scope, scope->values[0]->start, size); - value = (njs_value_t *) (p + size); - - for (n = scope_size - size; n != 0; n -= sizeof(njs_value_t)) { - njs_set_undefined(value++); - } - return code; } -static njs_int_t +static int64_t njs_generate_lambda_variables(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node) { - njs_index_t index; + int64_t nargs; njs_variable_t *var; njs_rbtree_node_t *rb_node; - njs_vmcode_move_t *move; - njs_vmcode_this_t *this; njs_variable_node_t *var_node; njs_vmcode_arguments_t *arguments; + nargs = 0; + rb_node = njs_rbtree_min(&node->scope->variables); while (njs_rbtree_is_there_successor(&node->scope->variables, rb_node)) { @@ -2598,16 +2626,8 @@ njs_generate_lambda_variables(njs_vm_t *vm, njs_generator_t *generator, break; } - if (var->argument != 0) { - index = njs_scope_index((var->argument - 1), NJS_SCOPE_ARGUMENTS); - - njs_generate_code_move(generator, move, var->index, index, node); - } - - if (var->this_object) { - njs_generate_code(generator, njs_vmcode_this_t, this, - NJS_VMCODE_THIS, 1, NULL); - this->dst = var->index; + if (var->argument) { + nargs++; } if (var->arguments_object) { @@ -2619,7 +2639,7 @@ njs_generate_lambda_variables(njs_vm_t *vm, njs_generator_t *generator, rb_node = njs_rbtree_node_successor(&node->scope->variables, rb_node); } - return NJS_OK; + return nargs; } @@ -2644,7 +2664,12 @@ njs_generate_return_statement(njs_vm_t *vm, njs_generator_t *generator, index = node->right->index; } else { - index = njs_value_index(vm, &njs_value_undefined, generator->runtime); + index = njs_scope_global_index(vm, &njs_value_undefined, + generator->runtime); + } + + if (njs_slow_path(index == NJS_INDEX_ERROR)) { + return NJS_ERROR; } immediate = njs_generate_lookup_block(generator->block, NJS_GENERATOR_TRY, @@ -2703,11 +2728,14 @@ static njs_int_t njs_generate_function_call(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node) { - njs_int_t ret; + njs_int_t ret, nargs; njs_jump_off_t func_offset; + njs_variable_t *var; njs_parser_node_t *name; njs_vmcode_function_frame_t *func; + var = NULL; + if (node->left != NULL) { /* Generate function code in function expression. */ ret = njs_generator(vm, generator, node->left); @@ -2718,7 +2746,7 @@ njs_generate_function_call(njs_vm_t *vm, njs_generator_t *generator, name = node->left; } else { - ret = njs_generate_variable(vm, generator, node, NJS_REFERENCE); + ret = njs_generate_variable(vm, generator, node, NJS_REFERENCE, &var); if (njs_slow_path(ret != NJS_OK)) { return ret; } @@ -2732,16 +2760,20 @@ njs_generate_function_call(njs_vm_t *vm, njs_generator_t *generator, func->ctor = node->ctor; func->name = name->index; - ret = njs_generate_call(vm, generator, node); + nargs = njs_generate_move_arguments(vm, generator, node); + if (njs_slow_path(nargs < 0)) { + return nargs; + } - if (njs_fast_path(ret >= 0)) { - func = njs_code_ptr(generator, njs_vmcode_function_frame_t, - func_offset); - func->nargs = ret; - return NJS_OK; + func = njs_code_ptr(generator, njs_vmcode_function_frame_t, func_offset); + func->nargs = nargs; + + ret = njs_generate_call(vm, generator, node); + if (njs_fast_path(ret != NJS_OK)) { + return ret; } - return ret; + return NJS_OK; } @@ -2749,7 +2781,7 @@ static njs_int_t njs_generate_method_call(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node) { - njs_int_t ret; + njs_int_t ret, nargs; njs_jump_off_t method_offset; njs_parser_node_t *prop; njs_vmcode_method_frame_t *method; @@ -2777,21 +2809,20 @@ njs_generate_method_call(njs_vm_t *vm, njs_generator_t *generator, method->object = prop->left->index; method->method = prop->right->index; - ret = njs_generate_children_indexes_release(vm, generator, prop); - if (njs_slow_path(ret != NJS_OK)) { - return ret; + nargs = njs_generate_move_arguments(vm, generator, node); + if (njs_slow_path(nargs < 0)) { + return nargs; } - ret = njs_generate_call(vm, generator, node); + method = njs_code_ptr(generator, njs_vmcode_method_frame_t, method_offset); + method->nargs = nargs; - if (njs_fast_path(ret >= 0)) { - method = njs_code_ptr(generator, njs_vmcode_method_frame_t, - method_offset); - method->nargs = ret; - return NJS_OK; + ret = njs_generate_call(vm, generator, node); + if (njs_fast_path(ret != NJS_OK)) { + return ret; } - return ret; + return NJS_OK; } @@ -2799,29 +2830,9 @@ static njs_int_t njs_generate_call(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node) { - njs_int_t ret; - njs_uint_t nargs; njs_index_t retval; - njs_parser_node_t *arg; - njs_vmcode_move_t *move; njs_vmcode_function_call_t *call; - nargs = 0; - - for (arg = node->right; arg != NULL; arg = arg->right) { - nargs++; - - ret = njs_generator(vm, generator, arg->left); - if (njs_slow_path(ret != NJS_OK)) { - return ret; - } - - if (arg->index != arg->left->index) { - njs_generate_code_move(generator, move, arg->index, - arg->left->index, node); - } - } - retval = njs_generate_dest_index(vm, generator, node); if (njs_slow_path(retval == NJS_INDEX_ERROR)) { return retval; @@ -2833,6 +2844,35 @@ njs_generate_call(njs_vm_t *vm, njs_generator_t *generator, NJS_VMCODE_FUNCTION_CALL, 1, node); call->retval = retval; + return NJS_OK; +} + + +static njs_int_t +njs_generate_move_arguments(njs_vm_t *vm, njs_generator_t *generator, + njs_parser_node_t *node) +{ + njs_int_t ret; + njs_uint_t nargs; + njs_parser_node_t *arg; + njs_vmcode_move_arg_t *move_arg; + + nargs = 0; + + for (arg = node->right; arg != NULL; arg = arg->right) { + ret = njs_generator(vm, generator, arg->left); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + njs_generate_code(generator, njs_vmcode_move_arg_t, move_arg, + NJS_VMCODE_MOVE_ARG, 0, node); + move_arg->src = arg->left->index; + move_arg->dst = nargs; + + nargs++; + } + return nargs; } @@ -2849,7 +2889,7 @@ njs_generate_call(njs_vm_t *vm, njs_generator_t *generator, #define njs_generate_code_finally(generator, _code, _retval, _exit, node) \ do { \ njs_generate_code(generator, njs_vmcode_finally_t, _code, \ - NJS_VMCODE_FINALLY, 2, node); \ + NJS_VMCODE_FINALLY, 1, node); \ _code->retval = _retval; \ _code->exit_value = _exit; \ _code->continue_offset = offsetof(njs_vmcode_finally_t, \ @@ -2869,6 +2909,7 @@ njs_generate_try_statement(njs_vm_t *vm, njs_generator_t *generator, njs_index_t exception_index, exit_index, catch_index; njs_jump_off_t try_offset, try_end_offset, catch_offset, catch_end_offset; + njs_variable_t *var; const njs_str_t *dest_label; njs_vmcode_catch_t *catch; njs_vmcode_finally_t *finally; @@ -2928,7 +2969,7 @@ njs_generate_try_statement(njs_vm_t *vm, njs_generator_t *generator, njs_generate_patch_block(vm, generator, try_block->exit); njs_generate_code(generator, njs_vmcode_try_trampoline_t, try_break, - NJS_VMCODE_TRY_BREAK, 2, NULL); + NJS_VMCODE_TRY_BREAK, 1, NULL); try_break->exit_value = exit_index; try_break->offset = -sizeof(njs_vmcode_try_end_t); @@ -2943,7 +2984,7 @@ njs_generate_try_statement(njs_vm_t *vm, njs_generator_t *generator, njs_generate_patch_block(vm, generator, try_block->continuation); njs_generate_code(generator, njs_vmcode_try_trampoline_t, try_continue, - NJS_VMCODE_TRY_CONTINUE, 2, NULL); + NJS_VMCODE_TRY_CONTINUE, 1, NULL); try_continue->exit_value = exit_index; try_continue->offset = -sizeof(njs_vmcode_try_end_t); @@ -2966,11 +3007,13 @@ njs_generate_try_statement(njs_vm_t *vm, njs_generator_t *generator, if (node->token_type == NJS_TOKEN_CATCH) { /* A "try/catch" case. */ - catch_index = njs_variable_index(vm, node->left); - if (njs_slow_path(catch_index == NJS_INDEX_NONE)) { + var = njs_variable_reference(vm, node->left); + if (njs_slow_path(var == NULL)) { return NJS_ERROR; } + catch_index = node->left->index; + njs_generate_code_catch(generator, catch, catch_index, node); ret = njs_generator(vm, generator, node->right); @@ -3025,11 +3068,13 @@ njs_generate_try_statement(njs_vm_t *vm, njs_generator_t *generator, if (node->left != NULL) { /* A try/catch/finally case. */ - catch_index = njs_variable_index(vm, node->left->left); - if (njs_slow_path(catch_index == NJS_INDEX_NONE)) { + var = njs_variable_reference(vm, node->left->left); + if (njs_slow_path(var == NULL)) { return NJS_ERROR; } + catch_index = node->left->left->index; + njs_generate_code_catch(generator, catch, catch_index, node); catch_offset = njs_code_offset(generator, catch); @@ -3057,7 +3102,7 @@ njs_generate_try_statement(njs_vm_t *vm, njs_generator_t *generator, njs_generate_patch_block(vm, generator, catch_block->exit); njs_generate_code(generator, njs_vmcode_try_trampoline_t, - try_break, NJS_VMCODE_TRY_BREAK, 2, NULL); + try_break, NJS_VMCODE_TRY_BREAK, 1, NULL); try_break->exit_value = exit_index; @@ -3074,7 +3119,7 @@ njs_generate_try_statement(njs_vm_t *vm, njs_generator_t *generator, catch_block->continuation); njs_generate_code(generator, njs_vmcode_try_trampoline_t, - try_continue, NJS_VMCODE_TRY_CONTINUE, 2, + try_continue, NJS_VMCODE_TRY_CONTINUE, 1, NULL); try_continue->exit_value = exit_index; @@ -3206,17 +3251,20 @@ njs_generate_import_statement(njs_vm_t *vm, njs_generator_t *generator, njs_int_t ret; njs_index_t index; njs_module_t *module; + njs_variable_t *var; njs_parser_node_t *lvalue, *expr; njs_vmcode_object_copy_t *copy; lvalue = node->left; expr = node->right; - index = njs_variable_index(vm, lvalue); - if (njs_slow_path(index == NJS_INDEX_NONE)) { + var = njs_variable_reference(vm, lvalue); + if (njs_slow_path(var == NULL)) { return NJS_ERROR; } + index = lvalue->index; + if (expr->left != NULL) { ret = njs_generator(vm, generator, expr->left); if (njs_slow_path(ret != NJS_OK)) { @@ -3293,11 +3341,6 @@ njs_generate_object_dest_index(njs_vm_t *vm, njs_generator_t *generator, if (dest != NULL && dest->index != NJS_INDEX_NONE) { index = dest->index; - if (njs_is_callee_argument_index(index)) { - /* Assgin object directly to a callee argument. */ - return index; - } - if (node->left == NULL) { /* Assign empty object directly to variable */ return index; @@ -3338,14 +3381,12 @@ njs_generate_temp_index_get(njs_vm_t *vm, njs_generator_t *generator, return *last; } - scope = node->scope; - - while (scope->type == NJS_SCOPE_BLOCK) { - scope = scope->parent; + scope = njs_function_scope(node->scope); + if (njs_slow_path(scope == NULL)) { + return NJS_ERROR; } - return njs_scope_next_index(vm, scope, NJS_SCOPE_INDEX_LOCAL, - &njs_value_invalid); + return njs_scope_index(scope->type, scope->temp++, NJS_LEVEL_TEMP); } @@ -3427,7 +3468,11 @@ njs_generate_global_reference(njs_vm_t *vm, njs_generator_t *generator, 3, node); prop_get->value = index; - prop_get->object = NJS_INDEX_GLOBAL_OBJECT; + + prop_get->object = njs_scope_global_this_index(); + if (njs_slow_path(prop_get->object == NJS_INDEX_ERROR)) { + return NJS_ERROR; + } lex_entry = njs_lexer_entry(node->u.reference.unique_id); if (njs_slow_path(lex_entry == NULL)) { @@ -3440,8 +3485,9 @@ njs_generate_global_reference(njs_vm_t *vm, njs_generator_t *generator, return NJS_ERROR; } - prop_get->property = njs_value_index(vm, &property, generator->runtime); - if (njs_slow_path(prop_get->property == NJS_INDEX_NONE)) { + prop_get->property = njs_scope_global_index(vm, &property, + generator->runtime); + if (njs_slow_path(prop_get->property == NJS_INDEX_ERROR)) { return NJS_ERROR; } diff --git a/src/njs_generator.h b/src/njs_generator.h index 7b7b0c7f..74d7a5ca 100644 --- a/src/njs_generator.h +++ b/src/njs_generator.h @@ -13,10 +13,9 @@ typedef struct njs_generator_block_s njs_generator_block_t; struct njs_generator_s { njs_value_t *local_scope; - size_t scope_size; - njs_generator_block_t *block; njs_arr_t *index_cache; + njs_arr_t *closures; njs_arr_t *lines; diff --git a/src/njs_lexer.h b/src/njs_lexer.h index fddc30a5..32c0dda4 100644 --- a/src/njs_lexer.h +++ b/src/njs_lexer.h @@ -141,6 +141,7 @@ typedef enum { NJS_TOKEN_TEMPLATE_LITERAL, NJS_TOKEN_FUNCTION, + NJS_TOKEN_FUNCTION_DECLARATION, NJS_TOKEN_FUNCTION_EXPRESSION, NJS_TOKEN_FUNCTION_CALL, NJS_TOKEN_METHOD_CALL, @@ -173,8 +174,6 @@ typedef enum { NJS_TOKEN_THROW, NJS_TOKEN_THIS, - NJS_TOKEN_GLOBAL_OBJECT, - NJS_TOKEN_NON_LOCAL_THIS, NJS_TOKEN_ARGUMENTS, NJS_TOKEN_EVAL, diff --git a/src/njs_main.h b/src/njs_main.h index 6e007b83..5b23d372 100644 --- a/src/njs_main.h +++ b/src/njs_main.h @@ -57,6 +57,7 @@ #include #include #include +#include #include #include diff --git a/src/njs_module.c b/src/njs_module.c index 92edbb90..fa3212ab 100644 --- a/src/njs_module.c +++ b/src/njs_module.c @@ -61,11 +61,12 @@ njs_module_load(njs_vm_t *vm) module = *item; if (module->function.native) { - value = njs_vmcode_operand(vm, module->index); + value = njs_scope_valid_value(vm, module->index); njs_set_object(value, &module->object); } else { - ret = njs_vm_invoke(vm, &module->function, NULL, 0, module->index); + ret = njs_vm_invoke(vm, &module->function, NULL, 0, + njs_scope_valid_value(vm, module->index)); if (ret == NJS_ERROR) { return ret; } @@ -573,11 +574,8 @@ njs_module_insert(njs_parser_t *parser, njs_module_t *module) scope = njs_parser_global_scope(parser); vm = parser->vm; - module->index = njs_scope_next_index(vm, scope, NJS_SCOPE_INDEX_LOCAL, - &njs_value_undefined); - if (njs_slow_path(module->index == NJS_INDEX_ERROR)) { - return NJS_ERROR; - } + module->index = njs_scope_index(scope->type, scope->items, NJS_LEVEL_LOCAL); + scope->items++; if (vm->modules == NULL) { vm->modules = njs_arr_create(vm->mem_pool, 4, sizeof(njs_module_t *)); diff --git a/src/njs_parser.c b/src/njs_parser.c index 65347c56..259e0dc2 100644 --- a/src/njs_parser.c +++ b/src/njs_parser.c @@ -9,7 +9,8 @@ #include -static njs_int_t njs_parser_scope_begin(njs_parser_t *parser, njs_scope_t type); +static njs_int_t njs_parser_scope_begin(njs_parser_t *parser, njs_scope_t type, + njs_bool_t init_this); static void njs_parser_scope_end(njs_parser_t *parser); static njs_int_t njs_parser_check_error_state(njs_parser_t *parser, @@ -426,7 +427,7 @@ static njs_int_t njs_parser_export_sink(njs_parser_t *parser); static njs_parser_node_t *njs_parser_return_set(njs_parser_t *parser, njs_parser_node_t *expr); static njs_parser_node_t *njs_parser_variable_node(njs_parser_t *parser, - uintptr_t unique_id, njs_variable_type_t type); + uintptr_t unique_id, njs_variable_type_t type, njs_variable_t **retvar); static njs_parser_node_t *njs_parser_reference(njs_parser_t *parser, njs_lexer_token_t *token); @@ -503,7 +504,7 @@ njs_parser_reject(njs_parser_t *parser) njs_int_t -njs_parser(njs_vm_t *vm, njs_parser_t *parser, njs_rbtree_t *prev_vars) +njs_parser(njs_vm_t *vm, njs_parser_t *parser) { njs_int_t ret; njs_lexer_token_t *token; @@ -512,20 +513,17 @@ njs_parser(njs_vm_t *vm, njs_parser_t *parser, njs_rbtree_t *prev_vars) njs_set_undefined(&vm->retval); - ret = njs_parser_scope_begin(parser, NJS_SCOPE_GLOBAL); - if (njs_slow_path(ret != NJS_OK)) { - return NJS_ERROR; - } - - if (prev_vars != NULL) { - /* - * Copy the global scope variables from the previous - * iteration of the accumulative mode. - */ - ret = njs_variables_copy(vm, &parser->scope->variables, prev_vars); - if (ret != NJS_OK) { - return ret; + if (parser->scope == NULL) { + ret = njs_parser_scope_begin(parser, NJS_SCOPE_GLOBAL, 1); + if (njs_slow_path(ret != NJS_OK)) { + return NJS_ERROR; } + + } else { + parser->scope->temp = 0; + parser->scope->top = NULL; + parser->node = NULL; + parser->ret = NJS_OK; } njs_queue_init(&parser->stack); @@ -600,33 +598,15 @@ njs_parser_failed_state(njs_parser_t *parser, njs_lexer_token_t *token, static njs_int_t -njs_parser_scope_begin(njs_parser_t *parser, njs_scope_t type) +njs_parser_scope_begin(njs_parser_t *parser, njs_scope_t type, + njs_bool_t init_this) { - njs_arr_t *values; - njs_uint_t nesting; - njs_lexer_t *lexer; - njs_parser_scope_t *scope, *parent; - - nesting = 0; - - if (type == NJS_SCOPE_FUNCTION) { - - for (scope = parser->scope; scope != NULL; scope = scope->parent) { + njs_lexer_t *lexer; + njs_variable_t *var; + njs_parser_scope_t *scope, *parent; + const njs_lexer_keyword_entry_t *keyword; - if (scope->type == NJS_SCOPE_FUNCTION) { - nesting = scope->nesting + 1; - - if (nesting < NJS_MAX_NESTING) { - break; - } - - njs_parser_syntax_error(parser, "The maximum function nesting " - "level is \"%d\"", NJS_MAX_NESTING); - - return NJS_ERROR; - } - } - } + static const njs_str_t njs_this_str = njs_str("this"); scope = njs_mp_zalloc(parser->vm->mem_pool, sizeof(njs_parser_scope_t)); if (njs_slow_path(scope == NULL)) { @@ -635,40 +615,10 @@ njs_parser_scope_begin(njs_parser_t *parser, njs_scope_t type) scope->type = type; - if (type == NJS_SCOPE_FUNCTION) { - scope->next_index[0] = type; - scope->next_index[1] = NJS_SCOPE_CLOSURE + nesting - + sizeof(njs_value_t); - - } else { - if (type == NJS_SCOPE_GLOBAL) { - type += NJS_INDEX_GLOBAL_OFFSET; - } - - scope->next_index[0] = type; - scope->next_index[1] = 0; - } - - scope->nesting = nesting; - scope->argument_closures = 0; - - njs_queue_init(&scope->nested); njs_rbtree_init(&scope->variables, njs_parser_scope_rbtree_compare); njs_rbtree_init(&scope->labels, njs_parser_scope_rbtree_compare); njs_rbtree_init(&scope->references, njs_parser_scope_rbtree_compare); - values = NULL; - - if (scope->type < NJS_SCOPE_BLOCK) { - values = njs_arr_create(parser->vm->mem_pool, 4, sizeof(njs_value_t)); - if (njs_slow_path(values == NULL)) { - return NJS_ERROR; - } - } - - scope->values[0] = values; - scope->values[1] = NULL; - lexer = parser->lexer; if (lexer->file.length != 0) { @@ -680,15 +630,27 @@ njs_parser_scope_begin(njs_parser_t *parser, njs_scope_t type) scope->parent = parent; parser->scope = scope; - if (parent != NULL) { - njs_queue_insert_tail(&parent->nested, &scope->link); + if (type == NJS_SCOPE_FUNCTION || type == NJS_SCOPE_GLOBAL) { + if (init_this) { + /* Add this as first variable. */ + keyword = njs_lexer_keyword(njs_this_str.start, + njs_this_str.length); + if (njs_slow_path(keyword == NULL)) { + return NJS_ERROR; + } - if (nesting == 0) { - /* Inherit function nesting in blocks. */ - scope->nesting = parent->nesting; + var = njs_variable_add(parser, scope, (uintptr_t) keyword->value, + NJS_VARIABLE_VAR); + if (njs_slow_path(var == NULL)) { + return NJS_ERROR; + } + + var->index = njs_scope_index(type, 0, NJS_LEVEL_LOCAL); } } + scope->items = 1; + return NJS_OK; } @@ -799,7 +761,7 @@ njs_parser_function_or_generator(njs_parser_t *parser, njs_parser_next(parser, njs_parser_generator_declaration); } else { - node = njs_parser_node_new(parser, NJS_TOKEN_FUNCTION); + node = njs_parser_node_new(parser, NJS_TOKEN_FUNCTION_DECLARATION); if (node == NULL) { return NJS_ERROR; } @@ -1324,7 +1286,11 @@ njs_parser_template_literal(njs_parser_t *parser, njs_lexer_token_t *token, array->token_line = token->line; template = parser->node; - index = NJS_SCOPE_CALLEE_ARGUMENTS; + + index = njs_scope_temp_index(template->scope); + if (njs_slow_path(index == NJS_INDEX_ERROR)) { + return NJS_ERROR; + } if (template->token_type != NJS_TOKEN_TEMPLATE_LITERAL) { node = njs_parser_argument(parser, array, index); @@ -1335,7 +1301,10 @@ njs_parser_template_literal(njs_parser_t *parser, njs_lexer_token_t *token, template->right = node; temp->right = node; - index += sizeof(njs_value_t); + index = njs_scope_temp_index(template->scope); + if (njs_slow_path(index == NJS_INDEX_ERROR)) { + return NJS_ERROR; + } } else { template->left = array; @@ -1431,7 +1400,10 @@ njs_parser_template_literal_expression(njs_parser_t *parser, parent->right = node; parent = node; - parser->target->index += sizeof(njs_value_t); + parser->target->index = njs_scope_temp_index(node->scope); + if (njs_slow_path(parser->target->index == NJS_INDEX_ERROR)) { + return NJS_ERROR; + } } else { ret = njs_parser_array_item(parser, template->left, parser->node); @@ -2068,7 +2040,7 @@ njs_parser_computed_property_name_after(njs_parser_t *parser, /* MethodDefinition */ } else if (token->type == NJS_TOKEN_OPEN_PARENTHESIS) { - expr = njs_parser_node_new(parser, NJS_TOKEN_FUNCTION_EXPRESSION); + expr = njs_parser_node_new(parser, NJS_TOKEN_FUNCTION); if (expr == NULL) { return NJS_ERROR; } @@ -2796,11 +2768,9 @@ njs_parser_argument_list_after(njs_parser_t *parser, njs_lexer_token_t *token, return NJS_ERROR; } - if (parser->target->index == 0) { - node->index = NJS_SCOPE_CALLEE_ARGUMENTS; - - } else { - node->index = parser->target->index + sizeof(njs_value_t); + node->index = njs_scope_temp_index(node->scope); + if (njs_slow_path(node->index == NJS_INDEX_ERROR)) { + return NJS_ERROR; } node->token_line = token->line; @@ -4628,7 +4598,7 @@ njs_parser_block_statement(njs_parser_t *parser, njs_lexer_token_t *token, void *target; njs_int_t ret; - ret = njs_parser_scope_begin(parser, NJS_SCOPE_BLOCK); + ret = njs_parser_scope_begin(parser, NJS_SCOPE_BLOCK, 0); if (ret != NJS_OK) { return NJS_ERROR; } @@ -4812,6 +4782,7 @@ njs_parser_variable_declaration(njs_parser_t *parser, njs_lexer_token_t *token, njs_queue_link_t *current) { njs_int_t ret; + njs_variable_t *var; njs_parser_node_t *name; ret = njs_parser_binding_pattern(parser, token, current); @@ -4830,11 +4801,17 @@ njs_parser_variable_declaration(njs_parser_t *parser, return NJS_DONE; } - name = njs_parser_variable_node(parser, token->unique_id, NJS_VARIABLE_VAR); + name = njs_parser_variable_node(parser, token->unique_id, NJS_VARIABLE_VAR, + &var); if (name == NULL) { return NJS_ERROR; } + if (var->self) { + var->type = NJS_VARIABLE_VAR; + var->self = 0; + } + name->token_line = token->line; parser->node = name; @@ -5340,7 +5317,7 @@ njs_parser_for_var_binding_or_var_list(njs_parser_t *parser, } var = njs_parser_variable_node(parser, token->unique_id, - NJS_VARIABLE_VAR); + NJS_VARIABLE_VAR, NULL); if (var == NULL) { return NJS_ERROR; } @@ -6259,7 +6236,7 @@ njs_parser_catch_or_finally(njs_parser_t *parser, njs_lexer_token_t *token, return NJS_ERROR; } - ret = njs_parser_scope_begin(parser, NJS_SCOPE_BLOCK); + ret = njs_parser_scope_begin(parser, NJS_SCOPE_BLOCK, 0); if (ret != NJS_OK) { return NJS_ERROR; } @@ -6292,7 +6269,7 @@ njs_parser_catch_or_finally(njs_parser_t *parser, njs_lexer_token_t *token, if (njs_lexer_token_is_binding_identifier(token)) { node = njs_parser_variable_node(parser, token->unique_id, - NJS_VARIABLE_CATCH); + NJS_VARIABLE_CATCH, NULL); if (node == NULL) { return NJS_ERROR; } @@ -6405,43 +6382,6 @@ njs_parser_debugger_statement(njs_parser_t *parser, njs_lexer_token_t *token, /* * 14.1 Function Definitions. */ -static njs_function_t * -njs_parser_function_alloc(njs_parser_t *parser, njs_variable_t *var) -{ - njs_value_t *value; - njs_function_t *function; - njs_function_lambda_t *lambda; - - lambda = njs_function_lambda_alloc(parser->vm, 1); - if (lambda == NULL) { - njs_memory_error(parser->vm); - return NULL; - } - - /* TODO: - * njs_function_t is used to pass lambda to - * njs_generate_function_declaration() and is not actually needed. - * real njs_function_t is created by njs_vmcode_function() in runtime. - */ - - function = njs_function_alloc(parser->vm, lambda, NULL, 1); - if (function == NULL) { - return NULL; - } - - njs_set_function(&var->value, function); - - if (var->index != NJS_INDEX_NONE - && njs_scope_accumulative(parser->vm, parser->scope)) - { - value = (njs_value_t *) var->index; - *value = var->value; - } - - return function; -} - - static njs_int_t njs_parser_function_declaration(njs_parser_t *parser, njs_lexer_token_t *token, njs_queue_link_t *current) @@ -6449,8 +6389,7 @@ njs_parser_function_declaration(njs_parser_t *parser, njs_lexer_token_t *token, njs_int_t ret; uintptr_t unique_id; njs_variable_t *var; - njs_function_t *function; - njs_parser_node_t *node, *temp; + njs_parser_node_t *node; if (!njs_lexer_token_is_binding_identifier(token)) { return njs_parser_failed(parser); @@ -6478,38 +6417,26 @@ njs_parser_function_declaration(njs_parser_t *parser, njs_lexer_token_t *token, njs_lexer_consume_token(parser->lexer, 1); - var = njs_variable_add(parser, parser->scope, unique_id, - NJS_VARIABLE_FUNCTION); + var = njs_variable_function_add(parser, parser->scope, unique_id, + NJS_VARIABLE_FUNCTION); if (var == NULL) { return NJS_ERROR; } - ret = njs_variable_reference(parser->vm, parser->scope, node, - unique_id, NJS_DECLARATION); - if (ret != NJS_OK) { - return NJS_ERROR; - } + node->u.value.data.u.lambda = var->value.data.u.lambda; - function = njs_parser_function_alloc(parser, var); - if (function == NULL) { - return NJS_ERROR; - } + node->left = (njs_parser_node_t *) unique_id; - temp = njs_parser_node_new(parser, 0); - if (temp == NULL) { + parser->node = node; + + ret = njs_parser_scope_begin(parser, NJS_SCOPE_FUNCTION, 1); + if (ret != NJS_OK) { return NJS_ERROR; } - temp->left = node; - temp->u.value.data.u.lambda = function->u.lambda; - - node->left = (njs_parser_node_t *) function; - - parser->node = temp; - njs_parser_next(parser, njs_parser_function_parse); - return njs_parser_after(parser, current, temp, 1, + return njs_parser_after(parser, current, node, 1, njs_parser_function_declaration_after); } @@ -6518,17 +6445,20 @@ static njs_int_t njs_parser_function_declaration_after(njs_parser_t *parser, njs_lexer_token_t *token, njs_queue_link_t *current) { - njs_function_t *function; + njs_int_t ret; + uintptr_t unique_id; - parser->node = parser->target->left; + unique_id = (uintptr_t) parser->node->left; - function = (njs_function_t *) parser->node->left; + parser->node->left = NULL; - function->args_count = function->u.lambda->nargs - - function->u.lambda->rest_parameters; + njs_value_null_set(&parser->node->u.value); - parser->node->right = parser->target->right; - parser->node->left = NULL; + ret = njs_parser_variable_reference(parser, parser->scope, parser->node, + unique_id, NJS_DECLARATION); + if (ret != NJS_OK) { + return NJS_ERROR; + } return njs_parser_stack_pop(parser); } @@ -6538,13 +6468,6 @@ static njs_int_t njs_parser_function_parse(njs_parser_t *parser, njs_lexer_token_t *token, njs_queue_link_t *current) { - njs_int_t ret; - - ret = njs_parser_scope_begin(parser, NJS_SCOPE_FUNCTION); - if (ret != NJS_OK) { - return NJS_ERROR; - } - parser->target = parser->node; parser->node = NULL; @@ -6555,26 +6478,30 @@ njs_parser_function_parse(njs_parser_t *parser, njs_lexer_token_t *token, } +static const njs_lexer_entry_t njs_parser_anonymous_entry = +{ + .name = njs_str("anonymous") +}; + + static njs_int_t njs_parser_function_expression(njs_parser_t *parser, njs_lexer_token_t *token, njs_queue_link_t *current) { njs_int_t ret; + uintptr_t unique_id; njs_variable_t *var; - njs_function_t *function; njs_function_lambda_t *lambda; - ret = njs_parser_scope_begin(parser, NJS_SCOPE_SHIM); + ret = njs_parser_scope_begin(parser, NJS_SCOPE_FUNCTION, 1); if (ret != NJS_OK) { return NJS_ERROR; } + var = NULL; + if (njs_lexer_token_is_binding_identifier(token)) { - var = njs_variable_add(parser, parser->scope, token->unique_id, - NJS_VARIABLE_SHIM); - if (var == NULL) { - return NJS_ERROR; - } + unique_id = token->unique_id; njs_lexer_consume_token(parser->lexer, 1); @@ -6583,19 +6510,8 @@ njs_parser_function_expression(njs_parser_t *parser, njs_lexer_token_t *token, return NJS_ERROR; } - function = njs_parser_function_alloc(parser, var); - if (function == NULL) { - return NJS_ERROR; - } - - lambda = function->u.lambda; - } else { - /* Anonymous function. */ - lambda = njs_function_lambda_alloc(parser->vm, 1); - if (lambda == NULL) { - return NJS_ERROR; - } + unique_id = (uintptr_t) &njs_parser_anonymous_entry; } if (token->type != NJS_TOKEN_OPEN_PARENTHESIS) { @@ -6604,11 +6520,36 @@ njs_parser_function_expression(njs_parser_t *parser, njs_lexer_token_t *token, njs_lexer_consume_token(parser->lexer, 1); + parser->node->left = njs_parser_node_new(parser, NJS_TOKEN_NAME); + if (parser->node->left == NULL) { + return NJS_ERROR; + } + + var = njs_variable_scope_add(parser, parser->scope, parser->scope, + unique_id, NJS_VARIABLE_FUNCTION, 1); + if (var == NULL) { + return NJS_ERROR; + } + + var->self = 1; + + ret = njs_parser_variable_reference(parser, parser->scope, + parser->node->left, unique_id, + NJS_DECLARATION); + if (ret != NJS_OK) { + return NJS_ERROR; + } + + lambda = njs_function_lambda_alloc(parser->vm, 1); + if (lambda == NULL) { + return NJS_ERROR; + } + parser->node->u.value.data.u.lambda = lambda; njs_parser_next(parser, njs_parser_function_parse); - return njs_parser_after(parser, current, NULL, 1, + return njs_parser_after(parser, current, var, 1, njs_parser_function_expression_after); } @@ -6617,7 +6558,17 @@ static njs_int_t njs_parser_function_expression_after(njs_parser_t *parser, njs_lexer_token_t *token, njs_queue_link_t *current) { - njs_parser_scope_end(parser); + njs_variable_t *var; + + var = (njs_variable_t *) parser->target; + + var->index = njs_scope_index(var->scope->type, var->scope->items, + NJS_LEVEL_LOCAL); + var->scope->items++; + + if (var->self) { + parser->node->u.value.data.u.lambda->self = var->index; + } return njs_parser_stack_pop(parser); } @@ -6639,7 +6590,9 @@ static njs_int_t njs_parser_formal_parameters(njs_parser_t *parser, njs_lexer_token_t *token, njs_queue_link_t *current) { - njs_variable_t *arg, *cur_arg; + njs_variable_t *arg; + njs_rbtree_node_t *rb_node; + njs_variable_node_t var_node; njs_function_lambda_t *lambda; lambda = parser->target->u.value.data.u.lambda; @@ -6667,29 +6620,32 @@ njs_parser_formal_parameters(njs_parser_t *parser, njs_lexer_token_t *token, default: /* SingleNameBinding */ if (njs_lexer_token_is_binding_identifier(token)) { - arg = njs_variable_add(parser, parser->scope, - token->unique_id, NJS_VARIABLE_VAR); - if (arg == NULL) { - return NJS_ERROR; - } + var_node.key = token->unique_id; - if (arg->index > 0) { - njs_parser_syntax_error(parser, "Duplicate parameter names"); - return NJS_DONE; - } + rb_node = njs_rbtree_find(&parser->scope->variables, + &var_node.node); + if (rb_node != NULL) { + arg = ((njs_variable_node_t *) rb_node)->variable; - cur_arg = (njs_variable_t *) parser->node; - - if (cur_arg == NULL) { - arg->index = NJS_SCOPE_ARGUMENTS; + if (!arg->self) { + njs_parser_syntax_error(parser, + "Duplicate parameter names"); + return NJS_DONE; + } - /* A "this" reservation. */ - arg->index += sizeof(njs_value_t); + arg->self = 0; } else { - arg->index = cur_arg->index + sizeof(njs_value_t); + arg = njs_variable_add(parser, parser->scope, + token->unique_id, NJS_VARIABLE_VAR); + } + + if (arg == NULL) { + return NJS_ERROR; } + arg->argument = 1; + lambda->nargs++; /* Crutch for temporary storage. */ @@ -6744,8 +6700,9 @@ njs_parser_arrow_function(njs_parser_t *parser, njs_lexer_token_t *token, njs_queue_link_t *current) { njs_int_t ret; - njs_variable_t *arg; - njs_parser_node_t *node; + uintptr_t unique_id; + njs_variable_t *arg, *var; + njs_parser_node_t *node, *name; njs_function_lambda_t *lambda; node = njs_parser_node_new(parser, NJS_TOKEN_FUNCTION_EXPRESSION); @@ -6756,18 +6713,41 @@ njs_parser_arrow_function(njs_parser_t *parser, njs_lexer_token_t *token, node->token_line = token->line; parser->node = node; - lambda = njs_function_lambda_alloc(parser->vm, 0); - if (lambda == NULL) { + ret = njs_parser_scope_begin(parser, NJS_SCOPE_FUNCTION, 0); + if (ret != NJS_OK) { return NJS_ERROR; } - node->u.value.data.u.lambda = lambda; + name = njs_parser_node_new(parser, NJS_TOKEN_NAME); + if (name == NULL) { + return NJS_ERROR; + } + + node->left = name; - ret = njs_parser_scope_begin(parser, NJS_SCOPE_FUNCTION); + unique_id = (uintptr_t) &njs_parser_anonymous_entry; + + var = njs_variable_scope_add(parser, parser->scope, parser->scope, + unique_id, NJS_VARIABLE_FUNCTION, 1); + if (var == NULL) { + return NJS_ERROR; + } + + ret = njs_parser_variable_reference(parser, parser->scope, node->left, + unique_id, NJS_DECLARATION); if (ret != NJS_OK) { return NJS_ERROR; } + node->left->u.reference.variable = var; + + lambda = njs_function_lambda_alloc(parser->vm, 0); + if (lambda == NULL) { + return NJS_ERROR; + } + + node->u.value.data.u.lambda = lambda; + parser->scope->arrow_function = 1; if (token->type == NJS_TOKEN_OPEN_PARENTHESIS) { @@ -6782,18 +6762,20 @@ njs_parser_arrow_function(njs_parser_t *parser, njs_lexer_token_t *token, njs_parser_arrow_function_args_after); } else if (njs_lexer_token_is_binding_identifier(token)) { - arg = njs_variable_add(parser, parser->scope, - token->unique_id, NJS_VARIABLE_VAR); + arg = njs_variable_add(parser, parser->scope, token->unique_id, + NJS_VARIABLE_VAR); if (arg == NULL) { return NJS_ERROR; } - arg->index = NJS_SCOPE_ARGUMENTS; + arg->argument = 1; - /* A "this" reservation. */ - arg->index += sizeof(njs_value_t); + var->index = njs_scope_index(parser->scope->type, parser->scope->items, + NJS_LEVEL_LOCAL); + parser->scope->items++; - lambda->nargs = 1; + lambda->self = var->index; + lambda->nargs++; njs_lexer_consume_token(parser->lexer, 1); @@ -6813,12 +6795,25 @@ static njs_int_t njs_parser_arrow_function_args_after(njs_parser_t *parser, njs_lexer_token_t *token, njs_queue_link_t *current) { + njs_variable_t *var, **vv; + if (token->type != NJS_TOKEN_CLOSE_PARENTHESIS) { return njs_parser_failed(parser); } njs_lexer_consume_token(parser->lexer, 1); + vv = &parser->target->left->u.reference.variable; + + var = *vv; + *vv = NULL; + + var->index = njs_scope_index(var->scope->type, var->scope->items, + NJS_LEVEL_LOCAL); + var->scope->items++; + + parser->target->u.value.data.u.lambda->self = var->index; + njs_parser_next(parser, njs_parser_arrow_function_arrow); return NJS_OK; @@ -6929,7 +6924,7 @@ njs_parser_method_definition(njs_parser_t *parser, njs_lexer_token_t *token, return njs_parser_failed(parser); } - expr = njs_parser_node_new(parser, NJS_TOKEN_FUNCTION_EXPRESSION); + expr = njs_parser_node_new(parser, NJS_TOKEN_FUNCTION); if (expr == NULL) { return NJS_ERROR; } @@ -7003,7 +6998,7 @@ njs_parser_get_set(njs_parser_t *parser, njs_lexer_token_t *token, return njs_parser_failed(parser); } - expression = njs_parser_node_new(parser, NJS_TOKEN_FUNCTION_EXPRESSION); + expression = njs_parser_node_new(parser, NJS_TOKEN_FUNCTION); if (expression == NULL) { return NJS_ERROR; } @@ -7047,7 +7042,7 @@ njs_parser_get_set_after(njs_parser_t *parser, njs_lexer_token_t *token, return njs_parser_failed(parser); } - expression = njs_parser_node_new(parser, NJS_TOKEN_FUNCTION_EXPRESSION); + expression = njs_parser_node_new(parser, NJS_TOKEN_FUNCTION); if (expression == NULL) { return NJS_ERROR; } @@ -7152,7 +7147,7 @@ njs_parser_function_lambda(njs_parser_t *parser, expr = parser->node; expr->u.value.data.u.lambda = lambda; - ret = njs_parser_scope_begin(parser, NJS_SCOPE_FUNCTION); + ret = njs_parser_scope_begin(parser, NJS_SCOPE_FUNCTION, 1); if (ret != NJS_OK) { return NJS_ERROR; } @@ -7321,7 +7316,8 @@ njs_parser_import(njs_parser_t *parser, njs_lexer_token_t *token, return NJS_DONE; } - name = njs_parser_variable_node(parser, token->unique_id, NJS_VARIABLE_VAR); + name = njs_parser_variable_node(parser, token->unique_id, NJS_VARIABLE_VAR, + NULL); if (name == NULL) { return NJS_ERROR; } @@ -7376,17 +7372,24 @@ njs_parser_import_after(njs_parser_t *parser, njs_lexer_token_t *token, parser->target->right = parser->node; parser->node = parser->target; - parser->node->hoist = 1; return njs_parser_stack_pop(parser); } +static const njs_lexer_entry_t njs_parser_module_entry = +{ + .name = njs_str("module") +}; + + njs_int_t njs_parser_module_lambda(njs_parser_t *parser, njs_lexer_token_t *token, njs_queue_link_t *current) { njs_int_t ret; + uintptr_t unique_id; + njs_variable_t *var; njs_parser_node_t *node, *parent; njs_function_lambda_t *lambda; @@ -7395,6 +7398,32 @@ njs_parser_module_lambda(njs_parser_t *parser, njs_lexer_token_t *token, return NJS_ERROR; } + ret = njs_parser_scope_begin(parser, NJS_SCOPE_FUNCTION, 0); + if (ret != NJS_OK) { + return NJS_ERROR; + } + + node->left = njs_parser_node_new(parser, NJS_TOKEN_NAME); + if (node->left == NULL) { + return NJS_ERROR; + } + + unique_id = (uintptr_t) &njs_parser_module_entry; + + var = njs_variable_scope_add(parser, parser->scope, parser->scope, + unique_id, NJS_VARIABLE_FUNCTION, 1); + if (var == NULL) { + return NJS_ERROR; + } + + ret = njs_parser_variable_reference(parser, parser->scope, node->left, + unique_id, NJS_DECLARATION); + if (ret != NJS_OK) { + return NJS_ERROR; + } + + node->left->u.reference.variable = var; + lambda = njs_function_lambda_alloc(parser->vm, 1); if (lambda == NULL) { return NJS_ERROR; @@ -7405,11 +7434,6 @@ njs_parser_module_lambda(njs_parser_t *parser, njs_lexer_token_t *token, parser->node = node; - ret = njs_parser_scope_begin(parser, NJS_SCOPE_FUNCTION); - if (ret != NJS_OK) { - return NJS_ERROR; - } - parser->scope->module = 1; parent = parser->node; @@ -7426,7 +7450,8 @@ static njs_int_t njs_parser_module_lambda_after(njs_parser_t *parser, njs_lexer_token_t *token, njs_queue_link_t *current) { - njs_int_t ret; + njs_int_t ret; + njs_variable_t *var, **vv; ret = njs_parser_export_sink(parser); if (ret != NJS_OK) { @@ -7437,6 +7462,17 @@ njs_parser_module_lambda_after(njs_parser_t *parser, njs_lexer_token_t *token, parser->node = parser->target; + vv = &parser->target->left->u.reference.variable; + + var = *vv; + *vv = NULL; + + var->index = njs_scope_index(var->scope->type, var->scope->items, + NJS_LEVEL_LOCAL); + var->scope->items++; + + parser->node->u.value.data.u.lambda->self = var->index; + njs_parser_scope_end(parser); return njs_parser_stack_pop(parser); @@ -7529,7 +7565,7 @@ njs_parser_return_set(njs_parser_t *parser, njs_parser_node_t *expr) static njs_parser_node_t * njs_parser_variable_node(njs_parser_t *parser, uintptr_t unique_id, - njs_variable_type_t type) + njs_variable_type_t type, njs_variable_t **retvar) { njs_int_t ret; njs_variable_t *var; @@ -7540,17 +7576,8 @@ njs_parser_variable_node(njs_parser_t *parser, uintptr_t unique_id, return NULL; } - if (njs_is_null(&var->value)) { - - switch (type) { - - case NJS_VARIABLE_VAR: - njs_set_undefined(&var->value); - break; - - default: - break; - } + if (retvar != NULL) { + *retvar = var; } node = njs_parser_node_new(parser, NJS_TOKEN_NAME); @@ -7558,8 +7585,8 @@ njs_parser_variable_node(njs_parser_t *parser, uintptr_t unique_id, return NULL; } - ret = njs_variable_reference(parser->vm, parser->scope, node, unique_id, - NJS_DECLARATION); + ret = njs_parser_variable_reference(parser, parser->scope, node, unique_id, + NJS_DECLARATION); if (njs_slow_path(ret != NJS_OK)) { return NULL; } @@ -7571,10 +7598,14 @@ njs_parser_variable_node(njs_parser_t *parser, uintptr_t unique_id, static njs_parser_node_t * njs_parser_reference(njs_parser_t *parser, njs_lexer_token_t *token) { - njs_int_t ret; - njs_variable_t *var; - njs_parser_node_t *node; - njs_parser_scope_t *scope; + njs_int_t ret; + njs_index_t index; + njs_variable_t *var; + njs_parser_node_t *node; + njs_parser_scope_t *scope; + const njs_lexer_keyword_entry_t *keyword; + + static const njs_str_t njs_undefined_str = njs_str("undefined"); node = njs_parser_node_new(parser, token->type); if (njs_slow_path(node == NULL)) { @@ -7585,51 +7616,58 @@ njs_parser_reference(njs_parser_t *parser, njs_lexer_token_t *token) case NJS_TOKEN_NULL: njs_thread_log_debug("JS: null"); - - node->u.value = njs_value_null; break; case NJS_TOKEN_THIS: njs_thread_log_debug("JS: this"); - scope = njs_function_scope(parser->scope, 0); - - if (scope != NULL) { - if (scope == njs_function_scope(parser->scope, 1)) { - node->index = NJS_INDEX_THIS; + scope = njs_function_scope(parser->scope); + if (njs_slow_path(scope == NULL)) { + njs_parser_syntax_error(parser, + "function or global scope not found"); + return NULL; + } - } else { - node->token_type = NJS_TOKEN_NON_LOCAL_THIS; - node->token_line = token->line; + if (parser->vm->options.module) { + keyword = njs_lexer_keyword(njs_undefined_str.start, + njs_undefined_str.length); + if (njs_slow_path(keyword == NULL)) { + return NULL; + } - ret = njs_variable_reference(parser->vm, scope, node, - token->unique_id, NJS_REFERENCE); - if (njs_slow_path(ret != NJS_OK)) { - return NULL; - } + token->unique_id = (uintptr_t) keyword->value; - var = njs_variable_add(parser, scope, token->unique_id, - NJS_VARIABLE_VAR); - if (njs_slow_path(var == NULL)) { - return NULL; - } + } else if (!scope->arrow_function) { + index = njs_scope_index(scope->type, 0, NJS_LEVEL_LOCAL); - var->this_object = 1; + var = njs_variable_scope_add(parser, scope, scope, token->unique_id, + NJS_VARIABLE_VAR, index); + if (njs_slow_path(var == NULL)) { + return NULL; } - - break; } - node->token_type = NJS_TOKEN_GLOBAL_OBJECT; + node->token_type = NJS_TOKEN_THIS; + node->token_line = token->line; + + ret = njs_parser_variable_reference(parser, parser->scope, node, + token->unique_id, NJS_REFERENCE); + if (njs_slow_path(ret != NJS_OK)) { + return NULL; + } break; case NJS_TOKEN_ARGUMENTS: njs_thread_log_debug("JS: arguments"); - scope = njs_function_scope(parser->scope, 0); + scope = njs_function_scope(parser->scope); - if (scope == NULL) { + while (scope->arrow_function) { + scope = njs_function_scope(scope->parent); + } + + if (scope == NULL || scope->type == NJS_SCOPE_GLOBAL) { njs_parser_syntax_error(parser, "\"%V\" object in global scope", &token->text); return NULL; @@ -7637,8 +7675,8 @@ njs_parser_reference(njs_parser_t *parser, njs_lexer_token_t *token) node->token_line = token->line; - ret = njs_variable_reference(parser->vm, scope, node, token->unique_id, - NJS_REFERENCE); + ret = njs_parser_variable_reference(parser, parser->scope, node, + token->unique_id, NJS_REFERENCE); if (njs_slow_path(ret != NJS_OK)) { return NULL; } @@ -7665,8 +7703,8 @@ njs_parser_reference(njs_parser_t *parser, njs_lexer_token_t *token) node->token_line = token->line; - ret = njs_variable_reference(parser->vm, parser->scope, node, - token->unique_id, NJS_REFERENCE); + ret = njs_parser_variable_reference(parser, parser->scope, node, + token->unique_id, NJS_REFERENCE); if (njs_slow_path(ret != NJS_OK)) { return NULL; } @@ -8322,6 +8360,41 @@ njs_parser_has_side_effect(njs_parser_node_t *node) } +njs_int_t +njs_parser_variable_reference(njs_parser_t *parser, njs_parser_scope_t *scope, + njs_parser_node_t *node, uintptr_t unique_id, njs_reference_type_t type) +{ + njs_rbtree_node_t *rb_node; + njs_variable_reference_t *vr; + njs_parser_rbtree_node_t parse_node, *rb_parse_node; + + vr = &node->u.reference; + + vr->unique_id = unique_id; + vr->type = type; + + parse_node.key = unique_id; + + rb_node = njs_rbtree_find(&scope->references, &parse_node.node); + if (rb_node != NULL) { + return NJS_OK; + } + + rb_parse_node = njs_mp_alloc(parser->vm->mem_pool, + sizeof(njs_parser_rbtree_node_t)); + if (njs_slow_path(rb_parse_node == NULL)) { + return NJS_ERROR; + } + + rb_parse_node->key = unique_id; + rb_parse_node->index = NJS_INDEX_NONE; + + njs_rbtree_insert(&scope->references, &rb_parse_node->node); + + return NJS_OK; +} + + njs_token_type_t njs_parser_unexpected_token(njs_vm_t *vm, njs_parser_t *parser, njs_str_t *name, njs_token_type_t type) @@ -8632,8 +8705,6 @@ njs_parser_serialize_node(njs_chb_t *chain, njs_parser_node_t *node) njs_token_serialize(NJS_TOKEN_FINALLY); njs_token_serialize(NJS_TOKEN_THROW); njs_token_serialize(NJS_TOKEN_THIS); - njs_token_serialize(NJS_TOKEN_GLOBAL_OBJECT); - njs_token_serialize(NJS_TOKEN_NON_LOCAL_THIS); njs_token_serialize(NJS_TOKEN_ARGUMENTS); njs_token_serialize(NJS_TOKEN_EVAL); njs_token_serialize(NJS_TOKEN_IMPORT); diff --git a/src/njs_parser.h b/src/njs_parser.h index 4fe8ea5e..854ac9b6 100644 --- a/src/njs_parser.h +++ b/src/njs_parser.h @@ -12,26 +12,21 @@ struct njs_parser_scope_s { njs_parser_node_t *top; - njs_queue_link_t link; - njs_queue_t nested; - njs_parser_scope_t *parent; njs_rbtree_t variables; njs_rbtree_t labels; njs_rbtree_t references; -#define NJS_SCOPE_INDEX_LOCAL 0 -#define NJS_SCOPE_INDEX_CLOSURE 1 + njs_arr_t *closures; + njs_arr_t *declarations; - njs_arr_t *values[2]; /* Array of njs_value_t. */ - njs_index_t next_index[2]; + uint32_t temp; + uint32_t items; njs_str_t cwd; njs_str_t file; njs_scope_t type:8; - uint8_t nesting; /* 4 bits */ - uint8_t argument_closures; uint8_t module; uint8_t arrow_function; }; @@ -41,7 +36,6 @@ struct njs_parser_node_s { njs_token_type_t token_type:16; uint8_t ctor:1; uint8_t temporary; /* 1 bit */ - uint8_t hoist; /* 1 bit */ uint32_t token_line; union { @@ -101,7 +95,7 @@ typedef struct { typedef struct { NJS_RBTREE_NODE (node); uintptr_t key; - njs_parser_node_t *parser_node; + njs_index_t index; } njs_parser_rbtree_node_t; @@ -110,14 +104,16 @@ njs_int_t njs_parser_failed_state(njs_parser_t *parser, intptr_t njs_parser_scope_rbtree_compare(njs_rbtree_node_t *node1, njs_rbtree_node_t *node2); -njs_int_t njs_parser(njs_vm_t *vm, njs_parser_t *parser, - njs_rbtree_t *prev_vars); +njs_int_t njs_parser(njs_vm_t *vm, njs_parser_t *parser); njs_int_t njs_parser_module_lambda(njs_parser_t *parser, njs_lexer_token_t *token, njs_queue_link_t *current); njs_variable_t *njs_variable_resolve(njs_vm_t *vm, njs_parser_node_t *node); njs_index_t njs_variable_index(njs_vm_t *vm, njs_parser_node_t *node); njs_bool_t njs_parser_has_side_effect(njs_parser_node_t *node); +njs_int_t njs_parser_variable_reference(njs_parser_t *parser, + njs_parser_scope_t *scope, njs_parser_node_t *node, uintptr_t unique_id, + njs_reference_type_t type); njs_token_type_t njs_parser_unexpected_token(njs_vm_t *vm, njs_parser_t *parser, njs_str_t *name, njs_token_type_t type); njs_int_t njs_parser_string_create(njs_vm_t *vm, njs_lexer_token_t *token, @@ -139,10 +135,6 @@ njs_int_t njs_parser_serialize_ast(njs_parser_node_t *node, njs_chb_t *chain); || (node)->token_type == NJS_TOKEN_PROPERTY) -#define njs_scope_accumulative(vm, scope) \ - ((vm)->options.accumulative && (scope)->type == NJS_SCOPE_GLOBAL) - - #define njs_parser_syntax_error(parser, fmt, ...) \ njs_parser_lexer_error(parser, NJS_OBJ_TYPE_SYNTAX_ERROR, fmt, \ ##__VA_ARGS__) @@ -215,17 +207,18 @@ njs_parser_global_scope(njs_parser_t *parser) njs_inline njs_parser_scope_t * -njs_function_scope(njs_parser_scope_t *scope, njs_bool_t any) +njs_function_scope(njs_parser_scope_t *scope) { - while (scope->type != NJS_SCOPE_GLOBAL) { - if (scope->type == NJS_SCOPE_FUNCTION - && (any || !scope->arrow_function)) + do { + if (scope->type == NJS_SCOPE_GLOBAL + || scope->type == NJS_SCOPE_FUNCTION) { return scope; } scope = scope->parent; - } + + } while (scope != NULL); return NULL; } diff --git a/src/njs_scope.c b/src/njs_scope.c new file mode 100644 index 00000000..4d903cdd --- /dev/null +++ b/src/njs_scope.c @@ -0,0 +1,259 @@ + +/* + * Copyright (C) Alexander Borisov + * Copyright (C) NGINX, Inc. + */ + + +#include + + +static njs_value_t *njs_scope_value_index(njs_vm_t *vm, const njs_value_t *src, + njs_uint_t runtime, njs_index_t **index); + + +njs_index_t +njs_scope_temp_index(njs_parser_scope_t *scope) +{ + scope = njs_function_scope(scope); + if (njs_slow_path(scope == NULL)) { + return NJS_INDEX_ERROR; + } + + return njs_scope_index(NJS_SCOPE_GLOBAL, scope->temp++, NJS_LEVEL_TEMP); +} + + +njs_value_t * +njs_scope_create_index_value(njs_vm_t *vm, njs_index_t index) +{ + njs_value_t *value; + + value = njs_mp_alloc(vm->mem_pool, sizeof(njs_value_t)); + if (njs_slow_path(value == NULL)) { + return NULL; + } + + njs_scope_value_set(vm, index, value); + + return value; +} + + +njs_value_t ** +njs_scope_make(njs_vm_t *vm, uint32_t count) +{ + size_t size; + njs_value_t **refs, *values; + + size = (count * sizeof(njs_value_t *)) + (count * sizeof(njs_value_t)); + + refs = njs_mp_alloc(vm->mem_pool, size); + if (njs_slow_path(refs == NULL)) { + njs_memory_error(vm); + return NULL; + } + + values = (njs_value_t *) ((u_char *) refs + + (count * sizeof(njs_value_t *))); + + while (count != 0) { + count--; + + refs[count] = &values[count]; + + njs_set_invalid(refs[count]); + } + + return refs; +} + + +njs_index_t +njs_scope_global_index(njs_vm_t *vm, const njs_value_t *src, njs_uint_t runtime) +{ + njs_index_t index, *retval; + njs_value_t **values, *value; + + value = njs_scope_value_index(vm, src, runtime, &retval); + if (njs_slow_path(value == NULL)) { + return NJS_INDEX_ERROR; + } + + if (*retval != NJS_INDEX_ERROR) { + return *retval; + } + + if (vm->scope_absolute == NULL) { + vm->scope_absolute = njs_arr_create(vm->mem_pool, 8, + sizeof(njs_value_t *)); + if (njs_slow_path(vm->scope_absolute == NULL)) { + return NJS_INDEX_ERROR; + } + } + + index = vm->scope_absolute->items; + + values = njs_arr_add(vm->scope_absolute); + if (njs_slow_path(values == NULL)) { + return NJS_INDEX_ERROR; + } + + *values = value; + + vm->levels[NJS_LEVEL_STATIC] = vm->scope_absolute->start; + + *retval = njs_scope_index(NJS_SCOPE_GLOBAL, index, NJS_LEVEL_STATIC); + + return *retval; +} + + +static njs_int_t +njs_scope_values_hash_test(njs_lvlhsh_query_t *lhq, void *data) +{ + njs_str_t string; + njs_value_t *value; + + value = data; + + if (njs_is_string(value)) { + njs_string_get(value, &string); + + } else { + string.start = (u_char *) value; + string.length = sizeof(njs_value_t); + } + + if (lhq->key.length == string.length + && memcmp(lhq->key.start, string.start, string.length) == 0) + { + return NJS_OK; + } + + return NJS_DECLINED; +} + + +static const njs_lvlhsh_proto_t njs_values_hash_proto + njs_aligned(64) = +{ + NJS_LVLHSH_DEFAULT, + njs_scope_values_hash_test, + njs_lvlhsh_alloc, + njs_lvlhsh_free, +}; + + +/* + * Constant values such as njs_value_true are copied to values_hash during + * code generation when they are used as operands to guarantee aligned value. + */ + +static njs_value_t * +njs_scope_value_index(njs_vm_t *vm, const njs_value_t *src, njs_uint_t runtime, + njs_index_t **index) +{ + u_char *start; + uint32_t value_size, size, length; + njs_int_t ret; + njs_str_t str; + njs_bool_t long_string; + njs_value_t *value; + njs_string_t *string; + njs_lvlhsh_t *values_hash; + njs_lvlhsh_query_t lhq; + + long_string = 0; + value_size = sizeof(njs_value_t); + + if (njs_is_string(src)) { + njs_string_get(src, &str); + + size = (uint32_t) str.length; + start = str.start; + + if (src->short_string.size == NJS_STRING_LONG) { + long_string = 1; + } + + } else { + size = value_size; + start = (u_char *) src; + } + + lhq.key_hash = njs_djb_hash(start, size); + lhq.key.length = size; + lhq.key.start = start; + lhq.proto = &njs_values_hash_proto; + + if (njs_lvlhsh_find(&vm->shared->values_hash, &lhq) == NJS_OK) { + value = lhq.value; + + *index = (njs_index_t *) ((u_char *) value + sizeof(njs_value_t)); + + } else if (runtime && njs_lvlhsh_find(&vm->values_hash, &lhq) == NJS_OK) { + value = lhq.value; + + *index = (njs_index_t *) ((u_char *) value + sizeof(njs_value_t)); + + } else { + if (long_string) { + length = src->long_string.data->length; + + if (size != length && length > NJS_STRING_MAP_STRIDE) { + size = njs_string_map_offset(size) + + njs_string_map_size(length); + } + + value_size += sizeof(njs_string_t) + size; + } + + value_size += sizeof(njs_index_t); + + value = njs_mp_align(vm->mem_pool, sizeof(njs_value_t), value_size); + if (njs_slow_path(value == NULL)) { + return NULL; + } + + *value = *src; + + if (long_string) { + string = (njs_string_t *) ((u_char *) value + sizeof(njs_value_t) + + sizeof(njs_index_t)); + + value->long_string.data = string; + + string->start = (u_char *) string + sizeof(njs_string_t); + string->length = src->long_string.data->length; + string->retain = 0xffff; + + memcpy(string->start, start, size); + } + + *index = (njs_index_t *) ((u_char *) value + sizeof(njs_value_t)); + **index = NJS_INDEX_ERROR; + + lhq.replace = 0; + lhq.value = value; + lhq.pool = vm->mem_pool; + + values_hash = runtime ? &vm->values_hash : &vm->shared->values_hash; + + ret = njs_lvlhsh_insert(values_hash, &lhq); + if (njs_slow_path(ret != NJS_OK)) { + return NULL; + } + } + + if (start != (u_char *) src) { + /* + * The source node value must be updated with the shared value + * allocated from the permanent memory pool because the node + * value can be used as a variable initial value. + */ + *(njs_value_t *) src = *value; + } + + return value; +} diff --git a/src/njs_scope.h b/src/njs_scope.h new file mode 100644 index 00000000..0303da47 --- /dev/null +++ b/src/njs_scope.h @@ -0,0 +1,122 @@ + +/* + * Copyright (C) Alexander Borisov + * Copyright (C) NGINX, Inc. + */ + +#ifndef _NJS_SCOPE_H_INCLUDED_ +#define _NJS_SCOPE_H_INCLUDED_ + + +#define NJS_SCOPE_TYPE_SIZE 4 +#define NJS_SCOPE_VALUE_OFFSET (NJS_SCOPE_TYPE_SIZE + 1) +#define NJS_SCOPE_VALUE_MAX ((1 << (32 - NJS_SCOPE_VALUE_OFFSET)) - 1) +#define NJS_SCOPE_TYPE_MASK ((NJS_SCOPE_VALUE_MAX) << NJS_SCOPE_TYPE_SIZE) + +#define NJS_INDEX_NONE ((njs_index_t) 0) +#define NJS_INDEX_ERROR ((njs_index_t) -1) + + +njs_index_t njs_scope_temp_index(njs_parser_scope_t *scope); +njs_value_t *njs_scope_create_index_value(njs_vm_t *vm, njs_index_t index); +njs_value_t **njs_scope_make(njs_vm_t *vm, uint32_t count); +njs_index_t njs_scope_global_index(njs_vm_t *vm, const njs_value_t *src, + njs_uint_t runtime); + + +njs_inline njs_index_t +njs_scope_index(njs_scope_t scope, njs_index_t index, njs_level_type_t type) +{ + if (index > NJS_SCOPE_VALUE_MAX || type >= NJS_LEVEL_MAX + || (scope != NJS_SCOPE_GLOBAL && scope != NJS_SCOPE_FUNCTION)) + { + return NJS_INDEX_ERROR; + } + + if (scope == NJS_SCOPE_GLOBAL && type == NJS_LEVEL_LOCAL) { + type = NJS_LEVEL_GLOBAL; + } + + return (index << NJS_SCOPE_VALUE_OFFSET) | type; +} + + +njs_inline njs_level_type_t +njs_scope_index_type(njs_index_t index) +{ + return (njs_level_type_t) (index & ~NJS_SCOPE_TYPE_MASK); +} + + +njs_inline uint32_t +njs_scope_index_value(njs_index_t index) +{ + return (uint32_t) (index >> NJS_SCOPE_VALUE_OFFSET); +} + + +njs_inline njs_value_t * +njs_scope_value(njs_vm_t *vm, njs_index_t index) +{ + return vm->levels[njs_scope_index_type(index)] + [njs_scope_index_value(index)]; +} + + +njs_inline njs_value_t * +njs_scope_valid_value(njs_vm_t *vm, njs_index_t index) +{ + njs_value_t *value; + + value = njs_scope_value(vm, index); + + if (!njs_is_valid(value)) { + njs_set_undefined(value); + } + + return value; +} + + +njs_inline void +njs_scope_value_set(njs_vm_t *vm, njs_index_t index, njs_value_t *value) +{ + vm->levels[njs_scope_index_type(index)] + [njs_scope_index_value(index)] = value; +} + + +njs_inline njs_value_t * +njs_scope_value_clone(njs_vm_t *vm, njs_index_t index, njs_value_t *value) +{ + njs_value_t *newval; + + newval = njs_mp_alloc(vm->mem_pool, sizeof(njs_value_t)); + if (njs_slow_path(newval == NULL)) { + njs_memory_error(vm); + return NULL; + } + + *newval = *value; + + njs_scope_value_set(vm, index, newval); + + return newval; +} + + +njs_inline njs_index_t +njs_scope_undefined_index(njs_vm_t *vm, njs_uint_t runtime) +{ + return njs_scope_global_index(vm, &njs_value_undefined, runtime); +} + + +njs_inline njs_index_t +njs_scope_global_this_index() +{ + return njs_scope_index(NJS_SCOPE_GLOBAL, 0, NJS_LEVEL_LOCAL); +} + + +#endif /* _NJS_PARSER_H_INCLUDED_ */ diff --git a/src/njs_shell.c b/src/njs_shell.c index 1a49f04b..51d9633e 100644 --- a/src/njs_shell.c +++ b/src/njs_shell.c @@ -258,7 +258,7 @@ main(int argc, char **argv) vm_options.file.length = njs_strlen(opts.file); vm_options.init = 1; - vm_options.accumulative = opts.interactive; + vm_options.interactive = opts.interactive; vm_options.disassemble = opts.disassemble; vm_options.backtrace = 1; vm_options.quiet = opts.quiet; @@ -797,7 +797,7 @@ njs_output(njs_opts_t *opts, njs_vm_t *vm, njs_int_t ret) return; } - if (vm->options.accumulative) { + if (vm->options.interactive) { njs_print(out.start, out.length); njs_print("\n", 1); } diff --git a/src/njs_string.c b/src/njs_string.c index f0159cda..d8659395 100644 --- a/src/njs_string.c +++ b/src/njs_string.c @@ -4788,145 +4788,6 @@ uri_error: } -static njs_int_t -njs_values_hash_test(njs_lvlhsh_query_t *lhq, void *data) -{ - njs_str_t string; - njs_value_t *value; - - value = data; - - if (njs_is_string(value)) { - njs_string_get(value, &string); - - } else { - string.start = (u_char *) value; - string.length = sizeof(njs_value_t); - } - - if (lhq->key.length == string.length - && memcmp(lhq->key.start, string.start, string.length) == 0) - { - return NJS_OK; - } - - return NJS_DECLINED; -} - - -static const njs_lvlhsh_proto_t njs_values_hash_proto - njs_aligned(64) = -{ - NJS_LVLHSH_DEFAULT, - njs_values_hash_test, - njs_lvlhsh_alloc, - njs_lvlhsh_free, -}; - - -/* - * Constant values such as njs_value_true are copied to values_hash during - * code generation when they are used as operands to guarantee aligned value. - */ - -njs_index_t -njs_value_index(njs_vm_t *vm, const njs_value_t *src, njs_uint_t runtime) -{ - u_char *start; - uint32_t value_size, size, length; - njs_int_t ret; - njs_str_t str; - njs_bool_t long_string; - njs_value_t *value; - njs_string_t *string; - njs_lvlhsh_t *values_hash; - njs_lvlhsh_query_t lhq; - - long_string = 0; - value_size = sizeof(njs_value_t); - - if (njs_is_string(src)) { - njs_string_get(src, &str); - - size = (uint32_t) str.length; - start = str.start; - - if (src->short_string.size == NJS_STRING_LONG) { - long_string = 1; - } - - } else { - size = value_size; - start = (u_char *) src; - } - - lhq.key_hash = njs_djb_hash(start, size); - lhq.key.length = size; - lhq.key.start = start; - lhq.proto = &njs_values_hash_proto; - - if (njs_lvlhsh_find(&vm->shared->values_hash, &lhq) == NJS_OK) { - value = lhq.value; - - } else if (runtime && njs_lvlhsh_find(&vm->values_hash, &lhq) == NJS_OK) { - value = lhq.value; - - } else { - if (long_string) { - length = src->long_string.data->length; - - if (size != length && length > NJS_STRING_MAP_STRIDE) { - size = njs_string_map_offset(size) - + njs_string_map_size(length); - } - - value_size += sizeof(njs_string_t) + size; - } - - value = njs_mp_align(vm->mem_pool, sizeof(njs_value_t), value_size); - if (njs_slow_path(value == NULL)) { - return NJS_INDEX_NONE; - } - - *value = *src; - - if (long_string) { - string = (njs_string_t *) ((u_char *) value + sizeof(njs_value_t)); - value->long_string.data = string; - - string->start = (u_char *) string + sizeof(njs_string_t); - string->length = src->long_string.data->length; - string->retain = 0xffff; - - memcpy(string->start, start, size); - } - - lhq.replace = 0; - lhq.value = value; - lhq.pool = vm->mem_pool; - - values_hash = runtime ? &vm->values_hash : &vm->shared->values_hash; - - ret = njs_lvlhsh_insert(values_hash, &lhq); - - if (njs_slow_path(ret != NJS_OK)) { - return NJS_INDEX_NONE; - } - } - - if (start != (u_char *) src) { - /* - * The source node value must be updated with the shared value - * allocated from the permanent memory pool because the node - * value can be used as a variable initial value. - */ - *(njs_value_t *) src = *value; - } - - return (njs_index_t) value; -} - - const njs_object_type_init_t njs_string_type_init = { .constructor = njs_native_ctor(njs_string_constructor, 1, 0), .constructor_props = &njs_string_constructor_init, diff --git a/src/njs_string.h b/src/njs_string.h index 8ac10ad3..2f43d09b 100644 --- a/src/njs_string.h +++ b/src/njs_string.h @@ -237,9 +237,6 @@ njs_int_t njs_string_encode_uri(njs_vm_t *vm, njs_value_t *args, njs_int_t njs_string_decode_uri(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t component); -njs_index_t njs_value_index(njs_vm_t *vm, const njs_value_t *src, - njs_uint_t runtime); - njs_int_t njs_string_prototype_concat(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused); njs_int_t njs_string_get_substitution(njs_vm_t *vm, njs_value_t *matched, diff --git a/src/njs_value.h b/src/njs_value.h index 06e4fd72..28aed7e1 100644 --- a/src/njs_value.h +++ b/src/njs_value.h @@ -266,16 +266,6 @@ struct njs_typed_array_s { }; -typedef struct { - union { - uint32_t count; - njs_value_t values; - } u; - - njs_value_t values[1]; -} njs_closure_t; - - struct njs_function_s { njs_object_t object; @@ -283,18 +273,11 @@ struct njs_function_s { uint8_t args_count:4; - /* - * If "closure" is true njs_closure_t[] is available right after the - * njs_function_t and njs_function_closures() may be used to access it. - */ - -#define njs_function_closures(function) \ - ((njs_closure_t **) ((u_char *) function + sizeof(njs_function_t))) - - uint8_t closure:1; + uint8_t closure_copied:1; uint8_t native:1; uint8_t ctor:1; uint8_t global_this:1; + uint8_t global:1; uint8_t magic8; diff --git a/src/njs_variable.c b/src/njs_variable.c index 6d485f71..ee796cb0 100644 --- a/src/njs_variable.c +++ b/src/njs_variable.c @@ -9,10 +9,10 @@ #include -static njs_variable_t *njs_variable_scope_add(njs_parser_t *parser, - njs_parser_scope_t *scope, uintptr_t unique_id, njs_variable_type_t type); -static njs_int_t njs_variable_reference_resolve(njs_vm_t *vm, - njs_variable_reference_t *vr, njs_parser_scope_t *node_scope); +static njs_value_t **njs_variable_scope_function_add(njs_parser_t *parser, + njs_parser_scope_t *scope); +static njs_parser_scope_t *njs_variable_scope_find(njs_parser_t *parser, + njs_parser_scope_t *scope, uintptr_t unique_id, njs_variable_type_t type); static njs_variable_t *njs_variable_alloc(njs_vm_t *vm, uintptr_t unique_id, njs_variable_type_t type); @@ -21,34 +21,87 @@ njs_variable_t * njs_variable_add(njs_parser_t *parser, njs_parser_scope_t *scope, uintptr_t unique_id, njs_variable_type_t type) { - njs_variable_t *var; + njs_parser_scope_t *root; - var = njs_variable_scope_add(parser, scope, unique_id, type); - if (njs_slow_path(var == NULL)) { + root = njs_variable_scope_find(parser, scope, unique_id, type); + if (njs_slow_path(root == NULL)) { + njs_parser_ref_error(parser, "scope not found"); return NULL; } - if (type == NJS_VARIABLE_VAR && scope->type == NJS_SCOPE_BLOCK) { - /* A "var" declaration is stored in function or global scope. */ - do { - scope = scope->parent; + return njs_variable_scope_add(parser, root, scope, unique_id, type, + NJS_INDEX_NONE); +} - var = njs_variable_scope_add(parser, scope, unique_id, type); - if (njs_slow_path(var == NULL)) { - return NULL; - } - } while (scope->type == NJS_SCOPE_BLOCK); +njs_variable_t * +njs_variable_function_add(njs_parser_t *parser, njs_parser_scope_t *scope, + uintptr_t unique_id, njs_variable_type_t type) +{ + njs_value_t **declr; + njs_variable_t *var; + njs_parser_scope_t *root; + njs_function_lambda_t *lambda; + + root = njs_variable_scope_find(parser, scope, unique_id, type); + if (njs_slow_path(root == NULL)) { + njs_parser_ref_error(parser, "scope not found"); + return NULL; + } + + var = njs_variable_scope_add(parser, root, scope, unique_id, type, + NJS_INDEX_ERROR); + if (njs_slow_path(var == NULL)) { + return NULL; } - if (type == NJS_VARIABLE_FUNCTION) { - var->type = type; + if (var->index == NJS_INDEX_ERROR || !var->function) { + root = njs_function_scope(scope); + if (njs_slow_path(scope == NULL)) { + return NULL; + } + + lambda = njs_function_lambda_alloc(parser->vm, 1); + if (lambda == NULL) { + return NULL; + } + + var->value.data.u.lambda = lambda; + + declr = njs_variable_scope_function_add(parser, root); + if (njs_slow_path(declr == NULL)) { + return NULL; + } + + *declr = &var->value; + + var->index = njs_scope_index(root->type, root->items, NJS_LEVEL_LOCAL); + root->items++; } + var->type = NJS_VARIABLE_FUNCTION; + var->function = 1; + return var; } +static njs_value_t ** +njs_variable_scope_function_add(njs_parser_t *parser, njs_parser_scope_t *scope) +{ + if (scope->declarations == NULL) { + scope->declarations = njs_arr_create(parser->vm->mem_pool, 1, + sizeof(njs_value_t *)); + if (njs_slow_path(scope->declarations == NULL)) { + return NULL; + } + } + + return njs_arr_add(scope->declarations); +} + + + njs_int_t njs_variables_copy(njs_vm_t *vm, njs_rbtree_t *variables, njs_rbtree_t *prev_variables) @@ -77,43 +130,128 @@ njs_variables_copy(njs_vm_t *vm, njs_rbtree_t *variables, } -static njs_variable_t * -njs_variable_scope_add(njs_parser_t *parser, njs_parser_scope_t *scope, - uintptr_t unique_id, njs_variable_type_t type) +static njs_parser_scope_t * +njs_variable_scope(njs_parser_scope_t *scope, uintptr_t unique_id, + njs_variable_t **retvar, njs_variable_type_t type) +{ + njs_variable_t *var; + njs_rbtree_node_t *node; + njs_variable_node_t var_node; + + *retvar = NULL; + + var_node.key = unique_id; + + do { + node = njs_rbtree_find(&scope->variables, &var_node.node); + + if (node != NULL) { + var = ((njs_variable_node_t *) node)->variable; + + if (var->type != NJS_VARIABLE_CATCH || type != NJS_VARIABLE_VAR) { + *retvar = var; + return scope; + } + } + + if (scope->type == NJS_SCOPE_GLOBAL + || scope->type == NJS_SCOPE_FUNCTION) + { + return scope; + } + + scope = scope->parent; + + } while (scope != NULL); + + return NULL; +} + + +static njs_parser_scope_t * +njs_variable_scope_find(njs_parser_t *parser, njs_parser_scope_t *scope, + uintptr_t unique_id, njs_variable_type_t type) { + njs_bool_t module; njs_variable_t *var; - njs_rbtree_node_t *node; - njs_variable_node_t var_node, *var_node_new; + njs_parser_scope_t *root; const njs_lexer_entry_t *entry; - var_node.key = unique_id; + if (type != NJS_VARIABLE_VAR && type != NJS_VARIABLE_FUNCTION) { + return scope; + } - node = njs_rbtree_find(&scope->variables, &var_node.node); + root = njs_variable_scope(scope, unique_id, &var, type); + if (njs_slow_path(root == NULL)) { + return NULL; + } - if (node != NULL) { - var = ((njs_variable_node_t *) node)->variable; + if (type == NJS_VARIABLE_FUNCTION) { + root = scope; + } - if (scope->module || scope->type == NJS_SCOPE_BLOCK) { + if (var == NULL) { + return root; + } - if (type == NJS_VARIABLE_FUNCTION - || var->type == NJS_VARIABLE_FUNCTION) - { - goto fail; + if (var->original->type == NJS_SCOPE_BLOCK) { + if (type == NJS_VARIABLE_FUNCTION + || var->type == NJS_VARIABLE_FUNCTION) + { + if (var->original == root) { + goto failed; } } + } - if (scope->type == NJS_SCOPE_GLOBAL) { + if (type != NJS_VARIABLE_FUNCTION + && var->type != NJS_VARIABLE_FUNCTION) + { + return var->scope; + } - if (parser->vm->options.module) { - if (type == NJS_VARIABLE_FUNCTION - || var->type == NJS_VARIABLE_FUNCTION) - { - goto fail; - } - } + if (root != scope) { + return root; + } + + module = parser->vm->options.module || scope->module; + + if (module) { + if (type == NJS_VARIABLE_FUNCTION + || var->type == NJS_VARIABLE_FUNCTION) + { + goto failed; } + } + + return root; + +failed: + + entry = njs_lexer_entry(unique_id); + + njs_parser_syntax_error(parser, "\"%V\" has already been declared", + &entry->name); + return NULL; +} + + +njs_variable_t * +njs_variable_scope_add(njs_parser_t *parser, njs_parser_scope_t *scope, + njs_parser_scope_t *original, uintptr_t unique_id, + njs_variable_type_t type, njs_index_t index) +{ + njs_variable_t *var; + njs_rbtree_node_t *node; + njs_parser_scope_t *root; + njs_variable_node_t var_node, *var_node_new; + + var_node.key = unique_id; + + node = njs_rbtree_find(&scope->variables, &var_node.node); - return var; + if (node != NULL) { + return ((njs_variable_node_t *) node)->variable; } var = njs_variable_alloc(parser->vm, unique_id, type); @@ -121,6 +259,20 @@ njs_variable_scope_add(njs_parser_t *parser, njs_parser_scope_t *scope, goto memory_error; } + var->scope = scope; + var->index = index; + var->original = original; + + if (index == NJS_INDEX_NONE) { + root = njs_function_scope(scope); + if (njs_slow_path(scope == NULL)) { + return NULL; + } + + var->index = njs_scope_index(root->type, root->items, NJS_LEVEL_LOCAL); + root->items++; + } + var_node_new = njs_variable_node_alloc(parser->vm, var, unique_id); if (njs_slow_path(var_node_new == NULL)) { goto memory_error; @@ -135,14 +287,6 @@ memory_error: njs_memory_error(parser->vm); return NULL; - -fail: - - entry = njs_lexer_entry(unique_id); - - njs_parser_syntax_error(parser, "\"%V\" has already been declared", - &entry->name); - return NULL; } @@ -204,191 +348,198 @@ njs_label_remove(njs_vm_t *vm, njs_parser_scope_t *scope, uintptr_t unique_id) } -njs_int_t -njs_variable_reference(njs_vm_t *vm, njs_parser_scope_t *scope, - njs_parser_node_t *node, uintptr_t unique_id, njs_reference_type_t type) +static njs_bool_t +njs_variable_closure_test(njs_parser_scope_t *root, njs_parser_scope_t *scope) { - njs_variable_reference_t *vr; - njs_parser_rbtree_node_t *rb_node; + if (root == scope) { + return 0; + } - vr = &node->u.reference; + do { + if (root->type == NJS_SCOPE_FUNCTION) { + return 1; + } - vr->unique_id = unique_id; - vr->type = type; + root = root->parent; - rb_node = njs_mp_alloc(vm->mem_pool, sizeof(njs_parser_rbtree_node_t)); - if (njs_slow_path(rb_node == NULL)) { - return NJS_ERROR; - } + } while (root != scope); - rb_node->key = unique_id; - rb_node->parser_node = node; + return 0; +} - njs_rbtree_insert(&scope->references, &rb_node->node); - return NJS_OK; +njs_variable_t * +njs_variable_resolve(njs_vm_t *vm, njs_parser_node_t *node) +{ + njs_rbtree_node_t *rb_node; + njs_parser_scope_t *scope; + njs_variable_node_t var_node; + njs_variable_reference_t *ref; + + ref = &node->u.reference; + scope = node->scope; + + var_node.key = ref->unique_id; + + do { + rb_node = njs_rbtree_find(&scope->variables, &var_node.node); + + if (rb_node != NULL) { + return ((njs_variable_node_t *) rb_node)->variable; + } + + scope = scope->parent; + + } while (scope != NULL); + + return NULL; } -static njs_int_t -njs_variables_scope_resolve(njs_vm_t *vm, njs_parser_scope_t *scope, - njs_bool_t closure) +static njs_index_t +njs_variable_closure(njs_vm_t *vm, njs_variable_t *var, + njs_parser_scope_t *scope) { - njs_int_t ret; - njs_queue_t *nested; - njs_queue_link_t *lnk; + njs_index_t index, prev_index, *idx; + njs_level_type_t type; njs_rbtree_node_t *rb_node; - njs_parser_node_t *node; - njs_parser_rbtree_node_t *parser_rb_node; - njs_variable_reference_t *vr; + njs_parser_scope_t **p, *root; + njs_parser_rbtree_node_t *parse_node, ref_node; +#define NJS_VAR_MAX_DEPTH 32 + njs_parser_scope_t *list[NJS_VAR_MAX_DEPTH]; - nested = &scope->nested; + ref_node.key = var->unique_id; - for (lnk = njs_queue_first(nested); - lnk != njs_queue_tail(nested); - lnk = njs_queue_next(lnk)) - { - scope = njs_queue_link_data(lnk, njs_parser_scope_t, link); + p = list; - ret = njs_variables_scope_resolve(vm, scope, closure); - if (njs_slow_path(ret != NJS_OK)) { - return NJS_ERROR; + do { + if (njs_slow_path(p == &list[NJS_VAR_MAX_DEPTH - 1])) { + njs_error(vm, "maximum depth of nested functions is reached"); + return NJS_INDEX_ERROR; } - rb_node = njs_rbtree_min(&scope->references); + if (scope->type == NJS_SCOPE_FUNCTION) { + *p++ = scope; + } - while (njs_rbtree_is_there_successor(&scope->references, rb_node)) { - parser_rb_node = (njs_parser_rbtree_node_t *) rb_node; - node = parser_rb_node->parser_node; + scope = scope->parent; - if (node == NULL) { - break; - } + } while (scope != var->scope && scope->type != NJS_SCOPE_GLOBAL); - vr = &node->u.reference; + prev_index = var->index; - if (closure) { - ret = njs_variable_reference_resolve(vm, vr, node->scope); - if (njs_slow_path(ret != NJS_OK)) { - goto next; - } + while (p != list) { + p--; - if (vr->scope_index == NJS_SCOPE_INDEX_LOCAL) { - goto next; - } - } + scope = *p; - (void) njs_variable_resolve(vm, node); + rb_node = njs_rbtree_find(&scope->references, &ref_node.node); - next: + parse_node = ((njs_parser_rbtree_node_t *) rb_node); - rb_node = njs_rbtree_node_successor(&scope->references, rb_node); - } - } + type = NJS_LEVEL_LOCAL; - return NJS_OK; -} + if (parse_node != NULL) { + type = njs_scope_index_type(parse_node->index); + if (p != list && parse_node->index != 0) { + prev_index = parse_node->index; + continue; + } + } -njs_int_t -njs_variables_scope_reference(njs_vm_t *vm, njs_parser_scope_t *scope) -{ - njs_int_t ret; + root = njs_function_scope(scope); - /* - * Calculating proper scope types for variables. - * A variable is considered to be local variable if it is referenced - * only in the local scope (reference and definition nestings are the same). - */ + if (type != NJS_LEVEL_CLOSURE && root == scope) { + /* Create new closure for scope. */ - ret = njs_variables_scope_resolve(vm, scope, 1); - if (njs_slow_path(ret != NJS_OK)) { - return NJS_ERROR; - } + index = njs_scope_index(root->type, root->closures->items, + NJS_LEVEL_CLOSURE); + if (njs_slow_path(index == NJS_INDEX_ERROR)) { + return NJS_INDEX_ERROR; + } - ret = njs_variables_scope_resolve(vm, scope, 0); - if (njs_slow_path(ret != NJS_OK)) { - return NJS_ERROR; - } + idx = njs_arr_add(root->closures); + if (njs_slow_path(idx == NULL)) { + return NJS_INDEX_ERROR; + } - return NJS_OK; -} + *idx = prev_index; + if (parse_node == NULL) { + /* Create new reference for closure. */ -njs_index_t -njs_variable_index(njs_vm_t *vm, njs_parser_node_t *node) -{ - njs_variable_t *var; + parse_node = njs_mp_alloc(vm->mem_pool, + sizeof(njs_parser_rbtree_node_t)); + if (njs_slow_path(parse_node == NULL)) { + return NJS_INDEX_ERROR; + } - if (node->index != NJS_INDEX_NONE) { - return node->index; - } + parse_node->key = var->unique_id; + + njs_rbtree_insert(&scope->references, &parse_node->node); + } - var = njs_variable_resolve(vm, node); + parse_node->index = index; + } - if (njs_fast_path(var != NULL)) { - return var->index; + prev_index = parse_node->index; } - return NJS_INDEX_NONE; + return prev_index; } njs_variable_t * -njs_variable_resolve(njs_vm_t *vm, njs_parser_node_t *node) +njs_variable_reference(njs_vm_t *vm, njs_parser_node_t *node) { - njs_int_t ret; - njs_uint_t scope_index; - njs_index_t index; - njs_variable_t *var; - njs_variable_reference_t *vr; + njs_rbtree_node_t *rb_node; + njs_parser_scope_t *scope; + njs_parser_rbtree_node_t *parse_node, ref_node; + njs_variable_reference_t *ref; - vr = &node->u.reference; + ref = &node->u.reference; + scope = node->scope; - ret = njs_variable_reference_resolve(vm, vr, node->scope); + if (ref->variable == NULL) { + ref->variable = njs_variable_resolve(vm, node); + if (njs_slow_path(ref->variable == NULL)) { + ref->not_defined = 1; - if (njs_slow_path(ret != NJS_OK)) { - node->u.reference.not_defined = 1; - return NULL; + return NULL; + } } - scope_index = vr->scope_index; - - var = vr->variable; - index = var->index; + ref->closure = njs_variable_closure_test(node->scope, ref->variable->scope); + ref->scope = node->scope; - if (index != NJS_INDEX_NONE) { + ref_node.key = ref->unique_id; - if (scope_index == NJS_SCOPE_INDEX_LOCAL - || njs_scope_type(index) != NJS_SCOPE_ARGUMENTS) - { - node->index = index; + rb_node = njs_rbtree_find(&scope->references, &ref_node.node); + if (njs_slow_path(rb_node == NULL)) { + return NULL; + } - return var; - } + parse_node = ((njs_parser_rbtree_node_t *) rb_node); - vr->scope->argument_closures++; - index = (index >> NJS_SCOPE_SHIFT) + 1; + if (parse_node->index != NJS_INDEX_NONE) { + node->index = parse_node->index; - if (index > 255 || vr->scope->argument_closures == 0) { - njs_internal_error(vm, "too many argument closures"); + return ref->variable; + } - return NULL; - } + if (!ref->closure) { + node->index = ref->variable->index; - var->argument = index; + return ref->variable; } - index = njs_scope_next_index(vm, vr->scope, scope_index, &var->value); - - if (njs_slow_path(index == NJS_INDEX_ERROR)) { + node->index = njs_variable_closure(vm, ref->variable, scope); + if (njs_slow_path(node->index == NJS_INDEX_ERROR)) { return NULL; } - var->index = index; - node->index = index; - - return var; + return ref->variable; } @@ -415,120 +566,6 @@ njs_label_find(njs_vm_t *vm, njs_parser_scope_t *scope, uintptr_t unique_id) } -static njs_int_t -njs_variable_reference_resolve(njs_vm_t *vm, njs_variable_reference_t *vr, - njs_parser_scope_t *node_scope) -{ - njs_rbtree_node_t *node; - njs_parser_scope_t *scope, *previous; - njs_variable_node_t var_node; - - var_node.key = vr->unique_id; - - scope = node_scope; - previous = NULL; - - for ( ;; ) { - node = njs_rbtree_find(&scope->variables, &var_node.node); - - if (node != NULL) { - vr->variable = ((njs_variable_node_t *) node)->variable; - - if (scope->type == NJS_SCOPE_BLOCK - && vr->variable->type == NJS_VARIABLE_VAR) - { - scope = scope->parent; - continue; - } - - if (scope->type == NJS_SCOPE_SHIM) { - scope = previous; - - } else { - /* - * Variables declared in a block with "let" or "const" - * keywords are actually stored in function or global scope. - */ - while (scope->type == NJS_SCOPE_BLOCK) { - scope = scope->parent; - } - } - - vr->scope = scope; - - vr->scope_index = NJS_SCOPE_INDEX_LOCAL; - - if (vr->scope->type > NJS_SCOPE_GLOBAL - && node_scope->nesting != vr->scope->nesting) - { - vr->scope_index = NJS_SCOPE_INDEX_CLOSURE; - } - - return NJS_OK; - } - - if (scope->parent == NULL) { - /* A global scope. */ - vr->scope = scope; - - return NJS_DECLINED; - } - - previous = scope; - scope = scope->parent; - } -} - - -njs_index_t -njs_scope_next_index(njs_vm_t *vm, njs_parser_scope_t *scope, - njs_uint_t scope_index, const njs_value_t *default_value) -{ - njs_arr_t *values; - njs_index_t index; - njs_value_t *value; - - if (njs_scope_accumulative(vm, scope)) { - /* - * When non-clonable VM runs in accumulative mode all - * global variables should be allocated in absolute scope - * to share them among consecutive VM invocations. - */ - value = njs_mp_align(vm->mem_pool, sizeof(njs_value_t), - sizeof(njs_value_t)); - if (njs_slow_path(value == NULL)) { - return NJS_INDEX_ERROR; - } - - index = (njs_index_t) value; - - } else { - values = scope->values[scope_index]; - - if (values == NULL) { - values = njs_arr_create(vm->mem_pool, 4, sizeof(njs_value_t)); - if (njs_slow_path(values == NULL)) { - return NJS_INDEX_ERROR; - } - - scope->values[scope_index] = values; - } - - value = njs_arr_add(values); - if (njs_slow_path(value == NULL)) { - return NJS_INDEX_ERROR; - } - - index = scope->next_index[scope_index]; - scope->next_index[scope_index] += sizeof(njs_value_t); - } - - *value = *default_value; - - return index; -} - - static njs_variable_t * njs_variable_alloc(njs_vm_t *vm, uintptr_t unique_id, njs_variable_type_t type) { diff --git a/src/njs_variable.h b/src/njs_variable.h index 03348812..8759e04f 100644 --- a/src/njs_variable.h +++ b/src/njs_variable.h @@ -12,7 +12,6 @@ typedef enum { NJS_VARIABLE_CONST = 0, NJS_VARIABLE_LET, NJS_VARIABLE_CATCH, - NJS_VARIABLE_SHIM, NJS_VARIABLE_VAR, NJS_VARIABLE_FUNCTION, } njs_variable_type_t; @@ -22,9 +21,13 @@ typedef struct { uintptr_t unique_id; njs_variable_type_t type:8; /* 3 bits */ - uint8_t argument; - uint8_t this_object; - uint8_t arguments_object; + njs_bool_t argument; + njs_bool_t arguments_object; + njs_bool_t self; + njs_bool_t function; + + njs_parser_scope_t *scope; + njs_parser_scope_t *original; njs_index_t index; njs_value_t value; @@ -43,8 +46,8 @@ typedef struct { uintptr_t unique_id; njs_variable_t *variable; njs_parser_scope_t *scope; - njs_uint_t scope_index; /* NJS_SCOPE_INDEX_... */ njs_bool_t not_defined; + njs_bool_t closure; } njs_variable_reference_t; @@ -57,6 +60,8 @@ typedef struct { njs_variable_t *njs_variable_add(njs_parser_t *parser, njs_parser_scope_t *scope, uintptr_t unique_id, njs_variable_type_t type); +njs_variable_t *njs_variable_function_add(njs_parser_t *parser, + njs_parser_scope_t *scope, uintptr_t unique_id, njs_variable_type_t type); njs_int_t njs_variables_copy(njs_vm_t *vm, njs_rbtree_t *variables, njs_rbtree_t *prev_variables); njs_variable_t * njs_label_add(njs_vm_t *vm, njs_parser_scope_t *scope, @@ -65,12 +70,10 @@ njs_variable_t *njs_label_find(njs_vm_t *vm, njs_parser_scope_t *scope, uintptr_t unique_id); njs_int_t njs_label_remove(njs_vm_t *vm, njs_parser_scope_t *scope, uintptr_t unique_id); -njs_int_t njs_variable_reference(njs_vm_t *vm, njs_parser_scope_t *scope, - njs_parser_node_t *node, uintptr_t unique_id, njs_reference_type_t type); -njs_int_t njs_variables_scope_reference(njs_vm_t *vm, - njs_parser_scope_t *scope); -njs_index_t njs_scope_next_index(njs_vm_t *vm, njs_parser_scope_t *scope, - njs_uint_t scope_index, const njs_value_t *default_value); +njs_variable_t *njs_variable_reference(njs_vm_t *vm, njs_parser_node_t *node); +njs_variable_t *njs_variable_scope_add(njs_parser_t *parser, + njs_parser_scope_t *scope, njs_parser_scope_t *original, + uintptr_t unique_id, njs_variable_type_t type, njs_index_t index); njs_int_t njs_name_copy(njs_vm_t *vm, njs_str_t *dst, const njs_str_t *src); diff --git a/src/njs_vm.c b/src/njs_vm.c index 22e4ee7d..144e3d9f 100644 --- a/src/njs_vm.c +++ b/src/njs_vm.c @@ -81,6 +81,10 @@ njs_vm_create(njs_vm_opt_t *options) vm->symbol_generator = NJS_SYMBOL_KNOWN_MAX; + if (njs_scope_undefined_index(vm, 0) == NJS_INDEX_ERROR) { + return NULL; + } + return vm; } @@ -116,15 +120,21 @@ njs_vm_destroy(njs_vm_t *vm) njs_int_t njs_vm_compile(njs_vm_t *vm, u_char **start, u_char *end) { - njs_int_t ret; - njs_str_t ast; - njs_chb_t chain; - njs_lexer_t lexer; - njs_parser_t parser; - njs_vm_code_t *code; - njs_generator_t generator; - - if (vm->modules != NULL && vm->options.accumulative) { + njs_int_t ret; + njs_str_t ast; + njs_chb_t chain; + njs_value_t **global, **new; + njs_lexer_t lexer; + njs_parser_t parser; + njs_vm_code_t *code; + njs_generator_t generator; + njs_parser_scope_t *scope; + + njs_memzero(&parser, sizeof(njs_parser_t)); + + parser.scope = vm->global_scope; + + if (parser.scope != NULL && vm->modules != NULL) { njs_module_reset(vm); } @@ -133,25 +143,19 @@ njs_vm_compile(njs_vm_t *vm, u_char **start, u_char *end) return NJS_ERROR; } - njs_memzero(&parser, sizeof(njs_parser_t)); - parser.lexer = &lexer; - ret = njs_parser(vm, &parser, vm->variables_hash); + ret = njs_parser(vm, &parser); if (njs_slow_path(ret != NJS_OK)) { return NJS_ERROR; } *start = lexer.start; - - ret = njs_variables_scope_reference(vm, parser.scope); - if (njs_slow_path(ret != NJS_OK)) { - return NJS_ERROR; - } + scope = parser.scope; njs_memzero(&generator, sizeof(njs_generator_t)); - code = njs_generate_scope(vm, &generator, parser.scope, &njs_entry_main); + code = njs_generate_scope(vm, &generator, scope, &njs_entry_main); if (njs_slow_path(code == NULL)) { if (!njs_is_error(&vm->retval)) { njs_internal_error(vm, "njs_generate_scope() failed"); @@ -160,17 +164,45 @@ njs_vm_compile(njs_vm_t *vm, u_char **start, u_char *end) return NJS_ERROR; } + vm->global_scope = scope; + + if (scope->items > vm->global_items) { + global = vm->levels[NJS_LEVEL_GLOBAL]; + + new = njs_scope_make(vm, scope->items); + if (njs_slow_path(new == NULL)) { + return ret; + } + + vm->levels[NJS_LEVEL_GLOBAL] = new; + + if (global != NULL) { + while (vm->global_items != 0) { + vm->global_items--; + + *new++ = *global++; + } + + njs_mp_free(vm->mem_pool, global); + } + } + + /* globalThis and this */ + njs_scope_value_set(vm, njs_scope_global_this_index(), &vm->global_value); + vm->start = generator.code_start; - vm->global_scope = generator.local_scope; - vm->scope_size = generator.scope_size; + vm->variables_hash = &scope->variables; + vm->global_items = scope->items; - vm->variables_hash = &parser.scope->variables; + vm->levels[NJS_LEVEL_TEMP] = NULL; - if (vm->options.init && !vm->options.accumulative) { - ret = njs_vm_init(vm); - if (njs_slow_path(ret != NJS_OK)) { + if (scope->temp != 0) { + new = njs_scope_make(vm, scope->temp); + if (njs_slow_path(new == NULL)) { return ret; } + + vm->levels[NJS_LEVEL_TEMP] = new; } if (vm->options.disassemble) { @@ -201,13 +233,14 @@ njs_vm_compile(njs_vm_t *vm, u_char **start, u_char *end) njs_vm_t * njs_vm_clone(njs_vm_t *vm, njs_external_ptr_t external) { - njs_mp_t *nmp; - njs_vm_t *nvm; - njs_int_t ret; + njs_mp_t *nmp; + njs_vm_t *nvm; + njs_int_t ret; + njs_value_t **global; njs_thread_log_debug("CLONE:"); - if (vm->options.accumulative) { + if (vm->options.interactive) { return NULL; } @@ -232,6 +265,20 @@ njs_vm_clone(njs_vm_t *vm, njs_external_ptr_t external) goto fail; } + global = njs_scope_make(nvm, nvm->global_items); + if (njs_slow_path(global == NULL)) { + goto fail; + } + + nvm->levels[NJS_LEVEL_GLOBAL] = global; + + njs_set_object(&nvm->global_value, &nvm->global_object); + + /* globalThis and this */ + njs_scope_value_set(nvm, njs_scope_global_this_index(), &nvm->global_value); + + nvm->levels[NJS_LEVEL_LOCAL] = NULL; + return nvm; fail: @@ -245,46 +292,27 @@ fail: static njs_int_t njs_vm_init(njs_vm_t *vm) { - size_t size, scope_size; - u_char *values; njs_int_t ret; - njs_value_t *global; njs_frame_t *frame; - scope_size = vm->scope_size + NJS_INDEX_GLOBAL_OFFSET; - - size = njs_frame_size(0) + scope_size + NJS_FRAME_SPARE_SIZE; - size = njs_align_size(size, NJS_FRAME_SPARE_SIZE); - - frame = njs_mp_align(vm->mem_pool, sizeof(njs_value_t), size); + frame = (njs_frame_t *) njs_function_frame_alloc(vm, NJS_FRAME_SIZE); if (njs_slow_path(frame == NULL)) { + njs_memory_error(vm); return NJS_ERROR; } - njs_memzero(frame, njs_frame_size(0)); + frame->exception.catch = NULL; + frame->exception.next = NULL; + frame->previous_active_frame = NULL; - vm->top_frame = &frame->native; vm->active_frame = frame; - frame->native.size = size; - frame->native.free_size = size - (njs_frame_size(0) + scope_size); - - values = (u_char *) frame + njs_frame_size(0); - - frame->native.free = values + scope_size; - - vm->scopes[NJS_SCOPE_GLOBAL] = (njs_value_t *) values; - - memcpy(values + NJS_INDEX_GLOBAL_OFFSET, vm->global_scope, vm->scope_size); - ret = njs_regexp_init(vm); if (njs_slow_path(ret != NJS_OK)) { return NJS_ERROR; } - global = (njs_value_t *) (values + NJS_INDEX_GLOBAL_OBJECT_OFFSET); - - ret = njs_builtin_objects_clone(vm, global); + ret = njs_builtin_objects_clone(vm, &vm->global_value); if (njs_slow_path(ret != NJS_OK)) { return NJS_ERROR; } @@ -303,13 +331,13 @@ njs_int_t njs_vm_call(njs_vm_t *vm, njs_function_t *function, const njs_value_t *args, njs_uint_t nargs) { - return njs_vm_invoke(vm, function, args, nargs, (njs_index_t) &vm->retval); + return njs_vm_invoke(vm, function, args, nargs, &vm->retval); } njs_int_t njs_vm_invoke(njs_vm_t *vm, njs_function_t *function, const njs_value_t *args, - njs_uint_t nargs, njs_index_t retval) + njs_uint_t nargs, njs_value_t *retval) { njs_int_t ret; @@ -327,55 +355,17 @@ void njs_vm_scopes_restore(njs_vm_t *vm, njs_native_frame_t *native, njs_native_frame_t *previous) { - njs_uint_t n, nesting; - njs_value_t *args; - njs_frame_t *frame; - njs_closure_t **closures; - njs_function_t *function; + njs_frame_t *frame; vm->top_frame = previous; - args = previous->arguments; - function = previous->function; - - if (function != NULL) { - args += function->args_offset; - } - - vm->scopes[NJS_SCOPE_CALLEE_ARGUMENTS] = args; - - function = native->function; - - if (function->native) { + if (native->function->native) { return; } - if (function->closure) { - /* GC: release function closures. */ - } - frame = (njs_frame_t *) native; frame = frame->previous_active_frame; vm->active_frame = frame; - - /* GC: arguments, local, and local block closures. */ - - vm->scopes[NJS_SCOPE_ARGUMENTS] = frame->native.arguments; - vm->scopes[NJS_SCOPE_LOCAL] = frame->local; - - function = frame->native.function; - - nesting = (function != NULL) ? function->u.lambda->nesting : 0; - closures = njs_frame_closures(frame); - - for (n = 0; n <= nesting; n++) { - vm->scopes[NJS_SCOPE_CLOSURE + n] = &closures[n]->u.values; - } - - while (n < NJS_MAX_NESTING) { - vm->scopes[NJS_SCOPE_CLOSURE + n] = NULL; - n++; - } } diff --git a/src/njs_vm.h b/src/njs_vm.h index d6ac1b80..5883b5d4 100644 --- a/src/njs_vm.h +++ b/src/njs_vm.h @@ -29,49 +29,12 @@ typedef struct njs_generator_s njs_generator_t; typedef enum { - NJS_SCOPE_ABSOLUTE = 0, - NJS_SCOPE_GLOBAL = 1, - NJS_SCOPE_CALLEE_ARGUMENTS = 2, - /* - * The argument and local VM scopes should be separated because a - * function may be called with any number of arguments. - */ - NJS_SCOPE_ARGUMENTS = 3, - NJS_SCOPE_LOCAL = 4, - NJS_SCOPE_FUNCTION = NJS_SCOPE_LOCAL, - - NJS_SCOPE_CLOSURE = 5, - /* - * The block and shim scopes are not really VM scopes. - * They are used only on parsing phase. - */ - NJS_SCOPE_BLOCK = 16, - NJS_SCOPE_SHIM = 17, + NJS_SCOPE_GLOBAL = 0, + NJS_SCOPE_FUNCTION, + NJS_SCOPE_BLOCK } njs_scope_t; -/* - * The maximum possible function nesting level is (16 - NJS_SCOPE_CLOSURE), - * that is 11. The 8 is reasonable limit. - */ -#define NJS_MAX_NESTING 8 - -#define NJS_SCOPES (NJS_SCOPE_CLOSURE + NJS_MAX_NESTING) - -#define NJS_SCOPE_SHIFT 4 -#define NJS_SCOPE_MASK ((uintptr_t) ((1 << NJS_SCOPE_SHIFT) - 1)) - -#define NJS_INDEX_NONE ((njs_index_t) 0) -#define NJS_INDEX_ERROR ((njs_index_t) -1) -#define NJS_INDEX_THIS ((njs_index_t) (0 | NJS_SCOPE_ARGUMENTS)) - -#define njs_scope_type(index) \ - ((uintptr_t) (index) & NJS_SCOPE_MASK) - -#define njs_is_callee_argument_index(index) \ - (((index) & NJS_SCOPE_CALLEE_ARGUMENTS) == NJS_SCOPE_CALLEE_ARGUMENTS) - - typedef enum { NJS_OBJ_TYPE_OBJECT = 0, NJS_OBJ_TYPE_ARRAY, @@ -149,38 +112,22 @@ enum njs_object_e { }; -#define njs_scope_index(value, type) \ - ((njs_index_t) (((value) << NJS_SCOPE_SHIFT) | (type))) - - -#define njs_global_scope_index(value) \ - (njs_scope_index(value, NJS_SCOPE_GLOBAL)) - - -#define NJS_INDEX_GLOBAL_OBJECT njs_global_scope_index(0) -#define NJS_INDEX_GLOBAL_OBJECT_OFFSET njs_scope_index(0, 0) - - -#define NJS_INDEX_GLOBAL_RETVAL njs_global_scope_index(1) -#define NJS_INDEX_GLOBAL_OFFSET njs_scope_index(1, 0) - - -#define njs_scope_offset(index) \ - ((uintptr_t) (index) & ~NJS_SCOPE_MASK) - - -#define njs_vmcode_operand(vm, index) \ - ((njs_value_t *) \ - ((u_char *) vm->scopes[(uintptr_t) (index) & NJS_SCOPE_MASK] \ - + njs_scope_offset(index))) - - enum njs_hook_e { NJS_HOOK_EXIT = 0, NJS_HOOK_MAX }; +typedef enum { + NJS_LEVEL_LOCAL = 0, + NJS_LEVEL_CLOSURE, + NJS_LEVEL_GLOBAL, + NJS_LEVEL_STATIC, + NJS_LEVEL_TEMP, + NJS_LEVEL_MAX +} njs_level_type_t; + + struct njs_vm_s { /* njs_vm_t must be aligned to njs_value_t due to scratch value. */ njs_value_t retval; @@ -188,7 +135,9 @@ struct njs_vm_s { njs_arr_t *paths; njs_arr_t *protos; - njs_value_t *scopes[NJS_SCOPES]; + njs_arr_t *scope_absolute; + njs_value_t **levels[NJS_LEVEL_MAX]; + size_t global_items; njs_external_ptr_t external; @@ -221,8 +170,6 @@ struct njs_vm_s { njs_mp_t *mem_pool; u_char *start; - njs_value_t *global_scope; - size_t scope_size; size_t stack_size; njs_vm_shared_t *shared; @@ -232,6 +179,8 @@ struct njs_vm_s { njs_array_t *promise_reason; + njs_parser_scope_t *global_scope; + /* * MemoryError is statically allocated immutable Error object * with the InternalError prototype. @@ -240,6 +189,7 @@ struct njs_vm_s { njs_object_t string_object; njs_object_t global_object; + njs_value_t global_value; njs_arr_t *codes; /* of njs_vm_code_t */ njs_arr_t *functions_name_cache; diff --git a/src/njs_vmcode.c b/src/njs_vmcode.c index 31b4239a..8eda0858 100644 --- a/src/njs_vmcode.c +++ b/src/njs_vmcode.c @@ -22,6 +22,8 @@ static njs_jump_off_t njs_vmcode_template_literal(njs_vm_t *vm, njs_value_t *inlvd1, njs_value_t *inlvd2); static njs_jump_off_t njs_vmcode_object_copy(njs_vm_t *vm, njs_value_t *value, njs_value_t *invld); +static njs_jump_off_t njs_vmcode_function_copy(njs_vm_t *vm, njs_value_t *value, + njs_index_t retval); 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); @@ -51,12 +53,6 @@ static njs_jump_off_t njs_vmcode_finally(njs_vm_t *vm, njs_value_t *invld, njs_value_t *retval, u_char *pc); static void njs_vmcode_error(njs_vm_t *vm, u_char *pc); -/* - * These functions are forbidden to inline to minimize JavaScript VM - * interpreter memory footprint. The size is less than 8K on AMD64 - * and should fit in CPU L1 instruction cache. - */ - static njs_jump_off_t njs_string_concat(njs_vm_t *vm, njs_value_t *val1, njs_value_t *val2); static njs_jump_off_t njs_values_equal(njs_vm_t *vm, njs_value_t *val1, @@ -67,11 +63,9 @@ static njs_jump_off_t njs_function_frame_create(njs_vm_t *vm, njs_value_t *value, const njs_value_t *this, uintptr_t nargs, njs_bool_t ctor); -/* - * The nJSVM is optimized for an ABIs where the first several arguments - * are passed in registers (AMD64, ARM32/64): two pointers to the operand - * values is passed as arguments although they are not always used. - */ + +#define njs_vmcode_operand(vm, index) njs_scope_valid_value(vm, index) + njs_int_t njs_vmcode_interpreter(njs_vm_t *vm, u_char *pc) @@ -89,10 +83,11 @@ njs_vmcode_interpreter(njs_vm_t *vm, u_char *pc) njs_value_t numeric1, numeric2, primitive1, primitive2; njs_frame_t *frame; njs_jump_off_t ret; - njs_vmcode_this_t *this; njs_native_frame_t *previous, *native; njs_property_next_t *next; + njs_vmcode_finally_t *finally; njs_vmcode_generic_t *vmcode; + njs_vmcode_move_arg_t *move_arg; njs_vmcode_prop_get_t *get; njs_vmcode_prop_set_t *set; njs_vmcode_operation_t op; @@ -101,7 +96,9 @@ njs_vmcode_interpreter(njs_vm_t *vm, u_char *pc) njs_vmcode_equal_jump_t *equal; njs_vmcode_try_return_t *try_return; njs_vmcode_method_frame_t *method_frame; + njs_vmcode_function_copy_t *fcopy; njs_vmcode_prop_accessor_t *accessor; + njs_vmcode_try_trampoline_t *try_trampoline; njs_vmcode_function_frame_t *function_frame; next: @@ -613,9 +610,24 @@ next: *retval = vm->retval; } else { + switch (op) { + case NJS_VMCODE_MOVE_ARG: + move_arg = (njs_vmcode_move_arg_t *) pc; + native = vm->top_frame; + + hint = move_arg->dst; + + value1 = &native->arguments_offset[hint]; + value2 = njs_vmcode_operand(vm, move_arg->src); + + *value1 = *value2; + + ret = sizeof(njs_vmcode_move_arg_t); + break; + case NJS_VMCODE_STOP: - value2 = njs_vmcode_operand(vm, value2); + value2 = njs_vmcode_operand(vm, (njs_index_t) value2); vm->retval = *value2; return NJS_OK; @@ -690,38 +702,14 @@ next: break; case NJS_VMCODE_RETURN: - value2 = njs_vmcode_operand(vm, value2); - - frame = (njs_frame_t *) vm->top_frame; + value2 = njs_vmcode_operand(vm, (njs_index_t) value2); + return njs_vmcode_return(vm, NULL, value2); - if (frame->native.ctor) { - if (njs_is_object(value2)) { - njs_release(vm, vm->scopes[NJS_SCOPE_ARGUMENTS]); - - } else { - value2 = vm->scopes[NJS_SCOPE_ARGUMENTS]; - } - } - - previous = njs_function_previous_frame(&frame->native); - - njs_vm_scopes_restore(vm, &frame->native, previous); - - /* - * If a retval is in a callee arguments scope it - * must be in the previous callee arguments scope. - */ - retval = njs_vmcode_operand(vm, frame->native.retval); - - /* - * GC: value external/internal++ depending on - * value and retval type - */ - *retval = *value2; - - njs_function_frame_free(vm, &frame->native); - - return NJS_OK; + case NJS_VMCODE_FUNCTION_COPY: + fcopy = (njs_vmcode_function_copy_t *) pc; + ret = njs_vmcode_function_copy(vm, fcopy->function, + fcopy->retval); + break; case NJS_VMCODE_FUNCTION_FRAME: function_frame = (njs_vmcode_function_frame_t *) pc; @@ -775,7 +763,9 @@ next: case NJS_VMCODE_FUNCTION_CALL: vm->active_frame->native.pc = pc; - ret = njs_function_frame_invoke(vm, (njs_index_t) value2); + value2 = njs_vmcode_operand(vm, (njs_index_t) value2); + + ret = njs_function_frame_invoke(vm, value2); if (njs_slow_path(ret == NJS_ERROR)) { goto error; } @@ -801,16 +791,6 @@ next: ret = sizeof(njs_vmcode_prop_next_t); break; - case NJS_VMCODE_THIS: - frame = vm->active_frame; - this = (njs_vmcode_this_t *) pc; - - retval = njs_vmcode_operand(vm, this->dst); - *retval = frame->native.arguments[0]; - - ret = sizeof(njs_vmcode_this_t); - break; - case NJS_VMCODE_ARGUMENTS: ret = njs_vmcode_arguments(vm, pc); if (njs_slow_path(ret == NJS_ERROR)) { @@ -838,15 +818,21 @@ next: break; case NJS_VMCODE_THROW: - value2 = njs_vmcode_operand(vm, value2); + value2 = njs_vmcode_operand(vm, (njs_index_t) value2); vm->retval = *value2; goto error; case NJS_VMCODE_TRY_BREAK: + try_trampoline = (njs_vmcode_try_trampoline_t *) pc; + value1 = njs_scope_value(vm, try_trampoline->exit_value); + ret = njs_vmcode_try_break(vm, value1, value2); break; case NJS_VMCODE_TRY_CONTINUE: + try_trampoline = (njs_vmcode_try_trampoline_t *) pc; + value1 = njs_scope_value(vm, try_trampoline->exit_value); + ret = njs_vmcode_try_continue(vm, value1, value2); break; @@ -877,6 +863,9 @@ next: break; case NJS_VMCODE_FINALLY: + finally = (njs_vmcode_finally_t *) pc; + value1 = njs_scope_value(vm, finally->exit_value); + ret = njs_vmcode_finally(vm, value1, value2, pc); switch (ret) { @@ -1005,18 +994,21 @@ static njs_jump_off_t njs_vmcode_function(njs_vm_t *vm, u_char *pc) { njs_function_t *function; - njs_function_lambda_t *lambda; njs_vmcode_function_t *code; + njs_function_lambda_t *lambda; code = (njs_vmcode_function_t *) pc; lambda = code->lambda; - function = njs_function_alloc(vm, lambda, - njs_frame_closures(vm->active_frame), 0); + function = njs_function_alloc(vm, lambda); if (njs_slow_path(function == NULL)) { return NJS_ERROR; } + if (njs_function_capture_closure(vm, function, lambda) != NJS_OK) { + return NJS_ERROR; + } + function->args_count = lambda->nargs - lambda->rest_parameters; njs_set_function(&vm->retval, function); @@ -1085,7 +1077,7 @@ njs_vmcode_template_literal(njs_vm_t *vm, njs_value_t *invld1, .u.native = njs_string_prototype_concat }; - value = njs_vmcode_operand(vm, retval); + value = njs_vmcode_operand(vm, (njs_index_t) retval); if (!njs_is_primitive(value)) { array = njs_array(value); @@ -1097,7 +1089,7 @@ njs_vmcode_template_literal(njs_vm_t *vm, njs_value_t *invld1, return ret; } - ret = njs_function_frame_invoke(vm, (njs_index_t) retval); + ret = njs_function_frame_invoke(vm, value); if (njs_slow_path(ret != NJS_OK)) { return ret; } @@ -1143,6 +1135,27 @@ njs_vmcode_object_copy(njs_vm_t *vm, njs_value_t *value, njs_value_t *invld) } +static njs_jump_off_t +njs_vmcode_function_copy(njs_vm_t *vm, njs_value_t *value, njs_index_t retidx) +{ + njs_value_t *retval; + njs_function_t *function; + + retval = njs_scope_valid_value(vm, retidx); + + if (njs_is_undefined(retval)) { + *retval = *value; + + function = njs_function_value_copy(vm, retval); + if (njs_slow_path(function == NULL)) { + return NJS_ERROR; + } + } + + return sizeof(njs_vmcode_function_copy_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) @@ -1674,20 +1687,17 @@ njs_function_new_object(njs_vm_t *vm, njs_value_t *constructor) static njs_jump_off_t njs_vmcode_return(njs_vm_t *vm, njs_value_t *invld, njs_value_t *retval) { - njs_value_t *value; njs_frame_t *frame; njs_native_frame_t *previous; - value = njs_vmcode_operand(vm, retval); - frame = (njs_frame_t *) vm->top_frame; if (frame->native.ctor) { - if (njs_is_object(value)) { - njs_release(vm, vm->scopes[NJS_SCOPE_ARGUMENTS]); + if (njs_is_object(retval)) { + njs_release(vm, frame->native.local[0]); } else { - value = vm->scopes[NJS_SCOPE_ARGUMENTS]; + retval = frame->native.local[0]; } } @@ -1695,14 +1705,7 @@ njs_vmcode_return(njs_vm_t *vm, njs_value_t *invld, njs_value_t *retval) njs_vm_scopes_restore(vm, &frame->native, previous); - /* - * If a retval is in a callee arguments scope it - * must be in the previous callee arguments scope. - */ - retval = njs_vmcode_operand(vm, frame->native.retval); - - /* GC: value external/internal++ depending on value and retval type */ - *retval = *value; + *frame->native.retval = *retval; njs_function_frame_free(vm, &frame->native); @@ -1743,7 +1746,7 @@ njs_vmcode_try_start(njs_vm_t *vm, njs_value_t *exception_value, njs_set_invalid(exception_value); try_start = (njs_vmcode_try_start_t *) pc; - exit_value = njs_vmcode_operand(vm, try_start->exit_value); + exit_value = njs_scope_value(vm, try_start->exit_value); njs_set_invalid(exit_value); njs_number(exit_value) = 0; @@ -1826,7 +1829,7 @@ njs_vmcode_finally(njs_vm_t *vm, njs_value_t *invld, njs_value_t *retval, njs_value_t *exception_value, *exit_value; njs_vmcode_finally_t *finally; - exception_value = njs_vmcode_operand(vm, retval); + exception_value = njs_scope_value(vm, (njs_index_t) retval); if (njs_is_valid(exception_value)) { vm->retval = *exception_value; @@ -1835,7 +1838,8 @@ njs_vmcode_finally(njs_vm_t *vm, njs_value_t *invld, njs_value_t *retval, } finally = (njs_vmcode_finally_t *) pc; - exit_value = njs_vmcode_operand(vm, finally->exit_value); + + exit_value = njs_scope_value(vm, finally->exit_value); /* * exit_value is set by: diff --git a/src/njs_vmcode.h b/src/njs_vmcode.h index 9f1dd206..76d13949 100644 --- a/src/njs_vmcode.h +++ b/src/njs_vmcode.h @@ -28,100 +28,100 @@ typedef uint8_t njs_vmcode_operation_t; #define NJS_VMCODE_3OPERANDS 0 #define NJS_VMCODE_2OPERANDS 1 -#define NJS_VMCODE_1OPERAND 2 -#define NJS_VMCODE_NO_OPERAND 3 - -#define NJS_VMCODE_NO_RETVAL 0 -#define NJS_VMCODE_RETVAL 1 - -#define VMCODE0(n) (n) -#define VMCODE1(n) ((n) + 128) - -#define NJS_VMCODE_STOP VMCODE0(0) -#define NJS_VMCODE_JUMP VMCODE0(1) -#define NJS_VMCODE_PROPERTY_SET VMCODE0(2) -#define NJS_VMCODE_PROPERTY_ACCESSOR VMCODE0(3) -#define NJS_VMCODE_IF_TRUE_JUMP VMCODE0(4) -#define NJS_VMCODE_IF_FALSE_JUMP VMCODE0(5) -#define NJS_VMCODE_IF_EQUAL_JUMP VMCODE0(6) -#define NJS_VMCODE_PROPERTY_INIT VMCODE0(7) -#define NJS_VMCODE_RETURN VMCODE0(8) -#define NJS_VMCODE_FUNCTION_FRAME VMCODE0(9) -#define NJS_VMCODE_METHOD_FRAME VMCODE0(10) -#define NJS_VMCODE_FUNCTION_CALL VMCODE0(11) -#define NJS_VMCODE_PROPERTY_NEXT VMCODE0(16) -#define NJS_VMCODE_THIS VMCODE0(17) -#define NJS_VMCODE_ARGUMENTS VMCODE0(18) -#define NJS_VMCODE_PROTO_INIT VMCODE0(19) - -#define NJS_VMCODE_TRY_START VMCODE0(32) -#define NJS_VMCODE_THROW VMCODE0(33) -#define NJS_VMCODE_TRY_BREAK VMCODE0(34) -#define NJS_VMCODE_TRY_CONTINUE VMCODE0(35) -#define NJS_VMCODE_TRY_END VMCODE0(37) -#define NJS_VMCODE_CATCH VMCODE0(38) -#define NJS_VMCODE_FINALLY VMCODE0(39) -#define NJS_VMCODE_ERROR VMCODE0(40) - -#define NJS_VMCODE_NORET 127 - -#define NJS_VMCODE_MOVE VMCODE1(0) -#define NJS_VMCODE_PROPERTY_GET VMCODE1(1) -#define NJS_VMCODE_INCREMENT VMCODE1(2) -#define NJS_VMCODE_POST_INCREMENT VMCODE1(3) -#define NJS_VMCODE_DECREMENT VMCODE1(4) -#define NJS_VMCODE_POST_DECREMENT VMCODE1(5) -#define NJS_VMCODE_TRY_RETURN VMCODE1(6) -#define NJS_VMCODE_GLOBAL_GET VMCODE1(7) - -#define NJS_VMCODE_LESS VMCODE1(8) -#define NJS_VMCODE_GREATER VMCODE1(9) -#define NJS_VMCODE_LESS_OR_EQUAL VMCODE1(10) -#define NJS_VMCODE_GREATER_OR_EQUAL VMCODE1(11) -#define NJS_VMCODE_ADDITION VMCODE1(12) -#define NJS_VMCODE_EQUAL VMCODE1(13) -#define NJS_VMCODE_NOT_EQUAL VMCODE1(14) - -#define NJS_VMCODE_SUBSTRACTION VMCODE1(16) -#define NJS_VMCODE_MULTIPLICATION VMCODE1(17) -#define NJS_VMCODE_EXPONENTIATION VMCODE1(18) -#define NJS_VMCODE_DIVISION VMCODE1(19) -#define NJS_VMCODE_REMAINDER VMCODE1(20) -#define NJS_VMCODE_BITWISE_AND VMCODE1(21) -#define NJS_VMCODE_BITWISE_OR VMCODE1(22) -#define NJS_VMCODE_BITWISE_XOR VMCODE1(23) -#define NJS_VMCODE_LEFT_SHIFT VMCODE1(24) -#define NJS_VMCODE_RIGHT_SHIFT VMCODE1(25) -#define NJS_VMCODE_UNSIGNED_RIGHT_SHIFT VMCODE1(26) -#define NJS_VMCODE_OBJECT_COPY VMCODE1(27) -#define NJS_VMCODE_TEMPLATE_LITERAL VMCODE1(28) -#define NJS_VMCODE_PROPERTY_IN VMCODE1(29) -#define NJS_VMCODE_PROPERTY_DELETE VMCODE1(30) -#define NJS_VMCODE_PROPERTY_FOREACH VMCODE1(31) - -#define NJS_VMCODE_STRICT_EQUAL VMCODE1(32) -#define NJS_VMCODE_STRICT_NOT_EQUAL VMCODE1(33) - -#define NJS_VMCODE_TEST_IF_TRUE VMCODE1(34) -#define NJS_VMCODE_TEST_IF_FALSE VMCODE1(35) - -#define NJS_VMCODE_COALESCE VMCODE1(36) - -#define NJS_VMCODE_UNARY_PLUS VMCODE1(37) -#define NJS_VMCODE_UNARY_NEGATION VMCODE1(38) -#define NJS_VMCODE_BITWISE_NOT VMCODE1(39) -#define NJS_VMCODE_LOGICAL_NOT VMCODE1(40) -#define NJS_VMCODE_OBJECT VMCODE1(41) -#define NJS_VMCODE_ARRAY VMCODE1(42) -#define NJS_VMCODE_FUNCTION VMCODE1(43) -#define NJS_VMCODE_REGEXP VMCODE1(44) - -#define NJS_VMCODE_INSTANCE_OF VMCODE1(45) -#define NJS_VMCODE_TYPEOF VMCODE1(46) -#define NJS_VMCODE_VOID VMCODE1(47) -#define NJS_VMCODE_DELETE VMCODE1(48) - -#define NJS_VMCODE_NOP 255 + + +enum { + NJS_VMCODE_MOVE_ARG = 0, + NJS_VMCODE_STOP, + NJS_VMCODE_JUMP, + NJS_VMCODE_PROPERTY_SET, + NJS_VMCODE_PROPERTY_ACCESSOR, + NJS_VMCODE_IF_TRUE_JUMP, + NJS_VMCODE_IF_FALSE_JUMP, + NJS_VMCODE_IF_EQUAL_JUMP, + NJS_VMCODE_PROPERTY_INIT, + NJS_VMCODE_RETURN, + NJS_VMCODE_FUNCTION_COPY, + NJS_VMCODE_FUNCTION_FRAME, + NJS_VMCODE_METHOD_FRAME, + NJS_VMCODE_FUNCTION_CALL, + NJS_VMCODE_PROPERTY_NEXT, + NJS_VMCODE_THIS, + NJS_VMCODE_ARGUMENTS, + NJS_VMCODE_PROTO_INIT, + + NJS_VMCODE_TRY_START, + NJS_VMCODE_THROW, + NJS_VMCODE_TRY_BREAK, + NJS_VMCODE_TRY_CONTINUE, + NJS_VMCODE_TRY_END, + NJS_VMCODE_CATCH, + NJS_VMCODE_FINALLY, + NJS_VMCODE_ERROR, + + NJS_VMCODE_NORET = 127 +}; + + +enum { + NJS_VMCODE_MOVE = NJS_VMCODE_NORET + 1, + NJS_VMCODE_PROPERTY_GET, + NJS_VMCODE_INCREMENT, + NJS_VMCODE_POST_INCREMENT, + NJS_VMCODE_DECREMENT, + NJS_VMCODE_POST_DECREMENT, + NJS_VMCODE_TRY_RETURN, + NJS_VMCODE_GLOBAL_GET, + + NJS_VMCODE_LESS, + NJS_VMCODE_GREATER, + NJS_VMCODE_LESS_OR_EQUAL, + NJS_VMCODE_GREATER_OR_EQUAL, + NJS_VMCODE_ADDITION, + NJS_VMCODE_EQUAL, + NJS_VMCODE_NOT_EQUAL, + + NJS_VMCODE_SUBSTRACTION, + NJS_VMCODE_MULTIPLICATION, + NJS_VMCODE_EXPONENTIATION, + NJS_VMCODE_DIVISION, + NJS_VMCODE_REMAINDER, + NJS_VMCODE_BITWISE_AND, + NJS_VMCODE_BITWISE_OR, + NJS_VMCODE_BITWISE_XOR, + NJS_VMCODE_LEFT_SHIFT, + NJS_VMCODE_RIGHT_SHIFT, + NJS_VMCODE_UNSIGNED_RIGHT_SHIFT, + NJS_VMCODE_OBJECT_COPY, + NJS_VMCODE_TEMPLATE_LITERAL, + NJS_VMCODE_PROPERTY_IN, + NJS_VMCODE_PROPERTY_DELETE, + NJS_VMCODE_PROPERTY_FOREACH, + + NJS_VMCODE_STRICT_EQUAL, + NJS_VMCODE_STRICT_NOT_EQUAL, + + NJS_VMCODE_TEST_IF_TRUE, + NJS_VMCODE_TEST_IF_FALSE, + + NJS_VMCODE_COALESCE, + + NJS_VMCODE_UNARY_PLUS, + NJS_VMCODE_UNARY_NEGATION, + NJS_VMCODE_BITWISE_NOT, + NJS_VMCODE_LOGICAL_NOT, + NJS_VMCODE_OBJECT, + NJS_VMCODE_ARRAY, + NJS_VMCODE_FUNCTION, + NJS_VMCODE_REGEXP, + + NJS_VMCODE_INSTANCE_OF, + NJS_VMCODE_TYPEOF, + NJS_VMCODE_VOID, + NJS_VMCODE_DELETE, + + NJS_VMCODE_NOP = 255 +}; typedef struct { @@ -394,6 +394,20 @@ typedef struct { } njs_vmcode_error_t; +typedef struct { + njs_vmcode_t code; + njs_index_t src; + njs_uint_t dst; +} njs_vmcode_move_arg_t; + + +typedef struct { + njs_vmcode_t code; + njs_value_t *function; + njs_index_t retval; +} njs_vmcode_function_copy_t; + + njs_int_t njs_vmcode_interpreter(njs_vm_t *vm, u_char *pc); njs_object_t *njs_function_new_object(njs_vm_t *vm, njs_value_t *constructor); diff --git a/src/test/njs_unit_test.c b/src/test/njs_unit_test.c index e49cc08f..5aa43f93 100644 --- a/src/test/njs_unit_test.c +++ b/src/test/njs_unit_test.c @@ -146,6 +146,47 @@ static njs_unit_test_t njs_test[] = { njs_str("var f = 1; function f() {}; f"), njs_str("1") }, + { njs_str("var f = 1; function f() {}; f"), + njs_str("1") }, + + { njs_str("function f(a) {return function (x) {return a(x)}} f(1)(0)"), + njs_str("TypeError: number is not a function") }, + + { njs_str("var x = 0;" + "" + "function f1() {" + " function f2() {" + " return x;" + " };" + "" + " return f2();" + "" + " var x = 1;" + "}" + "" + "f1() === undefined"), + njs_str("true") }, + + { njs_str("var fn = function fn() {return fn.test}; fn.test = 'test'; fn()"), + njs_str("test") }, + + { njs_str("var body;" + "var n = 'outside';" + "var before = function() {return n};" + "" + "var func = function n() {" + " var n;" + " body = function() {return n};" + "};" + "" + "func();" + "" + "[before(), body()]"), + njs_str("outside,") }, + + { njs_str("var func = function x(x) {return x}; func()"), + njs_str("undefined") }, + #if 0 /* TODO */ { njs_str("var a; Object.getOwnPropertyDescriptor(this, 'a').value"), njs_str("undefined") }, @@ -3660,6 +3701,9 @@ static njs_unit_test_t njs_test[] = { njs_str("delete undefined"), njs_str("SyntaxError: Delete of an unqualified identifier in 1") }, + { njs_str("delete this !== true"), + njs_str("false") }, + /* Object shorthand methods. */ { njs_str("var o = {m(){}}; new o.m();"), @@ -9305,7 +9349,7 @@ static njs_unit_test_t njs_test[] = "})" "})" "})"), - njs_str("SyntaxError: The maximum function nesting level is \"8\" in 1") }, + njs_str("[object Function]") }, { njs_str("Function.prototype.toString = function () {return 'X'};" "eval"), @@ -9365,6 +9409,72 @@ static njs_unit_test_t njs_test[] = { njs_str("(function (Object, Array, Boolean){ return Object + Array + Boolean})('x', 'y', 'z')"), njs_str("xyz") }, + { njs_str("var n = 11, res;" + "function a() {return b}" + "res = a()(2);" + "function b(k) {var x = b; return 1 + k + n} res"), + njs_str("14") }, + + { njs_str("var y = 9, res;" + "function a(n) {function b() {return c(n + 2)} return b()}" + "res = a(1);" + "function c(m) {var x = c; return m + 3 + y} res"), + njs_str("15") }, + + { njs_str("var res;" + "closure();" + "res = globalThis.funcall(1);" + "function closure() {" + " var y = 9, res;" + " globalThis.funcall = a;" + " function a(n) { function b() {return c(2)} return b() }" + " function c(m) {var x = c; return m + 3 + y}" + "} res"), + njs_str("14") }, + + { njs_str("function a() {" + " var x = 1;" + " function b() {var n = x; x = undefined; return n}" + " return b;" + "}" + "[a()(), a()()];"), + njs_str("1,1") }, + + { njs_str("function a(obj, name) {!Object.prototype.hasOwnProperty.call(obj, name)}" + "a(this, 'b');" + "function b() {}"), + njs_str("undefined") }, + + { njs_str("function abc() {" + "function x() {var a = x.arr; x.arr = 123; return a}" + "return x;" + "} [abc()(),abc()()]"), + njs_str(",") }, + + { njs_str("function x() {var a = x.arr; x.arr = 123; return a} [x(),x()]"), + njs_str(",123") }, + + { njs_str("var obj;" + "function make(desc) {obj = {'a': 123}}" + "function a(desc) {make()}" + "a(); obj.a"), + njs_str("123") }, + + { njs_str("var res;" + "function cls() {" + " var obj = {'a': 123};" + " Object.defineProperty(obj, \"length\", {" + " get: function() {res = obj}" + " });" + " return obj;" + "}" + "var obj = cls();" + "[].includes.call(obj); res.a"), + njs_str("123") }, + + { njs_str("function f(){} typeof(f)"), + njs_str("function") }, + /* Recursive factorial. */ { njs_str("function f(a) {" @@ -11283,6 +11393,38 @@ static njs_unit_test_t njs_test[] = { njs_str("function f(){}; (function(){try {f(f((new RegExp('a**'))))} catch (e) { return 1}})()"), njs_str("1") }, + { njs_str("var before, during, after;" + "" + "try {" + " throw 'exception';" + "} catch (err) {" + " before = err;" + "" + " for (var err in { name: null }) {" + " during = err;" + " }" + "" + " after = err;" + "}" + "" + "[before === 'exception', during === 'name', after === 'name']"), + njs_str("true,true,true") }, + + { njs_str("var arr = [];" + "foo = \"outside\";" + "" + "try {" + " throw new Error();" + "}" + "catch (foo) {" + " var foo = \"inside\";" + " arr.push(foo);" + "}" + "" + "arr.push(foo);" + "arr"), + njs_str("inside,outside") }, + { njs_str("var o = { valueOf: function() { return '3' } }; --o"), njs_str("2") }, @@ -20330,7 +20472,7 @@ static njs_unit_test_t njs_shell_test[] = { njs_str("/abc/i.test('ABC')" ENTER), njs_str("true") }, - /* Accumulative mode. */ + /* Interactive mode. */ { njs_str("var a = 1" ENTER "a" ENTER), @@ -20775,7 +20917,7 @@ njs_interactive_test(njs_unit_test_t tests[], size_t num, njs_str_t *name, njs_vm_opt_init(&options); options.init = 1; - options.accumulative = 1; + options.interactive = 1; options.backtrace = 1; vm = njs_vm_create(&options);