[njs] Scopes refactoring.

Alexander Borisov alexander.borisov at nginx.com
Fri Apr 30 13:04:40 UTC 2021


details:   https://hg.nginx.org/njs/rev/0a2a0b5a74f4
branches:  
changeset: 1629:0a2a0b5a74f4
user:      Alexander Borisov <alexander.borisov at nginx.com>
date:      Fri Apr 30 16:02:08 2021 +0300
description:
Scopes refactoring.

diffstat:

 auto/sources             |    1 +
 nginx/ngx_js.c           |    3 +-
 src/njs.h                |    7 +-
 src/njs_builtin.c        |   20 +-
 src/njs_disassembler.c   |   15 +
 src/njs_function.c       |  456 +++++++++++++++++++-----------
 src/njs_function.h       |   58 ++-
 src/njs_generator.c      |  466 +++++++++++++++++--------------
 src/njs_generator.h      |    3 +-
 src/njs_lexer.h          |    3 +-
 src/njs_main.h           |    1 +
 src/njs_module.c         |   12 +-
 src/njs_parser.c         |  667 +++++++++++++++++++++++++--------------------
 src/njs_parser.h         |   37 +-
 src/njs_scope.c          |  259 +++++++++++++++++
 src/njs_scope.h          |  122 ++++++++
 src/njs_shell.c          |    4 +-
 src/njs_string.c         |  139 ---------
 src/njs_string.h         |    3 -
 src/njs_value.h          |   21 +-
 src/njs_variable.c       |  689 ++++++++++++++++++++++++----------------------
 src/njs_variable.h       |   25 +-
 src/njs_vm.c             |  182 +++++------
 src/njs_vm.h             |   88 +----
 src/njs_vmcode.c         |  160 +++++-----
 src/njs_vmcode.h         |  182 ++++++-----
 src/test/njs_unit_test.c |  148 +++++++++-
 27 files changed, 2194 insertions(+), 1577 deletions(-)

diffs (truncated from 5843 to 1000 lines):

diff -r cb99db22e3e5 -r 0a2a0b5a74f4 auto/sources
--- a/auto/sources	Fri Apr 16 15:25:22 2021 +0000
+++ b/auto/sources	Fri Apr 30 16:02:08 2021 +0300
@@ -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 -r cb99db22e3e5 -r 0a2a0b5a74f4 nginx/ngx_js.c
--- a/nginx/ngx_js.c	Fri Apr 16 15:25:22 2021 +0000
+++ b/nginx/ngx_js.c	Fri Apr 30 16:02:08 2021 +0300
@@ -261,7 +261,8 @@ ngx_js_ext_log(njs_vm_t *vm, njs_value_t
     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 -r cb99db22e3e5 -r 0a2a0b5a74f4 src/njs.h
--- a/src/njs.h	Fri Apr 16 15:25:22 2021 +0000
+++ b/src/njs.h	Fri Apr 30 16:02:08 2021 +0300
@@ -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_v
 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 -r cb99db22e3e5 -r 0a2a0b5a74f4 src/njs_builtin.c
--- a/src/njs_builtin.c	Fri Apr 16 15:25:22 2021 +0000
+++ b/src/njs_builtin.c	Fri Apr 30 16:02:08 2021 +0300
@@ -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 *
     }
 
     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 *v
 {
     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 *v
     }
 
     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 -r cb99db22e3e5 -r 0a2a0b5a74f4 src/njs_disassembler.c
--- a/src/njs_disassembler.c	Fri Apr 16 15:25:22 2021 +0000
+++ b/src/njs_disassembler.c	Fri Apr 30 16:02:08 2021 +0300
@@ -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 -r cb99db22e3e5 -r 0a2a0b5a74f4 src/njs_function.c
--- a/src/njs_function.c	Fri Apr 16 15:25:22 2021 +0000
+++ b/src/njs_function.c	Fri Apr 30 16:02:08 2021 +0300
@@ -8,21 +8,13 @@
 #include <njs_main.h>
 
 
-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_fun
 
     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, nj
 }
 
 
-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_
 }
 
 
-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_func
     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;
-
-    closures = njs_function_active_closures(vm, function);
-
-    n = 0;
+    from = njs_function_closures(function);
+    to = njs_function_closures(copy);
 
     do {
-        /* GC: retain closure. */
-        njs_function_closures(copy)[n] = closures[n];
-        n++;
-    } while (n < nesting);
+        n--;
+
+        to[n] = from[n];
+
+    } while (n != 0);
 
     return copy;
 }
@@ -287,7 +255,7 @@ njs_function_arguments_object_init(njs_v
     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
         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, 
     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, 
         } 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, 
     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, 
         lambda = target->u.lambda;
     }
 
-    max_args = njs_max(nargs, lambda->nargs);
-
-    closures = lambda->nesting + lambda->block_closures;
+    args_count = function->args_offset + njs_max(nargs, lambda->nargs);
+    value_count = args_count + njs_max(args_count, lambda->nlocal);
 
-    size = njs_frame_size(closures)
-           + (function->args_offset + max_args) * sizeof(njs_value_t)
-           + lambda->local_size;
+    value_size = value_count * sizeof(njs_value_t *);
+    temp_size = lambda->temp * sizeof(njs_value_t *);
 
