MIRROR: javascript for ๐Ÿœ's, a tiny runtime with big ambitions
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

proper lowering for op_special [1]

+488 -8
+1 -1
src/builtins/node/http.mjs
··· 561 561 clientNotImplemented(); 562 562 } 563 563 564 - createSocket(options, callback) { 564 + createSocket(_options, callback) { 565 565 const error = new Error('node:http Agent client transport is not implemented yet'); 566 566 if (typeof callback === 'function') callback(error); 567 567 else throw error;
+34 -1
src/silver/compiler.c
··· 398 398 return c && !c->is_arrow && c->enclosing; 399 399 } 400 400 401 + static bool ast_arguments_captured_by_arrow(const sv_ast_t *node, bool in_arrow) { 402 + if (!node) return false; 403 + if (in_arrow && node->type == N_IDENT && 404 + is_ident_str(node->str, node->len, "arguments", 9)) 405 + return true; 406 + 407 + if (node->type == N_FUNC) { 408 + if (!(node->flags & FN_ARROW)) return false; 409 + 410 + for (int i = 0; i < node->args.count; i++) { 411 + if (ast_arguments_captured_by_arrow(node->args.items[i], true)) return true; 412 + } 413 + return ast_arguments_captured_by_arrow(node->body, true); 414 + } 415 + 416 + if (ast_arguments_captured_by_arrow(node->left, in_arrow)) return true; 417 + if (ast_arguments_captured_by_arrow(node->right, in_arrow)) return true; 418 + if (ast_arguments_captured_by_arrow(node->cond, in_arrow)) return true; 419 + if (ast_arguments_captured_by_arrow(node->body, in_arrow)) return true; 420 + if (ast_arguments_captured_by_arrow(node->catch_body, in_arrow)) return true; 421 + if (ast_arguments_captured_by_arrow(node->finally_body, in_arrow)) return true; 422 + if (ast_arguments_captured_by_arrow(node->catch_param, in_arrow)) return true; 423 + if (ast_arguments_captured_by_arrow(node->init, in_arrow)) return true; 424 + if (ast_arguments_captured_by_arrow(node->update, in_arrow)) return true; 425 + 426 + for (int i = 0; i < node->args.count; i++) { 427 + if (ast_arguments_captured_by_arrow(node->args.items[i], in_arrow)) return true; 428 + } 429 + 430 + return false; 431 + } 432 + 401 433 static int resolve_local(sv_compiler_t *c, const char *name, uint32_t len) { 402 434 if (c->local_lookup_heads && c->local_lookup_cap > 0) { 403 435 uint32_t hash = sv_compile_ctx_hash_local_name(name, len); ··· 4468 4500 } 4469 4501 4470 4502 if (!comp.is_arrow && has_implicit_arguments_obj(&comp) && 4471 - (node->flags & FN_USES_ARGS)) { 4503 + (node->flags & FN_USES_ARGS) && 4504 + ast_arguments_captured_by_arrow(node->body, false)) { 4472 4505 comp.strict_args_local = add_local(&comp, "", 0, false, comp.scope_depth); 4473 4506 emit_op(&comp, OP_SPECIAL_OBJ); 4474 4507 emit(&comp, 0);
+14 -6
src/silver/swarm.c
··· 1054 1054 case OP_RETURN: case OP_RETURN_UNDEF: 1055 1055 case OP_GET_FIELD: case OP_GET_FIELD2: case OP_GET_GLOBAL: 1056 1056 case OP_SPECIAL_OBJ: 1057 - if (op == OP_SPECIAL_OBJ && sv_get_u8(ip + 1) == 0 && 1058 - !f->is_strict && f->param_count > 0) return false; 1057 + // OP_SPECIAL_OBJ(0) materializes `arguments`. keep these functions on 1058 + // the interpreter until JIT routes a real per-call activation/object 1059 + // with matching lifetime and semantics. 1060 + if (sv_get_u8(ip + 1) == 0) return false; 1061 + break; 1059 1062 case OP_NOP: case OP_LINE_NUM: case OP_COL_NUM: case OP_LABEL: 1060 1063 break; 1061 1064 default: ··· 2004 2007 case OP_STR_ALC_SNAPSHOT: 2005 2008 case OP_TO_PROPKEY: 2006 2009 case OP_RETURN: case OP_RETURN_UNDEF: 2007 - case OP_SPECIAL_OBJ: if ( 2008 - op == OP_SPECIAL_OBJ && sv_get_u8(ip + 1) == 0 2009 - && !func->is_strict && func->param_count > 0 2010 - ) eligible = false; 2011 2010 case OP_SET_NAME: 2012 2011 case OP_TRY_PUSH: case OP_TRY_POP: 2013 2012 case OP_THROW: case OP_THROW_ERROR: ··· 2024 2023 } 2025 2024 case OP_RE_EXEC_TRUTHY: 2026 2025 eligible = false; 2026 + break; 2027 + case OP_SPECIAL_OBJ: 2028 + if (sv_get_u8(ip + 1) == 0) { 2029 + if (sv_jit_warn_unlikely) 2030 + fprintf(stderr, "jit: ineligible op SPECIAL_OBJ(%d) in %s\n", 2031 + sv_get_u8(ip + 1), 2032 + func->name ? func->name : "<anonymous>"); 2033 + eligible = false; 2034 + } 2027 2035 break; 2028 2036 default: 2029 2037 if (sv_jit_warn_unlikely)
+25
tests/test_array_join_number_stringification.cjs
··· 1 + function assert(condition, message) { 2 + if (!condition) throw new Error(message); 3 + } 4 + 5 + assert( 6 + [2201970, 40309, 6267400].join(',') === '2201970,40309,6267400', 7 + `expected decimal join output, got ${[2201970, 40309, 6267400].join(',')}` 8 + ); 9 + 10 + assert( 11 + [999999, 1000000, 1000001].join(',') === '999999,1000000,1000001', 12 + `expected plain integers around one million, got ${[999999, 1000000, 1000001].join(',')}` 13 + ); 14 + 15 + assert( 16 + [2201970].toString() === '2201970', 17 + `expected Array#toString to use decimal formatting, got ${[2201970].toString()}` 18 + ); 19 + 20 + assert( 21 + [1.5, 2201970].join(',') === '1.5,2201970', 22 + `expected mixed numeric join output, got ${[1.5, 2201970].join(',')}` 23 + ); 24 + 25 + console.log('array join number stringification test passed');
+136
tests/test_async_dead_await_fastpath.cjs
··· 1 + const now = 2 + typeof performance !== 'undefined' && performance && typeof performance.now === 'function' 3 + ? () => performance.now() 4 + : () => Date.now(); 5 + 6 + function median(values) { 7 + const sorted = [...values].sort((a, b) => a - b); 8 + const mid = sorted.length >> 1; 9 + return sorted.length % 2 === 0 10 + ? (sorted[mid - 1] + sorted[mid]) / 2 11 + : sorted[mid]; 12 + } 13 + 14 + function formatMetric(label, result) { 15 + console.log( 16 + `${label}: median=${result.medianMs.toFixed(3)}ms ` + 17 + `(${result.usPerOp.toFixed(3)}us/op, ${Math.round(result.opsPerSec)} ops/s)` 18 + ); 19 + } 20 + 21 + function yieldTick() { 22 + return new Promise((resolve) => setTimeout(resolve, 0)); 23 + } 24 + 25 + async function bench(label, iterations, fn, rounds = 5) { 26 + for (let i = 0; i < 2; i++) { 27 + await yieldTick(); 28 + for (let j = 0; j < Math.min(1000, iterations); j++) { 29 + await fn(); 30 + } 31 + } 32 + 33 + const samples = []; 34 + for (let round = 0; round < rounds; round++) { 35 + await yieldTick(); 36 + const start = now(); 37 + for (let i = 0; i < iterations; i++) { 38 + await fn(); 39 + } 40 + samples.push(now() - start); 41 + } 42 + 43 + const medianMs = median(samples); 44 + const opsPerSec = iterations / (medianMs / 1000); 45 + const usPerOp = (medianMs * 1000) / iterations; 46 + const result = { medianMs, opsPerSec, usPerOp, samples }; 47 + formatMetric(label, result); 48 + return result; 49 + } 50 + 51 + function getColorEnabled() { 52 + const { process, Deno } = globalThis; 53 + const isNoColor = 54 + typeof Deno?.noColor === 'boolean' 55 + ? Deno.noColor 56 + : process !== void 0 57 + ? 'NO_COLOR' in process?.env 58 + : false; 59 + return !isNoColor; 60 + } 61 + 62 + async function getColorEnabledAsync() { 63 + const { navigator } = globalThis; 64 + const cfWorkers = 'cloudflare:workers'; 65 + const isNoColor = 66 + navigator !== void 0 && navigator.userAgent === 'Cloudflare-Workers' 67 + ? await (async () => { 68 + try { 69 + return 'NO_COLOR' in ((await import(cfWorkers)).env ?? {}); 70 + } catch { 71 + return false; 72 + } 73 + })() 74 + : !getColorEnabled(); 75 + return !isNoColor; 76 + } 77 + 78 + async function asyncWrapSync() { 79 + return !getColorEnabled(); 80 + } 81 + 82 + async function navGuardOnly() { 83 + const { navigator } = globalThis; 84 + const isNoColor = 85 + navigator !== void 0 && navigator.userAgent === 'Cloudflare-Workers' 86 + ? false 87 + : !getColorEnabled(); 88 + return !isNoColor; 89 + } 90 + 91 + async function deadAwaitBranch() { 92 + const { navigator } = globalThis; 93 + const isNoColor = 94 + navigator !== void 0 && navigator.userAgent === 'Cloudflare-Workers' 95 + ? await (async () => false)() 96 + : !getColorEnabled(); 97 + return !isNoColor; 98 + } 99 + 100 + async function main() { 101 + const ua = globalThis.navigator?.userAgent ?? ''; 102 + console.log(`navigator.userAgent=${ua || '<missing>'}`); 103 + 104 + if (!globalThis.navigator || ua === 'Cloudflare-Workers') { 105 + console.log('skipping dead-await fast-path regression outside the normal fast path'); 106 + return; 107 + } 108 + 109 + const iterations = Number(process.env.ANT_ASYNC_DEAD_AWAIT_ITERS || 8000); 110 + const ratioLimit = Number(process.env.ANT_ASYNC_DEAD_AWAIT_RATIO_LIMIT || 6); 111 + 112 + const wrap = await bench('asyncWrapSync', iterations, asyncWrapSync); 113 + const guard = await bench('navGuardOnly', iterations, navGuardOnly); 114 + const dead = await bench('deadAwaitBranch', iterations, deadAwaitBranch); 115 + const real = await bench('getColorEnabledAsync', iterations, getColorEnabledAsync); 116 + 117 + const deadVsWrap = dead.usPerOp / wrap.usPerOp; 118 + const deadVsGuard = dead.usPerOp / guard.usPerOp; 119 + const realVsGuard = real.usPerOp / guard.usPerOp; 120 + 121 + console.log(`deadAwaitBranch/asyncWrapSync ratio=${deadVsWrap.toFixed(2)}x`); 122 + console.log(`deadAwaitBranch/navGuardOnly ratio=${deadVsGuard.toFixed(2)}x`); 123 + console.log(`getColorEnabledAsync/navGuardOnly ratio=${realVsGuard.toFixed(2)}x`); 124 + 125 + if (deadVsWrap > ratioLimit || deadVsGuard > ratioLimit) { 126 + throw new Error( 127 + 'untaken await branch regressed the async fast path ' + 128 + `(ratios ${deadVsWrap.toFixed(2)}x/${deadVsGuard.toFixed(2)}x, limit ${ratioLimit}x)` 129 + ); 130 + } 131 + } 132 + 133 + main().catch((error) => { 134 + console.error(error && error.stack ? error.stack : String(error)); 135 + process.exitCode = 1; 136 + });
+35
tests/test_async_reentrant_resume.cjs
··· 1 + function assertEq(name, actual, expected) { 2 + if (actual !== expected) { 3 + throw new Error(`${name}: expected ${expected}, got ${actual}`); 4 + } 5 + console.log(`ok ${name}`); 6 + } 7 + 8 + (async function() { 9 + const settled = Promise.resolve('ready'); 10 + 11 + async function leaf(label) { 12 + const value = await settled; 13 + return `${label}:${value}`; 14 + } 15 + 16 + async function middle(label) { 17 + return await leaf(label); 18 + } 19 + 20 + async function top() { 21 + const out = []; 22 + out.push(await middle('a')); 23 + out.push(await middle('b')); 24 + return out.join(','); 25 + } 26 + 27 + assertEq( 28 + 'nested async resume on settled promise', 29 + await top(), 30 + 'a:ready,b:ready' 31 + ); 32 + })().catch((err) => { 33 + console.error(err); 34 + process.exitCode = 1; 35 + });
+50
tests/test_await_thenables.cjs
··· 1 + function assertEq(name, actual, expected) { 2 + if (actual !== expected) { 3 + throw new Error(`${name}: expected ${expected}, got ${actual}`); 4 + } 5 + console.log(`ok ${name}`); 6 + } 7 + 8 + class ProtoThenable { 9 + constructor(value) { 10 + this.value = value; 11 + } 12 + } 13 + 14 + ProtoThenable.prototype.then = function(resolve, _reject) { 15 + resolve(this.value); 16 + }; 17 + 18 + class APIPromiseLike { 19 + constructor(value) { 20 + this.promise = Promise.resolve([{ ok: true, value }]); 21 + } 22 + 23 + then(onfulfilled, onrejected) { 24 + return this.promise.then( 25 + onfulfilled ? ([result]) => onfulfilled(result.value) : undefined, 26 + onrejected 27 + ); 28 + } 29 + } 30 + 31 + (async function() { 32 + assertEq('await plain thenable', await { 33 + then(resolve, _reject) { 34 + resolve(42); 35 + } 36 + }, 42); 37 + 38 + assertEq('await prototype thenable', await new ProtoThenable(99), 99); 39 + 40 + assertEq('await APIPromise-like thenable', await new APIPromiseLike('ok'), 'ok'); 41 + 42 + assertEq( 43 + 'Promise.resolve still adopts thenables', 44 + await Promise.resolve(new ProtoThenable('wrapped')), 45 + 'wrapped' 46 + ); 47 + })().catch((err) => { 48 + console.error(err); 49 + process.exitCode = 1; 50 + });
+31
tests/test_jit_arguments_object.cjs
··· 14 14 return a; 15 15 } 16 16 17 + function strictSlice(path) { 18 + "use strict"; 19 + return { 20 + argc: arguments.length, 21 + tail: Array.prototype.slice.call(arguments, 1), 22 + }; 23 + } 24 + 25 + function directRead(a) { 26 + return arguments[0] + a; 27 + } 28 + 29 + function arrowCapture(a) { 30 + const read = () => arguments[0] + a; 31 + return read(); 32 + } 33 + 17 34 for (let i = 0; i < 500; i++) { 18 35 assertEq(mappedRead(1), 2, "warm mapped read"); 19 36 assertEq(mappedWrite(1), 2, "warm mapped write"); 37 + assertEq(directRead(3), 6, "warm direct read"); 38 + assertEq(arrowCapture(4), 8, "warm arrow capture"); 39 + const warmStrict = strictSlice("/", "mw"); 40 + assertEq(warmStrict.argc, 2, "warm strict argc"); 41 + assertEq(warmStrict.tail.length, 1, "warm strict tail length"); 42 + assertEq(warmStrict.tail[0], "mw", "warm strict tail first"); 20 43 } 21 44 22 45 assertEq(mappedRead(1), 2, "hot mapped read"); 23 46 assertEq(mappedWrite(1), 2, "hot mapped write"); 47 + assertEq(directRead(3), 6, "hot direct read"); 48 + assertEq(arrowCapture(4), 8, "hot arrow capture"); 49 + 50 + const hotStrict = strictSlice("/route", "mw1", "mw2"); 51 + assertEq(hotStrict.argc, 3, "hot strict argc"); 52 + assertEq(hotStrict.tail.length, 2, "hot strict tail length"); 53 + assertEq(hotStrict.tail[0], "mw1", "hot strict tail first"); 54 + assertEq(hotStrict.tail[1], "mw2", "hot strict tail second"); 24 55 25 56 console.log("OK: test_jit_arguments_object");
+61
tests/test_js_entry_tla_resume.cjs
··· 1 + const { spawnSync } = require('child_process'); 2 + const fs = require('fs'); 3 + const os = require('os'); 4 + const path = require('path'); 5 + 6 + function assert(condition, message) { 7 + if (!condition) throw new Error(message); 8 + } 9 + 10 + const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ant-js-entry-tla-resume-')); 11 + const scriptPath = path.join(tmpRoot, 'entry.mjs'); 12 + 13 + fs.writeFileSync( 14 + scriptPath, 15 + [ 16 + 'const order = [];', 17 + 'const settled = Promise.resolve("ready");', 18 + 'async function leaf() {', 19 + ' order.push("leaf-before");', 20 + ' const value = await settled;', 21 + ' order.push(`leaf-after:${value}`);', 22 + ' return value;', 23 + '}', 24 + 'async function middle() {', 25 + ' order.push("middle-before");', 26 + ' const value = await leaf();', 27 + ' order.push(`middle-after:${value}`);', 28 + ' return value;', 29 + '}', 30 + 'order.push("start");', 31 + 'await Promise.resolve();', 32 + 'order.push("after-microtask");', 33 + 'const nested = await middle();', 34 + 'order.push(`nested:${nested}`);', 35 + 'await new Promise((resolve) => setTimeout(resolve, 0));', 36 + 'order.push("after-timer");', 37 + 'console.log(order.join(","));', 38 + '', 39 + ].join('\n') 40 + ); 41 + 42 + const result = spawnSync(process.execPath, [scriptPath], { 43 + encoding: 'utf8', 44 + }); 45 + 46 + if (result.error) throw result.error; 47 + 48 + assert( 49 + result.status === 0, 50 + `expected direct .mjs TLA entry to exit 0, got ${result.status}\nstdout:\n${result.stdout}\nstderr:\n${result.stderr}` 51 + ); 52 + assert( 53 + result.stdout === 'start,after-microtask,middle-before,leaf-before,leaf-after:ready,middle-after:ready,nested:ready,after-timer\n', 54 + `expected stdout to show full TLA resume order, got ${JSON.stringify(result.stdout)}` 55 + ); 56 + assert( 57 + !/invalid suspended frame state|EXC_BAD_ACCESS|segmentation fault/i.test(result.stderr), 58 + `expected no suspended-frame crash markers, got stderr:\n${result.stderr}` 59 + ); 60 + 61 + console.log('direct JavaScript entrypoint top-level await resumes correctly');
+101
tests/test_tla_dead_await_fastpath.cjs
··· 1 + const { spawnSync } = require('child_process'); 2 + const fs = require('fs'); 3 + const os = require('os'); 4 + const path = require('path'); 5 + 6 + function assert(condition, message) { 7 + if (!condition) throw new Error(message); 8 + } 9 + 10 + const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ant-tla-dead-await-fastpath-')); 11 + const modulePath = path.join(tmpRoot, 'bench.mjs'); 12 + 13 + fs.writeFileSync( 14 + modulePath, 15 + [ 16 + 'const now =', 17 + " typeof performance !== 'undefined' && performance && typeof performance.now === 'function'", 18 + ' ? () => performance.now()', 19 + ' : () => Date.now();', 20 + '', 21 + 'function getColorEnabled() {', 22 + ' const { process, Deno } = globalThis;', 23 + " const isNoColor = typeof Deno?.noColor === 'boolean'", 24 + ' ? Deno.noColor', 25 + " : process !== void 0 ? 'NO_COLOR' in process?.env : false;", 26 + ' return !isNoColor;', 27 + '}', 28 + '', 29 + 'const ua = globalThis.navigator?.userAgent ?? "";', 30 + 'if (!globalThis.navigator || ua === "Cloudflare-Workers") {', 31 + ' console.log(JSON.stringify({ skipped: true, ua }));', 32 + ' process.exit(0);', 33 + '}', 34 + '', 35 + 'const iterations = Number(process.env.ANT_TLA_DEAD_AWAIT_ITERS || 20000);', 36 + '', 37 + 'const guardStart = now();', 38 + 'for (let i = 0; i < iterations; i++) {', 39 + ' const isNoColor =', 40 + ' navigator !== void 0 && navigator.userAgent === "Cloudflare-Workers"', 41 + ' ? false', 42 + ' : !getColorEnabled();', 43 + ' void isNoColor;', 44 + '}', 45 + 'const guardMs = now() - guardStart;', 46 + '', 47 + 'const deadStart = now();', 48 + 'for (let i = 0; i < iterations; i++) {', 49 + ' const isNoColor =', 50 + ' navigator !== void 0 && navigator.userAgent === "Cloudflare-Workers"', 51 + ' ? await (async () => false)()', 52 + ' : !getColorEnabled();', 53 + ' void isNoColor;', 54 + '}', 55 + 'const deadMs = now() - deadStart;', 56 + '', 57 + 'console.log(JSON.stringify({', 58 + ' skipped: false,', 59 + ' ua,', 60 + ' iterations,', 61 + ' guardMs,', 62 + ' deadMs,', 63 + ' guardUsPerOp: (guardMs * 1000) / iterations,', 64 + ' deadUsPerOp: (deadMs * 1000) / iterations,', 65 + '}));', 66 + '', 67 + ].join('\n') 68 + ); 69 + 70 + const result = spawnSync(process.execPath, [modulePath], { 71 + encoding: 'utf8', 72 + }); 73 + 74 + if (result.error) throw result.error; 75 + assert( 76 + result.status === 0, 77 + `expected module benchmark to exit 0, got ${result.status}\nstdout:\n${result.stdout}\nstderr:\n${result.stderr}` 78 + ); 79 + 80 + const output = result.stdout.trim(); 81 + assert(output.length > 0, `expected benchmark JSON output, got stdout=${JSON.stringify(result.stdout)}`); 82 + 83 + const parsed = JSON.parse(output); 84 + console.log(`navigator.userAgent=${parsed.ua || '<missing>'}`); 85 + 86 + if (parsed.skipped) { 87 + console.log('skipping TLA dead-await fast-path regression outside the normal fast path'); 88 + process.exit(0); 89 + } 90 + 91 + const ratioLimit = Number(process.env.ANT_TLA_DEAD_AWAIT_RATIO_LIMIT || 6); 92 + const ratio = parsed.deadUsPerOp / parsed.guardUsPerOp; 93 + 94 + console.log( 95 + `guard=${parsed.guardUsPerOp.toFixed(3)}us/op dead=${parsed.deadUsPerOp.toFixed(3)}us/op ratio=${ratio.toFixed(2)}x` 96 + ); 97 + 98 + assert( 99 + ratio <= ratioLimit, 100 + `untaken TLA await branch regressed the fast path (ratio ${ratio.toFixed(2)}x, limit ${ratioLimit}x)` 101 + );