From: Dmitry Volyntsev Date: Thu, 29 Jan 2026 02:36:39 +0000 (-0800) Subject: Make Error.stack faster. X-Git-Tag: 0.9.6~25 X-Git-Url: http://www.kaiwu.me/postgresql/commit/static/gitweb.js?a=commitdiff_plain;h=869876c43b45e8c45d251630e4052e86daafbaf4;p=njs.git Make Error.stack faster. Previously, error.stack reported full names for native function (for example Array.prototype.map -> map), but it was achieved by iteration through a global object which is slow. The fix is to report only function name at hand, this loses a bit of verbosity but make it ~100 times faster. make benchmark before ... exception.stack: 38.421µs, 26027 times/s exception.native.stack: 226.711µs, 4410 times/s after ... exception.stack: 1.239µs, 807356 times/s exception.native.stack: 2.419µs, 413339 times/s --- diff --git a/src/njs_builtin.c b/src/njs_builtin.c index 1b486ba9..ff4352a7 100644 --- a/src/njs_builtin.c +++ b/src/njs_builtin.c @@ -10,19 +10,6 @@ #include -typedef struct { - enum { - NJS_BUILTIN_TRAVERSE_KEYS, - NJS_BUILTIN_TRAVERSE_MATCH, - } type; - - njs_function_t *func; - - njs_flathsh_t keys; - njs_str_t match; -} njs_builtin_traverse_t; - - typedef struct { njs_str_t name; int value; @@ -312,312 +299,6 @@ njs_builtin_objects_create(njs_vm_t *vm) } -static njs_int_t -njs_builtin_traverse(njs_vm_t *vm, njs_traverse_t *traverse, void *data) -{ - size_t len; - u_char *p, *start, *end; - njs_int_t ret, n; - njs_str_t name; - njs_bool_t symbol; - njs_value_t key, *value, prop_name; - njs_function_t *func, *target; - njs_object_prop_t *prop; - njs_flathsh_query_t fhq; - njs_builtin_traverse_t *ctx; - njs_traverse_t *path[NJS_TRAVERSE_MAX_DEPTH]; - u_char buf[256]; - - ctx = data; - - if (ctx->type == NJS_BUILTIN_TRAVERSE_MATCH) { - prop = traverse->prop; - func = ctx->func; - - if (njs_is_accessor_descriptor(prop)) { - target = njs_prop_getter(prop); - - } else { - value = njs_prop_value(prop); - target = (njs_is_function(value) && njs_function(value)->native) - ? njs_function(value) - : NULL; - } - - if (target == NULL - || !njs_native_function_same(target, func)) - { - return NJS_OK; - } - } - - if (traverse == NULL) { - njs_type_error(vm, "njs_builtin_traverse() traverse arg is NULL"); - return NJS_ERROR; - } - - n = 0; - - while (traverse != NULL) { - path[n++] = traverse; - traverse = traverse->parent; - } - - n--; - - p = buf; - end = buf + sizeof(buf); - - do { - symbol = 0; - - ret = njs_atom_to_value(vm, &key, path[n]->atom_id); - if (ret != NJS_OK) { - return NJS_ERROR; - } - - if (njs_slow_path(njs_is_symbol(&key))) { - symbol = 1; - key = *njs_symbol_description(&key); - if (njs_is_undefined(&key)) { - njs_set_empty_string(vm, &key); - } - } - - if (njs_slow_path(!njs_is_string(&key))) { - /* Skipping special properties (e.g. array index properties). */ - return NJS_OK; - } - - njs_string_get(vm, &key, &name); - - if (njs_slow_path((p + name.length + 3) > end)) { - njs_type_error(vm, "njs_builtin_traverse() key is too long"); - return NJS_ERROR; - } - - if (symbol) { - *p++ = '['; - - } else if (p != buf) { - *p++ = '.'; - } - - p = njs_cpymem(p, name.start, name.length); - - if (symbol) { - *p++ = ']'; - } - - } while (n-- > 0); - - if (ctx->type == NJS_BUILTIN_TRAVERSE_MATCH) { - len = ctx->match.length; - start = njs_mp_alloc(vm->mem_pool, len + (p - buf) + (len != 0)); - if (njs_slow_path(start == NULL)) { - njs_memory_error(vm); - return NJS_ERROR; - } - - if (len != 0) { - memcpy(start, ctx->match.start, len); - start[len++] = '.'; - } - - memcpy(start + len, buf, p - buf); - ctx->match.length = len + p - buf; - ctx->match.start = start; - - return NJS_DONE; - } - - /* NJS_BUILTIN_TRAVERSE_KEYS. */ - - ret = njs_atom_string_create(vm, &prop_name, buf, p - buf); - if (njs_slow_path(ret != NJS_OK)) { - return ret; - } - - fhq.key_hash = prop_name.atom_id; - fhq.replace = 1; - fhq.pool = vm->mem_pool; - fhq.proto = &njs_object_hash_proto; - - ret = njs_flathsh_unique_insert(&ctx->keys, &fhq); - if (njs_slow_path(ret != NJS_OK)) { - njs_internal_error(vm, "flathsh insert/replace failed"); - return NJS_ERROR; - } - - prop = fhq.value; - - prop->type = NJS_PROPERTY; - prop->enumerable = 0; - prop->configurable = 0; - prop->writable = 0; - prop->u.value = njs_value_null; - - return NJS_OK; -} - - -typedef struct { - njs_str_t name; - njs_function_native_t native; - uint8_t magic8; -} njs_function_name_t; - - -njs_int_t -njs_builtin_match_native_function(njs_vm_t *vm, njs_function_t *function, - njs_str_t *name) -{ - uint8_t magic8; - njs_int_t ret; - njs_arr_t **pprotos; - njs_mod_t *module; - njs_uint_t i, n; - njs_value_t value, tag; - njs_object_t object; - njs_object_prop_t *prop; - njs_flathsh_each_t lhe; - njs_exotic_slots_t *slots; - njs_function_name_t *fn; - njs_function_native_t native; - njs_builtin_traverse_t ctx; - - if (vm->functions_name_cache != NULL) { - n = vm->functions_name_cache->items; - fn = vm->functions_name_cache->start; - - magic8 = function->magic8; - native = function->u.native; - - while (n != 0) { - if (fn->native == native && fn->magic8 == magic8) { - *name = fn->name; - return NJS_OK; - } - - fn++; - n--; - } - } - - ctx.type = NJS_BUILTIN_TRAVERSE_MATCH; - ctx.func = function; - - /* Global object. */ - - ctx.match = njs_str_value(""); - - ret = njs_object_traverse(vm, njs_object(&vm->global_value), &ctx, - njs_builtin_traverse); - - if (ret == NJS_DONE) { - goto found; - } - - /* Constructor from built-in modules (not-mapped to global object). */ - - for (i = NJS_OBJ_TYPE_HIDDEN_MIN; i < NJS_OBJ_TYPE_HIDDEN_MAX; i++) { - njs_set_object(&value, &njs_vm_ctor(vm, i).object); - - ret = njs_value_property(vm, &value, NJS_ATOM_STRING_name, &tag); - if (ret == NJS_OK && njs_is_string(&tag)) { - njs_string_get(vm, &tag, &ctx.match); - } - - ret = njs_object_traverse(vm, njs_object(&value), &ctx, - njs_builtin_traverse); - - if (ret == NJS_DONE) { - goto found; - } - } - - /* Modules. */ - - njs_flathsh_each_init(&lhe, &njs_modules_hash_proto); - - for ( ;; ) { - prop = (njs_object_prop_t *) njs_flathsh_each(&vm->modules_hash, &lhe); - if (prop == NULL) { - break; - } - - module = prop->u.mod; - - if (njs_is_object(&module->value) - && !njs_object(&module->value)->shared) - { - ctx.match = module->name; - - ret = njs_object_traverse(vm, njs_object(&module->value), &ctx, - njs_builtin_traverse); - - if (ret == NJS_DONE) { - goto found; - } - } - } - - /* External prototypes (not mapped to global object). */ - - ctx.match = njs_str_value(""); - - for (i = 0; i< vm->protos->items; i++) { - njs_memzero(&object, sizeof(njs_object_t)); - - pprotos = njs_arr_item(vm->protos, i); - slots = (*pprotos)->start; - - object.shared_hash = slots->external_shared_hash; - object.slots = slots; - - njs_set_object(&value, &object); - - ret = njs_object_string_tag(vm, &value, &tag); - if (ret == NJS_OK && njs_is_string(&tag)) { - njs_string_get(vm, &tag, &ctx.match); - } - - ret = njs_object_traverse(vm, njs_object(&value), &ctx, - njs_builtin_traverse); - - if (ret == NJS_DONE) { - goto found; - } - } - - return NJS_DECLINED; - -found: - - if (vm->functions_name_cache == NULL) { - vm->functions_name_cache = njs_arr_create(vm->mem_pool, 4, - sizeof(njs_function_name_t)); - if (njs_slow_path(vm->functions_name_cache == NULL)) { - return NJS_ERROR; - } - } - - fn = njs_arr_add(vm->functions_name_cache); - if (njs_slow_path(fn == NULL)) { - njs_memory_error(vm); - return NJS_ERROR; - } - - fn->name = ctx.match; - fn->native = function->u.native; - fn->magic8 = function->magic8; - - *name = fn->name; - - return NJS_OK; -} - - static njs_int_t njs_ext_dump(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused, njs_value_t *retval) diff --git a/src/njs_error.c b/src/njs_error.c index e8de835f..13ed32aa 100644 --- a/src/njs_error.c +++ b/src/njs_error.c @@ -70,15 +70,17 @@ njs_error_fmt_new(njs_vm_t *vm, njs_value_t *dst, njs_object_type_t type, void njs_error_stack_attach(njs_vm_t *vm, njs_value_t value) { - size_t count; - uint32_t line, prev_line; - njs_int_t ret; - njs_str_t name, file, prev_name; - njs_chb_t chain; - njs_value_t *stackval; - njs_vm_code_t *code; - njs_function_t *function; - njs_native_frame_t *frame; + size_t count; + uint32_t line, prev_line; + njs_int_t ret; + njs_str_t name, file, prev_name; + njs_chb_t chain; + njs_value_t *stackval, retval, fobj; + njs_vm_code_t *code; + njs_function_t *function; + njs_object_prop_t *prop; + njs_native_frame_t *frame; + njs_flathsh_query_t fhq; if (njs_slow_path(!vm->options.backtrace || !njs_is_error(&value)) @@ -128,9 +130,40 @@ njs_error_stack_attach(njs_vm_t *vm, njs_value_t value) } } else { - ret = njs_builtin_match_native_function(vm, function, &name); - if (ret != NJS_OK) { - name = njs_entry_unknown; + name.length = 0; + fhq.key_hash = NJS_ATOM_STRING_name; + + ret = njs_flathsh_unique_find(&function->object.hash, &fhq); + if (ret == NJS_OK) { + prop = fhq.value; + + if (njs_is_string(njs_prop_value(prop))) { + njs_string_get(vm, njs_prop_value(prop), &name); + } + } + + if (name.length == 0) { + njs_set_function(&fobj, function); + + ret = njs_value_property(vm, &fobj, NJS_ATOM_STRING_name, + &retval); + if (ret != NJS_OK) { + continue; + } + + if (njs_is_string(&retval)) { + njs_string_get(vm, &retval, &name); + + if (name.length == 0) { + continue; + } + + } else if (njs_is_symbol(&retval)) { + name = njs_str_value(""); + + } else { + name = njs_entry_unknown; + } } } diff --git a/src/njs_function.c b/src/njs_function.c index 9ed4bb42..b6a8a467 100644 --- a/src/njs_function.c +++ b/src/njs_function.c @@ -617,12 +617,16 @@ njs_function_native_call(njs_vm_t *vm, njs_value_t *retval) #ifdef NJS_DEBUG_OPCODE njs_str_t name; + njs_value_t fname, fobj; if (vm->options.opcode_debug) { + name = njs_str_value("unmapped"); - ret = njs_builtin_match_native_function(vm, function, &name); - if (ret != NJS_OK) { - name = njs_str_value("unmapped"); + njs_set_function(&fobj, function); + + ret = njs_value_property(vm, &fobj, NJS_ATOM_STRING_name, &fname); + if (ret == NJS_OK && njs_is_string(&fname)) { + njs_string_get(vm, &fname, &name); } njs_printf("CALL NATIVE %V %P\n", &name, function->u.native); diff --git a/src/njs_vm.h b/src/njs_vm.h index 53c14c9e..4329c926 100644 --- a/src/njs_vm.h +++ b/src/njs_vm.h @@ -177,7 +177,6 @@ struct njs_vm_s { njs_value_t global_value; njs_arr_t *codes; /* of njs_vm_code_t */ - njs_arr_t *functions_name_cache; njs_trace_t trace; njs_random_t random; @@ -247,8 +246,6 @@ njs_value_t njs_vm_exception(njs_vm_t *vm); void njs_vm_scopes_restore(njs_vm_t *vm, njs_native_frame_t *frame); njs_int_t njs_builtin_objects_create(njs_vm_t *vm); -njs_int_t njs_builtin_match_native_function(njs_vm_t *vm, - njs_function_t *function, njs_str_t *name); void njs_disassemble(u_char *start, u_char *end, njs_int_t count, njs_arr_t *lines); diff --git a/src/test/njs_benchmark.c b/src/test/njs_benchmark.c index 7f1a7400..e83e239c 100644 --- a/src/test/njs_benchmark.c +++ b/src/test/njs_benchmark.c @@ -477,7 +477,17 @@ static njs_benchmark_test_t njs_test[] = { "exception.stack", njs_str("function f() { try { throw new Error('test') } catch (e) { return e.stack } } [f].map(v=>v())[0]"), - njs_str("Error: test\n at f (:1)\n at anonymous (:1)\n at Array.prototype.map (native)\n at main (:1)\n"), + njs_str("Error: test\n at f (:1)\n at anonymous (:1)\n at map (native)\n at main (:1)\n"), + 100 }, + + { "exception.native.stack", + njs_str("function f() { try { 'str'.replace(/t/g,function(m) {return m.a.a}) } catch (e) { return e.stack } }; f()"), + njs_str("TypeError: cannot get property \"a\" of undefined\n" + " at anonymous (:1)\n" + " at [Symbol.replace] (native)\n" + " at replace (native)\n" + " at f (:1)\n" + " at main (:1)\n"), 100 }, }; diff --git a/src/test/njs_unit_test.c b/src/test/njs_unit_test.c index 9de4eb41..84a8709a 100644 --- a/src/test/njs_unit_test.c +++ b/src/test/njs_unit_test.c @@ -21138,12 +21138,12 @@ static njs_unit_test_t njs_shared_test[] = { njs_str("var fs = require('fs'); fs.readFileSync()"), njs_str("TypeError: \"path\" must be a string or Buffer\n" - " at fs.readFileSync (native)\n" + " at readFileSync (native)\n" " at main (:1)\n") }, { njs_str("import fs from 'fs'; fs.readFileSync()"), njs_str("TypeError: \"path\" must be a string or Buffer\n" - " at fs.readFileSync (native)\n" + " at readFileSync (native)\n" " at main (:1)\n") }, { njs_str("var f = new Function('return 1;'); f();"), @@ -21193,8 +21193,8 @@ static njs_unit_test_t njs_shared_test[] = { njs_str("Array.prototype.push.call(preload.a, 'waka')"), njs_str("TypeError: Cannot add property \"2\", object is not extensible\n" - " at Array.prototype.push (native)\n" - " at Function.prototype.call (native)\n" + " at push (native)\n" + " at call (native)\n" " at main (:1)\n") }, }; @@ -21544,42 +21544,42 @@ static njs_unit_test_t njs_backtraces_test[] = " function(m) {return m.a.a})"), njs_str("TypeError: cannot get property \"a\" of undefined\n" " at anonymous (:1)\n" - " at RegExp.prototype[Symbol.replace] (native)\n" - " at String.prototype.replace (native)\n" + " at [Symbol.replace] (native)\n" + " at replace (native)\n" " at main (:1)\n") }, { njs_str("function f(o) {return Object.keys(o)};" "f()"), njs_str("TypeError: cannot convert undefined argument to object\n" - " at Object.keys (native)\n" + " at keys (native)\n" " at f (:1)\n" " at main (:1)\n") }, { njs_str("[].concat({}.a.a)"), njs_str("TypeError: cannot get property \"a\" of undefined\n" - " at Array.prototype.concat (native)\n" + " at concat (native)\n" " at main (:1)\n") }, { njs_str("''.repeat(-1)"), njs_str("RangeError: invalid count value\n" - " at String.prototype.repeat (native)\n" + " at repeat (native)\n" " at main (:1)\n") }, { njs_str("Math.log({}.a.a)"), njs_str("TypeError: cannot get property \"a\" of undefined\n" - " at Math.log (native)\n" + " at log (native)\n" " at main (:1)\n") }, { njs_str("var bound = Math.max.bind(null, {toString(){return {}}}); bound(1)"), njs_str("TypeError: Cannot convert object to primitive value\n" - " at Math.max (native)\n" + " at max (native)\n" " at main (:1)\n") }, { njs_str("var ab = new ArrayBuffer(1);" "$262.detachArrayBuffer(ab);" "ab.slice(0)"), njs_str("TypeError: detached buffer\n" - " at ArrayBuffer.prototype.slice (native)\n" + " at slice (native)\n" " at main (:1)\n") }, { njs_str("Object.prototype()"), @@ -21593,7 +21593,7 @@ static njs_unit_test_t njs_backtraces_test[] = { njs_str("$shared.method({}.a.a)"), njs_str("TypeError: cannot get property \"a\" of undefined\n" - " at $shared.method (native)\n" + " at method (native)\n" " at main (:1)\n") }, { njs_str("new Function(\n\n@)"), @@ -21606,24 +21606,24 @@ static njs_unit_test_t njs_backtraces_test[] = { njs_str("require('crypto').createHash('sha')"), njs_str("TypeError: not supported algorithm: \"sha\"\n" - " at crypto.createHash (native)\n" + " at createHash (native)\n" " at main (:1)\n") }, { njs_str("var h = require('crypto').createHash('sha1');" "h.update([])"), njs_str("TypeError: data is not a string or Buffer-like object\n" - " at Hash.update (native)\n" + " at update (native)\n" " at main (:1)\n") }, { njs_str("require('crypto').createHmac('sha1', [])"), njs_str("TypeError: key is not a string or Buffer-like object\n" - " at crypto.createHmac (native)\n" + " at createHmac (native)\n" " at main (:1)\n") }, { njs_str("var h = require('crypto').createHmac('sha1', 'secret');" "h.update([])"), njs_str("TypeError: data is not a string or Buffer-like object\n" - " at Hmac.update (native)\n" + " at update (native)\n" " at main (:1)\n") }, { njs_str("function f(o) {function f_in(o) {return o.a.a};" @@ -21659,7 +21659,7 @@ static njs_unit_test_t njs_backtraces_test[] = " 'realpath'," " 'realpathSync'," "]" - ".every(v=>{ try {fs[v]();} catch (e) { return e.stack.search(`fs.${v} `) >= 0}})"), + ".every(v=>{ try {fs[v]();} catch (e) { return e.stack.search(`at ${v} `) >= 0}})"), njs_str("true") }, { njs_str("parseInt({ toString: function() { return [1] } })"), @@ -21673,7 +21673,7 @@ static njs_unit_test_t njs_backtraces_test[] = { njs_str("Object.defineProperty(Function.__proto__, 'name', {get() { typeof 1;}});" "(new Uint8Array()).every()"), njs_str("TypeError: callback argument is not callable\n" - " at TypedArray.prototype.every (native)\n" + " at every (native)\n" " at main (:1)\n") }, { njs_str("var e = new Error('oops'); e.stack = 123; e.stack"), @@ -21722,7 +21722,7 @@ static njs_unit_test_t njs_backtraces_test[] = { njs_str("Math\n.min(1,\na)"), njs_str("ReferenceError: \"a\" is not defined\n" - " at Math.min (native)\n" + " at min (native)\n" " at main (:3)\n") }, }; diff --git a/test/shell_test_njs.exp b/test/shell_test_njs.exp index 9c15b81a..6dea0442 100644 --- a/test/shell_test_njs.exp +++ b/test/shell_test_njs.exp @@ -67,7 +67,7 @@ njs_test { # Backtraces for external objects njs_test { {"console.info(console.a.a)\r\n" - "console.info(console.a.a)\r\nThrown:\r\nTypeError:*at console.info (native)"} + "console.info(console.a.a)\r\nThrown:\r\nTypeError:*at info (native)"} } # dumper