]> git.kaiwu.me - njs.git/commitdiff
Function.bind() implementation.
authorIgor Sysoev <igor@sysoev.ru>
Mon, 15 Feb 2016 14:31:59 +0000 (17:31 +0300)
committerIgor Sysoev <igor@sysoev.ru>
Mon, 15 Feb 2016 14:31:59 +0000 (17:31 +0300)
njs/njs_array.c
njs/njs_function.c
njs/njs_function.h
njs/njs_vm.c
njs/njs_vm.h
njs/test/njs_unit_test.c

index 85d1bf67b3bbd7b8a70fed42c6105095a0242b7d..9cf8d150a436a4fdfd5eab2a97a8258b4a04e56a 100644 (file)
@@ -980,7 +980,8 @@ static const njs_object_prop_t  njs_array_prototype_properties[] =
     {
         .type = NJS_METHOD,
         .name = njs_string("join"),
-        .value = njs_native_function(njs_array_prototype_join, 0,
+        .value = njs_native_function(njs_array_prototype_join,
+                     njs_continuation_size(njs_array_join_t),
                      NJS_OBJECT_ARG, NJS_STRING_ARG),
     },
 
@@ -994,21 +995,21 @@ static const njs_object_prop_t  njs_array_prototype_properties[] =
         .type = NJS_METHOD,
         .name = njs_string("forEach"),
         .value = njs_native_function(njs_array_prototype_for_each,
-                     njs_method_data_size(sizeof(njs_array_next_t)), 0),
+                     njs_continuation_size(njs_array_next_t), 0),
     },
 
     {
         .type = NJS_METHOD,
         .name = njs_string("some"),
         .value = njs_native_function(njs_array_prototype_some,
-                     njs_method_data_size(sizeof(njs_array_next_t)), 0),
+                     njs_continuation_size(njs_array_next_t), 0),
     },
 
     {
         .type = NJS_METHOD,
         .name = njs_string("every"),
         .value = njs_native_function(njs_array_prototype_every,
-                     njs_method_data_size(sizeof(njs_array_next_t)), 0),
+                     njs_continuation_size(njs_array_next_t), 0),
     },
 };
 
index 95909e3148c5d8fd5aa6b9755156d87a14e377d8..c7bdb5c1cfec0d2dec42fd01a35e86bcb386f616 100644 (file)
@@ -25,9 +25,6 @@ typedef struct {
 } njs_function_apply_t;
 
 
-static nxt_int_t njs_function_apply_frame(njs_vm_t *vm,
-    njs_function_t *function, njs_value_t *this, njs_value_t *args,
-    nxt_uint_t nargs);
 static njs_ret_t njs_function_prototype_apply_continuation(njs_vm_t *vm,
     njs_value_t *args, nxt_uint_t nargs, njs_index_t retval);
 
@@ -54,31 +51,53 @@ njs_function_alloc(njs_vm_t *vm)
 }
 
 
-njs_value_t *
+njs_ret_t
 njs_function_native_frame(njs_vm_t *vm, njs_function_t *function,
-    njs_vmcode_t *code)
+    const njs_value_t *this, njs_value_t *args, nxt_uint_t nargs,
+    nxt_bool_t ctor)
 {
     size_t              size;
-    njs_value_t         *this;
+    nxt_uint_t          n;
+    njs_value_t         *value, *bound;
     njs_native_frame_t  *frame;
 
-    size = NJS_NATIVE_FRAME_SIZE + function->local_state_size
-           + code->nargs * sizeof(njs_value_t);
+    size = NJS_NATIVE_FRAME_SIZE
+           + function->continuation_size
+           + (function->args_offset + nargs - 1) * sizeof(njs_value_t);
 
     frame = njs_function_frame_alloc(vm, size);
     if (nxt_slow_path(frame == NULL)) {
-        return NULL;
+        return NXT_ERROR;
     }
 
     frame->function = function;
-    frame->ctor = code->ctor;
+    frame->ctor = ctor;
+
+    value = (njs_value_t *) ((u_char *) njs_native_data(frame)
+                             + function->continuation_size);
+
+    bound = function->bound;
 
-    this = (njs_value_t *) ((u_char *) njs_native_data(frame)
-                            + function->local_state_size);
-    frame->arguments = this + 1;
-    vm->scopes[NJS_SCOPE_CALLEE_ARGUMENTS] = frame->arguments;
+    if (bound == NULL) {
+        *value++ = *this;
 
-    return this;
+    } else {
+        n = function->args_offset;
+
+        do {
+            *value++ = *bound++;
+            n--;
+        } while (n != 0);
+    }
+
+    frame->arguments = value;
+    vm->scopes[NJS_SCOPE_CALLEE_ARGUMENTS] = value;
+
+    if (args != NULL) {
+        memcpy(value, args, (nargs - 1) * sizeof(njs_value_t));
+    }
+
+    return NXT_OK;
 }
 
 