-    native_frame = njs_function_frame_alloc(vm, size);
+    frame_size = value_size + temp_size
+                        + ((value_count + lambda->temp) * sizeof(njs_value_t));
+
+    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. */
-
-    value = (njs_value_t *) ((u_char *) native_frame +
-                             njs_frame_size(closures));
-    native_frame->arguments = value;
-
-    if (bound == NULL) {
-        *value = *this;
+    /* Set this and bound arguments. */
+    *native_frame->local[0] = *this;
 
-        if (njs_slow_path(function->global_this
-                          && njs_is_null_or_undefined(this))) {
-            njs_set_object(value, &vm->global_object);
-        }
+    if (njs_slow_path(function->global_this
+                      && njs_is_null_or_undefined(this)))
+    {
+        njs_set_object(native_frame->local[0], &vm->global_object);
+    }
 
-        value++;
-
-    } 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, s
     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_fun
         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,78 +586,50 @@ njs_function_call2(njs_vm_t *vm, njs_fun
 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;
 
+    if (function->global && !function->closure_copied) {
+        ret = njs_function_capture_global_closures(vm, function);
+        if (njs_slow_path(ret != NJS_OK)) {
+            return NJS_ERROR;
+        }
+    }
+
     lambda = function->u.lambda;
 
-#if (NJS_DEBUG)
-    vm->scopes[NJS_SCOPE_CALLEE_ARGUMENTS] = NULL;
-#endif
-
-    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. */
-
-    vm->scopes[NJS_SCOPE_LOCAL] = frame->local;
-
-    memcpy(frame->local, lambda->local_scope, lambda->local_size);
-
-    /* Parent closures values. */
+    /* Move all arguments. */
 
-    n = 0;
-    nesting = lambda->nesting;
+    for (n = 0; n < function->args_count; n++) {
+        if (!njs_is_valid(args)) {
+            njs_set_undefined(args);
+        }
 
-    if (nesting != 0) {
-        closures = njs_function_active_closures(vm, function);
-        do {
-            closure = *closures++;
-
-            njs_frame_closures(frame)[n] = closure;
-            vm->scopes[NJS_SCOPE_CLOSURE + n] = &closure->u.values;
-
-            n++;
-        } while (n < nesting);
+        *local++ = args++;
     }
 
-    /* Function closure values. */
-
-    if (lambda->block_closures > 0) {
-        closure = NULL;
-
-        size = lambda->closure_size;
+    /* Store current level. */
 
-        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;
-            }
+    cur_local = vm->levels[NJS_LEVEL_LOCAL];
+    cur_closures = vm->levels[NJS_LEVEL_CLOSURE];
+    cur_temp = vm->levels[NJS_LEVEL_TEMP];
 
-            size -= sizeof(njs_value_t);
-            closure->u.count = 0;
-            dst = closure->values;
-
-            src = lambda->closure_scope;
+    /* Replace current level. */
 
-            do {
-                *dst++ = *src++;
-                size -= sizeof(njs_value_t);
-            } while (size != 0);
-        }
-
-        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;
 
     if (lambda->rest_parameters) {
         ret = njs_function_rest_parameters_init(vm, &frame->native);
@@ -695,9 +638,41 @@ njs_function_lambda_call(njs_vm_t *vm)
         }
     }
 
+    /* Self */
+
+    if (lambda->self != NJS_INDEX_NONE) {
+        value = njs_scope_value(vm, lambda->self);
+
+        if (!njs_is_valid(value)) {
+            njs_set_function(value, function);
+        }
+    }
+
     vm->active_frame = frame;
 
-    return njs_vmcode_interpreter(vm, lambda->start);
+    /* Closures */
+
+    n = lambda->ndeclarations;
+
+    while (n != 0) {
+        n--;
+
+        function = njs_function(lambda->declarations[n]);
+
+        ret = njs_function_capture_closure(vm, function, function->u.lambda);
+        if (njs_slow_path(ret != NJS_OK)) {
+            return ret;
+        }
+    }
+
+    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 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, nj
 }
 
 
+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, n
 
     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, n
         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, n
         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, n
 
     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 -r cb99db22e3e5 -r 0a2a0b5a74f4 src/njs_function.h
--- a/src/njs_function.h	Fri Apr 16 15:25:22 2021 +0000
+++ b/src/njs_function.h	Fri Apr 30 16:02:08 2021 +0300
@@ -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;
 
-    /* Function nesting level. */
-    uint8_t                        nesting;           /* 4 bits */
+    njs_value_t                    **declarations;
+    uint32_t                       ndeclarations;
 
-    /* Function internal block closures levels. */
-    uint8_t                        block_closures;    /* 4 bits */
+    njs_index_t                    self;
+
+    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 *v
     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_f
 
 
 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_funct
 }
 
 
+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 -r cb99db22e3e5 -r 0a2a0b5a74f4 src/njs_generator.c
--- a/src/njs_generator.c	Fri Apr 16 15:25:22 2021 +0000
+++ b/src/njs_generator.c	Fri Apr 30 16:02:08 2021 +0300
@@ -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(n
 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);


More information about the nginx-devel mailing list