From: Igor Sysoev Date: Tue, 23 Feb 2016 18:09:42 +0000 (+0300) Subject: Declared function objects are copied on demand to allow X-Git-Tag: 0.1.0~54 X-Git-Url: http://www.kaiwu.me/postgresql/commit/?a=commitdiff_plain;h=b48b8cd32b460c0ada51d9ce74adee8c7bba2a6f;p=njs.git Declared function objects are copied on demand to allow property manipulations and to have correct Function.prototype. --- diff --git a/njs/njs_builtin.c b/njs/njs_builtin.c index 37ca2c54..d50953ea 100644 --- a/njs/njs_builtin.c +++ b/njs/njs_builtin.c @@ -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]; diff --git a/njs/njs_disassembler.c b/njs/njs_disassembler.c index 78cc6527..ef4893b7 100644 --- a/njs/njs_disassembler.c +++ b/njs/njs_disassembler.c @@ -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 ") }, diff --git a/njs/njs_function.c b/njs/njs_function.c index 3112d33e..7586133a 100644 --- a/njs/njs_function.c +++ b/njs/njs_function.c @@ -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; diff --git a/njs/njs_function.h b/njs/njs_function.h index cca94b65..8f56921c 100644 --- a/njs/njs_function.h +++ b/njs/njs_function.h @@ -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); diff --git a/njs/njs_generator.c b/njs/njs_generator.c index 5d565d65..5d37adb1 100644 --- a/njs/njs_generator.c +++ b/njs/njs_generator.c @@ -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; } diff --git a/njs/njs_parser.c b/njs/njs_parser.c index 69c62e54..90241ab8 100644 --- a/njs/njs_parser.c +++ b/njs/njs_parser.c @@ -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; diff --git a/njs/njs_vm.c b/njs/njs_vm.c index 543328c9..dbfc0d3e 100644 --- a/njs/njs_vm.c +++ b/njs/njs_vm.c @@ -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; diff --git a/njs/njs_vm.h b/njs/njs_vm.h index 6bc9fbed..bae66278 100644 --- a/njs/njs_vm.h +++ b/njs/njs_vm.h @@ -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); diff --git a/njs/test/njs_unit_test.c b/njs/test/njs_unit_test.c index 5283e1db..adbc11f7 100644 --- a/njs/test/njs_unit_test.c +++ b/njs/test/njs_unit_test.c @@ -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") },