From 32686430cf638935b06f7ff16481a23f39b9ad0d Mon Sep 17 00:00:00 2001 From: Igor Sysoev Date: Mon, 15 Feb 2016 17:31:59 +0300 Subject: [PATCH] Function.bind() implementation. --- njs/njs_array.c | 9 +- njs/njs_function.c | 193 +++++++++++++++++++++------------------ njs/njs_function.h | 14 ++- njs/njs_vm.c | 45 ++++----- njs/njs_vm.h | 10 +- njs/test/njs_unit_test.c | 71 ++++++++++++++ 6 files changed, 219 insertions(+), 123 deletions(-) diff --git a/njs/njs_array.c b/njs/njs_array.c index 85d1bf67..9cf8d150 100644 --- a/njs/njs_array.c +++ b/njs/njs_array.c @@ -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), }, }; diff --git a/njs/njs_function.c b/njs/njs_function.c index 95909e31..c7bdb5c1 100644 --- a/njs/njs_function.c +++ b/njs/njs_function.c @@ -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), }, { diff --git a/njs/njs_function.h b/njs/njs_function.h index 6843668b..3cc20c56 100644 --- a/njs/njs_function.h +++ b/njs/njs_function.h @@ -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); diff --git a/njs/njs_vm.c b/njs/njs_vm.c index d9ff811a..3f7d4222 100644 --- a/njs/njs_vm.c +++ b/njs/njs_vm.c @@ -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) { diff --git a/njs/njs_vm.h b/njs/njs_vm.h index 10b6c9eb..04778250 100644 --- a/njs/njs_vm.h +++ b/njs/njs_vm.h @@ -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, \ diff --git a/njs/test/njs_unit_test.c b/njs/test/njs_unit_test.c index baab9466..bc251e13 100644 --- a/njs/test/njs_unit_test.c +++ b/njs/test/njs_unit_test.c @@ -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"), -- 2.47.3