@@ -151,18 +170,18 @@ njs_function_apply(njs_vm_t *vm, njs_function_t *function, njs_value_t *args,
 
 nxt_noinline njs_ret_t
 njs_function_frame(njs_vm_t *vm, njs_function_t *function, njs_value_t *this,
-    njs_value_t *args0, nxt_uint_t nargs0, nxt_bool_t ctor)
+    njs_value_t *args, nxt_uint_t nargs, nxt_bool_t ctor)
 {
     size_t              size;
-    uintptr_t           nargs, n;
-    njs_value_t         *args, *arguments;
+    nxt_uint_t          n, max_args;
+    njs_value_t         *value, *bound;
     njs_frame_t         *frame;
     njs_native_frame_t  *native_frame;
 
-    nargs = nxt_max(nargs0, function->u.lambda->nargs);
+    max_args = nxt_max(nargs, function->u.lambda->nargs) - 1;
 
     size = NJS_FRAME_SIZE
-           + nargs * sizeof(njs_value_t)
+           + (function->args_offset + max_args) * sizeof(njs_value_t)
            + function->u.lambda->local_size;
 
     native_frame = njs_function_frame_alloc(vm, size);
@@ -173,34 +192,41 @@ njs_function_frame(njs_vm_t *vm, njs_function_t *function, njs_value_t *this,
     native_frame->function = function;
     native_frame->ctor = ctor;
 
-    args = (njs_value_t *) ((u_char *) native_frame + NJS_FRAME_SIZE);
-    native_frame->arguments = args + function->args_offset;
-    vm->scopes[NJS_SCOPE_CALLEE_ARGUMENTS] = native_frame->arguments;
+    value = (njs_value_t *) ((u_char *) native_frame + NJS_FRAME_SIZE);
 
-    frame = (njs_frame_t *) native_frame;
+    bound = function->bound;
 
-    frame->local = &args[nargs];
+    if (bound == NULL) {
+        *value++ = *this;
 
-    *args++ = *this;
-    nargs--;
+    } else {
+        n = function->args_offset;
 
-    arguments = args0;
+        do {
+            *value++ = *bound++;
+            n--;
+        } while (n != 0);
+    }
 
-    if (arguments != NULL) {
-        n = nargs0;
+    native_frame->arguments = value;
+    vm->scopes[NJS_SCOPE_CALLEE_ARGUMENTS] = value;
 
-        while (n != 0) {
-            *args++ = *arguments++;
+    if (args != NULL) {
+        while (nargs != 0) {
+            *value++ = *args++;
+            max_args--;
             nargs--;
-            n--;
         }
     }
 
-    while (nargs != 0) {
-        *args++ = njs_value_void;
-        nargs--;
+    while (max_args != 0) {
+        *value++ = njs_value_void;
+        max_args--;
     }
 
+    frame = (njs_frame_t *) native_frame;
+    frame->local = value;
+
     memcpy(frame->local, function->u.lambda->local_scope,
            function->u.lambda->local_size);
 
@@ -295,12 +321,12 @@ njs_function_prototype_call(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs,
             return ret;
         }
 
-        if (function->local_state_size == 0) {
+        if (function->continuation_size == 0) {
             args = &args[1];
 
         } else {
-            ret = njs_function_apply_frame(vm, function, &args[1], &args[2],
-                                           nargs - 1);
+            ret = njs_function_native_frame(vm, function, &args[1], &args[2],
+                                            nargs, 0);
             if (ret != NJS_OK) {
                 return ret;
             }
@@ -371,11 +397,14 @@ njs_function_prototype_apply(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs,
     }
 
     if (function->native) {
-        ret = njs_function_apply_frame(vm, function, this, args, nargs);
+        nargs++;
+
+        ret = njs_function_native_frame(vm, function, this, args, nargs, 0);
         if (nxt_slow_path(ret != NXT_OK)) {
             return ret;
         }
 
+//        apply = njs_continuation(vm->frame);
         apply = nxt_mem_cache_alloc(vm->mem_cache_pool,
                                     sizeof(njs_function_apply_t));
         if (nxt_slow_path(apply == NULL)) {
@@ -383,7 +412,6 @@ njs_function_prototype_apply(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs,
         }
 
         args = vm->frame->arguments - function->args_offset;
-        nargs = nargs + 1;
 
         /* Skip the "apply" method frame. */
         vm->frame->previous->skip = 1;
@@ -418,38 +446,6 @@ type_error:
 }
 
 
-static nxt_int_t
-njs_function_apply_frame(njs_vm_t *vm, njs_function_t *function,
-    njs_value_t *this, njs_value_t *args, nxt_uint_t nargs)
-{
-    size_t              size;
-    njs_value_t         *arguments;
-    njs_native_frame_t  *frame;
-
-    size = NJS_NATIVE_FRAME_SIZE + function->local_state_size
-           + (nargs + 1) * sizeof(njs_value_t);
-
-    frame = njs_function_frame_alloc(vm, size);
-    if (nxt_slow_path(frame == NULL)) {
-        return NXT_ERROR;
-    }
-
-    frame->function = function;
-
-    arguments = (njs_value_t *) ((u_char *) njs_native_data(frame)
-                                 + function->local_state_size);
-
-    frame->arguments = arguments + function->args_offset;
-    vm->scopes[NJS_SCOPE_CALLEE_ARGUMENTS] = frame->arguments;
-
-    *arguments++ = *this;
-
-    memcpy(arguments, args, nargs * sizeof(njs_value_t));
-
-    return NXT_OK;
-}
-
-
 static njs_ret_t
 njs_function_prototype_apply_continuation(njs_vm_t *vm, njs_value_t *args,
     nxt_uint_t nargs, njs_index_t retval)
@@ -475,30 +471,52 @@ static njs_ret_t
 njs_function_prototype_bind(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs,
     njs_index_t unused)
 {
-    njs_function_t  *bound;
+    size_t          size;
+    njs_value_t     *values;
+    njs_function_t  *function;
 
     if (!njs_is_function(&args[0])) {
         vm->exception = &njs_exception_type_error;
         return NXT_ERROR;
     }
 
-    bound = nxt_mem_cache_alloc(vm->mem_cache_pool, sizeof(njs_function_t));
+    function = nxt_mem_cache_alloc(vm->mem_cache_pool, sizeof(njs_function_t));
+    if (nxt_slow_path(function == NULL)) {
+        return NXT_ERROR;
+    }
+
+    *function = *args[0].data.u.function;
 
-    if (nxt_fast_path(bound != NULL)) {
-        nxt_lvlhsh_init(&bound->object.hash);
-        nxt_lvlhsh_init(&bound->object.shared_hash);
-        bound->object.__proto__ = &vm->prototypes[NJS_PROTOTYPE_FUNCTION];
-        bound->args_offset = 1;
-        bound->u.lambda = args[0].data.u.function->u.lambda;
+    function->object.__proto__ = &vm->prototypes[NJS_PROTOTYPE_FUNCTION];
 
-        vm->retval.data.u.function = bound;
-        vm->retval.type = NJS_FUNCTION;
-        vm->retval.data.truth = 1;
+    if (nargs == 1) {
+        args = (njs_value_t *) &njs_value_void;
 
-        return NXT_OK;
+    } else {
+        nargs--;
+        args++;
     }
 
-    return NXT_ERROR;
+    function->args_offset = nargs;
+    size = nargs * sizeof(njs_value_t);
+
+    values = nxt_mem_cache_alloc(vm->mem_cache_pool, size);
+    if (nxt_slow_path(values == NULL)) {
+        nxt_mem_cache_free(vm->mem_cache_pool, function);
+        return NXT_ERROR;
+    }
+
+    function->bound = values;
+
+    /* GC: ? retain args. */
+
+    memcpy(values, args, size);
+
+    vm->retval.data.u.function = function;
+    vm->retval.type = NJS_FUNCTION;
+    vm->retval.data.truth = 1;
+
+    return NXT_OK;
 }
 
 
@@ -513,7 +531,8 @@ static const njs_object_prop_t  njs_function_prototype_properties[] =
     {
         .type = NJS_METHOD,
         .name = njs_string("apply"),
-        .value = njs_native_function(njs_function_prototype_apply, 0, 0),
+        .value = njs_native_function(njs_function_prototype_apply,
+                     njs_continuation_size(njs_function_apply_t), 0),
     },
 
     {
index 6843668b426a7d59c45a2a1b25f8575420ee08fc..3cc20c56b09934257798a699dd4ca0121bd52422 100644 (file)
@@ -45,12 +45,15 @@ struct njs_function_lambda_s {
 
 #define NJS_FRAME_SPARE_SIZE       512
 
-#define njs_method_data_size(size)                                            \
-    nxt_align_size(size, sizeof(njs_value_t))
-
 #define njs_native_data(frame)                                                \
     (void *) ((u_char *) frame + NJS_NATIVE_FRAME_SIZE)
 
+#define njs_continuation(frame)                                               \
+    (void *) ((u_char *) frame + NJS_NATIVE_FRAME_SIZE)
+
+#define njs_continuation_size(size)                                           \
+    nxt_align_size(sizeof(size), sizeof(njs_value_t))
+
 
 typedef struct {
     njs_function_native_t          function;
@@ -136,8 +139,9 @@ njs_ret_t njs_function_constructor(njs_vm_t *vm, njs_value_t *args,
     nxt_uint_t nargs, njs_index_t unused);
 njs_ret_t njs_function_apply(njs_vm_t *vm, njs_function_t *function,
     njs_value_t *args, nxt_uint_t nargs, njs_index_t retval);
-njs_value_t *njs_function_native_frame(njs_vm_t *vm, njs_function_t *function,
-    njs_vmcode_t *code);
+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,
+    nxt_bool_t ctor);
 njs_ret_t njs_function_frame(njs_vm_t *vm, njs_function_t *function,
     njs_value_t *this, njs_value_t *args, nxt_uint_t nargs, nxt_bool_t ctor);
 njs_ret_t njs_function_call(njs_vm_t *vm, njs_index_t retval, size_t advance);
index d9ff811a0588528cf7dedb1de46650116e6405ce..3f7d4222fcc473619632e423c95c9b1a1b7fe870 100644 (file)
@@ -2097,14 +2097,14 @@ njs_vmcode_function_frame(njs_vm_t *vm, njs_value_t *invld, njs_value_t *name)
         function = value->data.u.function;
 
         if (function->native) {
-            this = njs_function_native_frame(vm, function, &func->code);
-            if (nxt_fast_path(this != NULL)) {
-                *this = njs_value_void;
+            ret = njs_function_native_frame(vm, function, &njs_value_void, NULL,
+                                            func->code.nargs, func->code.ctor);
 
+            if (nxt_fast_path(ret == NXT_OK)) {
                 return sizeof(njs_vmcode_function_frame_t);
             }
 
-            return NXT_ERROR;
+            return ret;
         }
 
         if (func->code.ctor) {
@@ -2143,7 +2143,7 @@ njs_ret_t
 njs_vmcode_method_frame(njs_vm_t *vm, njs_value_t *name, njs_value_t *object)
 {
     njs_ret_t                  ret;
-    njs_value_t                *this;
+    njs_value_t                this;
     njs_extern_t               *ext;
     njs_function_t             *function;
     njs_object_prop_t          *prop;
@@ -2166,8 +2166,7 @@ njs_vmcode_method_frame(njs_vm_t *vm, njs_value_t *name, njs_value_t *object)
 
             if (!function->native) {
                 ret = njs_function_frame(vm, function, object, NULL,
-                                         method->code.nargs,
-                                         method->code.ctor);
+                                         method->code.nargs, method->code.ctor);
 
                 if (nxt_fast_path(ret == NXT_OK)) {
                     return sizeof(njs_vmcode_method_frame_t);
@@ -2176,15 +2175,16 @@ njs_vmcode_method_frame(njs_vm_t *vm, njs_value_t *name, njs_value_t *object)
                 return ret;
             }
 
-            this = njs_function_native_frame(vm, function, &method->code);
-            if (nxt_slow_path(this == NULL)) {
-                return NXT_ERROR;
-            }
+            ret = njs_function_native_frame(vm, function, object, NULL,
+                                            method->code.nargs,
+                                            method->code.ctor);
 
-            njs_retain(object);
-            *this = *object;
+            if (nxt_fast_path(ret == NXT_OK)) {
+                njs_retain(object);
+                return sizeof(njs_vmcode_method_frame_t);
+            }
 
-            return sizeof(njs_vmcode_method_frame_t);
+            return ret;
         }
 
         break;
@@ -2199,16 +2199,17 @@ njs_vmcode_method_frame(njs_vm_t *vm, njs_value_t *name, njs_value_t *object)
             ext = pq.lhq.value;
 
             if (ext->type == NJS_EXTERN_METHOD) {
-                this = njs_function_native_frame(vm, ext->function,
-                                                 &method->code);
+                this.data.u.data = vm->external[ext->object];
 
-                if (nxt_slow_path(this == NULL)) {
-                    return NXT_ERROR;
-                }
+                ret = njs_function_native_frame(vm, ext->function, &this, NULL,
+                                                method->code.nargs,
+                                                method->code.ctor);
 
-                this->data.u.data = vm->external[ext->object];
+                if (nxt_fast_path(ret == NXT_OK)) {
+                    return sizeof(njs_vmcode_method_frame_t);
+                }
 
-                return sizeof(njs_vmcode_method_frame_t);
+                return ret;
             }
         }
 
@@ -2250,7 +2251,7 @@ njs_vmcode_function_call(njs_vm_t *vm, njs_value_t *invld, njs_value_t *retval)
     continuation = vm->frame->continuation;
 
     if (continuation == NULL) {
-        nargs = call->code.nargs;
+        nargs = function->args_offset + call->code.nargs - 1;
 
         ret = njs_normalize_args(vm, args, function->args_types, nargs);
         if (ret != NJS_OK) {
index 10b6c9eb91e716076ca99ca27a6e94978d5db679..047782500f060439549fc5971bd045aff6158b5b 100644 (file)
@@ -138,11 +138,11 @@ typedef struct {
 
 #if (NXT_64BIT)
     uint8_t                           native;
-    uint8_t                           local_state_size;
+    uint8_t                           continuation_size;
     uint32_t                          args_offset;
 #else
     uint8_t                           native;
-    uint8_t                           local_state_size;
+    uint8_t                           continuation_size;
     uint16_t                          args_offset;
 #endif
 
@@ -151,7 +151,7 @@ typedef struct {
         njs_function_native_t         native;
     } u;
 
-    njs_value_t                       *args;
+    njs_value_t                       *bound;
 } njs_function_t;
 
 
@@ -255,13 +255,13 @@ union njs_value_s {
 }
 
 
-#define njs_native_function(_function, _local_size, ...) {                    \
+#define njs_native_function(_function, _size, ...) {                          \
     .data = {                                                                 \
         .type = NJS_FUNCTION,                                                 \
         .truth = 1,                                                           \
         .u.function = & (njs_function_t) {                                    \
             .native = 1,                                                      \
-            .local_state_size = _local_size,                                  \
+            .continuation_size = _size,                                       \
             .args_types = { __VA_ARGS__ },                                    \
             .args_offset = 1,                                                 \
             .u.native = _function,                                            \
index baab9466f36457e562f31ac93d0cf166884b4999..bc251e13e458c5352eba8dcdb942836c08052c23 100644 (file)
@@ -2321,6 +2321,33 @@ static njs_unit_test_t  njs_test[] =
     { nxt_string("''.concat.apply(0, [1, 2, 3, 4, 5, 6, 7, 8, 9])"),
       nxt_string("0123456789") },
 
+    { nxt_string("var f = ''.concat.bind(0, 1, 2, 3, 4); f(5, 6, 7, 8, 9)"),
+      nxt_string("0123456789") },
+
+    { nxt_string("var f = String.prototype.concat.bind(0, 1); f(2)"),
+      nxt_string("012") },
+
+    { nxt_string("var f = Function.prototype.call.bind"
+                 "                            (String.prototype.concat, 0, 1);"
+                 "f(2)"),
+      nxt_string("012") },
+
+#if 0
+    { nxt_string("var f = String.prototype.concat.bind(0, 1);"
+                 "var o = { toString: f }; o"),
+      nxt_string("01") },
+#endif
+
+#if 0
+    { nxt_string("''.concat.bind(1,2,3,4).call(5,6,7,8)"),
+      nxt_string("012346789") },
+#endif
+
+#if 0
+    { nxt_string("''.concat.bind(1,2,3,4).apply(5,[6,7,8])"),
+      nxt_string("012346789") },
+#endif
+
     { nxt_string("var s = { toString: function() { return '123' } };"
                  "var a = 'abc'; a.concat('абв', s)"),
       nxt_string("abcабв123") },
@@ -3096,6 +3123,50 @@ static njs_unit_test_t  njs_test[] =
     { nxt_string("[].slice.call()"),
       nxt_string("TypeError") },
 
+    { nxt_string("var f = function(a, b) { return this + a + b }"
+                 "var b = f.bind('1'); b('2', '3')"),
+      nxt_string("123") },
+
+    { nxt_string("var f = function(a, b) { return this + a + b }"
+                 "var b = f.bind('1', '2'); b('3')"),
+      nxt_string("123") },
+
+    { nxt_string("var f = function(a, b) { return this + a + b }"
+                 "var b = f.bind('1', 2, '3'); b()"),
+      nxt_string("123") },
+
+    { nxt_string("var f = function(a, b) { return this + a + b }"
+                 "var b = f.bind('1'); b.call('0', '2', '3')"),
+      nxt_string("123") },
+
+    { nxt_string("var f = function(a, b) { return this + a + b }"
+                 "var b = f.bind('1', '2'); b.call('0', '3')"),
+      nxt_string("123") },
+
+    { nxt_string("var f = function(a, b) { return this + a + b }"
+                 "var b = f.bind('1', '2', '3'); b.call('0')"),
+      nxt_string("123") },
+
+    { nxt_string("var f = function(a, b) { return this + a + b }"
+                 "var b = f.bind('1', '2', '3'); b.call()"),
+      nxt_string("123") },
+
+    { nxt_string("var f = function(a, b) { return this + a + b }"
+                 "var b = f.bind('1'); b.apply('0', ['2', '3'])"),
+      nxt_string("123") },
+
+    { nxt_string("var f = function(a, b) { return this + a + b }"
+                 "var b = f.bind('1', '2'); b.apply('0', ['3'])"),
+      nxt_string("123") },
+
+    { nxt_string("var f = function(a, b) { return this + a + b }"
+                 "var b = f.bind('1', '2', '3'); b.apply('0')"),
+      nxt_string("123") },
+
+    { nxt_string("var f = function(a, b) { return this + a + b }"
+                 "var b = f.bind('1', '2', '3'); b.apply()"),
+      nxt_string("123") },
+
     { nxt_string("function F(a, b) { this.a = a + b }"
                  "var o = new F(1, 2)"
                  "o.a"),