]> git.kaiwu.me - njs.git/commitdiff
Declared function objects are copied on demand to allow
authorIgor Sysoev <igor@sysoev.ru>
Tue, 23 Feb 2016 18:09:42 +0000 (21:09 +0300)
committerIgor Sysoev <igor@sysoev.ru>
Tue, 23 Feb 2016 18:09:42 +0000 (21:09 +0300)
property manipulations and to have correct Function.prototype.

njs/njs_builtin.c
njs/njs_disassembler.c
njs/njs_function.c
njs/njs_function.h
njs/njs_generator.c
njs/njs_parser.c
njs/njs_vm.c
njs/njs_vm.h
njs/test/njs_unit_test.c

index 37ca2c54c9b4bcb54d3fc93025f63e65ae818744..d50953ea3959246527ede57f2f19932c356dc933 100644 (file)
@@ -101,6 +101,7 @@ njs_builtin_objects_create(njs_vm_t *vm)
     for (i = NJS_FUNCTION_OBJECT; i < NJS_FUNCTION_MAX; i++) {
         functions[i].native = 1;
         functions[i].args_offset = 1;
+        functions[i].shared = 0;
         functions[i].u.native = native_functions[i].native;
         functions[i].args_types[0] = native_functions[i].args_types[0];
         functions[i].args_types[1] = native_functions[i].args_types[1];
index 78cc652744af0aab14fd72164c8e2cb137555a26..ef4893b701e5e557c1190968a29b17d893d55841 100644 (file)
@@ -35,6 +35,8 @@ static njs_code_name_t  code_names[] = {
           nxt_string("OBJECT          ") },
     { njs_vmcode_function, sizeof(njs_vmcode_function_t),
           nxt_string("FUNCTION        ") },
+    { njs_vmcode_function_copy, sizeof(njs_vmcode_function_copy_t),
+          nxt_string("FUNCTION COPY   ") },
     { njs_vmcode_regexp, sizeof(njs_vmcode_regexp_t),
           nxt_string("REGEXP          ") },
 
index 3112d33e8c06357fceed2d4a6a1be1d01dcb5523..7586133a108324c290a860c901b21ca8f5877d66 100644 (file)
@@ -31,8 +31,8 @@ njs_function_alloc(njs_vm_t *vm)
     function = nxt_mem_cache_zalloc(vm->mem_cache_pool, sizeof(njs_function_t));
 
     if (nxt_fast_path(function != NULL)) {
-        function->object.__proto__ = &vm->prototypes[NJS_PROTOTYPE_FUNCTION];
         function->args_offset = 1;
+        function->shared = 1;
 
         function->u.lambda = nxt_mem_cache_zalloc(vm->mem_cache_pool,
                                                  sizeof(njs_function_lambda_t));
@@ -45,6 +45,30 @@ 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;
+
+    function = value->data.u.function;
+
+    if (!function->shared) {
+        return function;
+    }
+
+    function = nxt_mem_cache_alloc(vm->mem_cache_pool, sizeof(njs_function_t));
+
+    if (nxt_fast_path(function != NULL)) {
+        *function = *value->data.u.function;
+        function->object.__proto__ = &vm->prototypes[NJS_PROTOTYPE_FUNCTION];
+        function->shared = 0;
+        value->data.u.function = function;
+    }
+
+    return function;
+}
+
+
 njs_ret_t
 njs_function_native_frame(njs_vm_t *vm, njs_function_t *function,
     const njs_value_t *this, njs_value_t *args, nxt_uint_t nargs,
@@ -434,6 +458,7 @@ njs_function_prototype_bind(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs,
     *function = *args[0].data.u.function;
 
     function->object.__proto__ = &vm->prototypes[NJS_PROTOTYPE_FUNCTION];
+    function->shared = 0;
 
     if (nargs == 1) {
         args = (njs_value_t *) &njs_value_void;
index cca94b65f4a1a65012f8491574b4405487a9bfaa..8f56921c8e1d49af9a8a5ce365338a39a23aeb15 100644 (file)
@@ -120,6 +120,7 @@ typedef struct {
 
 
 njs_function_t *njs_function_alloc(njs_vm_t *vm);
+njs_function_t *njs_function_value_copy(njs_vm_t *vm, njs_value_t *value);
 njs_native_frame_t *njs_function_frame_alloc(njs_vm_t *vm, size_t size);
 njs_ret_t njs_function_constructor(njs_vm_t *vm, njs_value_t *args,
     nxt_uint_t nargs, njs_index_t unused);
index 5d565d653733870356642a3f5d93ae0ffc66161f..5d37adb1281479bfe3afe27d4b5cc181654d04c9 100644 (file)
@@ -23,6 +23,8 @@
 
 static nxt_int_t njs_generator(njs_vm_t *vm, njs_parser_t *parser,
     njs_parser_node_t *node);
+static nxt_int_t njs_generate_name(njs_vm_t *vm, njs_parser_t *parser,
+    njs_parser_node_t *node);
 static nxt_int_t njs_generate_variable(njs_parser_t *parser,
     njs_parser_node_t *node);
 static nxt_int_t njs_generate_if_statement(njs_vm_t *vm, njs_parser_t *parser,
@@ -89,6 +91,8 @@ static nxt_int_t njs_generate_function_call(njs_vm_t *vm, njs_parser_t *parser,
     njs_parser_node_t *node);
 static nxt_int_t njs_generate_method_call(njs_vm_t *vm, njs_parser_t *parser,
     njs_parser_node_t *node);
+static nxt_noinline nxt_int_t njs_generate_call(njs_vm_t *vm,
+    njs_parser_t *parser, njs_parser_node_t *node);
 static nxt_int_t njs_generate_try_statement(njs_vm_t *vm, njs_parser_t *parser,
     njs_parser_node_t *node);
 static nxt_int_t njs_generate_throw_statement(njs_vm_t *vm,
@@ -280,7 +284,7 @@ njs_generator(njs_vm_t *vm, njs_parser_t *parser, njs_parser_node_t *node)
         return NXT_OK;
 
     case NJS_TOKEN_NAME:
-        return njs_generate_variable(parser, node);
+        return njs_generate_name(vm, parser, node);
 
     case NJS_TOKEN_FUNCTION:
         return njs_generate_function_declaration(vm, parser, node);
@@ -309,6 +313,37 @@ njs_generator(njs_vm_t *vm, njs_parser_t *parser, njs_parser_node_t *node)
 }
 
 
+static nxt_int_t
+njs_generate_name(njs_vm_t *vm, njs_parser_t *parser, njs_parser_node_t *node)
+{
+    njs_index_t                 index;
+    njs_value_t                 *value;
+    njs_vmcode_function_copy_t  *copy;
+
+    index = node->u.variable->index;
+    value = njs_variable_value(parser, index);
+
+    if (value->type == NJS_FUNCTION) {
+
+        node->index = njs_generator_dest_index(vm, parser, node);
+        if (nxt_slow_path(node->index == NJS_INDEX_ERROR)) {
+            return node->index;
+        }
+
+        njs_generate_code(parser, njs_vmcode_function_copy_t, copy);
+        copy->code.operation = njs_vmcode_function_copy;
+        copy->code.operands = NJS_VMCODE_2OPERANDS;
+        copy->code.retval = NJS_VMCODE_RETVAL;
+        copy->retval = node->index;
+        copy->function = index;
+
+        return NXT_OK;
+    }
+
+    return njs_generate_variable(parser, node);
+}
+
+
 static nxt_int_t
 njs_generate_variable(njs_parser_t *parser, njs_parser_node_t *node)
 {
@@ -1146,11 +1181,11 @@ static nxt_int_t
 njs_generate_assignment(njs_vm_t *vm, njs_parser_t *parser,
     njs_parser_node_t *node)
 {
-    nxt_int_t              ret;
-    njs_value_t            *value;
-    njs_parser_node_t      *lvalue, *expr, *object, *property;
-    njs_vmcode_move_t      *move;
-    njs_vmcode_prop_set_t  *prop_set;
+    nxt_int_t                   ret;
+    njs_value_t                 *value;
+    njs_parser_node_t           *lvalue, *expr, *object, *property;
+    njs_vmcode_move_t           *move;
+    njs_vmcode_prop_set_t       *prop_set;
 
     lvalue = node->left;
     expr = node->right;
@@ -1954,12 +1989,8 @@ static nxt_int_t
 njs_generate_function_call(njs_vm_t *vm, njs_parser_t *parser,
     njs_parser_node_t *node)
 {
-    uintptr_t                    nargs;
     nxt_int_t                    ret;
-    njs_index_t                  retval;
-    njs_parser_node_t            *arg, *name;
-    njs_vmcode_move_t            *move;
-    njs_vmcode_function_call_t   *call;
+    njs_parser_node_t            *name;
     njs_vmcode_function_frame_t  *func;
 
     if (node->left != NULL) {
@@ -1989,42 +2020,14 @@ njs_generate_function_call(njs_vm_t *vm, njs_parser_t *parser,
         return ret;
     }
 
-    nargs = 0;
-
-    for (arg = node->right; arg != NULL; arg = arg->right) {
-        nargs++;
-
-        ret = njs_generator(vm, parser, arg->left);
-        if (nxt_slow_path(ret != NXT_OK)) {
-            return ret;
-        }
+    ret = njs_generate_call(vm, parser, node);
 
-        if (arg->index != arg->left->index) {
-            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 = arg->index;
-            move->src = arg->left->index;
-        }
-    }
-
-    func->nargs = nargs;
-
-    retval = njs_generator_dest_index(vm, parser, node);
-    if (nxt_slow_path(retval == NJS_INDEX_ERROR)) {
-        return retval;
+    if (nxt_fast_path(ret >= 0)) {
+        func->nargs = ret;
+        return NXT_OK;
     }
 
-    node->index = retval;
-
-    njs_generate_code(parser, njs_vmcode_function_call_t, call);
-    call->code.operation = njs_vmcode_function_call;
-    call->code.operands = NJS_VMCODE_1OPERAND;
-    call->code.retval = NJS_VMCODE_NO_RETVAL;
-    call->retval = retval;
-
-    return NXT_OK;
+    return ret;
 }
 
 
@@ -2032,13 +2035,9 @@ static nxt_int_t
 njs_generate_method_call(njs_vm_t *vm, njs_parser_t *parser,
     njs_parser_node_t *node)
 {
-    uintptr_t                   nargs;
-    nxt_int_t                   ret;
-    njs_index_t                 retval;
-    njs_parser_node_t           *arg, *prop;
-    njs_vmcode_move_t           *move;
-    njs_vmcode_method_frame_t   *method;
-    njs_vmcode_function_call_t  *call;
+    nxt_int_t                  ret;
+    njs_parser_node_t          *prop;
+    njs_vmcode_method_frame_t  *method;
 
     prop = node->left;
 
@@ -2069,6 +2068,27 @@ njs_generate_method_call(njs_vm_t *vm, njs_parser_t *parser,
         return ret;
     }
 
+    ret = njs_generate_call(vm, parser, node);
+
+    if (nxt_fast_path(ret >= 0)) {
+        method->nargs = ret;
+        return NXT_OK;
+    }
+
+    return ret;
+}
+
+
+static nxt_noinline nxt_int_t
+njs_generate_call(njs_vm_t *vm, njs_parser_t *parser, njs_parser_node_t *node)
+{
+    nxt_int_t                   ret;
+    nxt_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) {
@@ -2089,8 +2109,6 @@ njs_generate_method_call(njs_vm_t *vm, njs_parser_t *parser,
         }
     }
 
-    method->nargs = nargs;
-
     retval = njs_generator_dest_index(vm, parser, node);
     if (nxt_slow_path(retval == NJS_INDEX_ERROR)) {
         return retval;
@@ -2104,7 +2122,7 @@ njs_generate_method_call(njs_vm_t *vm, njs_parser_t *parser,
     call->code.retval = NJS_VMCODE_NO_RETVAL;
     call->retval = retval;
 
-    return NXT_OK;
+    return nargs;
 }
 
 
index 69c62e541f2c73f65a7d30992d6d586a60f7400e..90241ab8a094dac80faf3ac18910007ff3494393 100644 (file)
@@ -318,6 +318,7 @@ njs_parser_function_declaration(njs_vm_t *vm, njs_parser_t *parser)
     value = njs_variable_value(parser, node->index);
     value->data.u.function = function;
     value->type = NJS_FUNCTION;
+    value->data.truth = 1;
 
     parser = njs_parser_function_create(vm, parser);
     if (nxt_slow_path(parser == NULL)) {
@@ -384,6 +385,7 @@ njs_parser_function_expression(njs_vm_t *vm, njs_parser_t *parser)
 
         value->data.u.function = function;
         value->type = NJS_FUNCTION;
+        value->data.truth = 1;
         lambda = function->u.lambda;
 
     } else {
@@ -1462,6 +1464,7 @@ njs_parser_terminal(njs_vm_t *vm, njs_parser_t *parser, njs_token_t token)
             break;
         }
 
+        parser->code_size += sizeof(njs_vmcode_function_copy_t);
         node->lvalue = NJS_LVALUE_ENABLED;
         node->u.variable = var;
         break;
index 543328c94a0621a2dcaace9c0cc104c9602e9999..dbfc0d3e21aa8628de943f9d6535e1e79ecabb7e 100644 (file)
@@ -72,7 +72,7 @@ static njs_ret_t njs_array_property_query(njs_vm_t *vm,
     njs_property_query_t *pq, njs_value_t *object, int32_t index);
 static njs_ret_t njs_object_property_query(njs_vm_t *vm,
     njs_property_query_t *pq, njs_value_t *value, njs_object_t *object);
-static njs_ret_t njs_function_private_copy(njs_vm_t *vm,
+static njs_ret_t njs_method_private_copy(njs_vm_t *vm,
     njs_property_query_t *pq);
 static nxt_noinline uint32_t njs_integer_value(double num);
 static nxt_noinline njs_ret_t njs_values_equal(njs_value_t *val1,
@@ -392,8 +392,7 @@ 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)
+njs_vmcode_function(njs_vm_t *vm, njs_value_t *invld1, njs_value_t *invld2)
 {
     njs_function_t         *function;
     njs_vmcode_function_t  *code;
@@ -406,6 +405,7 @@ njs_vmcode_function(njs_vm_t *vm, njs_value_t *invld1,
 
         code = (njs_vmcode_function_t *) vm->current;
         function->u.lambda = code->lambda;
+
         vm->retval.data.u.function = function;
         vm->retval.type = NJS_FUNCTION;
         vm->retval.data.truth = 1;
@@ -417,6 +417,34 @@ njs_vmcode_function(njs_vm_t *vm, njs_value_t *invld1,
 }
 
 
+njs_ret_t
+njs_vmcode_function_copy(njs_vm_t *vm, njs_value_t *value, njs_value_t *invld)
+{
+    njs_function_t  *function;
+
+    if (njs_is_function(value)) {
+
+        function = njs_function_value_copy(vm, value);
+
+        if (nxt_fast_path(function != NULL)) {
+            vm->retval.data.u.function = function;
+            vm->retval.type = NJS_FUNCTION;
+            vm->retval.data.truth = 1;
+
+            return sizeof(njs_vmcode_function_copy_t);
+        }
+
+        return NXT_ERROR;
+    }
+
+    vm->retval = *value;
+
+    njs_retain(value);
+
+    return sizeof(njs_vmcode_function_copy_t);
+}
+
+
 njs_ret_t
 njs_vmcode_regexp(njs_vm_t *vm, njs_value_t *invld1, njs_value_t *invld2)
 {
@@ -470,7 +498,7 @@ njs_vmcode_property_get(njs_vm_t *vm, njs_value_t *object,
 
         case NJS_METHOD:
             if (pq.shared) {
-                ret = njs_function_private_copy(vm, &pq);
+                ret = njs_method_private_copy(vm, &pq);
 
                 if (nxt_slow_path(ret != NXT_OK)) {
                     return ret;
@@ -870,12 +898,13 @@ static nxt_noinline njs_ret_t
 njs_property_query(njs_vm_t *vm, njs_property_query_t *pq, njs_value_t *object,
     njs_value_t *property)
 {
-    double        num;
-    int32_t       index;
-    uint32_t      (*hash)(const void *, size_t);
-    njs_ret_t     ret;
-    njs_extern_t  *ext;
-    njs_object_t  *obj;
+    double          num;
+    int32_t         index;
+    uint32_t        (*hash)(const void *, size_t);
+    njs_ret_t       ret;
+    njs_extern_t    *ext;
+    njs_object_t    *obj;
+    njs_function_t  *function;
 
     hash = nxt_djb_hash;
 
@@ -921,11 +950,19 @@ njs_property_query(njs_vm_t *vm, njs_property_query_t *pq, njs_value_t *object,
     case NJS_OBJECT_BOOLEAN:
     case NJS_OBJECT_NUMBER:
     case NJS_OBJECT_STRING:
-    case NJS_FUNCTION:
     case NJS_REGEXP:
         obj = object->data.u.object;
         break;
 
+    case NJS_FUNCTION:
+        function = njs_function_value_copy(vm, object);
+        if (nxt_slow_path(function == NULL)) {
+            return NXT_ERROR;
+        }
+
+        obj = &function->object;
+        break;
+
     case NJS_EXTERNAL:
         ext = object->data.u.external;
 
@@ -1074,9 +1111,9 @@ njs_object_property_query(njs_vm_t *vm, njs_property_query_t *pq,
 
 
 static njs_ret_t
-njs_function_private_copy(njs_vm_t *vm, njs_property_query_t *pq)
+njs_method_private_copy(njs_vm_t *vm, njs_property_query_t *pq)
 {
-    njs_function_t     *func;
+    njs_function_t     *function;
     njs_object_prop_t  *prop, *shared;
 
     prop = nxt_mem_cache_alloc(vm->mem_cache_pool, sizeof(njs_object_prop_t));
@@ -1087,15 +1124,11 @@ njs_function_private_copy(njs_vm_t *vm, njs_property_query_t *pq)
     shared = pq->lhq.value;
     *prop = *shared;
 
-    func = nxt_mem_cache_alloc(vm->mem_cache_pool, sizeof(njs_function_t));
-    if (nxt_slow_path(func == NULL)) {
+    function = njs_function_value_copy(vm, &prop->value);
+    if (nxt_slow_path(function == NULL)) {
         return NXT_ERROR;
     }
 
-    *func = *prop->value.data.u.function;
-    func->object.__proto__ = &vm->prototypes[NJS_PROTOTYPE_FUNCTION];
-    prop->value.data.u.function = func;
-
     pq->lhq.replace = 0;
     pq->lhq.value = prop;
     pq->lhq.pool = vm->mem_cache_pool;
index 6bc9fbeddc1079d1a8b566cc7fca971d668f0323..bae66278dac899cd3ede261a315952fbef09da27 100644 (file)
@@ -134,15 +134,22 @@ typedef struct {
     njs_object_t                      object;
 
     uint8_t                           args_types[NJS_ARGS_TYPES_MAX];
+    uint8_t                           args_offset;
+
+    /*
+     * TODO Shared
+     * When function object is used as value: in assignments,
+     * as function argument, as property and as object to get properties.
+     */
 
 #if (NXT_64BIT)
     uint8_t                           native;
     uint8_t                           continuation_size;
-    uint32_t                          args_offset;
+    uint8_t                           shared;
 #else
     uint8_t                           native;
     uint8_t                           continuation_size;
-    uint16_t                          args_offset;
+    uint8_t                           shared;
 #endif
 
     union {
@@ -272,6 +279,7 @@ union njs_value_s {
             .continuation_size = _size,                                       \
             .args_types = { __VA_ARGS__ },                                    \
             .args_offset = 1,                                                 \
+            .shared = 1,                                                      \
             .u.native = _function,                                            \
         }                                                                     \
     }                                                                         \
@@ -468,6 +476,13 @@ typedef struct {
 } njs_vmcode_function_t;
 
 
+typedef struct {
+    njs_vmcode_t               code;
+    njs_index_t                retval;
+    njs_index_t                function;
+} njs_vmcode_function_copy_t;
+
+
 typedef struct {
     njs_vmcode_t               code;
     njs_index_t                retval;
@@ -783,6 +798,8 @@ njs_ret_t njs_vmcode_array(njs_vm_t *vm, njs_value_t *inlvd1,
     njs_value_t *inlvd2);
 njs_ret_t njs_vmcode_function(njs_vm_t *vm, njs_value_t *inlvd1,
     njs_value_t *invld2);
+njs_ret_t njs_vmcode_function_copy(njs_vm_t *vm, njs_value_t *value,
+    njs_value_t *invld);
 njs_ret_t njs_vmcode_regexp(njs_vm_t *vm, njs_value_t *inlvd1,
     njs_value_t *invld2);
 
index 5283e1db0ba108fcbee2b3b7f1fcc81bb98a3306..adbc11f7cc081c1263cb25202e04223848b820b9 100644 (file)
@@ -3138,6 +3138,53 @@ static njs_unit_test_t  njs_test[] =
     { nxt_string("[].slice.call()"),
       nxt_string("TypeError") },
 
+    { nxt_string("function f(a) {} ; var a = f; var b = f; a === b"),
+      nxt_string("true") },
+
+    { nxt_string("function f() {} ; f.toString()"),
+      nxt_string("[object Function]") },
+
+    { nxt_string("function f() {}; f"),
+      nxt_string("[object Function]") },
+
+    { nxt_string("function f() {}; f = f + 1; f"),
+      nxt_string("[object Function]1") },
+
+#if 0
+    { nxt_string("function f() {}; f += 1; f"),
+      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") },
+
+    { nxt_string("function f(a) { return this+a }; f.call('0', 1)"),
+      nxt_string("01") },
+
+    { nxt_string("function f(a) { return this+a };"
+                 "function g(f, a, b) { return f.call(a, b) }; g(f, '0', 1)"),
+      nxt_string("01") },
+
+    { nxt_string("function f(a) { return this+a };"
+                 "o = { g: function (f, a, b) { return f.call(a, b) } };"
+                 "o.g(f, '0', 1)"),
+      nxt_string("01") },
+
+    { nxt_string("var concat = ''.concat; concat(1,2,3)"),
+      nxt_string("TypeError") },
+
+    { nxt_string("var concat = ''.concat; concat.call(1,2,3)"),
+      nxt_string("123") },
+
+    { nxt_string("var concat = ''.concat; concat.yes = 'OK';"
+                 "concat.call(1,2,3, concat.yes)"),
+      nxt_string("123OK") },
+
     { nxt_string("var f = function(a, b) { return this + a + b }"
                  "var b = f.bind('1'); b('2', '3')"),
       nxt_string("123") },