From: Igor Sysoev Date: Tue, 28 Mar 2017 04:50:05 +0000 (+0300) Subject: Closures support. X-Git-Tag: 0.1.10~17 X-Git-Url: http://git.kaiwu.me/postgresql/log/contrib/postgres_fdw/static/gitweb.js?a=commitdiff_plain;h=0059d0f5e36d5460c88d2da12da1fe20592067bf;p=njs.git Closures support. --- diff --git a/njs/njs_array.c b/njs/njs_array.c index 0b85a12d..9149cbf7 100644 --- a/njs/njs_array.c +++ b/njs/njs_array.c @@ -725,7 +725,7 @@ njs_array_prototype_to_string_continuation(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs, njs_index_t retval) { /* Skip retval update. */ - vm->frame->skip = 1; + vm->top_frame->skip = 1; return NXT_OK; } diff --git a/njs/njs_boolean.c b/njs/njs_boolean.c index 77e86249..d75476a1 100644 --- a/njs/njs_boolean.c +++ b/njs/njs_boolean.c @@ -34,7 +34,7 @@ njs_boolean_constructor(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs, value = njs_is_true(&args[1]) ? &njs_value_true : &njs_value_false; } - if (vm->frame->ctor) { + if (vm->top_frame->ctor) { object = njs_object_value_alloc(vm, value, value->type); if (nxt_slow_path(object == NULL)) { return NXT_ERROR; diff --git a/njs/njs_date.c b/njs/njs_date.c index e1bcec11..8d5bfe6d 100644 --- a/njs/njs_date.c +++ b/njs/njs_date.c @@ -91,7 +91,7 @@ njs_date_constructor(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs, njs_date_t *date; struct tm tm; - if (vm->frame->ctor) { + if (vm->top_frame->ctor) { if (nargs == 1) { time = njs_gettime(); @@ -1892,7 +1892,7 @@ njs_date_prototype_to_json_continuation(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs, njs_index_t retval) { /* Skip retval update. */ - vm->frame->skip = 1; + vm->top_frame->skip = 1; return NXT_OK; } diff --git a/njs/njs_function.c b/njs/njs_function.c index a44bd0aa..df620d85 100644 --- a/njs/njs_function.c +++ b/njs/njs_function.c @@ -60,7 +60,9 @@ njs_function_alloc(njs_vm_t *vm) njs_function_t * njs_function_value_copy(njs_vm_t *vm, njs_value_t *value) { - njs_function_t *function; + size_t size; + nxt_uint_t n, nesting; + njs_function_t *function, *copy; function = value->data.u.function; @@ -68,17 +70,36 @@ njs_function_value_copy(njs_vm_t *vm, njs_value_t *value) return function; } - function = nxt_mem_cache_alloc(vm->mem_cache_pool, sizeof(njs_function_t)); + nesting = (function->native) ? 0 : function->u.lambda->nesting; - if (nxt_fast_path(function != NULL)) { - *function = *value->data.u.function; - function->object.__proto__ = - &vm->prototypes[NJS_PROTOTYPE_FUNCTION].object; - function->object.shared = 0; - value->data.u.function = function; + size = sizeof(njs_function_t) + nesting * sizeof(njs_closure_t *); + + copy = nxt_mem_cache_alloc(vm->mem_cache_pool, size); + if (nxt_slow_path(copy == NULL)) { + return copy; } - return function; + value->data.u.function = copy; + + *copy = *function; + copy->object.__proto__ = &vm->prototypes[NJS_PROTOTYPE_FUNCTION].object; + copy->object.shared = 0; + + if (nesting == 0) { + return copy; + } + + copy->closure = 1; + + n = 0; + + do { + /* GC: retain closure. */ + copy->closures[n] = vm->active_frame->closures[n]; + n++; + } while (n < nesting); + + return copy; } @@ -107,6 +128,7 @@ njs_function_native_frame(njs_vm_t *vm, njs_function_t *function, frame->ctor = ctor; value = (njs_value_t *) (njs_continuation(frame) + reserve); + frame->arguments = value; bound = function->bound; @@ -124,7 +146,6 @@ njs_function_native_frame(njs_vm_t *vm, njs_function_t *function, } while (n != 0); } - frame->arguments = value; vm->scopes[NJS_SCOPE_CALLEE_ARGUMENTS] = value; if (args != NULL) { @@ -140,17 +161,23 @@ njs_function_frame(njs_vm_t *vm, njs_function_t *function, const njs_value_t *this, njs_value_t *args, nxt_uint_t nargs, nxt_bool_t ctor) { - size_t size; - nxt_uint_t n, max_args; - njs_value_t *value, *bound; - njs_frame_t *frame; - njs_native_frame_t *native_frame; + size_t size; + nxt_uint_t n, max_args, closures;; + njs_value_t *value, *bound; + njs_frame_t *frame; + njs_native_frame_t *native_frame; + njs_function_lambda_t *lambda; + + lambda = function->u.lambda; - max_args = nxt_max(nargs, function->u.lambda->nargs); + max_args = nxt_max(nargs, lambda->nargs); + + closures = lambda->nesting + lambda->block_closures; size = NJS_FRAME_SIZE + (function->args_offset + max_args) * sizeof(njs_value_t) - + function->u.lambda->local_size; + + lambda->local_size + + closures * sizeof(njs_closure_t *); native_frame = njs_function_frame_alloc(vm, size); if (nxt_slow_path(native_frame == NULL)) { @@ -161,7 +188,10 @@ njs_function_frame(njs_vm_t *vm, njs_function_t *function, native_frame->nargs = nargs; native_frame->ctor = ctor; + /* Function arguments. */ + value = (njs_value_t *) ((u_char *) native_frame + NJS_FRAME_SIZE); + native_frame->arguments = value; bound = function->bound; @@ -177,7 +207,6 @@ njs_function_frame(njs_vm_t *vm, njs_function_t *function, } while (n != 0); } - native_frame->arguments = value; vm->scopes[NJS_SCOPE_CALLEE_ARGUMENTS] = value; if (args != NULL) { @@ -196,9 +225,6 @@ njs_function_frame(njs_vm_t *vm, njs_function_t *function, frame = (njs_frame_t *) native_frame; frame->local = value; - memcpy(frame->local, function->u.lambda->local_scope, - function->u.lambda->local_size); - return NXT_OK; } @@ -213,10 +239,10 @@ njs_function_frame_alloc(njs_vm_t *vm, size_t size) size_t spare_size, chunk_size; njs_native_frame_t *frame; - spare_size = vm->frame->free_size; + spare_size = vm->top_frame->free_size; if (nxt_fast_path(size <= spare_size)) { - frame = (njs_native_frame_t *) vm->frame->free; + frame = (njs_native_frame_t *) vm->top_frame->free; chunk_size = 0; } else { @@ -244,8 +270,8 @@ njs_function_frame_alloc(njs_vm_t *vm, size_t size) frame->free_size = spare_size - size; frame->free = (u_char *) frame + size; - frame->previous = vm->frame; - vm->frame = frame; + frame->previous = vm->top_frame; + vm->top_frame = frame; return frame; } @@ -290,25 +316,86 @@ njs_function_apply(njs_vm_t *vm, njs_function_t *function, njs_value_t *args, nxt_noinline njs_ret_t njs_function_call(njs_vm_t *vm, njs_index_t retval, size_t advance) { - njs_frame_t *frame; - njs_function_t *function; + size_t size; + nxt_uint_t n, nesting; + njs_frame_t *frame; + njs_value_t *value; + njs_closure_t *closure, **closures; + njs_function_t *function; + njs_function_lambda_t *lambda; - frame = (njs_frame_t *) vm->frame; + frame = (njs_frame_t *) vm->top_frame; frame->retval = retval; function = frame->native.function; frame->return_address = vm->current + advance; - vm->current = function->u.lambda->u.start; - frame->prev_arguments = vm->scopes[NJS_SCOPE_ARGUMENTS]; - vm->scopes[NJS_SCOPE_ARGUMENTS] = frame->native.arguments - - function->args_offset; + lambda = function->u.lambda; + vm->current = lambda->u.start; + #if (NXT_DEBUG) vm->scopes[NJS_SCOPE_CALLEE_ARGUMENTS] = NULL; #endif - frame->prev_local = vm->scopes[NJS_SCOPE_FUNCTION]; - vm->scopes[NJS_SCOPE_FUNCTION] = frame->local; + + vm->scopes[NJS_SCOPE_ARGUMENTS] = frame->native.arguments; + + /* Function local variables and temporary values. */ + + vm->scopes[NJS_SCOPE_LOCAL] = frame->local; + + memcpy(frame->local, lambda->local_scope, lambda->local_size); + + /* Parent closures values. */ + + n = 0; + nesting = lambda->nesting; + + if (nesting != 0) { + closures = (function->closure) ? function->closures + : vm->active_frame->closures; + do { + closure = *closures++; + + frame->closures[n] = closure; + vm->scopes[NJS_SCOPE_CLOSURE + n] = &closure->u.values; + + n++; + } while (n < nesting); + } + + /* Function closure values. */ + + if (lambda->block_closures > 0) { + closure = NULL; + + size = lambda->closure_size; + + if (size != 0) { + closure = nxt_mem_cache_align(vm->mem_cache_pool, + sizeof(njs_value_t), size); + if (nxt_slow_path(closure == NULL)) { + return NXT_ERROR; + } + + /* TODO: copy initialzed values. */ + + size -= sizeof(njs_value_t); + closure->u.count = 0; + value = closure->values; + + do { + *value++ = njs_value_void; + size -= sizeof(njs_value_t); + } while (size != 0); + } + + frame->closures[n] = closure; + vm->scopes[NJS_SCOPE_CLOSURE + n] = &closure->u.values; + } + + frame->previous_active_frame = vm->active_frame; + vm->active_frame = frame; return NJS_APPLIED; } @@ -498,7 +585,7 @@ njs_function_activate(njs_vm_t *vm, njs_function_t *function, njs_value_t *this, } /* Skip the "call/apply" method frame. */ - vm->frame->previous->skip = 1; + vm->top_frame->previous->skip = 1; cont = njs_vm_continuation(vm); @@ -520,7 +607,7 @@ njs_function_activate(njs_vm_t *vm, njs_function_t *function, njs_value_t *this, } /* Skip the "call/apply" method frame. */ - vm->frame->previous->skip = 1; + vm->top_frame->previous->skip = 1; return njs_function_call(vm, retval, sizeof(njs_vmcode_function_call_t)); } diff --git a/njs/njs_function.h b/njs/njs_function.h index c30fd171..6bcba89b 100644 --- a/njs/njs_function.h +++ b/njs/njs_function.h @@ -22,6 +22,13 @@ struct njs_function_lambda_s { uint32_t nargs; uint32_t local_size; + uint32_t closure_size; + + /* Function nesting level. */ + uint8_t nesting; /* 4 bits */ + + /* Function internal block closures levels. */ + uint8_t block_closures; /* 4 bits */ /* Initial values of local scope. */ njs_value_t *local_scope; @@ -57,7 +64,7 @@ typedef struct { #define njs_vm_continuation(vm) \ - (void *) njs_continuation((vm)->frame) + (void *) njs_continuation((vm)->top_frame) #define njs_continuation(frame) \ ((u_char *) frame + NJS_NATIVE_FRAME_SIZE) @@ -69,7 +76,7 @@ typedef struct { #define njs_vm_trap_value(vm, val) \ - (vm)->frame->trap_scratch.data.u.value = val + (vm)->top_frame->trap_scratch.data.u.value = val @@ -119,17 +126,21 @@ struct njs_native_frame_s { }; -typedef struct { +struct njs_frame_s { njs_native_frame_t native; + njs_index_t retval; + u_char *return_address; - njs_value_t *prev_arguments; - njs_value_t *prev_local; - njs_value_t *local; - njs_value_t *closure; + njs_frame_t *previous_active_frame; - njs_index_t retval; -} njs_frame_t; + njs_value_t *local; +#if (NXT_SUNC) + njs_closure_t *closures[1]; +#else + njs_closure_t *closures[]; +#endif +}; njs_function_t *njs_function_alloc(njs_vm_t *vm); diff --git a/njs/njs_generator.c b/njs/njs_generator.c index 5476ab5d..ceeb12a4 100644 --- a/njs/njs_generator.c +++ b/njs/njs_generator.c @@ -92,6 +92,8 @@ static nxt_int_t njs_generate_function_declaration(njs_vm_t *vm, njs_parser_t *parser, njs_parser_node_t *node); static nxt_int_t njs_generate_function_scope(njs_vm_t *vm, njs_function_lambda_t *lambda, njs_parser_node_t *node); +static void njs_generate_argument_closures(njs_parser_t *parser, + njs_parser_node_t *node); static nxt_int_t njs_generate_return_statement(njs_vm_t *vm, njs_parser_t *parser, njs_parser_node_t *node); static nxt_int_t njs_generate_function_call(njs_vm_t *vm, njs_parser_t *parser, @@ -338,7 +340,7 @@ njs_generate_name(njs_vm_t *vm, njs_parser_t *parser, njs_parser_node_t *node) njs_variable_t *var; njs_vmcode_object_copy_t *copy; - var = njs_variable_get(vm, node, NJS_NAME_REFERENCE); + var = njs_variable_get(vm, node); if (nxt_slow_path(var == NULL)) { return NXT_ERROR; } @@ -371,7 +373,7 @@ njs_generate_builtin_object(njs_vm_t *vm, njs_parser_t *parser, njs_index_t index; njs_vmcode_object_copy_t *copy; - index = njs_variable_index(vm, node, NJS_NAME_REFERENCE); + index = njs_variable_index(vm, node); if (nxt_slow_path(index == NJS_INDEX_ERROR)) { return NXT_ERROR; } @@ -398,7 +400,7 @@ njs_generate_variable(njs_vm_t *vm, njs_parser_t *parser, { njs_index_t index; - index = njs_variable_index(vm, node, NJS_NAME_REFERENCE); + index = njs_variable_index(vm, node); if (nxt_slow_path(index == NJS_INDEX_ERROR)) { return NXT_ERROR; } @@ -420,7 +422,7 @@ njs_generate_var_statement(njs_vm_t *vm, njs_parser_t *parser, lvalue = node->left; - index = njs_variable_index(vm, lvalue, NJS_NAME_DECLARATION); + index = njs_variable_index(vm, lvalue); if (nxt_slow_path(index == NJS_INDEX_ERROR)) { return NXT_ERROR; } @@ -1307,7 +1309,7 @@ njs_generate_assignment(njs_vm_t *vm, njs_parser_t *parser, if (lvalue->token == NJS_TOKEN_NAME) { - index = njs_variable_index(vm, lvalue, NJS_NAME_REFERENCE); + index = njs_variable_index(vm, lvalue); if (nxt_slow_path(index == NJS_INDEX_ERROR)) { return NXT_ERROR; } @@ -1813,7 +1815,7 @@ njs_generate_typeof_operation(njs_vm_t *vm, njs_parser_t *parser, expr = node->left; if (expr->token == NJS_TOKEN_NAME) { - index = njs_variable_index(vm, expr, NJS_NAME_TYPEOF); + index = njs_variable_typeof(vm, expr); if (nxt_slow_path(index == NJS_INDEX_ERROR)) { return NXT_ERROR; } @@ -1964,7 +1966,7 @@ njs_generate_function_declaration(njs_vm_t *vm, njs_parser_t *parser, njs_variable_t *var; njs_function_lambda_t *lambda; - var = njs_variable_get(vm, node, NJS_NAME_DECLARATION); + var = njs_variable_get(vm, node); if (nxt_slow_path(var == NULL)) { return NXT_ERROR; } @@ -1979,14 +1981,34 @@ static nxt_int_t njs_generate_function_scope(njs_vm_t *vm, njs_function_lambda_t *lambda, njs_parser_node_t *node) { - nxt_int_t ret; + size_t size; + nxt_int_t ret; + nxt_array_t *closure; + njs_parser_t *parser; + + parser = lambda->u.parser; + node = node->right; + + parser->code_size += node->scope->argument_closures + * sizeof(njs_vmcode_move_t); - ret = njs_generate_scope(vm, lambda->u.parser, node->right); + ret = njs_generate_scope(vm, parser, node); if (nxt_fast_path(ret == NXT_OK)) { - lambda->local_size = lambda->u.parser->scope_size; - lambda->local_scope = lambda->u.parser->local_scope; - lambda->u.start = lambda->u.parser->code_start; + size = 0; + closure = node->scope->values[1]; + + if (closure != NULL) { + lambda->block_closures = 1; + size = (1 + closure->items) * sizeof(njs_value_t); + } + + lambda->nesting = node->scope->nesting; + lambda->closure_size = size; + + lambda->local_size = parser->scope_size; + lambda->local_scope = parser->local_scope; + lambda->u.start = parser->code_start; } return ret; @@ -2004,6 +2026,8 @@ njs_generate_scope(njs_vm_t *vm, njs_parser_t *parser, njs_parser_node_t *node) njs_vm_code_t *code; njs_parser_scope_t *scope; + scope = node->scope; + p = nxt_mem_cache_alloc(vm->mem_cache_pool, parser->code_size); if (nxt_slow_path(p == NULL)) { return NXT_ERROR; @@ -2012,12 +2036,12 @@ njs_generate_scope(njs_vm_t *vm, njs_parser_t *parser, njs_parser_node_t *node) parser->code_start = p; parser->code_end = p; + njs_generate_argument_closures(parser, node); + if (nxt_slow_path(njs_generator(vm, parser, node) != NXT_OK)) { return NXT_ERROR; } - scope = node->scope; - code_size = parser->code_end - parser->code_start; nxt_thread_log_debug("SCOPE CODE SIZE: %uz %uz", @@ -2028,7 +2052,7 @@ njs_generate_scope(njs_vm_t *vm, njs_parser_t *parser, njs_parser_node_t *node) return NXT_ERROR; } - scope_size = njs_offset(scope->next_index); + scope_size = njs_scope_offset(scope->next_index[0]); if (scope->type == NJS_SCOPE_GLOBAL) { scope_size -= NJS_INDEX_GLOBAL_OFFSET; @@ -2041,11 +2065,11 @@ njs_generate_scope(njs_vm_t *vm, njs_parser_t *parser, njs_parser_node_t *node) parser->scope_size = scope_size; - size = scope->values->items * sizeof(njs_value_t); + size = scope->values[0]->items * sizeof(njs_value_t); nxt_thread_log_debug("SCOPE SIZE: %uz %uz", size, scope_size); - p = memcpy(parser->local_scope, scope->values->start, size); + p = memcpy(parser->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)) { @@ -2072,6 +2096,44 @@ njs_generate_scope(njs_vm_t *vm, njs_parser_t *parser, njs_parser_node_t *node) } +static void +njs_generate_argument_closures(njs_parser_t *parser, njs_parser_node_t *node) +{ + nxt_uint_t n; + njs_index_t index; + njs_variable_t *var; + njs_vmcode_move_t *move; + nxt_lvlhsh_each_t lhe; + + n = node->scope->argument_closures; + + if (n == 0) { + return; + } + + memset(&lhe, 0, sizeof(nxt_lvlhsh_each_t)); + lhe.proto = &njs_variables_hash_proto; + + do { + var = nxt_lvlhsh_each(&node->scope->variables, &lhe); + + if (var->argument != 0) { + index = njs_scope_index((var->argument - 1), NJS_SCOPE_ARGUMENTS); + + njs_generate_code(parser, njs_vmcode_move_t, move); + move->code.operation = njs_vmcode_move; + move->code.operands = NJS_VMCODE_2OPERANDS; + move->code.retval = NJS_VMCODE_RETVAL; + move->dst = var->index; + move->src = index; + + n--; + } + + } while(n != 0); +} + + static nxt_int_t njs_generate_return_statement(njs_vm_t *vm, njs_parser_t *parser, njs_parser_node_t *node) @@ -2281,7 +2343,7 @@ njs_generate_try_statement(njs_vm_t *vm, njs_parser_t *parser, if (node->token == NJS_TOKEN_CATCH) { /* A "try/catch" case. */ - catch_index = njs_variable_index(vm, node->left, NJS_NAME_DECLARATION); + catch_index = njs_variable_index(vm, node->left); if (nxt_slow_path(catch_index == NJS_INDEX_ERROR)) { return NXT_ERROR; } @@ -2306,8 +2368,7 @@ njs_generate_try_statement(njs_vm_t *vm, njs_parser_t *parser, if (node->left != NULL) { /* A try/catch/finally case. */ - catch_index = njs_variable_index(vm, node->left->left, - NJS_NAME_DECLARATION); + catch_index = njs_variable_index(vm, node->left->left); if (nxt_slow_path(catch_index == NJS_INDEX_ERROR)) { return NXT_ERROR; } @@ -2479,7 +2540,7 @@ njs_generator_temp_index_get(njs_vm_t *vm, njs_parser_t *parser, scope = scope->parent; } - value = nxt_array_add(scope->values, &njs_array_mem_proto, + value = nxt_array_add(scope->values[0], &njs_array_mem_proto, vm->mem_cache_pool); if (nxt_slow_path(value == NULL)) { return NJS_INDEX_ERROR; @@ -2487,8 +2548,8 @@ njs_generator_temp_index_get(njs_vm_t *vm, njs_parser_t *parser, *value = njs_value_invalid; - index = scope->next_index; - scope->next_index += sizeof(njs_value_t); + index = scope->next_index[0]; + scope->next_index[0] += sizeof(njs_value_t); return index; } diff --git a/njs/njs_number.c b/njs/njs_number.c index 45e816e2..544c7147 100644 --- a/njs/njs_number.c +++ b/njs/njs_number.c @@ -242,7 +242,7 @@ njs_number_constructor(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs, value = &args[1]; } - if (vm->frame->ctor) { + if (vm->top_frame->ctor) { object = njs_object_value_alloc(vm, value, value->type); if (nxt_slow_path(object == NULL)) { return NXT_ERROR; diff --git a/njs/njs_parser.c b/njs/njs_parser.c index f45bb635..c2eca2b5 100644 --- a/njs/njs_parser.c +++ b/njs/njs_parser.c @@ -142,9 +142,32 @@ njs_parser(njs_vm_t *vm, njs_parser_t *parser) static njs_ret_t njs_parser_scope_begin(njs_vm_t *vm, njs_parser_t *parser, njs_scope_t type) { + nxt_uint_t nesting; nxt_array_t *values; njs_parser_scope_t *scope, *parent; + nesting = 0; + + if (type == NJS_SCOPE_FUNCTION) { + + for (scope = parser->scope; scope != NULL; scope = scope->parent) { + + if (scope->type == NJS_SCOPE_FUNCTION) { + nesting = scope->nesting + 1; + + if (nesting <= NJS_MAX_NESTING) { + break; + } + + nxt_alert(&vm->trace, NXT_LEVEL_ERROR, "SyntaxError: " + "The maximum function nesting level is \"%d\"", + NJS_MAX_NESTING); + + return NXT_ERROR; + } + } + } + scope = nxt_mem_cache_alloc(vm->mem_cache_pool, sizeof(njs_parser_scope_t)); if (nxt_slow_path(scope == NULL)) { return NXT_ERROR; @@ -152,14 +175,26 @@ njs_parser_scope_begin(njs_vm_t *vm, njs_parser_t *parser, njs_scope_t type) scope->type = type; - if (type == NJS_SCOPE_GLOBAL) { - type += NJS_INDEX_GLOBAL_OFFSET; + 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->next_index = type; + scope->nesting = nesting; + scope->argument_closures = 0; - scope->inclusive = 0; + nxt_queue_init(&scope->nested); nxt_lvlhsh_init(&scope->variables); + nxt_lvlhsh_init(&scope->references); values = NULL; @@ -171,17 +206,17 @@ njs_parser_scope_begin(njs_vm_t *vm, njs_parser_t *parser, njs_scope_t type) } } - scope->values = values; + scope->values[0] = values; + scope->values[1] = NULL; parent = parser->scope; + scope->parent = parent; + parser->scope = scope; if (parent != NULL) { - parent->inclusive++; + nxt_queue_insert_tail(&parent->nested, &scope->link); } - scope->parent = parent; - parser->scope = scope; - return NXT_OK; } @@ -194,18 +229,6 @@ njs_parser_scope_end(njs_vm_t *vm, njs_parser_t *parser) scope = parser->scope; parent = scope->parent; - -#if 0 - if (scope->inclusive == 0 - && scope->type == NJS_SCOPE_BLOCK - && nxt_lvlhsh_is_empty(&scope->variables)) - { - parent->inclusive--; - - nxt_mem_cache_free(vm->mem_cache_pool, scope); - } -#endif - parser->scope = parent; } @@ -411,7 +434,7 @@ njs_parser_function_declaration(njs_vm_t *vm, njs_parser_t *parser) return NJS_TOKEN_ERROR; } - ret = njs_variable_reference(vm, parser, node); + ret = njs_variable_reference(vm, parser, node, 0); if (nxt_slow_path(ret != NXT_OK)) { return NJS_TOKEN_ERROR; } @@ -608,7 +631,7 @@ njs_parser_function_lambda(njs_vm_t *vm, njs_function_lambda_t *lambda, } } - lambda->nargs = njs_index_size(index) / sizeof(njs_value_t) - 1; + lambda->nargs = njs_scope_offset(index) / sizeof(njs_value_t) - 1; token = njs_parser_token(parser); if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) { @@ -784,7 +807,7 @@ njs_parser_var_statement(njs_vm_t *vm, njs_parser_t *parser) name->token = NJS_TOKEN_NAME; - ret = njs_variable_reference(vm, parser, name); + ret = njs_variable_reference(vm, parser, name, 0); if (nxt_slow_path(ret != NXT_OK)) { return NJS_TOKEN_ERROR; } @@ -1275,7 +1298,7 @@ njs_parser_for_var_statement(njs_vm_t *vm, njs_parser_t *parser) name->token = NJS_TOKEN_NAME; - ret = njs_variable_reference(vm, parser, name); + ret = njs_variable_reference(vm, parser, name, 0); if (nxt_slow_path(ret != NXT_OK)) { return NJS_TOKEN_ERROR; } @@ -1557,7 +1580,7 @@ njs_parser_try_statement(njs_vm_t *vm, njs_parser_t *parser) return NJS_TOKEN_ERROR; } - var = njs_variable_add(vm, parser, NJS_VARIABLE_LET); + var = njs_variable_add(vm, parser, NJS_VARIABLE_CATCH); if (nxt_slow_path(var == NULL)) { return NJS_TOKEN_ERROR; } @@ -1569,7 +1592,7 @@ njs_parser_try_statement(njs_vm_t *vm, njs_parser_t *parser) node->token = NJS_TOKEN_NAME; - ret = njs_variable_reference(vm, parser, node); + ret = njs_variable_reference(vm, parser, node, 0); if (nxt_slow_path(ret != NXT_OK)) { return NJS_TOKEN_ERROR; } @@ -1788,7 +1811,7 @@ njs_parser_terminal(njs_vm_t *vm, njs_parser_t *parser, njs_token_t token) break; } - ret = njs_variable_reference(vm, parser, node); + ret = njs_variable_reference(vm, parser, node, 1); if (nxt_slow_path(ret != NXT_OK)) { return NJS_TOKEN_ERROR; } @@ -2001,7 +2024,7 @@ njs_parser_builtin_object(njs_vm_t *vm, njs_parser_t *parser, var->value.type = NJS_OBJECT; var->value.data.truth = 1; - ret = njs_variable_reference(vm, parser, node); + ret = njs_variable_reference(vm, parser, node, 1); if (nxt_slow_path(ret != NXT_OK)) { return NJS_TOKEN_ERROR; } @@ -2034,7 +2057,7 @@ njs_parser_builtin_function(njs_vm_t *vm, njs_parser_t *parser, var->value.type = NJS_FUNCTION; var->value.data.truth = 1; - ret = njs_variable_reference(vm, parser, node); + ret = njs_variable_reference(vm, parser, node, 1); if (nxt_slow_path(ret != NXT_OK)) { return NJS_TOKEN_ERROR; } diff --git a/njs/njs_parser.h b/njs/njs_parser.h index 502efa93..96ba7fd8 100644 --- a/njs/njs_parser.h +++ b/njs/njs_parser.h @@ -216,16 +216,20 @@ typedef struct { ((node)->token == NJS_TOKEN_NAME || (node)->token == NJS_TOKEN_PROPERTY) -typedef struct njs_parser_scope_s njs_parser_scope_t; - struct njs_parser_scope_s { - nxt_array_t *values; /* Array of njs_value_t. */ + nxt_queue_link_t link; + nxt_queue_t nested; - nxt_lvlhsh_t variables; njs_parser_scope_t *parent; - njs_index_t next_index; - uint32_t inclusive; + nxt_lvlhsh_t variables; + nxt_lvlhsh_t references; + + nxt_array_t *values[2]; /* Array of njs_value_t. */ + njs_index_t next_index[2]; + njs_scope_t type:8; + uint8_t nesting; /* 4 bits */ + uint8_t argument_closures; }; @@ -235,6 +239,7 @@ struct njs_parser_node_s { njs_token_t token:16; uint8_t ctor:1; /* 1 bit */ uint8_t temporary; /* 1 bit */ + uint8_t reference; /* 1 bit */ uint32_t token_line; uint32_t variable_name_hash; @@ -353,11 +358,10 @@ njs_token_t njs_parser_property_token(njs_parser_t *parser); njs_token_t njs_parser_token(njs_parser_t *parser); nxt_int_t njs_parser_string_create(njs_vm_t *vm, njs_value_t *value); njs_ret_t njs_variable_reference(njs_vm_t *vm, njs_parser_t *parser, - njs_parser_node_t *node); -njs_variable_t *njs_variable_get(njs_vm_t *vm, njs_parser_node_t *node, - njs_name_reference_t reference); -njs_index_t njs_variable_index(njs_vm_t *vm, njs_parser_node_t *node, - njs_name_reference_t reference); + njs_parser_node_t *node, nxt_bool_t reference); +njs_variable_t *njs_variable_get(njs_vm_t *vm, njs_parser_node_t *node); +njs_index_t njs_variable_typeof(njs_vm_t *vm, njs_parser_node_t *node); +njs_index_t njs_variable_index(njs_vm_t *vm, njs_parser_node_t *node); nxt_bool_t njs_parser_has_side_effect(njs_parser_node_t *node); u_char *njs_parser_trace_handler(nxt_trace_t *trace, nxt_trace_data_t *td, u_char *start); diff --git a/njs/njs_string.c b/njs/njs_string.c index 39c8806f..d78c7923 100644 --- a/njs/njs_string.c +++ b/njs/njs_string.c @@ -367,7 +367,7 @@ njs_string_constructor(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs, value = &args[1]; } - if (vm->frame->ctor) { + if (vm->top_frame->ctor) { object = njs_object_value_alloc(vm, value, value->type); if (nxt_slow_path(object == NULL)) { return NXT_ERROR; diff --git a/njs/njs_variable.c b/njs/njs_variable.c index 7cc48bfc..f6e05591 100644 --- a/njs/njs_variable.c +++ b/njs/njs_variable.c @@ -23,6 +23,15 @@ #include +typedef struct { + nxt_lvlhsh_query_t lhq; + njs_variable_t *variable; + njs_parser_scope_t *scope; +} njs_variable_scope_t; + + +static njs_ret_t njs_variable_find(njs_vm_t *vm, njs_parser_node_t *node, + njs_variable_scope_t *vs); static njs_variable_t *njs_variable_alloc(njs_vm_t *vm, nxt_str_t *name, njs_variable_type_t type); @@ -42,7 +51,7 @@ njs_variables_hash_test(nxt_lvlhsh_query_t *lhq, void *data) } -static const nxt_lvlhsh_proto_t njs_variables_hash_proto +const nxt_lvlhsh_proto_t njs_variables_hash_proto nxt_aligned(64) = { NXT_LVLHSH_DEFAULT, @@ -155,103 +164,195 @@ njs_variable_add(njs_vm_t *vm, njs_parser_t *parser, njs_variable_type_t type) } +static nxt_int_t +njs_reference_hash_test(nxt_lvlhsh_query_t *lhq, void *data) +{ + njs_parser_node_t *node; + + node = data; + + if (nxt_strstr_eq(&lhq->key, &node->u.variable_name)) { + return NXT_OK; + } + + return NXT_DECLINED; +} + + +const nxt_lvlhsh_proto_t njs_reference_hash_proto + nxt_aligned(64) = +{ + NXT_LVLHSH_DEFAULT, + 0, + njs_reference_hash_test, + njs_lvlhsh_alloc, + njs_lvlhsh_free, +}; + + njs_ret_t njs_variable_reference(njs_vm_t *vm, njs_parser_t *parser, - njs_parser_node_t *node) + njs_parser_node_t *node, nxt_bool_t reference) { - njs_ret_t ret; + njs_ret_t ret; + nxt_lvlhsh_query_t lhq; ret = njs_name_copy(vm, &node->u.variable_name, &parser->lexer->text); if (nxt_fast_path(ret == NXT_OK)) { node->variable_name_hash = parser->lexer->key_hash; node->scope = parser->scope; + node->reference = reference; + + lhq.key_hash = node->variable_name_hash; + lhq.key = node->u.variable_name; + lhq.proto = &njs_reference_hash_proto; + lhq.replace = 0; + lhq.value = node; + lhq.pool = vm->mem_cache_pool; + + ret = nxt_lvlhsh_insert(&parser->scope->references, &lhq); + + if (nxt_slow_path(ret != NXT_ERROR)) { + ret = NXT_OK; + } } return ret; } -njs_variable_t * -njs_variable_get(njs_vm_t *vm, njs_parser_node_t *node, - njs_name_reference_t reference) +njs_ret_t +njs_variables_scope_reference(njs_vm_t *vm, njs_parser_scope_t *scope) { - nxt_array_t *values; - njs_index_t index; - njs_value_t *value; + njs_ret_t ret; + nxt_queue_t *nested; njs_variable_t *var; - njs_parser_scope_t *scope, *parent, *inclusive; - nxt_lvlhsh_query_t lhq; + nxt_queue_link_t *lnk; + njs_parser_node_t *node; + nxt_lvlhsh_each_t lhe; - lhq.key_hash = node->variable_name_hash; - lhq.key = node->u.variable_name; - lhq.proto = &njs_variables_hash_proto; + nested = &scope->nested; - inclusive = NULL; - scope = node->scope; + for (lnk = nxt_queue_first(nested); + lnk != nxt_queue_tail(nested); + lnk = nxt_queue_next(lnk)) + { + scope = nxt_queue_link_data(lnk, njs_parser_scope_t, link); - for ( ;; ) { - if (nxt_lvlhsh_find(&scope->variables, &lhq) == NXT_OK) { - var = lhq.value; + ret = njs_variables_scope_reference(vm, scope); + if (nxt_slow_path(ret != NXT_OK)) { + return NXT_ERROR; + } - if (scope->type == NJS_SCOPE_SHIM) { - scope = inclusive; + memset(&lhe, 0, sizeof(nxt_lvlhsh_each_t)); + lhe.proto = &njs_variables_hash_proto; - } 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; - } + for ( ;; ) { + node = nxt_lvlhsh_each(&scope->references, &lhe); + + if (node == NULL) { + break; } - goto found; + var = njs_variable_get(vm, node); + if (nxt_slow_path(var == NULL)) { + return NXT_ERROR; + } } + } - parent = scope->parent; + return NXT_OK; +} - if (parent == NULL) { - /* A global scope. */ - break; - } - inclusive = scope; - scope = parent; +njs_index_t +njs_variable_typeof(njs_vm_t *vm, njs_parser_node_t *node) +{ + nxt_int_t ret; + njs_variable_scope_t vs; + + if (node->index != NJS_INDEX_NONE) { + return node->index; } - if (reference != NJS_NAME_TYPEOF) { - goto not_found; + ret = njs_variable_find(vm, node, &vs); + + if (nxt_fast_path(ret == NXT_OK)) { + return vs.variable->index; } - var = njs_variable_alloc(vm, &lhq.key, NJS_VARIABLE_TYPEOF); - if (nxt_slow_path(var == NULL)) { - return NULL; + return NJS_INDEX_NONE; +} + + +njs_index_t +njs_variable_index(njs_vm_t *vm, njs_parser_node_t *node) +{ + njs_variable_t *var; + + if (node->index != NJS_INDEX_NONE) { + return node->index; } - var->index = NJS_INDEX_NONE; + var = njs_variable_get(vm, node); - return var; + if (nxt_fast_path(var != NULL)) { + return var->index; + } + + return NJS_INDEX_ERROR; +} + + +njs_variable_t * +njs_variable_get(njs_vm_t *vm, njs_parser_node_t *node) +{ + nxt_int_t ret; + nxt_uint_t n; + nxt_array_t *values; + njs_index_t index; + njs_value_t *value; + njs_variable_t *var; + njs_variable_scope_t vs; -found: + ret = njs_variable_find(vm, node, &vs); - if (reference == NJS_NAME_REFERENCE && var->type == NJS_VARIABLE_TYPEOF) { + if (nxt_slow_path(ret != NXT_OK)) { goto not_found; } + n = (node->scope->nesting != vs.scope->nesting); + + var = vs.variable; index = var->index; if (index != NJS_INDEX_NONE) { - node->index = index; - return var; + + if (n == 0 || njs_scope_type(index) != NJS_SCOPE_ARGUMENTS) { + node->index = index; + + return var; + } + + vs.scope->argument_closures++; + index = (index >> NJS_SCOPE_SHIFT) + 1; + + if (index > 255 || vs.scope->argument_closures == 0) { + nxt_alert(&vm->trace, NXT_LEVEL_ERROR, + "InternalError: too many argument closures"); + + return NULL; + } + + var->argument = index; } - if (reference == NJS_NAME_REFERENCE && var->type <= NJS_VARIABLE_LET) { + if (node->reference && var->type <= NJS_VARIABLE_LET) { goto not_found; } - values = scope->values; + values = vs.scope->values[n]; if (values == NULL) { values = nxt_array_create(4, sizeof(njs_value_t), &njs_array_mem_proto, @@ -260,7 +361,7 @@ found: return NULL; } - scope->values = values; + vs.scope->values[n] = values; } value = nxt_array_add(values, &njs_array_mem_proto, vm->mem_cache_pool); @@ -275,8 +376,8 @@ found: *value = njs_value_void; } - index = scope->next_index; - scope->next_index += sizeof(njs_value_t); + index = vs.scope->next_index[n]; + vs.scope->next_index[n] += sizeof(njs_value_t); var->index = index; node->index = index; @@ -287,25 +388,59 @@ not_found: nxt_alert(&vm->trace, NXT_LEVEL_ERROR, "ReferenceError: \"%.*s\" is not defined", - (int) lhq.key.length, lhq.key.start); + (int) vs.lhq.key.length, vs.lhq.key.start); return NULL; } -njs_index_t -njs_variable_index(njs_vm_t *vm, njs_parser_node_t *node, - njs_name_reference_t reference) +static njs_ret_t +njs_variable_find(njs_vm_t *vm, njs_parser_node_t *node, + njs_variable_scope_t *vs) { - njs_variable_t *var; + njs_parser_scope_t *scope, *parent, *previous; - var = njs_variable_get(vm, node, reference); + vs->lhq.key_hash = node->variable_name_hash; + vs->lhq.key = node->u.variable_name; + vs->lhq.proto = &njs_variables_hash_proto; - if (nxt_fast_path(var != NULL)) { - return var->index; - } + previous = NULL; + scope = node->scope; - return NJS_INDEX_ERROR; + for ( ;; ) { + if (nxt_lvlhsh_find(&scope->variables, &vs->lhq) == NXT_OK) { + vs->variable = vs->lhq.value; + + 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; + } + } + + vs->scope = scope; + + return NXT_OK; + } + + parent = scope->parent; + + if (parent == NULL) { + /* A global scope. */ + vs->scope = scope; + + return NXT_DECLINED; + } + + previous = scope; + scope = parent; + } } diff --git a/njs/njs_variable.h b/njs/njs_variable.h index 60294b37..c86e9e2f 100644 --- a/njs/njs_variable.h +++ b/njs/njs_variable.h @@ -11,23 +11,19 @@ typedef enum { NJS_VARIABLE_CONST = 0, NJS_VARIABLE_LET, - NJS_VARIABLE_TYPEOF, + NJS_VARIABLE_CATCH, NJS_VARIABLE_SHIM, NJS_VARIABLE_VAR, NJS_VARIABLE_FUNCTION, } njs_variable_type_t; -typedef enum { - NJS_NAME_REFERENCE = 0, - NJS_NAME_DECLARATION, - NJS_NAME_TYPEOF, -} njs_name_reference_t; - - typedef struct { nxt_str_t name; + njs_variable_type_t type:8; /* 3 bits */ + uint8_t closure; /* 1 bit */ + uint8_t argument; njs_index_t index; njs_value_t value; @@ -36,15 +32,19 @@ typedef struct { #define njs_global_variable_value(vm, var) \ (njs_value_t *) ((u_char *) vm->global_scope \ - + njs_offset((var)->index) - NJS_INDEX_GLOBAL_OFFSET) + + njs_scope_offset((var)->index) - NJS_INDEX_GLOBAL_OFFSET) njs_variable_t *njs_builtin_add(njs_vm_t *vm, njs_parser_t *parser); njs_variable_t *njs_variable_add(njs_vm_t *vm, njs_parser_t *parser, njs_variable_type_t type); +njs_ret_t njs_variables_scope_reference(njs_vm_t *vm, + njs_parser_scope_t *scope); njs_ret_t njs_name_copy(njs_vm_t *vm, nxt_str_t *dst, nxt_str_t *src); nxt_str_t *njs_vm_export_functions(njs_vm_t *vm); +extern const nxt_lvlhsh_proto_t njs_variables_hash_proto; + #endif /* _NJS_VARIABLE_H_INCLUDED_ */ diff --git a/njs/njs_vm.c b/njs/njs_vm.c index 7b15e8f5..d16cbf60 100644 --- a/njs/njs_vm.c +++ b/njs/njs_vm.c @@ -85,6 +85,8 @@ static nxt_noinline njs_ret_t njs_values_compare(const njs_value_t *val1, static njs_ret_t njs_function_frame_create(njs_vm_t *vm, njs_value_t *value, const njs_value_t *this, uintptr_t nargs, nxt_bool_t ctor); static njs_object_t *njs_function_new_object(njs_vm_t *vm, njs_value_t *value); +static void njs_vm_scopes_restore(njs_vm_t *vm, njs_frame_t *frame, + njs_native_frame_t *previous); static njs_ret_t njs_vmcode_continuation(njs_vm_t *vm, njs_value_t *invld1, njs_value_t *invld2); static njs_native_frame_t * @@ -257,7 +259,7 @@ start: if (ret == NXT_ERROR) { for ( ;; ) { - frame = (njs_frame_t *) vm->frame; + frame = (njs_frame_t *) vm->top_frame; catch = frame->native.exception.catch; if (catch != NULL) { @@ -270,13 +272,7 @@ start: return NXT_ERROR; } - vm->frame = previous; - - /* GC: NJS_SCOPE_ARGUMENTS and NJS_SCOPE_FUNCTION. */ - - vm->scopes[NJS_SCOPE_CALLEE_ARGUMENTS] = previous->arguments; - vm->scopes[NJS_SCOPE_FUNCTION] = frame->prev_local; - vm->scopes[NJS_SCOPE_ARGUMENTS] = frame->prev_arguments; + njs_vm_scopes_restore(vm, frame, previous); if (frame->native.size != 0) { vm->stack_size -= frame->native.size; @@ -409,28 +405,45 @@ njs_vmcode_array(njs_vm_t *vm, njs_value_t *invld1, njs_value_t *invld2) njs_ret_t njs_vmcode_function(njs_vm_t *vm, njs_value_t *invld1, njs_value_t *invld2) { + size_t size; + nxt_uint_t n, nesting; njs_function_t *function; + njs_function_lambda_t *lambda; njs_vmcode_function_t *code; - function = nxt_mem_cache_zalloc(vm->mem_cache_pool, sizeof(njs_function_t)); + code = (njs_vmcode_function_t *) vm->current; + lambda = code->lambda; + nesting = lambda->nesting; - if (nxt_fast_path(function != NULL)) { - function->object.shared_hash = vm->shared->function_prototype_hash; - function->object.__proto__ = - &vm->prototypes[NJS_PROTOTYPE_FUNCTION].object; - function->args_offset = 1; + size = sizeof(njs_function_t) + nesting * sizeof(njs_closure_t *); - code = (njs_vmcode_function_t *) vm->current; - function->u.lambda = code->lambda; + function = nxt_mem_cache_zalloc(vm->mem_cache_pool, size); + if (nxt_slow_path(function == NULL)) { + return NXT_ERROR; + } - vm->retval.data.u.function = function; - vm->retval.type = NJS_FUNCTION; - vm->retval.data.truth = 1; + function->u.lambda = lambda; + function->object.shared_hash = vm->shared->function_prototype_hash; + function->object.__proto__ = &vm->prototypes[NJS_PROTOTYPE_FUNCTION].object; + function->args_offset = 1; - return sizeof(njs_vmcode_function_t); + if (nesting != 0) { + function->closure = 1; + + n = 0; + + do { + /* GC: retain closure. */ + function->closures[n] = vm->active_frame->closures[n]; + n++; + } while (n < nesting); } - return NXT_ERROR; + vm->retval.data.u.function = function; + vm->retval.type = NJS_FUNCTION; + vm->retval.data.truth = 1; + + return sizeof(njs_vmcode_function_t); } @@ -2331,16 +2344,22 @@ njs_vmcode_function_call(njs_vm_t *vm, njs_value_t *invld, njs_value_t *retval) njs_continuation_t *cont; njs_native_frame_t *frame; - function = vm->frame->function; + frame = vm->top_frame; + function = frame->function; if (!function->native) { - (void) njs_function_call(vm, (njs_index_t) retval, - sizeof(njs_vmcode_function_call_t)); - return 0; + ret = njs_function_call(vm, (njs_index_t) retval, + sizeof(njs_vmcode_function_call_t)); + + if (nxt_fast_path(ret != NJS_ERROR)) { + return 0; + } + + return ret; } - args = vm->frame->arguments - function->args_offset; - nargs = vm->frame->nargs; + args = frame->arguments; + nargs = frame->nargs; ret = njs_normalize_args(vm, args, function->args_types, nargs); if (ret != NJS_OK) { @@ -2373,16 +2392,17 @@ njs_vmcode_function_call(njs_vm_t *vm, njs_value_t *invld, njs_value_t *retval) * for NJS_APPLIED and NXT_AGAIN cases. */ if (ret == NXT_OK) { - frame = vm->frame; + frame = vm->top_frame; - vm->frame = njs_function_previous_frame(frame); + vm->top_frame = njs_function_previous_frame(frame); (void) njs_function_frame_free(vm, frame); /* * If a retval is in a callee arguments scope it * must be in the previous callee arguments scope. */ - vm->scopes[NJS_SCOPE_CALLEE_ARGUMENTS] = vm->frame->arguments; + vm->scopes[NJS_SCOPE_CALLEE_ARGUMENTS] = + vm->top_frame->arguments + function->args_offset; retval = njs_vmcode_operand(vm, retval); /* @@ -2540,12 +2560,11 @@ njs_vmcode_return(njs_vm_t *vm, njs_value_t *invld, njs_value_t *retval) { njs_value_t *value; njs_frame_t *frame; - njs_value_t *args; njs_native_frame_t *previous; value = njs_vmcode_operand(vm, retval); - frame = (njs_frame_t *) vm->frame; + frame = (njs_frame_t *) vm->top_frame; if (frame->native.ctor) { if (njs_is_object(value)) { @@ -2557,12 +2576,8 @@ njs_vmcode_return(njs_vm_t *vm, njs_value_t *invld, njs_value_t *retval) } previous = njs_function_previous_frame(&frame->native); - vm->frame = previous; - vm->scopes[NJS_SCOPE_CALLEE_ARGUMENTS] = previous->arguments; - vm->scopes[NJS_SCOPE_FUNCTION] = frame->prev_local; - args = vm->scopes[NJS_SCOPE_ARGUMENTS]; - vm->scopes[NJS_SCOPE_ARGUMENTS] = frame->prev_arguments; + njs_vm_scopes_restore(vm, frame, previous); /* * If a retval is in a callee arguments scope it @@ -2575,11 +2590,59 @@ njs_vmcode_return(njs_vm_t *vm, njs_value_t *invld, njs_value_t *retval) vm->current = frame->return_address; - /* GC: arguments and local. */ + return njs_function_frame_free(vm, &frame->native); +} - njs_release(vm, &args[0]); - return njs_function_frame_free(vm, &frame->native); +static void +njs_vm_scopes_restore(njs_vm_t *vm, njs_frame_t *frame, + njs_native_frame_t *previous) +{ + nxt_uint_t n, nesting; + njs_value_t *args; + njs_function_t *function; + + 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 = frame->native.function; + + if (function->native) { + return; + } + + if (function->closure) { + /* GC: release function closures. */ + } + + 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; + + for (n = 0; n <= nesting; n++) { + vm->scopes[NJS_SCOPE_CLOSURE + n] = &frame->closures[n]->u.values; + } + + while (n < NJS_MAX_NESTING) { + vm->scopes[NJS_SCOPE_CLOSURE + n] = NULL; + n++; + } } @@ -2596,12 +2659,13 @@ njs_vmcode_continuation(njs_vm_t *vm, njs_value_t *invld1, njs_value_t *invld2) njs_ret_t ret; nxt_bool_t skip; njs_value_t *args, *retval; + njs_function_t *function; njs_native_frame_t *frame; njs_continuation_t *cont; cont = njs_vm_continuation(vm); - frame = vm->frame; - args = frame->arguments - frame->function->args_offset; + frame = vm->top_frame; + args = frame->arguments; if (cont->args_types != NULL) { ret = njs_normalize_args(vm, args, cont->args_types, frame->nargs); @@ -2616,16 +2680,23 @@ njs_vmcode_continuation(njs_vm_t *vm, njs_value_t *invld1, njs_value_t *invld2) case NXT_OK: - frame = vm->frame; + frame = vm->top_frame; skip = frame->skip; - vm->frame = njs_function_previous_frame(frame); + vm->top_frame = njs_function_previous_frame(frame); /* * If a retval is in a callee arguments scope it * must be in the previous callee arguments scope. */ - vm->scopes[NJS_SCOPE_CALLEE_ARGUMENTS] = vm->frame->arguments; + args = vm->top_frame->arguments; + function = vm->top_frame->function; + + if (function != NULL) { + args += function->args_offset; + } + + vm->scopes[NJS_SCOPE_CALLEE_ARGUMENTS] = args; if (!skip) { retval = njs_vmcode_operand(vm, cont->retval); @@ -2712,17 +2783,17 @@ njs_vmcode_try_start(njs_vm_t *vm, njs_value_t *value, njs_value_t *offset) { njs_exception_t *e; - if (vm->frame->exception.catch != NULL) { + if (vm->top_frame->exception.catch != NULL) { e = nxt_mem_cache_alloc(vm->mem_cache_pool, sizeof(njs_exception_t)); if (nxt_slow_path(e == NULL)) { return NXT_ERROR; } - *e = vm->frame->exception; - vm->frame->exception.next = e; + *e = vm->top_frame->exception; + vm->top_frame->exception.next = e; } - vm->frame->exception.catch = vm->current + (njs_ret_t) offset; + vm->top_frame->exception.catch = vm->current + (njs_ret_t) offset; njs_set_invalid(value); @@ -2740,13 +2811,13 @@ njs_vmcode_try_end(njs_vm_t *vm, njs_value_t *invld, njs_value_t *offset) { njs_exception_t *e; - e = vm->frame->exception.next; + e = vm->top_frame->exception.next; if (e == NULL) { - vm->frame->exception.catch = NULL; + vm->top_frame->exception.catch = NULL; } else { - vm->frame->exception = *e; + vm->top_frame->exception = *e; nxt_mem_cache_free(vm->mem_cache_pool, e); } @@ -2784,7 +2855,7 @@ njs_vmcode_catch(njs_vm_t *vm, njs_value_t *exception, njs_value_t *offset) return njs_vmcode_try_end(vm, exception, offset); } - vm->frame->exception.catch = vm->current + (njs_ret_t) offset; + vm->top_frame->exception.catch = vm->current + (njs_ret_t) offset; return sizeof(njs_vmcode_catch_t); } @@ -2884,7 +2955,7 @@ njs_vm_trap(njs_vm_t *vm, nxt_uint_t trap, njs_value_t *value1, { njs_native_frame_t *frame; - frame = vm->frame; + frame = vm->top_frame; /* * The trap_scratch value is for results of "valueOf" and "toString" @@ -2916,7 +2987,7 @@ njs_vm_trap_argument(njs_vm_t *vm, nxt_uint_t trap) njs_value_t *value; njs_native_frame_t *frame; - frame = vm->frame; + frame = vm->top_frame; value = frame->trap_scratch.data.u.value; njs_set_invalid(&frame->trap_scratch); @@ -2939,7 +3010,7 @@ njs_vmcode_number_primitive(njs_vm_t *vm, njs_value_t *invld, njs_value_t *narg) njs_ret_t ret; njs_value_t *value; - value = &vm->frame->trap_values[(uintptr_t) narg]; + value = &vm->top_frame->trap_values[(uintptr_t) narg]; ret = njs_primitive_value(vm, value, 0); @@ -2968,7 +3039,7 @@ njs_vmcode_string_primitive(njs_vm_t *vm, njs_value_t *invld, njs_value_t *narg) njs_ret_t ret; njs_value_t *value; - value = &vm->frame->trap_values[(uintptr_t) narg]; + value = &vm->top_frame->trap_values[(uintptr_t) narg]; ret = njs_primitive_value(vm, value, 1); @@ -2992,7 +3063,7 @@ njs_vmcode_number_argument(njs_vm_t *vm, njs_value_t *invld1, njs_ret_t ret; njs_value_t *value; - value = &vm->frame->trap_values[0]; + value = &vm->top_frame->trap_values[0]; ret = njs_primitive_value(vm, value, 0); @@ -3008,10 +3079,10 @@ njs_vmcode_number_argument(njs_vm_t *vm, njs_value_t *invld1, njs_number_set(value, num); } - *vm->frame->trap_values[1].data.u.value = *value; + *vm->top_frame->trap_values[1].data.u.value = *value; - vm->current = vm->frame->trap_restart; - vm->frame->trap_restart = NULL; + vm->current = vm->top_frame->trap_restart; + vm->top_frame->trap_restart = NULL; return 0; } @@ -3027,7 +3098,7 @@ njs_vmcode_string_argument(njs_vm_t *vm, njs_value_t *invld1, njs_ret_t ret; njs_value_t *value; - value = &vm->frame->trap_values[0]; + value = &vm->top_frame->trap_values[0]; ret = njs_primitive_value(vm, value, 1); @@ -3035,10 +3106,10 @@ njs_vmcode_string_argument(njs_vm_t *vm, njs_value_t *invld1, ret = njs_primitive_value_to_string(vm, value, value); if (nxt_fast_path(ret == NXT_OK)) { - *vm->frame->trap_values[1].data.u.value = *value; + *vm->top_frame->trap_values[1].data.u.value = *value; - vm->current = vm->frame->trap_restart; - vm->frame->trap_restart = NULL; + vm->current = vm->top_frame->trap_restart; + vm->top_frame->trap_restart = NULL; } } @@ -3072,7 +3143,7 @@ njs_primitive_value(njs_vm_t *vm, njs_value_t *value, nxt_uint_t hint) }; if (!njs_is_primitive(value)) { - retval = &vm->frame->trap_scratch; + retval = &vm->top_frame->trap_scratch; if (!njs_is_primitive(retval)) { @@ -3080,8 +3151,8 @@ njs_primitive_value(njs_vm_t *vm, njs_value_t *value, nxt_uint_t hint) vm->exception = &njs_exception_type_error; ret = NXT_ERROR; - if (njs_is_object(value) && vm->frame->trap_tries < 2) { - hint ^= vm->frame->trap_tries++; + if (njs_is_object(value) && vm->top_frame->trap_tries < 2) { + hint ^= vm->top_frame->trap_tries++; lhq.key_hash = hashes[hint]; lhq.key = names[hint]; @@ -3134,7 +3205,7 @@ njs_primitive_value(njs_vm_t *vm, njs_value_t *value, nxt_uint_t hint) njs_set_invalid(retval); } - vm->frame->trap_tries = 0; + vm->top_frame->trap_tries = 0; return 1; } @@ -3149,7 +3220,7 @@ njs_vmcode_restart(njs_vm_t *vm, njs_value_t *invld1, njs_value_t *invld2) njs_native_frame_t *frame; njs_vmcode_generic_t *vmcode; - frame = vm->frame; + frame = vm->top_frame; restart = frame->trap_restart; frame->trap_restart = NULL; vm->current = restart; @@ -3253,14 +3324,14 @@ njs_object_value_to_string(njs_vm_t *vm, njs_value_t *value) current = vm->current; vm->current = (u_char *) value_to_string; - njs_set_invalid(&vm->frame->trap_scratch); - vm->frame->trap_values[0] = *value; + njs_set_invalid(&vm->top_frame->trap_scratch); + vm->top_frame->trap_values[0] = *value; ret = njs_vmcode_interpreter(vm); if (ret == NJS_STOP) { ret = NXT_OK; - *value = vm->frame->trap_values[0]; + *value = vm->top_frame->trap_values[0]; } vm->current = current; @@ -3275,7 +3346,7 @@ njs_vmcode_value_to_string(njs_vm_t *vm, njs_value_t *invld1, { njs_ret_t ret; - ret = njs_primitive_value(vm, &vm->frame->trap_values[0], 1); + ret = njs_primitive_value(vm, &vm->top_frame->trap_values[0], 1); if (nxt_fast_path(ret > 0)) { return NJS_STOP; diff --git a/njs/njs_vm.h b/njs/njs_vm.h index 772526af..a50285d3 100644 --- a/njs/njs_vm.h +++ b/njs/njs_vm.h @@ -9,6 +9,7 @@ #include +#include #include @@ -122,8 +123,10 @@ typedef struct njs_regexp_s njs_regexp_t; typedef struct njs_regexp_pattern_s njs_regexp_pattern_t; typedef struct njs_date_s njs_date_t; typedef struct njs_extern_s njs_extern_t; +typedef struct njs_frame_s njs_frame_t; typedef struct njs_native_frame_s njs_native_frame_t; typedef struct njs_property_next_s njs_property_next_t; +typedef struct njs_parser_scope_s njs_parser_scope_t; union njs_value_s { @@ -225,6 +228,16 @@ struct njs_array_s { }; +typedef struct { + union { + uint32_t count; + njs_value_t values; + } u; + + njs_value_t values[1]; +} njs_closure_t; + + #define NJS_ARGS_TYPES_MAX 5 struct njs_function_s { @@ -234,6 +247,9 @@ struct njs_function_s { uint8_t args_offset; uint8_t continuation_size; + /* Function is a closure. */ + uint8_t closure:1; + uint8_t native:1; uint8_t ctor:1; @@ -243,6 +259,11 @@ struct njs_function_s { } u; njs_value_t *bound; +#if (NXT_SUNC) + njs_closure_t *closures[1]; +#else + njs_closure_t *closures[]; +#endif }; @@ -667,14 +688,17 @@ typedef struct { typedef enum { NJS_SCOPE_ABSOLUTE = 0, - NJS_SCOPE_GLOBAL, - NJS_SCOPE_FUNCTION, - NJS_SCOPE_CALLEE_ARGUMENTS, - NJS_SCOPE_ARGUMENTS, - NJS_SCOPE_CLOSURE, - NJS_SCOPE_PARENT_LOCAL, - NJS_SCOPE_PARENT_ARGUMENTS, - NJS_SCOPE_PARENT_CLOSURE, + NJS_SCOPE_GLOBAL = 1, + NJS_SCOPE_CALLEE_ARGUMENTS = 2, + /* + * The argument and local VM scopes should separate 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 used only on parsing phase. @@ -684,7 +708,13 @@ typedef enum { } njs_scope_t; -#define NJS_SCOPES (NJS_SCOPE_PARENT_CLOSURE + 1) +/* + * The maximum possible function nesting level is (16 - NJS_SCOPE_CLOSURE), + * that is 11. The 5 is reasonable limit. + */ +#define NJS_MAX_NESTING 5 + +#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)) @@ -695,6 +725,9 @@ typedef enum { #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) @@ -754,8 +787,8 @@ enum njs_function_e { }; -#define njs_scope_index(value) \ - ((njs_index_t) ((value) << NJS_SCOPE_SHIFT)) +#define njs_scope_index(value, type) \ + ((njs_index_t) (((value) << NJS_SCOPE_SHIFT) | (type))) #define njs_global_scope_index(value) \ ((njs_index_t) (((value) << NJS_SCOPE_SHIFT) | NJS_SCOPE_GLOBAL)) @@ -772,21 +805,17 @@ enum njs_function_e { #define NJS_INDEX_DATE njs_global_scope_index(NJS_CONSTRUCTOR_DATE) #define NJS_INDEX_GLOBAL_RETVAL njs_global_scope_index(NJS_CONSTRUCTOR_MAX) -#define NJS_INDEX_GLOBAL_OFFSET njs_scope_index(NJS_CONSTRUCTOR_MAX + 1) +#define NJS_INDEX_GLOBAL_OFFSET njs_scope_index(NJS_CONSTRUCTOR_MAX + 1, 0) -#define njs_offset(index) \ +#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_offset(index))) - - -#define njs_index_size(index) \ - njs_offset(index) + + njs_scope_offset(index))) typedef struct { @@ -811,7 +840,8 @@ struct njs_vm_s { void **external; - njs_native_frame_t *frame; + njs_native_frame_t *top_frame; + njs_frame_t *active_frame; const njs_value_t *exception; diff --git a/njs/njscript.c b/njs/njscript.c index 6b1b261b..d8c8dd94 100644 --- a/njs/njscript.c +++ b/njs/njscript.c @@ -217,6 +217,11 @@ njs_vm_compile(njs_vm_t *vm, u_char **start, u_char *end, nxt_str_t **export) return NJS_ERROR; } + ret = njs_variables_scope_reference(vm, parser->scope); + if (nxt_slow_path(ret != NXT_OK)) { + return NJS_ERROR; + } + *start = parser->lexer->start; ret = njs_generate_scope(vm, parser, node); @@ -293,7 +298,8 @@ njs_vm_clone(njs_vm_t *vm, nxt_mem_cache_pool_t *mcp, void **external) memset(frame, 0, NJS_GLOBAL_FRAME_SIZE); - nvm->frame = &frame->native; + nvm->top_frame = &frame->native; + nvm->active_frame = frame; frame->native.size = size; frame->native.free_size = size - (NJS_GLOBAL_FRAME_SIZE + scope_size); @@ -353,7 +359,6 @@ njs_vm_call(njs_vm_t *vm, njs_function_t *function, njs_opaque_value_t *args, ret = njs_function_frame(vm, function, this, (njs_value_t *) args, nargs, 0); - if (nxt_slow_path(ret != NXT_OK)) { return ret; } @@ -361,7 +366,10 @@ njs_vm_call(njs_vm_t *vm, njs_function_t *function, njs_opaque_value_t *args, current = vm->current; vm->current = (u_char *) stop; - (void) njs_function_call(vm, NJS_INDEX_GLOBAL_RETVAL, 0); + ret = njs_function_call(vm, NJS_INDEX_GLOBAL_RETVAL, 0); + if (nxt_slow_path(ret == NXT_ERROR)) { + return ret; + } ret = njs_vmcode_interpreter(vm); diff --git a/njs/test/njs_unit_test.c b/njs/test/njs_unit_test.c index b01576ec..495e0288 100644 --- a/njs/test/njs_unit_test.c +++ b/njs/test/njs_unit_test.c @@ -4127,6 +4127,39 @@ static njs_unit_test_t njs_test[] = { nxt_string("(function f(a) { return (a > 1) ? a * f(a - 1) : 1 })(10)"), nxt_string("3628800") }, + /* Nested functions and closures. */ + + { nxt_string("function f() { var x = 4; " + "function g() { return x }; return g(); } f()"), + nxt_string("4") }, + + { nxt_string("function f(a) { function g(b) { return a + b } return g }" + "var k = f('a'); k('b')"), + nxt_string("ab") }, + + { nxt_string("function f(a) { return function(b) { return a + b } }" + "var k = f('a'); k('b')"), + nxt_string("ab") }, + + { nxt_string("function f(a) { return function(b) { return a + b } }" + "var k = f('a'), m = f('b'); k('c') + m('d')"), + nxt_string("acbd") }, + + { nxt_string("function f(a) { return " + "function(b) { return function(c) { return a + b + c } } }" + "var g = f('a'), k = g('b'), m = g('c'); k('d') + m('e')"), + nxt_string("abdace") }, + + { nxt_string("function f(a) {" + "function g() { return a }; return g; }" + "var y = f(4); y()"), + nxt_string("4") }, + + { nxt_string("function f() { var x = 4; " + "return function() { return x } }" + "var y = f(); y()"), + nxt_string("4") }, + /* Recursive fibonacci. */ { nxt_string("function fibo(n) {" @@ -4396,10 +4429,8 @@ static njs_unit_test_t njs_test[] = nxt_string("[object Function]1") }, #endif -#if 0 { nxt_string("function f() {}; function g() { return f }; g()"), nxt_string("[object Function]") }, -#endif { nxt_string("function f(a) { return this+a }; var a = f; a.call('0', 1)"), nxt_string("01") },