diff --git a/build.cm b/build.cm index 16f5d65b..74d642bd 100644 --- a/build.cm +++ b/build.cm @@ -121,11 +121,11 @@ Build.compile_file = function(pkg, file, target, buildtype = 'release') { // Add buildtype-specific flags if (buildtype == 'release') { - push(cmd_parts, '-O3', '-DNDEBUG') + cmd_parts = array(cmd_parts, ['-O3', '-DNDEBUG']) } else if (buildtype == 'debug') { - push(cmd_parts, '-O2', '-g') + cmd_parts = array(cmd_parts, ['-O2', '-g']) } else if (buildtype == 'minsize') { - push(cmd_parts, '-Os', '-DNDEBUG') + cmd_parts = array(cmd_parts, ['-Os', '-DNDEBUG']) } push(cmd_parts, '-DCELL_USE_NAME=' + sym_name) @@ -278,23 +278,20 @@ Build.build_dynamic = function(pkg, target = Build.detect_host_target(), buildty // Platform-specific flags for undefined symbols (resolved at dlopen) and size optimization if (tc.system == 'darwin') { - // Allow undefined symbols - they will be resolved when dlopen'd into the main executable - push(cmd_parts, '-undefined', 'dynamic_lookup') - // Dead-strip unused code - push(cmd_parts, '-Wl,-dead_strip') - // Set install_name to stable path so runtime finds it correctly - push(cmd_parts, '-Wl,-install_name,' + stable_path) - // rpath for .cell/local libraries - push(cmd_parts, '-Wl,-rpath,@loader_path/../local') - push(cmd_parts, '-Wl,-rpath,' + local_dir) + cmd_parts = array(cmd_parts, [ + '-undefined', 'dynamic_lookup', + '-Wl,-dead_strip', + '-Wl,-install_name,' + stable_path, + '-Wl,-rpath,@loader_path/../local', + '-Wl,-rpath,' + local_dir + ]) } else if (tc.system == 'linux') { - // Allow undefined symbols at link time - push(cmd_parts, '-Wl,--allow-shlib-undefined') - // Garbage collect unused sections - push(cmd_parts, '-Wl,--gc-sections') - // rpath for .cell/local libraries - push(cmd_parts, '-Wl,-rpath,$ORIGIN/../local') - push(cmd_parts, '-Wl,-rpath,' + local_dir) + cmd_parts = array(cmd_parts, [ + '-Wl,--allow-shlib-undefined', + '-Wl,--gc-sections', + '-Wl,-rpath,$ORIGIN/../local', + '-Wl,-rpath,' + local_dir + ]) } else if (tc.system == 'windows') { // Windows DLLs: use --allow-shlib-undefined for mingw push(cmd_parts, '-Wl,--allow-shlib-undefined') @@ -308,17 +305,11 @@ Build.build_dynamic = function(pkg, target = Build.detect_host_target(), buildty }) // Do NOT link against core library - symbols resolved at dlopen time + cmd_parts = array(cmd_parts, resolved_ldflags) + cmd_parts = array(cmd_parts, target_ldflags) - // Add LDFLAGS - arrfor(resolved_ldflags, function(flag) { - push(cmd_parts, flag) - }) - - arrfor(target_ldflags, function(flag) { - push(cmd_parts, flag) - }) - - push(cmd_parts, '-o', '"' + store_path + '"') + push(cmd_parts, '-o') + push(cmd_parts, '"' + store_path + '"') var cmd_str = text(cmd_parts, ' ') diff --git a/crypto.c b/crypto.c index 5dca343c..0c79b2a0 100644 --- a/crypto.c +++ b/crypto.c @@ -231,7 +231,7 @@ JSValue js_crypto_unlock(JSContext *js, JSValue self, int argc, JSValue *argv) { static const JSCFunctionListEntry js_crypto_funcs[] = { JS_CFUNC_DEF("shared", 2, js_crypto_shared), - JS_CFUNC_DEF("blake2", 1, js_crypto_blake2), + JS_CFUNC_DEF("blake2", 2, js_crypto_blake2), JS_CFUNC_DEF("sign", 2, js_crypto_sign), JS_CFUNC_DEF("verify", 3, js_crypto_verify), JS_CFUNC_DEF("lock", 3, js_crypto_lock), diff --git a/internal/engine.cm b/internal/engine.cm index 5620f4ba..5d02be07 100644 --- a/internal/engine.cm +++ b/internal/engine.cm @@ -88,7 +88,7 @@ function use_core(path) { var script = text(script_blob) var mod = `(function setup_module(use){${script}})` var fn = js.eval('core:' + path, mod) - var result = call(fn,sym, use_core) + var result = call(fn,sym, [use_core]) use_cache[cache_key] = result; return result; } @@ -185,7 +185,7 @@ function disrupt(err) if (underlings) { var unders = array(underlings) - arrfor(unders, function(id) { + arrfor(unders, function(id, index) { log.console(`calling on ${id} to disrupt too`) $_.stop(create_actor({id})) }) @@ -416,14 +416,14 @@ function handle_host(e) { peers[`${e.peer.address}:${e.peer.port}`] = e.peer var queue = peer_queue.get(e.peer) if (queue) { - arrfor(queue, msg => e.peer.send(nota.encode(msg))) + arrfor(queue, (msg, index) => e.peer.send(nota.encode(msg))) log.system(`sent ${msg} out of queue`) peer_queue.delete(e.peer) } break case "disconnect": peer_queue.delete(e.peer) - arrfor(array(peers), function(id) { + arrfor(array(peers), function(id, index) { if (peers[id] == e.peer) delete peers[id] }) log.system('portal got disconnect from ' + e.peer.address + ":" + e.peer.port) @@ -440,7 +440,7 @@ function handle_host(e) { obj[ACTORDATA].address = e.peer.address obj[ACTORDATA].port = e.peer.port } - arrfor(array(obj), function(key) { + arrfor(array(obj), function(key, index) { if (key in obj) populate_actor_addresses(obj[key]) }) @@ -496,11 +496,6 @@ $_.unneeded = function unneeded(fn, seconds) { // schedules the invocation of a function after a specified amount of time. $_.delay = function delay(fn, seconds = 0) { - if (seconds <= 0) { - $_.clock(fn) - return - } - function delay_turn() { fn() send_messages() @@ -592,7 +587,7 @@ var need_stop = false return } - arrfor(message_queue, function(msg) { + arrfor(message_queue, function(msg, index) { if (msg.startup) { // now is the time to actually spin up the actor actor_mod.createactor(msg.startup) @@ -817,7 +812,7 @@ $_.clock(_ => { // Call with signature: setup_module(args, use, env) // The script wrapper binds $delay, $start, etc. from env - var val = call(locator.symbol, null, _cell.args.arg, use_fn, env) + var val = call(locator.symbol, null, [_cell.args.arg, use_fn, env]) if (val) throw Error('Program must not return anything'); diff --git a/internal/shop.cm b/internal/shop.cm index 2ccd983a..60926b2f 100644 --- a/internal/shop.cm +++ b/internal/shop.cm @@ -561,7 +561,7 @@ Shop.open_package_dylib = function(pkg) { var content = text(fd.slurp(toml_path)) var cfg = toml.decode(content) if (cfg.dependencies) { - arrfor(array(cfg.dependencies), function(alias) { + arrfor(array(cfg.dependencies), function(alias, i) { var dep_pkg = cfg.dependencies[alias] try { Shop.open_package_dylib(dep_pkg) @@ -812,7 +812,7 @@ function execute_module(info) // Call with signature: setup_module(args, use, env) // args is null for module loading - used = call(mod_resolve.symbol, context, null, use_fn, env) + used = call(mod_resolve.symbol, context, [null, use_fn, env]) } else if (c_resolve.scope < 900) { // C only used = call_c_module(c_resolve) @@ -1195,11 +1195,11 @@ Shop.module_reload = function(path, package) { var old = use_cache[cache_key] var newmod = get_module(path, package) - arrfor(array(newmod), function(i) { + arrfor(array(newmod), function(i, idx) { old[i] = newmod[i] }) - arrfor(array(old), function(i) { + arrfor(array(old), function(i, idx) { if (!(i in newmod)) old[i] = null }) @@ -1226,7 +1226,7 @@ Shop.build_package_scripts = function(package) var scripts = get_package_scripts(package) var pkg_dir = get_package_abs_dir(package) - arrfor(scripts, function(script) { + arrfor(scripts, function(script, i) { resolve_mod_fn(pkg_dir + '/' + script, package) }) } @@ -1284,7 +1284,7 @@ Shop.audit_packages = function() { var bad = [] - arrfor(packages, function(package) { + arrfor(packages, function(package, i) { if (package == 'core') return if (fd.is_dir(package)) return if (fetch_remote_hash(package)) return diff --git a/source/quickjs.c b/source/quickjs.c index 107fdcc0..e9afbd25 100644 --- a/source/quickjs.c +++ b/source/quickjs.c @@ -686,7 +686,7 @@ typedef enum { typedef struct JSFunction { JSGCObjectHeader header; /* must come first */ JSAtom name; - uint8_t length; + uint16_t length; /* arity: max allowed arguments */ uint8_t kind; uint8_t free_mark : 1; union { @@ -4986,7 +4986,7 @@ JSValue JS_NewCFunction2(JSContext *ctx, JSCFunction *func, typedef struct JSCFunctionDataRecord { JSCFunctionData *func; - uint8_t length; + uint16_t length; /* arity: max allowed arguments */ uint8_t data_len; uint16_t magic; JSValue data[0]; @@ -10133,7 +10133,7 @@ static JSValue js_closure(JSContext *ctx, JSValue bfunc, if (name_atom == JS_ATOM_NULL) name_atom = JS_ATOM_empty_string; f->name = JS_DupAtom(ctx, name_atom); - f->length = b->defined_arg_count; + f->length = b->arg_count; /* arity = total parameter count */ return func_obj; fail: /* bfunc is freed when func_obj is freed */ @@ -10263,6 +10263,19 @@ static JSValue js_call_c_function(JSContext *ctx, JSValueConst func_obj, ret_val = JS_NewFloat64(ctx, func.f_f_f(d1, d2)); } break; + /* Fixed-arity fast paths - direct call without argc/argv marshaling */ + case JS_CFUNC_0: + ret_val = func.f0(ctx, this_obj); + break; + case JS_CFUNC_1: + ret_val = func.f1(ctx, this_obj, arg_buf[0]); + break; + case JS_CFUNC_2: + ret_val = func.f2(ctx, this_obj, arg_buf[0], arg_buf[1]); + break; + case JS_CFUNC_3: + ret_val = func.f3(ctx, this_obj, arg_buf[0], arg_buf[1], arg_buf[2]); + break; default: abort(); } @@ -10436,10 +10449,15 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, return JS_ThrowTypeError(caller_ctx, "not a function"); } f = JS_VALUE_GET_FUNCTION(func_obj); + /* Strict arity enforcement: too many arguments throws */ + if (unlikely(argc > f->length)) { + char buf[ATOM_GET_STR_BUF_SIZE]; + return JS_ThrowTypeError(caller_ctx, "too many arguments for %s: expected %d, got %d", JS_AtomGetStr(caller_ctx,buf, ATOM_GET_STR_BUF_SIZE, f->name), f->length, argc); + } switch (f->kind) { case JS_FUNC_KIND_C: return js_call_c_function(caller_ctx, func_obj, this_obj, argc, - (JSValueConst *)argv); + (JSValueConst *)argv); case JS_FUNC_KIND_BOUND: return js_call_bound_function(caller_ctx, func_obj, this_obj, argc, (JSValueConst *)argv); @@ -10985,6 +11003,16 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, scope_idx = get_u16(pc) + ARG_SCOPE_END; pc += 2; sf->cur_pc = pc; + /* Fast path: check arity before building arg list */ + if (JS_VALUE_GET_TAG(sp[-2]) == JS_TAG_FUNCTION && + JS_VALUE_GET_TAG(sp[-1]) == JS_TAG_ARRAY) { + JSFunction *callee = JS_VALUE_GET_FUNCTION(sp[-2]); + JSArray *arr = JS_VALUE_GET_ARRAY(sp[-1]); + if (unlikely(arr->len > callee->length)) { + JS_ThrowTypeError(ctx, "too many arguments"); + goto exception; + } + } tab = build_arg_list(ctx, &len, sp[-1]); if (!tab) goto exception; @@ -22070,7 +22098,6 @@ static __exception int js_parse_function_decl2(JSParseState *s, JSFunctionDef *fd = s->cur_func; BOOL is_expr; int func_idx, lexical_func_idx = -1; - BOOL has_opt_arg; BOOL create_func_var = FALSE; is_expr = (func_type != JS_PARSE_FUNC_STATEMENT && @@ -22154,7 +22181,6 @@ static __exception int js_parse_function_decl2(JSParseState *s, /* parse arguments */ fd->has_simple_parameter_list = TRUE; fd->has_parameter_expressions = FALSE; - has_opt_arg = FALSE; if (func_type == JS_PARSE_FUNC_ARROW && s->token.val == TOK_IDENT) { JSAtom name; if (s->token.u.ident.is_reserved) { @@ -22164,7 +22190,6 @@ static __exception int js_parse_function_decl2(JSParseState *s, name = s->token.u.ident.atom; if (add_arg(ctx, fd, name) < 0) goto fail; - fd->defined_arg_count = 1; } else if (func_type != JS_PARSE_FUNC_CLASS_STATIC_INIT) { if (s->token.val == '(') { int skip_bits; @@ -22199,10 +22224,6 @@ static __exception int js_parse_function_decl2(JSParseState *s, has_initializer = js_parse_destructuring_element(s, TOK_VAR, 1, TRUE, TRUE, FALSE); if (has_initializer < 0) goto fail; - if (has_initializer) - has_opt_arg = TRUE; - if (!has_opt_arg) - fd->defined_arg_count++; } else if (s->token.val == TOK_IDENT) { if (s->token.u.ident.is_reserved) { js_parse_error_reserved_identifier(s); @@ -22224,7 +22245,6 @@ static __exception int js_parse_function_decl2(JSParseState *s, int label; fd->has_simple_parameter_list = FALSE; - has_opt_arg = TRUE; if (next_token(s)) goto fail; @@ -22248,9 +22268,6 @@ static __exception int js_parse_function_decl2(JSParseState *s, emit_atom(s, name); emit_u16(s, fd->scope_level); } else { - if (!has_opt_arg) { - fd->defined_arg_count++; - } if (fd->has_parameter_expressions) { /* copy the argument to the argument scope */ emit_op(s, OP_get_arg); @@ -22276,6 +22293,8 @@ static __exception int js_parse_function_decl2(JSParseState *s, goto fail; } } + /* Explicit arity: defined_arg_count == arg_count always */ + fd->defined_arg_count = fd->arg_count; if (fd->has_parameter_expressions) { int idx; @@ -24518,14 +24537,22 @@ static JSValue js_function_apply(JSContext *ctx, JSValueConst this_val, JSValueConst this_arg, array_arg; uint32_t len; JSValue *tab, ret; + JSFunction *f; if (check_function(ctx, this_val)) return JS_EXCEPTION; + f = JS_VALUE_GET_FUNCTION(this_val); this_arg = argv[0]; array_arg = argv[1]; if (JS_VALUE_GET_TAG(array_arg) == JS_TAG_NULL && magic != 2) { return JS_Call(ctx, this_val, this_arg, 0, NULL); } + /* Fast path: check arity before building arg list */ + if (JS_VALUE_GET_TAG(array_arg) == JS_TAG_ARRAY) { + JSArray *arr = JS_VALUE_GET_ARRAY(array_arg); + if (unlikely(arr->len > f->length)) + return JS_ThrowTypeError(ctx, "too many arguments"); + } tab = build_arg_list(ctx, &len, array_arg); if (!tab) return JS_EXCEPTION; @@ -27872,6 +27899,8 @@ static JSValue js_cell_array(JSContext *ctx, JSValueConst this_val, if (JS_IsFunction(ctx, argv[1])) { /* Map */ JSValueConst func = argv[1]; + int arity = (JSFunction*)JS_VALUE_GET_FUNCTION(argv[1])->length; + int reverse = argc > 2 && JS_ToBool(ctx, argv[2]); JSValue exit_val = argc > 3 ? argv[3] : JS_NULL; @@ -27886,10 +27915,15 @@ static JSValue js_cell_array(JSContext *ctx, JSValueConst this_val, JS_FreeValue(ctx, result); return JS_EXCEPTION; } - JSValue args[2] = { item, JS_NewInt64(ctx, i) }; - JSValue val = JS_Call(ctx, func, JS_NULL, 2, args); - JS_FreeValue(ctx, args[0]); - JS_FreeValue(ctx, args[1]); + JSValue val; + if (arity >= 2) { + JSValue args[2] = { item, JS_NewInt64(ctx, i) }; + val = JS_Call(ctx, func, JS_NULL, 2, args); + JS_FreeValue(ctx, args[1]); + } else { + val = JS_Call(ctx, func, JS_NULL, 1, &item); + } + JS_FreeValue(ctx, item); if (JS_IsException(val)) { JS_FreeValue(ctx, result); return JS_EXCEPTION; @@ -27908,10 +27942,15 @@ static JSValue js_cell_array(JSContext *ctx, JSValueConst this_val, JS_FreeValue(ctx, result); return JS_EXCEPTION; } - JSValue args[2] = { item, JS_NewInt64(ctx, i) }; - JSValue val = JS_Call(ctx, func, JS_NULL, 2, args); - JS_FreeValue(ctx, args[0]); - JS_FreeValue(ctx, args[1]); + JSValue val; + if (arity >= 2) { + JSValue args[2] = { item, JS_NewInt64(ctx, i) }; + val = JS_Call(ctx, func, JS_NULL, 2, args); + JS_FreeValue(ctx, args[1]); + } else { + val = JS_Call(ctx, func, JS_NULL, 1, &item); + } + JS_FreeValue(ctx, item); if (JS_IsException(val)) { JS_FreeValue(ctx, result); return JS_EXCEPTION; @@ -28360,14 +28399,22 @@ static JSValue js_cell_array_for(JSContext *ctx, JSValueConst this_val, int reverse = argc > 2 && JS_ToBool(ctx, argv[2]); JSValue exit_val = argc > 3 ? argv[3] : JS_NULL; + /* Determine function arity */ + int arity = (JSFunction*)JS_VALUE_GET_FUNCTION(argv[1])->length; + if (reverse) { for (int64_t i = len - 1; i >= 0; i--) { JSValue item = JS_GetPropertyInt64(ctx, arr, i); if (JS_IsException(item)) return JS_EXCEPTION; - JSValue args[2] = { item, JS_NewInt64(ctx, i) }; - JSValue result = JS_Call(ctx, func, JS_NULL, 2, args); + JSValue result; + if (arity == 1) { + result = JS_Call(ctx, func, JS_NULL, 1, &item); + } else { + JSValue args[2] = { item, JS_NewInt64(ctx, i) }; + result = JS_Call(ctx, func, JS_NULL, 2, args); + JS_FreeValue(ctx, args[1]); + } JS_FreeValue(ctx, item); - JS_FreeValue(ctx, args[1]); if (JS_IsException(result)) return JS_EXCEPTION; if (!JS_IsNull(exit_val) && js_strict_eq(ctx, result, exit_val)) { return result; @@ -28378,10 +28425,15 @@ static JSValue js_cell_array_for(JSContext *ctx, JSValueConst this_val, for (int64_t i = 0; i < len; i++) { JSValue item = JS_GetPropertyInt64(ctx, arr, i); if (JS_IsException(item)) return JS_EXCEPTION; - JSValue args[2] = { item, JS_NewInt64(ctx, i) }; - JSValue result = JS_Call(ctx, func, JS_NULL, 2, args); + JSValue result; + if (arity == 1) { + result = JS_Call(ctx, func, JS_NULL, 1, &item); + } else { + JSValue args[2] = { item, JS_NewInt64(ctx, i) }; + result = JS_Call(ctx, func, JS_NULL, 2, args); + JS_FreeValue(ctx, args[1]); + } JS_FreeValue(ctx, item); - JS_FreeValue(ctx, args[1]); if (JS_IsException(result)) return JS_EXCEPTION; if (!JS_IsNull(exit_val) && js_strict_eq(ctx, result, exit_val)) { return result; @@ -28443,14 +28495,21 @@ static JSValue js_cell_array_find(JSContext *ctx, JSValueConst this_val, /* Use function predicate */ JSValueConst func = argv[1]; + int arity = (JSFunction*)JS_VALUE_GET_FUNCTION(argv[1])->length; if (reverse) { for (int64_t i = from; i >= 0; i--) { JSValue item = JS_GetPropertyInt64(ctx, arr, i); if (JS_IsException(item)) return JS_EXCEPTION; - JSValue args[2] = { item, JS_NewInt64(ctx, i) }; - JSValue result = JS_Call(ctx, func, JS_NULL, 2, args); + JSValue result; + if (arity == 2) { + JSValue args[2] = { item, JS_NewInt64(ctx, i) }; + result = JS_Call(ctx, func, JS_NULL, 2, args); + JS_FreeValue(ctx, args[1]); + } else { + result = JS_Call(ctx, func, JS_NULL, 1, &item); + } JS_FreeValue(ctx, item); - JS_FreeValue(ctx, args[1]); + if (JS_IsException(result)) return JS_EXCEPTION; if (JS_ToBool(ctx, result)) { JS_FreeValue(ctx, result); @@ -28462,10 +28521,16 @@ static JSValue js_cell_array_find(JSContext *ctx, JSValueConst this_val, for (int64_t i = from; i < len; i++) { JSValue item = JS_GetPropertyInt64(ctx, arr, i); if (JS_IsException(item)) return JS_EXCEPTION; - JSValue args[2] = { item, JS_NewInt64(ctx, i) }; - JSValue result = JS_Call(ctx, func, JS_NULL, 2, args); + JSValue result; + if (arity == 2) { + JSValue args[2] = { item, JS_NewInt64(ctx, i) }; + result = JS_Call(ctx, func, JS_NULL, 2, args); + JS_FreeValue(ctx, args[1]); + } else { + result = JS_Call(ctx, func, JS_NULL, 1, &item); + } JS_FreeValue(ctx, item); - JS_FreeValue(ctx, args[1]); + if (JS_IsException(result)) return JS_EXCEPTION; if (JS_ToBool(ctx, result)) { JS_FreeValue(ctx, result); @@ -28495,6 +28560,8 @@ static JSValue js_cell_array_filter(JSContext *ctx, JSValueConst this_val, JSValue result = JS_NewArray(ctx); if (JS_IsException(result)) return result; + int arity = ((JSFunction*)JS_VALUE_GET_FUNCTION(func))->length; + int64_t out_idx = 0; for (int64_t i = 0; i < len; i++) { JSValue item = JS_GetPropertyInt64(ctx, arr, i); @@ -28502,9 +28569,16 @@ static JSValue js_cell_array_filter(JSContext *ctx, JSValueConst this_val, JS_FreeValue(ctx, result); return JS_EXCEPTION; } - JSValue args[2] = { item, JS_NewInt64(ctx, i) }; - JSValue val = JS_Call(ctx, func, JS_NULL, 2, args); - JS_FreeValue(ctx, args[1]); + JSValue val; + if (arity == 0) { + val = JS_Call(ctx, func, JS_NULL, 0, NULL); + } else if (arity == 1) { + val = JS_Call(ctx, func, JS_NULL, 1, &item); + } else { + JSValue args[2] = { item, JS_NewInt64(ctx, i) }; + val = JS_Call(ctx, func, JS_NULL, 2, args); + JS_FreeValue(ctx, args[1]); + } if (JS_IsException(val)) { JS_FreeValue(ctx, item); JS_FreeValue(ctx, result); @@ -28848,7 +28922,7 @@ static JSValue js_cell_object(JSContext *ctx, JSValueConst this_val, * fn function and sub-functions * ---------------------------------------------------------------------------- */ -/* fn.apply(func, args) */ +/* fn.apply(func, args) - arity is enforced in JS_CallInternal */ static JSValue js_cell_fn_apply(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { @@ -28871,19 +28945,6 @@ static JSValue js_cell_fn_apply(JSContext *ctx, JSValueConst this_val, if (js_get_length64(ctx, &len, args_val)) return JS_EXCEPTION; - /* Check arity */ - JSValue func_len = JS_GetPropertyStr(ctx, func, "length"); - if (!JS_IsException(func_len)) { - int arity; - if (!JS_ToInt32(ctx, &arity, func_len)) { - if (len > arity) { - JS_FreeValue(ctx, func_len); - return JS_ThrowTypeError(ctx, "fn.apply: too many arguments"); - } - } - JS_FreeValue(ctx, func_len); - } - JSValue *args = js_malloc(ctx, sizeof(JSValue) * (len > 0 ? len : 1)); if (!args) return JS_EXCEPTION; @@ -29221,22 +29282,22 @@ static JSValue js_blob_w32(JSContext *ctx, JSValueConst this_val, } /* blob.wf(value) - write float */ -static JSValue js_blob_wf(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) +static JSValue js_blob_wf(JSContext *ctx, JSValueConst this_val, JSValueConst arg0) { - if (argc < 1) return JS_ThrowTypeError(ctx, "wf(value) requires 1 argument"); + if (JS_IsNull(arg0)) return JS_ThrowTypeError(ctx, "wf(value) requires 1 argument"); blob *bd = js_get_blob(ctx, this_val); if (!bd) return JS_ThrowTypeError(ctx, "wf: not called on a blob"); float f; - int tag = JS_VALUE_GET_TAG(argv[0]); + int tag = JS_VALUE_GET_TAG(arg0); if (tag == JS_TAG_INT) { - f = (float)JS_VALUE_GET_INT(argv[0]); + f = (float)JS_VALUE_GET_INT(arg0); } else if (tag == JS_TAG_FLOAT64) { - f = (float)JS_VALUE_GET_FLOAT64(argv[0]); + f = (float)JS_VALUE_GET_FLOAT64(arg0); } else { double d; - if (JS_ToFloat64(ctx, &d, argv[0]) < 0) return JS_EXCEPTION; + if (JS_ToFloat64(ctx, &d, arg0) < 0) return JS_EXCEPTION; f = (float)d; } @@ -29409,7 +29470,7 @@ static const JSCFunctionListEntry js_blob_proto_funcs[] = { JS_CFUNC_DEF("write_fit", 2, js_blob_write_fit), JS_CFUNC_DEF("write_text", 1, js_blob_write_text), JS_CFUNC_DEF("write_pad", 1, js_blob_write_pad), - JS_CFUNC_DEF("wf", 1, js_blob_wf), + JS_CFUNC1_DEF("wf", js_blob_wf), JS_CFUNC_DEF("w16", 1, js_blob_w16), JS_CFUNC_DEF("w32", 1, js_blob_w32), @@ -29922,7 +29983,7 @@ static JSValue js_cell_length(JSContext *ctx, JSValueConst this_val, * call() function - call a function with explicit this and arguments * ============================================================================ */ -/* call(func, this_val, ...args) */ +/* call(func, this_val, args_array) */ static JSValue js_cell_call(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { @@ -29937,10 +29998,20 @@ static JSValue js_cell_call(JSContext *ctx, JSValueConst this_val, if (argc >= 2) this_arg = argv[1]; - int call_argc = argc > 2 ? argc - 2 : 0; - JSValueConst *call_argv = call_argc > 0 ? &argv[2] : NULL; + if (argc < 3 || JS_IsNull(argv[2])) + return JS_Call(ctx, func, this_arg, 0, NULL); - return JS_Call(ctx, func, this_arg, call_argc, call_argv); + if (!JS_IsArray(ctx, argv[2])) + return JS_ThrowTypeError(ctx, "third argument must be an array"); + + uint32_t len; + JSValue *tab = build_arg_list(ctx, &len, argv[2]); + if (!tab) + return JS_EXCEPTION; + + JSValue ret = JS_Call(ctx, func, this_arg, len, (JSValueConst *)tab); + free_arg_list(ctx, tab, len); + return ret; } /* ============================================================================ @@ -30199,7 +30270,7 @@ void JS_AddIntrinsicBaseObjects(JSContext *ctx) /* Cell Script global functions: text, number, array, object, fn */ { - JSValue text_func = JS_NewCFunction(ctx, js_cell_text, "text", 2); + JSValue text_func = JS_NewCFunction(ctx, js_cell_text, "text", 3); JS_SetPropertyStr(ctx, ctx->global_obj, "text", text_func); JSValue number_func = JS_NewCFunction(ctx, js_cell_number, "number", 2); @@ -30267,7 +30338,7 @@ void JS_AddIntrinsicBaseObjects(JSContext *ctx) JS_SetPropertyStr(ctx, ctx->global_obj, "apply", JS_NewCFunction(ctx, js_cell_fn_apply, "apply", 2)); JS_SetPropertyStr(ctx, ctx->global_obj, "replace", - JS_NewCFunction(ctx, js_cell_text_replace, "replace", 2)); + JS_NewCFunction(ctx, js_cell_text_replace, "replace", 4)); JS_SetPropertyStr(ctx, ctx->global_obj, "lower", JS_NewCFunction(ctx, js_cell_text_lower, "lower", 1)); JS_SetPropertyStr(ctx, ctx->global_obj, "upper", @@ -30279,7 +30350,7 @@ void JS_AddIntrinsicBaseObjects(JSContext *ctx) JS_SetPropertyStr(ctx, ctx->global_obj, "search", JS_NewCFunction(ctx, js_cell_text_search, "search", 3)); JS_SetPropertyStr(ctx, ctx->global_obj, "extract", - JS_NewCFunction(ctx, js_cell_text_extract, "extract", 3)); + JS_NewCFunction(ctx, js_cell_text_extract, "extract", 4)); JS_SetPropertyStr(ctx, ctx->global_obj, "reduce", JS_NewCFunction(ctx, js_cell_array_reduce, "reduce", 4)); JS_SetPropertyStr(ctx, ctx->global_obj, "arrfor", @@ -30309,9 +30380,9 @@ void JS_AddIntrinsicBaseObjects(JSContext *ctx) JS_SetPropertyStr(ctx, ctx->global_obj, "trunc", JS_NewCFunction(ctx, js_cell_number_trunc, "trunc", 2)); JS_SetPropertyStr(ctx, ctx->global_obj, "min", - JS_NewCFunction(ctx, js_cell_number_min, "min", 0)); + JS_NewCFunction(ctx, js_cell_number_min, "min", 2)); JS_SetPropertyStr(ctx, ctx->global_obj, "max", - JS_NewCFunction(ctx, js_cell_number_max, "max", 0)); + JS_NewCFunction(ctx, js_cell_number_max, "max", 2)); JS_SetPropertyStr(ctx, ctx->global_obj, "remainder", JS_NewCFunction(ctx, js_cell_number_remainder, "remainder", 2)); JS_SetPropertyStr(ctx, ctx->global_obj, "character", diff --git a/source/quickjs.h b/source/quickjs.h index 3885a185..1a9746c8 100644 --- a/source/quickjs.h +++ b/source/quickjs.h @@ -746,13 +746,32 @@ typedef enum JSCFunctionEnum { JS_CFUNC_generic_magic, JS_CFUNC_f_f, JS_CFUNC_f_f_f, + /* Fixed-arity fast paths - no argc/argv overhead */ + JS_CFUNC_0, /* JSValue f(ctx, this_val) */ + JS_CFUNC_1, /* JSValue f(ctx, this_val, arg0) */ + JS_CFUNC_2, /* JSValue f(ctx, this_val, arg0, arg1) */ + JS_CFUNC_3, /* JSValue f(ctx, this_val, arg0, arg1, arg2) */ + JS_CFUNC_4 } JSCFunctionEnum; +/* Fixed-arity C function types for fast paths */ +typedef JSValue JSCFunction0(JSContext *ctx, JSValueConst this_val); +typedef JSValue JSCFunction1(JSContext *ctx, JSValueConst this_val, JSValueConst arg0); +typedef JSValue JSCFunction2(JSContext *ctx, JSValueConst this_val, JSValueConst arg0, JSValueConst arg1); +typedef JSValue JSCFunction3(JSContext *ctx, JSValueConst this_val, JSValueConst arg0, JSValueConst arg1, JSValueConst arg2); +typedef JSValue JSCFunction4(JSContext *ctx, JSValueConst this_val, JSValueConst arg0, JSValueConst arg1, JSValueConst arg2, JSValueConst arg3); + typedef union JSCFunctionType { JSCFunction *generic; JSValue (*generic_magic)(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic); double (*f_f)(double); double (*f_f_f)(double, double); + /* Fixed-arity fast paths */ + JSCFunction0 *f0; + JSCFunction1 *f1; + JSCFunction2 *f2; + JSCFunction3 *f3; + JSCFunction4 *f4; } JSCFunctionType; JSValue JS_NewCFunction2(JSContext *ctx, JSCFunction *func, @@ -775,6 +794,32 @@ static inline JSValue JS_NewCFunctionMagic(JSContext *ctx, JSCFunctionMagic *fun return JS_NewCFunction2(ctx, (JSCFunction *)func, name, length, cproto, magic); } +/* Fixed-arity fast path constructors */ +static inline JSValue JS_NewCFuncFixed0(JSContext *ctx, JSCFunction0 *func, const char *name) +{ + return JS_NewCFunction2(ctx, (JSCFunction *)func, name, 0, JS_CFUNC_0, 0); +} + +static inline JSValue JS_NewCFuncFixed1(JSContext *ctx, JSCFunction1 *func, const char *name) +{ + return JS_NewCFunction2(ctx, (JSCFunction *)func, name, 1, JS_CFUNC_1, 0); +} + +static inline JSValue JS_NewCFuncFixed2(JSContext *ctx, JSCFunction2 *func, const char *name) +{ + return JS_NewCFunction2(ctx, (JSCFunction *)func, name, 2, JS_CFUNC_2, 0); +} + +static inline JSValue JS_NewCFuncFixed3(JSContext *ctx, JSCFunction3 *func, const char *name) +{ + return JS_NewCFunction2(ctx, (JSCFunction *)func, name, 3, JS_CFUNC_3, 0); +} + +static inline JSValue JS_NewCFuncFixed4(JSContext *ctx, JSCFunction4 *func, const char *name) +{ + return JS_NewCFunction2(ctx, (JSCFunction *)func, name, 4, JS_CFUNC_4, 0); +} + /* C property definition */ typedef struct JSCFunctionListEntry { @@ -816,6 +861,11 @@ typedef struct JSCFunctionListEntry { #define JS_CFUNC_DEF(name, length, func1) { name, 0, JS_DEF_CFUNC, 0, .u = { .func = { length, JS_CFUNC_generic, { .generic = func1 } } } } #define JS_CFUNC_MAGIC_DEF(name, length, func1, magic) { name, 0, JS_DEF_CFUNC, magic, .u = { .func = { length, JS_CFUNC_generic_magic, { .generic_magic = func1 } } } } #define JS_CFUNC_SPECIAL_DEF(name, length, cproto, func1) { name, 0, JS_DEF_CFUNC, 0, .u = { .func = { length, JS_CFUNC_ ## cproto, { .cproto = func1 } } } } +/* Fixed-arity fast path macros */ +#define JS_CFUNC0_DEF(name, func1) { name, 0, JS_DEF_CFUNC, 0, .u = { .func = { 0, JS_CFUNC_0, { .f0 = func1 } } } } +#define JS_CFUNC1_DEF(name, func1) { name, 0, JS_DEF_CFUNC, 0, .u = { .func = { 1, JS_CFUNC_1, { .f1 = func1 } } } } +#define JS_CFUNC2_DEF(name, func1) { name, 0, JS_DEF_CFUNC, 0, .u = { .func = { 2, JS_CFUNC_2, { .f2 = func1 } } } } +#define JS_CFUNC3_DEF(name, func1) { name, 0, JS_DEF_CFUNC, 0, .u = { .func = { 3, JS_CFUNC_3, { .f3 = func1 } } } } #define JS_ITERATOR_NEXT_DEF(name, length, func1, magic) { name, 0, JS_DEF_CFUNC, magic, .u = { .func = { length, JS_CFUNC_iterator_next, { .iterator_next = func1 } } } } #define JS_PROP_STRING_DEF(name, cstr, prop_flags) { name, prop_flags, JS_DEF_PROP_STRING, 0, .u = { .str = cstr } } #define JS_PROP_INT32_DEF(name, val, prop_flags) { name, prop_flags, JS_DEF_PROP_INT32, 0, .u = { .i32 = val } }