From 700f59c1b2df987870b65487f967d75e4cfb24a5 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 18 Oct 2026 12:11:05 -0700 Subject: [PATCH 001/122] Serve Temporal's absence, v8, stream/web, dns/promises, EvalError and graceful-fs T10 and T12 of the zero-cost compatibility plan, plus the maintainer decisions: - Programs that name Temporal type-check against TS 7's temporal lib (added only for them) and lower to Node 24's answers: typeof is "undefined", globalThis.Temporal is undefined, other uses throw ReferenceError at the root read. - node:v8 is importable; getHeapStatistics answers allocator-derived figures in Node's shape; other members fence with the by-design reason. vm's refusal says why. - node:stream/web exports the web stream globals themselves. - node:dns/promises and dns.promises: lookup and lookupService as promises, in the link-gated scr_dns_promises.c unit. - eval and the Function constructor throw Node's EvalError under --disallow-code-generation-from-strings; the corpus harness runs the Node oracle with that flag for `// @disallow-code-generation`. - graceful-fs is served as node:fs. Every new lowering replaces a refusal, and the new runtime code links only when used, so other programs compile to identical bytes (zero-cost gate). Co-Authored-By: Claude Opus 5.5 --- docs/content/docs/dependencies.mdx | 2 + docs/content/docs/limitations.mdx | 6 + .../limitations/builtin-apis/globals.mdx | 2 +- .../builtin-apis/streams-and-readline.mdx | 2 +- docs/content/limitations/builtin-apis/v8.mdx | 1 + .../content/limitations/dns-lookup-errors.mdx | 2 +- docs/content/limitations/graceful-fs.mdx | 1 + .../limitations/runtime-code-evaluation.mdx | 2 +- .../node-v24-compatibility-meta.json | 2 +- .../src/generated/node-v24-compatibility.json | 80 ++-- .../generated/node-v24-backlog.json | 190 +++++--- .../generated/node-v24-internal.json | 122 +++-- internal/compatibility/static-support.json | 9 +- .../ambient/scriptc-node-fallback.d.ts | 92 ++++ .../src/backend/executable-features.ts | 1 + packages/compiler/src/backend/library-pack.ts | 1 + .../compiler/src/backend/llvm/lib-datagram.ts | 4 + .../src/backend/native-link-info-core.ts | 2 + .../compiler/src/backend/native/contracts.ts | 5 + .../compiler/src/backend/native/executable.ts | 1 + .../src/backend/runtime-pack-targets.test.ts | 1 + .../compiler/src/backend/runtime-pack.test.ts | 1 + packages/compiler/src/backend/warm-cache.ts | 1 + .../compiler/src/diagnostics/diagnostic.ts | 6 +- .../src/executable/executable-cache.test.ts | 1 + .../src/executable/executable-cache.ts | 2 + .../compiler/src/frontend/builtin-modules.ts | 53 ++- .../src/frontend/lowering/builtin-calls.ts | 36 ++ .../lowering/builtins/module-bindings.ts | 11 + .../lowering/builtins/module-calls.ts | 4 + .../src/frontend/lowering/builtins/v8.ts | 111 +++++ .../src/frontend/lowering/code-generation.ts | 134 ++++++ .../src/frontend/lowering/lower-calls.ts | 9 + .../src/frontend/lowering/lower-classes.ts | 17 + .../src/frontend/lowering/lower-dgram.ts | 121 +++++ .../src/frontend/lowering/lower-exprs.ts | 33 +- .../frontend/lowering/lower-global-value.ts | 6 +- .../src/frontend/lowering/lower-namespaces.ts | 21 +- .../src/frontend/lowering/lower-stdlib.ts | 15 +- .../compiler/src/frontend/lowering/lowerer.ts | 6 + .../src/frontend/lowering/surfaces.ts | 23 +- packages/compiler/src/frontend/npm-static.ts | 5 +- packages/compiler/src/frontend/pipeline.ts | 4 + packages/compiler/src/frontend/program.ts | 57 +++ packages/compiler/src/frontend/type-mapper.ts | 28 ++ packages/compiler/src/index.ts | 1 + packages/compiler/src/ir/build.ts | 4 +- packages/compiler/src/ir/builtin-effects.ts | 2 + .../compiler/src/ir/builtin-signatures.ts | 6 + packages/compiler/src/ir/capture-values.ts | 2 +- packages/compiler/src/ir/runtime-features.ts | 5 + packages/compiler/src/native/cli.ts | 1 + packages/compiler/src/native/prepare.ts | 1 + .../compiler/src/native/sanitizer.test.ts | 1 + packages/compiler/surface-manifest.json | 29 +- .../code-generation-disallowed.cjs.json | 6 + .../corpus/code-generation-disallowed.ts.json | 6 + .../tests/corpus/dns-promises-lookup.ts.json | 6 + .../corpus/dns-promises-untyped.cjs.json | 6 + .../corpus/graceful-fs-alias/main.cjs.json | 6 + .../tests/corpus/stream-web-readable.ts.json | 6 + .../tests/corpus/stream-web-require.cjs.json | 6 + .../corpus/temporal-absent-probe.js.json | 6 + .../tests/corpus/temporal-absent.ts.json | 6 + .../tests/corpus/v8-heap-statistics.ts.json | 6 + .../tests/corpus/v8-require-optional.cjs.json | 6 + .../tests/diagnostics/imports.ts.json | 2 +- .../diagnostics/module-out-of-scope.ts.json | 68 +-- .../pkg-import-fences/main.ts.json | 6 +- .../fixtures/node-types/import-fences.ts.json | 8 +- .../runtime-pack-matrix.mjs | 2 + .../runtime-pack-matrix.mjs | 2 + .../runtime-wasm32-wasi/scripts/build.mjs | 1 + packages/runtime/src/scr_dgram.h | 5 + packages/runtime/src/scr_dns_promises.c | 312 ++++++++++++ tests/corpus/code-generation-disallowed.cjs | 36 ++ tests/corpus/code-generation-disallowed.ts | 44 ++ tests/corpus/dns-promises-lookup.ts | 40 ++ tests/corpus/dns-promises-untyped.cjs | 34 ++ tests/corpus/graceful-fs-alias/main.cjs | 34 ++ .../node_modules/graceful-fs/LICENSE | 15 + .../node_modules/graceful-fs/clone.js | 23 + .../node_modules/graceful-fs/graceful-fs.js | 448 ++++++++++++++++++ .../graceful-fs/legacy-streams.js | 118 +++++ .../node_modules/graceful-fs/package.json | 53 +++ .../node_modules/graceful-fs/polyfills.js | 355 ++++++++++++++ tests/corpus/stream-web-readable.ts | 22 + tests/corpus/stream-web-require.cjs | 10 + tests/corpus/temporal-absent-probe.js | 17 + tests/corpus/temporal-absent.ts | 35 ++ tests/corpus/v8-heap-statistics.ts | 16 + tests/corpus/v8-require-optional.cjs | 18 + tests/diagnostics/module-out-of-scope.ts | 14 +- tests/fixtures/node-types/import-fences.ts | 8 +- .../self-hosting/frontend-collections.ts | 2 +- .../__snapshots__/coverage-import-fences.txt | 4 +- tests/harness/__snapshots__/imports.ts.txt | 2 +- .../__snapshots__/module-out-of-scope.ts.txt | 48 +- .../pkg-import-fences/main.ts.txt | 6 +- tests/harness/differential.test.ts | 14 +- tests/harness/linux-differential.test.ts | 3 + tests/harness/llvm-differential.test.ts | 6 +- tests/harness/windows-differential.test.ts | 5 +- 103 files changed, 2934 insertions(+), 253 deletions(-) create mode 100644 docs/content/limitations/builtin-apis/v8.mdx create mode 100644 docs/content/limitations/graceful-fs.mdx create mode 100644 packages/compiler/src/frontend/lowering/builtins/v8.ts create mode 100644 packages/compiler/src/frontend/lowering/code-generation.ts create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/code-generation-disallowed.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/code-generation-disallowed.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/dns-promises-lookup.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/dns-promises-untyped.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/graceful-fs-alias/main.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/stream-web-readable.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/stream-web-require.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/temporal-absent-probe.js.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/temporal-absent.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/v8-heap-statistics.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/v8-require-optional.cjs.json create mode 100644 packages/runtime/src/scr_dns_promises.c create mode 100644 tests/corpus/code-generation-disallowed.cjs create mode 100644 tests/corpus/code-generation-disallowed.ts create mode 100644 tests/corpus/dns-promises-lookup.ts create mode 100644 tests/corpus/dns-promises-untyped.cjs create mode 100644 tests/corpus/graceful-fs-alias/main.cjs create mode 100644 tests/corpus/graceful-fs-alias/node_modules/graceful-fs/LICENSE create mode 100644 tests/corpus/graceful-fs-alias/node_modules/graceful-fs/clone.js create mode 100644 tests/corpus/graceful-fs-alias/node_modules/graceful-fs/graceful-fs.js create mode 100644 tests/corpus/graceful-fs-alias/node_modules/graceful-fs/legacy-streams.js create mode 100644 tests/corpus/graceful-fs-alias/node_modules/graceful-fs/package.json create mode 100644 tests/corpus/graceful-fs-alias/node_modules/graceful-fs/polyfills.js create mode 100644 tests/corpus/stream-web-readable.ts create mode 100644 tests/corpus/stream-web-require.cjs create mode 100644 tests/corpus/temporal-absent-probe.js create mode 100644 tests/corpus/temporal-absent.ts create mode 100644 tests/corpus/v8-heap-statistics.ts create mode 100644 tests/corpus/v8-require-optional.cjs diff --git a/docs/content/docs/dependencies.mdx b/docs/content/docs/dependencies.mdx index 5ecc144c6..6545629c9 100644 --- a/docs/content/docs/dependencies.mdx +++ b/docs/content/docs/dependencies.mdx @@ -55,6 +55,8 @@ scriptc compiles a package automatically when it: A package that doesn't meet these requirements, or that uses something scriptc can't compile, gets a package-specific diagnostic that explains why. The usual fixes are to switch to a version or alternative package that compiles, or to replace the call with code you write yourself. +`graceful-fs` is the exception: it compiles as `node:fs` instead of from its JavaScript, with one [documented difference](/docs/limitations#graceful-fs). + diff --git a/docs/content/docs/limitations.mdx b/docs/content/docs/limitations.mdx index 544bd2b2d..ac8fa2bb2 100644 --- a/docs/content/docs/limitations.mdx +++ b/docs/content/docs/limitations.mdx @@ -98,6 +98,8 @@ title: "Limitations" ../limitations/builtin-apis/process-memory.mdx +../limitations/builtin-apis/v8.mdx + ../limitations/builtin-apis/performance-entries.mdx ../limitations/builtin-apis/builtin-module-lookup.mdx @@ -206,6 +208,10 @@ title: "Limitations" ../limitations/console-and-process-streams.mdx +### graceful-fs + +../limitations/graceful-fs.mdx + ### DNS lookup errors ../limitations/dns-lookup-errors.mdx diff --git a/docs/content/limitations/builtin-apis/globals.mdx b/docs/content/limitations/builtin-apis/globals.mdx index db6691f20..c246c4da9 100644 --- a/docs/content/limitations/builtin-apis/globals.mdx +++ b/docs/content/limitations/builtin-apis/globals.mdx @@ -1 +1 @@ -- **Globals:** Stored `globalThis` references support identity and probes for absent capabilities. Most builtin members require direct global access; access through a stored global object can raise `SC2020`. +- **Globals:** Stored `globalThis` references support identity and probes for absent capabilities. Most builtin members require direct global access; access through a stored global object can raise `SC2020`. `Temporal` is not defined, as in Node.js 24: `typeof Temporal` is `"undefined"`, `globalThis.Temporal` is `undefined`, and any other use throws `ReferenceError: Temporal is not defined`. Programs that name it type-check against TypeScript's `Temporal` declarations. diff --git a/docs/content/limitations/builtin-apis/streams-and-readline.mdx b/docs/content/limitations/builtin-apis/streams-and-readline.mdx index c48f04bcc..1cfb4880d 100644 --- a/docs/content/limitations/builtin-apis/streams-and-readline.mdx +++ b/docs/content/limitations/builtin-apis/streams-and-readline.mdx @@ -1 +1 @@ -- **Streams and readline:** Stream chunks are Buffers or strings. `Readable.from` accepts arrays, Sets, Map and Set iterators, generators, async generators, and class iterables and async iterables whose values are strings or Buffers, and in JavaScript any iterable or async iterable of them; a stream that mixes strings and Buffers is unsupported. In JavaScript, a sync iterator value with a `then` method that is not a promise, and an object whose `[Symbol.asyncIterator]` or `[Symbol.iterator]` method is a generator method, are unsupported. Object-mode streams of other values (including `Readable.from` over a `Map`, whose values are entry arrays) are unsupported. `pipeline` accepts one run of function stages: async generator functions between streams (`async function* (source)`, iterating `source` with `for await`), an async generator source function (`async function* ({ signal })`), and an async function destination whose value resolves the promise form and reaches the callback's second argument. Stages may take the `{ signal }` argument. An async generator function as the destination, using the PassThrough that `pipeline()` returns for a function destination, several runs of function stages, and function stages together with pipeline options are unsupported. `Readable.from` accepts `objectMode` and `highWaterMark` options. The `Readable` operators (`map`, `filter`, `forEach`, `toArray`, `some`, `find`, `every`, `flatMap`, `drop`, `take`, and `reduce`) accept `concurrency`, `highWaterMark`, and `signal` options; their callbacks take only the chunk, `map` returns strings or Buffers, and `flatMap` returns strings or arrays of them. `readable.compose` is unsupported. `stream.compose` accepts two or more streams, or one `Duplex`; functions, iterables, and web streams as stages are unsupported, and the `ERR_INVALID_ARG_VALUE` message for a stream that is no longer readable or writable names the stream's class instead of printing the stream. `pipeline` from `node:stream/promises` accepts a trailing `{ signal, end }` object, and `finished` accepts `readable`, `writable`, `error`, `cleanup`, and `signal` options, with boolean literals for the flags. Stream constructors accept a `signal` option in an options object literal, and `stream.addAbortSignal` accepts any stream. `node:readline` and `node:readline/promises` (also reached as `readline.promises`) interfaces, from `createInterface(options)` or `createInterface(input, output, completer, terminal)`, read `process.stdin` or a readable stream: `question`, `prompt`, `write` (with a key object literal of `name`, `ctrl`, `meta`, and `shift`), `pause`, `resume`, `'line'`/`'close'`/`'pause'`/`'resume'`/`'error'`/`'history'`/`'SIGINT'`/`'SIGTSTP'`/`'SIGCONT'` listeners, and `for await` compile. The `history`, `historySize`, `removeHistoryDuplicates`, `completer`, `tabSize`, and `escapeCodeTimeout` options are validated as in Node. With `terminal: true`, or with no `terminal` option and `process.stdout` as the output when it is a TTY, the interface runs Node's terminal mode: the editing keys, the kill ring, undo and redo, history recording and recall, Tab completion through the completer, Ctrl+C, Ctrl+D, and Ctrl+Z, `TERM=dumb`, and the same escape sequences written to the output. The output's `'resize'` event does not redraw the line. The cursor never stops between the two UTF-16 units of a supplementary character: where Node's Up or Down movement through a multiline line would, the cursor moves to the character's start or end. In JavaScript, a `'history'` listener receives a copy of the history; its changes are written back when the listener returns, but the copy is not `rl.history` itself. `rl.history`, `historySize`, `historyIndex`, `crlfDelay`, `tabSize`, `escapeCodeTimeout`, `line`, `cursor`, and `terminal` read as in Node, except that `rl.cursor` of an interface that is not a terminal throws instead of answering `undefined`. `readline.cursorTo`, `moveCursor`, `clearLine`, and `clearScreenDown` write to `process.stdout`, `process.stderr`, a writable stream, or nothing for `null` and `undefined`; their callbacks take no parameters. The `readline/promises` `Readline` class writes to the same outputs. `readline.emitKeypressEvents` emits `'keypress'` events on `process.stdin` or a readable stream, shared with terminal interfaces on that stream as in Node; a listener's first parameter must admit `undefined`, which it receives for escape sequences. Chained listener registration is unsupported. +- **Streams and readline:** Stream chunks are Buffers or strings. `Readable.from` accepts arrays, Sets, Map and Set iterators, generators, async generators, and class iterables and async iterables whose values are strings or Buffers, and in JavaScript any iterable or async iterable of them; a stream that mixes strings and Buffers is unsupported. In JavaScript, a sync iterator value with a `then` method that is not a promise, and an object whose `[Symbol.asyncIterator]` or `[Symbol.iterator]` method is a generator method, are unsupported. Object-mode streams of other values (including `Readable.from` over a `Map`, whose values are entry arrays) are unsupported. `pipeline` accepts one run of function stages: async generator functions between streams (`async function* (source)`, iterating `source` with `for await`), an async generator source function (`async function* ({ signal })`), and an async function destination whose value resolves the promise form and reaches the callback's second argument. Stages may take the `{ signal }` argument. An async generator function as the destination, using the PassThrough that `pipeline()` returns for a function destination, several runs of function stages, and function stages together with pipeline options are unsupported. `Readable.from` accepts `objectMode` and `highWaterMark` options. The `Readable` operators (`map`, `filter`, `forEach`, `toArray`, `some`, `find`, `every`, `flatMap`, `drop`, `take`, and `reduce`) accept `concurrency`, `highWaterMark`, and `signal` options; their callbacks take only the chunk, `map` returns strings or Buffers, and `flatMap` returns strings or arrays of them. `readable.compose` is unsupported. `stream.compose` accepts two or more streams, or one `Duplex`; functions, iterables, and web streams as stages are unsupported, and the `ERR_INVALID_ARG_VALUE` message for a stream that is no longer readable or writable names the stream's class instead of printing the stream. `pipeline` from `node:stream/promises` accepts a trailing `{ signal, end }` object, and `finished` accepts `readable`, `writable`, `error`, `cleanup`, and `signal` options, with boolean literals for the flags. Stream constructors accept a `signal` option in an options object literal, and `stream.addAbortSignal` accepts any stream. `node:readline` and `node:readline/promises` (also reached as `readline.promises`) interfaces, from `createInterface(options)` or `createInterface(input, output, completer, terminal)`, read `process.stdin` or a readable stream: `question`, `prompt`, `write` (with a key object literal of `name`, `ctrl`, `meta`, and `shift`), `pause`, `resume`, `'line'`/`'close'`/`'pause'`/`'resume'`/`'error'`/`'history'`/`'SIGINT'`/`'SIGTSTP'`/`'SIGCONT'` listeners, and `for await` compile. The `history`, `historySize`, `removeHistoryDuplicates`, `completer`, `tabSize`, and `escapeCodeTimeout` options are validated as in Node. With `terminal: true`, or with no `terminal` option and `process.stdout` as the output when it is a TTY, the interface runs Node's terminal mode: the editing keys, the kill ring, undo and redo, history recording and recall, Tab completion through the completer, Ctrl+C, Ctrl+D, and Ctrl+Z, `TERM=dumb`, and the same escape sequences written to the output. The output's `'resize'` event does not redraw the line. The cursor never stops between the two UTF-16 units of a supplementary character: where Node's Up or Down movement through a multiline line would, the cursor moves to the character's start or end. In JavaScript, a `'history'` listener receives a copy of the history; its changes are written back when the listener returns, but the copy is not `rl.history` itself. `rl.history`, `historySize`, `historyIndex`, `crlfDelay`, `tabSize`, `escapeCodeTimeout`, `line`, `cursor`, and `terminal` read as in Node, except that `rl.cursor` of an interface that is not a terminal throws instead of answering `undefined`. `readline.cursorTo`, `moveCursor`, `clearLine`, and `clearScreenDown` write to `process.stdout`, `process.stderr`, a writable stream, or nothing for `null` and `undefined`; their callbacks take no parameters. The `readline/promises` `Readline` class writes to the same outputs. `readline.emitKeypressEvents` emits `'keypress'` events on `process.stdin` or a readable stream, shared with terminal interfaces on that stream as in Node; a listener's first parameter must admit `undefined`, which it receives for escape sequences. Chained listener registration is unsupported. `node:stream/web` exports the same `ReadableStream` class as the global; its other classes are unsupported. diff --git a/docs/content/limitations/builtin-apis/v8.mdx b/docs/content/limitations/builtin-apis/v8.mdx new file mode 100644 index 000000000..b28b6bacd --- /dev/null +++ b/docs/content/limitations/builtin-apis/v8.mdx @@ -0,0 +1 @@ +- **V8 heap statistics:** `node:v8` can be imported. `v8.getHeapStatistics()` returns Node's fields in Node's order, computed from the runtime allocator like `process.memoryUsage()`: `total_heap_size` and `total_physical_size` are the bytes the heap holds, `used_heap_size` the bytes allocated, `heap_size_limit` the machine's memory (the heap has no fixed ceiling), and `total_available_size` the limit minus the used bytes. Fields that describe V8 itself (`total_heap_size_executable`, `malloced_memory`, `peak_malloced_memory`, the global handle sizes, `external_memory`, and `does_zap_garbage`) are `0`, and `number_of_native_contexts` is `1`. The values are approximate and are not V8 heap figures. The other `node:v8` members observe the V8 engine, such as heap snapshots, profiles, flags, and serialization, and throw `SC2020` where they are used. diff --git a/docs/content/limitations/dns-lookup-errors.mdx b/docs/content/limitations/dns-lookup-errors.mdx index e686ad47a..97d8bc549 100644 --- a/docs/content/limitations/dns-lookup-errors.mdx +++ b/docs/content/limitations/dns-lookup-errors.mdx @@ -1 +1 @@ -After a failed `dns.lookup` or `dns.lookupService`, Node calls the callback with the error alone, so the address and family parameters are `undefined`. A callback whose address parameter is typed `string` (as Node's own type declarations type it) receives `""` and `NaN` there, or an empty array for `all: true`, because a typed string or number cannot hold `undefined`. An untyped callback receives the error alone, as in Node. +After a failed `dns.lookup` or `dns.lookupService`, Node calls the callback with the error alone, so the address and family parameters are `undefined`. A callback whose address parameter is typed `string` (as Node's own type declarations type it) receives `""` and `NaN` there, or an empty array for `all: true`, because a typed string or number cannot hold `undefined`. An untyped callback receives the error alone, as in Node. The promise forms in `node:dns/promises` (also reached as `dns.promises`) reject with the error and are unaffected. diff --git a/docs/content/limitations/graceful-fs.mdx b/docs/content/limitations/graceful-fs.mdx new file mode 100644 index 000000000..a337486eb --- /dev/null +++ b/docs/content/limitations/graceful-fs.mdx @@ -0,0 +1 @@ +The `graceful-fs` package compiles as `node:fs`. Its imports and `require("graceful-fs")` calls resolve to the builtin module, so the package's JavaScript is not compiled, and `gracefulify(fs)` as a statement does nothing. Programs behave as with the package except under file-descriptor exhaustion: graceful-fs queues operations that fail with `EMFILE` or `ENFILE` and retries them when a descriptor closes, while a compiled program reports the error to its caller, as `fs` does. In a compiled program, `require("graceful-fs") === require("fs")` is `true`. diff --git a/docs/content/limitations/runtime-code-evaluation.mdx b/docs/content/limitations/runtime-code-evaluation.mdx index a00e0727d..66ad448f1 100644 --- a/docs/content/limitations/runtime-code-evaluation.mdx +++ b/docs/content/limitations/runtime-code-evaluation.mdx @@ -1 +1 @@ -`eval`, `new Function`, and other ways of running JavaScript source at runtime are unsupported, in application code and in packages. Rewrite code that depends on them, or replace the package. To compute a value from code at build time instead, use `comptime`. See [npm Dependencies](/docs/dependencies#which-packages-compile) for which packages compile. +A compiled program embeds no JavaScript engine, so it cannot run JavaScript source at runtime. `eval`, indirect `eval`, `new Function`, and `Function` behave as they do under `node --disallow-code-generation-from-strings`, a deliberate difference from default Node.js: every call throws a catchable `EvalError` with the message `Code generation from strings disallowed for this context`. The arguments are evaluated first, and the Function constructor converts them to strings before it throws, as Node.js does. Packages that test for code generation before using it, such as zod 4, take their interpreted path. Code that needs to evaluate source at runtime must be rewritten; to compute a value from code at build time, use `comptime`. The `vm` module is refused for the same reason. See [npm Dependencies](/docs/dependencies#which-packages-compile) for which packages compile. diff --git a/docs/src/generated/node-v24-compatibility-meta.json b/docs/src/generated/node-v24-compatibility-meta.json index f2bb89f6e..223e9c854 100644 --- a/docs/src/generated/node-v24-compatibility-meta.json +++ b/docs/src/generated/node-v24-compatibility-meta.json @@ -2,6 +2,6 @@ "schemaVersion": 3, "nodeVersion": "24.22.0", "nodeCommit": "f14cd3aa7bf9ca8df2cc5b8bb14beb0f335b1d93", - "artifactVersion": "be8d5b094695385e34ea", + "artifactVersion": "f88d62ab68019e678e6b", "rowCount": 3880 } diff --git a/docs/src/generated/node-v24-compatibility.json b/docs/src/generated/node-v24-compatibility.json index 3c0350dc3..ed7d90295 100644 --- a/docs/src/generated/node-v24-compatibility.json +++ b/docs/src/generated/node-v24-compatibility.json @@ -239,9 +239,9 @@ "apiEntries": 51, "static": { "supported": 0, - "partial": 0, + "partial": 10, "refused": 0, - "not-implemented": 51, + "not-implemented": 41, "by-design": 0, "unreviewed": 0, "not-applicable": 0 @@ -809,10 +809,10 @@ "apiEntries": 68, "static": { "supported": 0, - "partial": 3, + "partial": 4, "refused": 0, "not-implemented": 0, - "by-design": 65, + "by-design": 64, "unreviewed": 0, "not-applicable": 0 } @@ -869,9 +869,9 @@ "apiEntries": 89, "static": { "supported": 0, - "partial": 7, + "partial": 8, "refused": 1, - "not-implemented": 81, + "not-implemented": 80, "by-design": 0, "unreviewed": 0, "not-applicable": 0 @@ -16626,7 +16626,7 @@ "inherited": false }, "static": { - "status": "not-implemented", + "status": "partial", "detail": "Derived from the exact API rows in this chapter.", "verification": "derived" } @@ -16718,9 +16718,9 @@ "inherited": true }, "static": { - "status": "not-implemented", - "detail": "No static compiler lowering is registered for this API yet.", - "verification": "registry-gap" + "status": "partial", + "detail": "Implemented for the documented scriptc module-loader subset.", + "verification": "test-backed" } }, { @@ -16741,9 +16741,9 @@ "inherited": true }, "static": { - "status": "not-implemented", - "detail": "No static compiler lowering is registered for this API yet.", - "verification": "registry-gap" + "status": "partial", + "detail": "Implemented for the documented scriptc module-loader subset.", + "verification": "test-backed" } }, { @@ -17132,9 +17132,9 @@ "inherited": true }, "static": { - "status": "not-implemented", - "detail": "No static compiler lowering is registered for this API yet.", - "verification": "registry-gap" + "status": "partial", + "detail": "Implemented for the documented scriptc module-loader subset.", + "verification": "test-backed" } }, { @@ -17201,9 +17201,9 @@ "inherited": true }, "static": { - "status": "not-implemented", - "detail": "No static compiler lowering is registered for this API yet.", - "verification": "registry-gap" + "status": "partial", + "detail": "Implemented for the documented scriptc module-loader subset.", + "verification": "test-backed" } }, { @@ -17316,9 +17316,9 @@ "inherited": true }, "static": { - "status": "not-implemented", - "detail": "No static compiler lowering is registered for this API yet.", - "verification": "registry-gap" + "status": "partial", + "detail": "Implemented for the documented scriptc module-loader subset.", + "verification": "test-backed" } }, { @@ -17339,9 +17339,9 @@ "inherited": true }, "static": { - "status": "not-implemented", - "detail": "No static compiler lowering is registered for this API yet.", - "verification": "registry-gap" + "status": "partial", + "detail": "Implemented for the documented scriptc module-loader subset.", + "verification": "test-backed" } }, { @@ -17362,9 +17362,9 @@ "inherited": true }, "static": { - "status": "not-implemented", - "detail": "No static compiler lowering is registered for this API yet.", - "verification": "registry-gap" + "status": "partial", + "detail": "Implemented for the documented scriptc module-loader subset.", + "verification": "test-backed" } }, { @@ -17730,9 +17730,9 @@ "inherited": true }, "static": { - "status": "not-implemented", - "detail": "No static compiler lowering is registered for this API yet.", - "verification": "registry-gap" + "status": "partial", + "detail": "Implemented for the documented scriptc module-loader subset.", + "verification": "test-backed" } }, { @@ -17753,9 +17753,9 @@ "inherited": true }, "static": { - "status": "not-implemented", - "detail": "No static compiler lowering is registered for this API yet.", - "verification": "registry-gap" + "status": "partial", + "detail": "Implemented for the documented scriptc module-loader subset.", + "verification": "test-backed" } }, { @@ -75973,9 +75973,9 @@ "anchor": "v8getheapstatistics", "nodeStability": null, "static": { - "status": "by-design", - "detail": "Intentionally outside the static native execution model.", - "verification": "architectural-policy" + "status": "partial", + "detail": "Implemented for the call shapes accepted by the compiler lowering.", + "verification": "test-backed" } }, { @@ -81011,9 +81011,9 @@ "inherited": true }, "static": { - "status": "not-implemented", - "detail": "No static compiler lowering is registered for this API yet.", - "verification": "registry-gap" + "status": "partial", + "detail": "Implemented for the documented static async-iteration subset.", + "verification": "test-backed" } }, { diff --git a/internal/compatibility/generated/node-v24-backlog.json b/internal/compatibility/generated/node-v24-backlog.json index 24821377e..d37c46a3b 100644 --- a/internal/compatibility/generated/node-v24-backlog.json +++ b/internal/compatibility/generated/node-v24-backlog.json @@ -3,12 +3,12 @@ "nodeVersion": "24.22.0", "nodeCommit": "f14cd3aa7bf9ca8df2cc5b8bb14beb0f335b1d93", "summary": { - "taskCount": 3456, - "tierItemCount": 3456, + "taskCount": 3457, + "tierItemCount": 3457, "tiers": { "static": { - "audit-partial": 1144, - "verify-gap": 2185, + "audit-partial": 1155, + "verify-gap": 2175, "replace-refusal": 40, "classify": 2, "implement": 85 @@ -16,7 +16,7 @@ }, "priorities": { "high": 2289, - "normal": 694, + "normal": 695, "low": 473 } }, @@ -12294,12 +12294,15 @@ "priority": "high", "tiers": { "static": { - "status": "not-implemented", - "action": "verify-gap", - "verification": "registry-gap", - "confidence": "low", - "source": "compiler-unmatched:dns", - "tests": [] + "status": "partial", + "action": "audit-partial", + "verification": "test-backed", + "confidence": "high", + "source": "compiler-feature:dns.dnsPromises.lookup", + "tests": [ + "tests/corpus/dns-promises-lookup.ts", + "tests/corpus/dns-promises-untyped.cjs" + ] } } }, @@ -12319,12 +12322,15 @@ "priority": "high", "tiers": { "static": { - "status": "not-implemented", - "action": "verify-gap", - "verification": "registry-gap", - "confidence": "low", - "source": "compiler-unmatched:dns", - "tests": [] + "status": "partial", + "action": "audit-partial", + "verification": "test-backed", + "confidence": "high", + "source": "compiler-feature:dns.dnsPromises.lookupService", + "tests": [ + "tests/corpus/dns-promises-lookup.ts", + "tests/corpus/dns-promises-untyped.cjs" + ] } } }, @@ -12744,12 +12750,15 @@ "priority": "high", "tiers": { "static": { - "status": "not-implemented", - "action": "verify-gap", - "verification": "registry-gap", - "confidence": "low", - "source": "compiler-unmatched:dns", - "tests": [] + "status": "partial", + "action": "audit-partial", + "verification": "test-backed", + "confidence": "high", + "source": "compiler-feature:dns.dnsPromises.getDefaultResultOrder", + "tests": [ + "tests/corpus/dns-promises-lookup.ts", + "tests/corpus/dns-promises-untyped.cjs" + ] } } }, @@ -12819,12 +12828,16 @@ "priority": "high", "tiers": { "static": { - "status": "not-implemented", - "action": "verify-gap", - "verification": "registry-gap", - "confidence": "low", - "source": "compiler-unmatched:dns", - "tests": [] + "status": "partial", + "action": "audit-partial", + "verification": "test-backed", + "confidence": "high", + "source": "compiler-feature:dns.dns.lookup", + "tests": [ + "tests/corpus/5648-dns-lookup-forms.ts", + "tests/corpus/5649-dns-service-servers.ts", + "tests/corpus/5650-dns-lookup-errors-untyped.cjs" + ] } } }, @@ -12944,12 +12957,16 @@ "priority": "high", "tiers": { "static": { - "status": "not-implemented", - "action": "verify-gap", - "verification": "registry-gap", - "confidence": "low", - "source": "compiler-unmatched:dns", - "tests": [] + "status": "partial", + "action": "audit-partial", + "verification": "test-backed", + "confidence": "high", + "source": "compiler-feature:dns.dns.getServers", + "tests": [ + "tests/corpus/5648-dns-lookup-forms.ts", + "tests/corpus/5649-dns-service-servers.ts", + "tests/corpus/5650-dns-lookup-errors-untyped.cjs" + ] } } }, @@ -12969,12 +12986,16 @@ "priority": "high", "tiers": { "static": { - "status": "not-implemented", - "action": "verify-gap", - "verification": "registry-gap", - "confidence": "low", - "source": "compiler-unmatched:dns", - "tests": [] + "status": "partial", + "action": "audit-partial", + "verification": "test-backed", + "confidence": "high", + "source": "compiler-feature:dns.dns.lookup", + "tests": [ + "tests/corpus/5648-dns-lookup-forms.ts", + "tests/corpus/5649-dns-service-servers.ts", + "tests/corpus/5650-dns-lookup-errors-untyped.cjs" + ] } } }, @@ -12994,12 +13015,16 @@ "priority": "high", "tiers": { "static": { - "status": "not-implemented", - "action": "verify-gap", - "verification": "registry-gap", - "confidence": "low", - "source": "compiler-unmatched:dns", - "tests": [] + "status": "partial", + "action": "audit-partial", + "verification": "test-backed", + "confidence": "high", + "source": "compiler-feature:dns.dns.lookupService", + "tests": [ + "tests/corpus/5648-dns-lookup-forms.ts", + "tests/corpus/5649-dns-service-servers.ts", + "tests/corpus/5650-dns-lookup-errors-untyped.cjs" + ] } } }, @@ -13394,12 +13419,16 @@ "priority": "high", "tiers": { "static": { - "status": "not-implemented", - "action": "verify-gap", - "verification": "registry-gap", - "confidence": "low", - "source": "compiler-unmatched:dns", - "tests": [] + "status": "partial", + "action": "audit-partial", + "verification": "test-backed", + "confidence": "high", + "source": "compiler-feature:dns.dns.setDefaultResultOrder", + "tests": [ + "tests/corpus/5648-dns-lookup-forms.ts", + "tests/corpus/5649-dns-service-servers.ts", + "tests/corpus/5650-dns-lookup-errors-untyped.cjs" + ] } } }, @@ -13419,12 +13448,16 @@ "priority": "high", "tiers": { "static": { - "status": "not-implemented", - "action": "verify-gap", - "verification": "registry-gap", - "confidence": "low", - "source": "compiler-unmatched:dns", - "tests": [] + "status": "partial", + "action": "audit-partial", + "verification": "test-backed", + "confidence": "high", + "source": "compiler-feature:dns.dns.getDefaultResultOrder", + "tests": [ + "tests/corpus/5648-dns-lookup-forms.ts", + "tests/corpus/5649-dns-service-servers.ts", + "tests/corpus/5650-dns-lookup-errors-untyped.cjs" + ] } } }, @@ -77687,6 +77720,29 @@ } } }, + { + "id": "v8:cefde4a8297c", + "chapter": "v8", + "label": "v8.getHeapStatistics()", + "signature": "`v8.getHeapStatistics()`", + "apiSymbol": "v8.getHeapStatistics", + "docsUrl": "https://nodejs.org/docs/v24.22.0/api/v8.html#v8getheapstatistics", + "nodeStability": null, + "priority": "normal", + "tiers": { + "static": { + "status": "partial", + "action": "audit-partial", + "verification": "test-backed", + "confidence": "high", + "source": "surface-manifest:node-builtin.v8.getHeapStatistics", + "tests": [ + "tests/corpus/v8-heap-statistics.ts", + "tests/corpus/v8-require-optional.cjs" + ] + } + } + }, { "id": "webcrypto:22408f834c48", "chapter": "webcrypto", @@ -81804,12 +81860,16 @@ "priority": "high", "tiers": { "static": { - "status": "not-implemented", - "action": "verify-gap", - "verification": "registry-gap", - "confidence": "low", - "source": "compiler-unmatched:webstreams", - "tests": [] + "status": "partial", + "action": "audit-partial", + "verification": "test-backed", + "confidence": "high", + "source": "compiler-feature:webstreams.ReadableStream", + "tests": [ + "tests/corpus/stream-web-readable.ts", + "tests/corpus/stream-web-require.cjs", + "tests/corpus/2862-web-readable-async-iteration.ts" + ] } } }, diff --git a/internal/compatibility/generated/node-v24-internal.json b/internal/compatibility/generated/node-v24-internal.json index 3587ae2cb..c630c88e1 100644 --- a/internal/compatibility/generated/node-v24-internal.json +++ b/internal/compatibility/generated/node-v24-internal.json @@ -251,9 +251,9 @@ "apiEntries": 51, "static": { "supported": 0, - "partial": 0, + "partial": 10, "refused": 0, - "not-implemented": 51, + "not-implemented": 41, "by-design": 0, "unreviewed": 0, "not-applicable": 0 @@ -866,10 +866,10 @@ "apiEntries": 68, "static": { "supported": 0, - "partial": 3, + "partial": 4, "refused": 0, "not-implemented": 0, - "by-design": 65, + "by-design": 64, "unreviewed": 0, "not-applicable": 0 } @@ -926,9 +926,9 @@ "apiEntries": 89, "static": { "supported": 0, - "partial": 7, + "partial": 8, "refused": 1, - "not-implemented": 81, + "not-implemented": 80, "by-design": 0, "unreviewed": 0, "not-applicable": 0 @@ -30673,8 +30673,15 @@ "inherited": false }, "static": { - "status": "not-implemented", - "evidence": "derived:descendants" + "status": "partial", + "evidence": "derived:descendants", + "tests": [ + "tests/corpus/5648-dns-lookup-forms.ts", + "tests/corpus/5649-dns-service-servers.ts", + "tests/corpus/5650-dns-lookup-errors-untyped.cjs", + "tests/corpus/dns-promises-lookup.ts", + "tests/corpus/dns-promises-untyped.cjs" + ] }, "anchorSource": "chapter" }, @@ -30788,8 +30795,12 @@ "inherited": true }, "static": { - "status": "not-implemented", - "evidence": "compiler-unmatched:dns" + "status": "partial", + "evidence": "compiler-feature:dns.dnsPromises.lookup", + "tests": [ + "tests/corpus/dns-promises-lookup.ts", + "tests/corpus/dns-promises-untyped.cjs" + ] }, "anchorSource": "exact" }, @@ -30811,8 +30822,12 @@ "inherited": true }, "static": { - "status": "not-implemented", - "evidence": "compiler-unmatched:dns" + "status": "partial", + "evidence": "compiler-feature:dns.dnsPromises.lookupService", + "tests": [ + "tests/corpus/dns-promises-lookup.ts", + "tests/corpus/dns-promises-untyped.cjs" + ] }, "anchorSource": "exact" }, @@ -31202,8 +31217,12 @@ "inherited": true }, "static": { - "status": "not-implemented", - "evidence": "compiler-unmatched:dns" + "status": "partial", + "evidence": "compiler-feature:dns.dnsPromises.getDefaultResultOrder", + "tests": [ + "tests/corpus/dns-promises-lookup.ts", + "tests/corpus/dns-promises-untyped.cjs" + ] }, "anchorSource": "exact" }, @@ -31317,8 +31336,13 @@ "inherited": true }, "static": { - "status": "not-implemented", - "evidence": "compiler-unmatched:dns" + "status": "partial", + "evidence": "compiler-feature:dns.dns.lookup", + "tests": [ + "tests/corpus/5648-dns-lookup-forms.ts", + "tests/corpus/5649-dns-service-servers.ts", + "tests/corpus/5650-dns-lookup-errors-untyped.cjs" + ] }, "anchorSource": "exact" }, @@ -31432,8 +31456,13 @@ "inherited": true }, "static": { - "status": "not-implemented", - "evidence": "compiler-unmatched:dns" + "status": "partial", + "evidence": "compiler-feature:dns.dns.getServers", + "tests": [ + "tests/corpus/5648-dns-lookup-forms.ts", + "tests/corpus/5649-dns-service-servers.ts", + "tests/corpus/5650-dns-lookup-errors-untyped.cjs" + ] }, "anchorSource": "exact" }, @@ -31455,8 +31484,13 @@ "inherited": true }, "static": { - "status": "not-implemented", - "evidence": "compiler-unmatched:dns" + "status": "partial", + "evidence": "compiler-feature:dns.dns.lookup", + "tests": [ + "tests/corpus/5648-dns-lookup-forms.ts", + "tests/corpus/5649-dns-service-servers.ts", + "tests/corpus/5650-dns-lookup-errors-untyped.cjs" + ] }, "anchorSource": "exact" }, @@ -31501,8 +31535,13 @@ "inherited": true }, "static": { - "status": "not-implemented", - "evidence": "compiler-unmatched:dns" + "status": "partial", + "evidence": "compiler-feature:dns.dns.lookupService", + "tests": [ + "tests/corpus/5648-dns-lookup-forms.ts", + "tests/corpus/5649-dns-service-servers.ts", + "tests/corpus/5650-dns-lookup-errors-untyped.cjs" + ] }, "anchorSource": "exact" }, @@ -31869,8 +31908,13 @@ "inherited": true }, "static": { - "status": "not-implemented", - "evidence": "compiler-unmatched:dns" + "status": "partial", + "evidence": "compiler-feature:dns.dns.setDefaultResultOrder", + "tests": [ + "tests/corpus/5648-dns-lookup-forms.ts", + "tests/corpus/5649-dns-service-servers.ts", + "tests/corpus/5650-dns-lookup-errors-untyped.cjs" + ] }, "anchorSource": "exact" }, @@ -31892,8 +31936,13 @@ "inherited": true }, "static": { - "status": "not-implemented", - "evidence": "compiler-unmatched:dns" + "status": "partial", + "evidence": "compiler-feature:dns.dns.getDefaultResultOrder", + "tests": [ + "tests/corpus/5648-dns-lookup-forms.ts", + "tests/corpus/5649-dns-service-servers.ts", + "tests/corpus/5650-dns-lookup-errors-untyped.cjs" + ] }, "anchorSource": "exact" }, @@ -104223,6 +104272,8 @@ "status": "partial", "evidence": "derived:descendants", "tests": [ + "tests/corpus/v8-heap-statistics.ts", + "tests/corpus/v8-require-optional.cjs", "tests/harness/node-test.test.ts" ] }, @@ -105345,8 +105396,12 @@ "anchor": "v8getheapstatistics", "nodeStability": null, "static": { - "status": "by-design", - "evidence": "compiler-chapter-policy:v8" + "status": "partial", + "evidence": "surface-manifest:node-builtin.v8.getHeapStatistics", + "tests": [ + "tests/corpus/v8-heap-statistics.ts", + "tests/corpus/v8-require-optional.cjs" + ] }, "anchorSource": "exact" }, @@ -110875,6 +110930,8 @@ "tests/corpus/2630-stream-consumers-errors.ts", "tests/corpus/2862-web-readable-async-iteration.ts", "tests/corpus/4875-stream-consumers-array-buffer.ts", + "tests/corpus/stream-web-readable.ts", + "tests/corpus/stream-web-require.cjs", "tests/harness/fetch-conformance.test.ts" ] }, @@ -111143,8 +111200,13 @@ "inherited": true }, "static": { - "status": "not-implemented", - "evidence": "compiler-unmatched:webstreams" + "status": "partial", + "evidence": "compiler-feature:webstreams.ReadableStream", + "tests": [ + "tests/corpus/stream-web-readable.ts", + "tests/corpus/stream-web-require.cjs", + "tests/corpus/2862-web-readable-async-iteration.ts" + ] }, "anchorSource": "exact" }, diff --git a/internal/compatibility/static-support.json b/internal/compatibility/static-support.json index fded9de97..43649b548 100644 --- a/internal/compatibility/static-support.json +++ b/internal/compatibility/static-support.json @@ -47,6 +47,10 @@ "node-builtin.dgram": ["tests/corpus/2597-dgram-send-ladders.cjs"], "node-builtin.diagnostics_channel": ["tests/corpus/2214-dc-bind-store.cjs"], "node-builtin.dns": ["tests/corpus/2597-dgram-send-ladders.cjs"], + "node-builtin.dns.promises": ["tests/corpus/dns-promises-lookup.ts", "tests/corpus/dns-promises-untyped.cjs"], + "node-builtin.stream.web": ["tests/corpus/stream-web-readable.ts", "tests/corpus/stream-web-require.cjs"], + "node-builtin.v8": ["tests/corpus/v8-require-optional.cjs"], + "node-builtin.v8.getHeapStatistics": ["tests/corpus/v8-heap-statistics.ts", "tests/corpus/v8-require-optional.cjs"], "node-builtin.events": ["tests/corpus/1677-emitter-listeners.ts", "tests/corpus/2620-ee-override-once-order.ts"], "node-builtin.fs": ["tests/corpus/1403-buffer-fs.ts", "tests/corpus/2595-fs-arg-ladders.cjs", "tests/corpus/2919-fs-function-values.ts", "tests/corpus/2920-fs-promises-function-values.ts"], "node-builtin.fs.realpathSync.native": ["tests/corpus/fs-native-realpath.ts"], @@ -367,7 +371,10 @@ { "chapter": "util", "signatures": ["`util.promisify(original)`"], "status": "partial", "evidence": ["tests/corpus/1473-promisify-execfile.ts", "tests/corpus/1573-promisify-execfile-env-spread.ts", "tests/corpus/2912-promisify-fs-readfile.ts", "tests/corpus/5625-promisify-exec.ts", "tests/corpus/5626-promisify-forms.ts", "tests/fixtures/node-types/promisify-fs.ts", "tests/corpus/5628-promisify-timers.ts", "tests/fixtures/node-types/promisify-timers.ts", "tests/corpus/5629-promisify-stream.ts", "tests/corpus/5630-promisify-zlib.ts", "tests/fixtures/node-types/promisify-zlib.ts", "tests/harness/project-config.test.ts"] }, { "chapter": "typescript", "symbols": ["type"], "status": "supported", "evidence": ["tests/corpus/2702-type-import-link-static/main.ts", "tests/corpus/2703-type-import-link-static-reexport/main.ts", "tests/corpus/2704-type-import-link-static-success/main.ts", "tests/corpus/2707-type-import-link-static-default-reexport/main.ts", "tests/harness/errors.test.ts"] }, { "chapter": "zlib", "signatures": ["Class: `zlib.Deflate`", "Class: `zlib.Inflate`", "Class: `zlib.Gzip`", "Class: `zlib.Gunzip`", "Class: `zlib.DeflateRaw`", "Class: `zlib.InflateRaw`", "Class: `zlib.Unzip`", "Class: `zlib.ZlibBase`", "`zlib.close([callback])`", "`zlib.flush([kind, ]callback)`", "`zlib.params(level, strategy, callback)`", "`zlib.reset()`", "`bytesWritten` Type: {number}", "Class: `Options`"], "status": "partial", "evidence": ["tests/corpus/5370-zlib-stream-roundtrip.ts", "tests/corpus/5371-zlib-stream-chunking.cjs", "tests/corpus/5372-zlib-stream-errors.cjs", "tests/corpus/5373-zlib-stream-options.cjs", "tests/corpus/5374-zlib-stream-flush-params.ts", "tests/corpus/5375-zlib-stream-promises.ts", "tests/corpus/5377-zlib-stream-backpressure.cjs"] }, - { "chapter": "zlib", "signatures": ["`zlib.constants`"], "status": "partial", "evidence": ["tests/corpus/5376-zlib-constants.cjs", "tests/corpus/5374-zlib-stream-flush-params.ts"] } + { "chapter": "zlib", "signatures": ["`zlib.constants`"], "status": "partial", "evidence": ["tests/corpus/5376-zlib-constants.cjs", "tests/corpus/5374-zlib-stream-flush-params.ts"] }, + { "chapter": "dns", "symbols": ["dns.lookup", "dns.lookupService", "dns.getServers", "dns.getDefaultResultOrder", "dns.setDefaultResultOrder"], "status": "partial", "evidence": ["tests/corpus/5648-dns-lookup-forms.ts", "tests/corpus/5649-dns-service-servers.ts", "tests/corpus/5650-dns-lookup-errors-untyped.cjs"] }, + { "chapter": "dns", "symbols": ["dnsPromises.lookup", "dnsPromises.lookupService", "dnsPromises.getDefaultResultOrder"], "status": "partial", "evidence": ["tests/corpus/dns-promises-lookup.ts", "tests/corpus/dns-promises-untyped.cjs"] }, + { "chapter": "webstreams", "symbols": ["ReadableStream"], "status": "partial", "evidence": ["tests/corpus/stream-web-readable.ts", "tests/corpus/stream-web-require.cjs", "tests/corpus/2862-web-readable-async-iteration.ts"] } ], "dedicated": [ { "symbols": ["assert.doesNotThrow", "assert.doesNotReject"], "status": "partial", "evidence": ["tests/corpus/node24-no-error-assertions.ts", "tests/corpus/node24-no-error-assertions.cjs", "tests/corpus/node24-no-error-assertions-coercion.js", "tests/fixtures/node-types/no-error-assertions.ts", "tests/harness/project-config.test.ts"] }, diff --git a/packages/compiler/ambient/scriptc-node-fallback.d.ts b/packages/compiler/ambient/scriptc-node-fallback.d.ts index 69093a600..a0018ca5f 100644 --- a/packages/compiler/ambient/scriptc-node-fallback.d.ts +++ b/packages/compiler/ambient/scriptc-node-fallback.d.ts @@ -1728,6 +1728,13 @@ declare module "node:fs" { declare module "fs" { export * from "node:fs"; } +/* graceful-fs — served as node:fs (it monkeypatches fs and retries on + * EMFILE/ENFILE; compiled programs call fs directly, without the retry + * queue). gracefulify is a no-op statement. The @types/graceful-fs shape. */ +declare module "graceful-fs" { + export * from "node:fs"; + export function gracefulify(fsModule: T): T; +} /* fs/promises — the SAME synchronous operations behind already-settled * promises: success fulfills, failure REJECTS with the would-be thrown @@ -5835,10 +5842,79 @@ declare module "dns" { export function getServers(): string[]; export function getDefaultResultOrder(): "ipv4first" | "ipv6first" | "verbatim"; export function setDefaultResultOrder(order: "ipv4first" | "ipv6first" | "verbatim"): void; + /* The promise forms (node:dns/promises). */ + export const promises: typeof import("dns/promises"); } declare module "node:dns" { export * from "dns"; } +/* node:dns/promises — lookup and lookupService as promises over the same + * resolution as the callback forms: argument errors throw synchronously, + * failures reject with the callbacks' errors. */ +declare module "dns/promises" { + import type { LookupAddress, LookupAllOptions, LookupOneOptions, LookupOptions } from "dns"; + export function lookup(hostname: string, family: number): Promise; + export function lookup(hostname: string, options: LookupOneOptions): Promise; + export function lookup(hostname: string, options: LookupAllOptions): Promise; + export function lookup( + hostname: string, + options: LookupOptions, + ): Promise; + export function lookup(hostname: string): Promise; + export function lookupService( + address: string, + port: number, + ): Promise<{ hostname: string; service: string }>; + export function getServers(): string[]; + export function getDefaultResultOrder(): "ipv4first" | "ipv6first" | "verbatim"; + export function setDefaultResultOrder(order: "ipv4first" | "ipv6first" | "verbatim"): void; +} +declare module "node:dns/promises" { + export * from "dns/promises"; +} + +/* node:v8 — importable for the packages that require it at top level. + * getHeapStatistics answers allocator-derived figures in Node's shape + * (approximate: the runtime heap's total and used bytes, the machine's + * memory as the limit; V8-only figures are 0). The other members observe + * V8 itself and fence at their use with the by-design reason; they are + * declared so programs that type-check under their own tsc reach that + * fence. */ +declare module "v8" { + type DoesZapCodeSpaceFlag = 0 | 1; + export interface HeapInfo { + total_heap_size: number; + total_heap_size_executable: number; + total_physical_size: number; + total_available_size: number; + used_heap_size: number; + heap_size_limit: number; + malloced_memory: number; + peak_malloced_memory: number; + does_zap_garbage: DoesZapCodeSpaceFlag; + number_of_native_contexts: number; + number_of_detached_contexts: number; + total_global_handles_size: number; + used_global_handles_size: number; + external_memory: number; + } + export function getHeapStatistics(): HeapInfo; + export function cachedDataVersionTag(): number; + export function getHeapSpaceStatistics(): unknown[]; + export function getHeapCodeStatistics(): unknown; + export function getHeapSnapshot(options?: unknown): unknown; + export function writeHeapSnapshot(fileName?: string, options?: unknown): string; + export function setFlagsFromString(flags: string): void; + export function setHeapSnapshotNearHeapLimit(limit: number): void; + export function serialize(value: unknown): Buffer; + export function deserialize(buffer: Uint8Array): unknown; + export function takeCoverage(): void; + export function stopCoverage(): void; + export function isStringOneByteRepresentation(content: string): boolean; +} +declare module "node:v8" { + export * from "v8"; +} /* node:worker_threads — native workers use statically compiled entry points. * Lowering and runtime diagnostics define the supported options and methods. */ @@ -6628,6 +6704,22 @@ declare module "node:stream/consumers" { import streamConsumers = require("stream/consumers"); export = streamConsumers; } +/* node:stream/web — the module exports the web stream globals themselves + * (require("stream/web").ReadableStream === globalThis.ReadableStream). + * ReadableStream is the implemented class; its reader and controller are + * exported as types. */ +declare module "stream/web" { + export const ReadableStream: typeof globalThis.ReadableStream; + export type ReadableStream = globalThis.ReadableStream; + export type ReadableStreamDefaultReader = globalThis.ReadableStreamDefaultReader; + export type ReadableStreamDefaultController = + globalThis.ReadableStreamDefaultController; + export type UnderlyingSource = globalThis.UnderlyingSource; + export type QueuingStrategy = globalThis.QueuingStrategy; +} +declare module "node:stream/web" { + export * from "stream/web"; +} interface ErrorConstructor { stackTraceLimit: number; } diff --git a/packages/compiler/src/backend/executable-features.ts b/packages/compiler/src/backend/executable-features.ts index 553643470..597fc5d58 100644 --- a/packages/compiler/src/backend/executable-features.ts +++ b/packages/compiler/src/backend/executable-features.ts @@ -32,6 +32,7 @@ export function executableLinkFeatures(mod: IrModule): NativeLinkFeatures { http: features.http, http2: features.http2, dgram: features.dgram, + dnsPromises: features.dnsPromises, watch: features.fsWatch, foreignFfi: hasForeignFfiCallback(mod.ffiImports ?? []), nodeTest: features.nodeTest, diff --git a/packages/compiler/src/backend/library-pack.ts b/packages/compiler/src/backend/library-pack.ts index 833ef93e7..cfa00e077 100644 --- a/packages/compiler/src/backend/library-pack.ts +++ b/packages/compiler/src/backend/library-pack.ts @@ -47,6 +47,7 @@ export async function compilePackedLibrary( http: false, http2: false, dgram: false, + dnsPromises: false, watch: false, foreignFfi: false, nodeTest: false, diff --git a/packages/compiler/src/backend/llvm/lib-datagram.ts b/packages/compiler/src/backend/llvm/lib-datagram.ts index 1553ecf4f..cee32c2a8 100644 --- a/packages/compiler/src/backend/llvm/lib-datagram.ts +++ b/packages/compiler/src/backend/llvm/lib-datagram.ts @@ -234,6 +234,10 @@ export function emitDatagramLibCall(host: LlvmEmitterContext, e: LibCallExpr): L return finishRuntimeCall(host, e, "scr_dns_lookup_dyn", a); case "dns.lookupService": return finishRuntimeCall(host, e, "scr_dns_lookup_service", a); + case "dns.lookupPromise": + return finishRuntimeCall(host, e, "scr_dns_lookup_promise", a); + case "dns.lookupServicePromise": + return finishRuntimeCall(host, e, "scr_dns_lookup_service_promise", a); case "dns.getServers": return finishRuntimeCall(host, e, "scr_dns_get_servers", a); case "dns.getDefaultResultOrder": diff --git a/packages/compiler/src/backend/native-link-info-core.ts b/packages/compiler/src/backend/native-link-info-core.ts index 8f69cea57..e77876d4c 100644 --- a/packages/compiler/src/backend/native-link-info-core.ts +++ b/packages/compiler/src/backend/native-link-info-core.ts @@ -40,6 +40,8 @@ export interface NativeLinkFeatures { http: boolean; http2: boolean; dgram: boolean; + /** node:dns/promises: scr_dns_promises.c. */ + dnsPromises: boolean; watch: boolean; foreignFfi: boolean; nodeTest: boolean; diff --git a/packages/compiler/src/backend/native/contracts.ts b/packages/compiler/src/backend/native/contracts.ts index 5e981fe44..e0d7052f9 100644 --- a/packages/compiler/src/backend/native/contracts.ts +++ b/packages/compiler/src/backend/native/contracts.ts @@ -167,6 +167,11 @@ export interface CcOptions { * on the IR): compiles scr_dgram.c into the binary — the net gating * precedent, so dgram-free binaries keep their exact link line. */ dgram?: boolean; + /** The program uses node:dns/promises (the dns.lookupPromise libCalls): + * compiles scr_dns_promises.c over the dgram unit, so callback-only + * binaries keep their exact link line. */ + dnsPromises?: boolean; + /** The program uses fs.watch (moduleUsesFsWatch on the IR): compiles * scr_watch.c into the binary — the net gating precedent, so watch-free * binaries keep their exact link line. */ diff --git a/packages/compiler/src/backend/native/executable.ts b/packages/compiler/src/backend/native/executable.ts index 549e9ebbf..b924fbeaf 100644 --- a/packages/compiler/src/backend/native/executable.ts +++ b/packages/compiler/src/backend/native/executable.ts @@ -574,6 +574,7 @@ export async function compileCInternal( ...(http ? [rt(join(rtDir, "scr_http.c"))] : []), ...((opts.http2 ?? false) ? [rt(join(rtDir, "scr_http2.c"))] : []), ...(opts.dgram ? [rt(join(rtDir, "scr_dgram.c"))] : []), + ...(opts.dgram && opts.dnsPromises ? [rt(join(rtDir, "scr_dns_promises.c"))] : []), ...(opts.watch ? [rt(join(rtDir, "scr_watch.c"))] : []), ...(opts.foreignFfi ? [rt(join(rtDir, "scr_ffi_queue.c"))] : []), ...(opts.nodeTest ? [rt(join(rtDir, "scr_test.c"))] : []), diff --git a/packages/compiler/src/backend/runtime-pack-targets.test.ts b/packages/compiler/src/backend/runtime-pack-targets.test.ts index faa335e33..7a0cb8e5f 100644 --- a/packages/compiler/src/backend/runtime-pack-targets.test.ts +++ b/packages/compiler/src/backend/runtime-pack-targets.test.ts @@ -113,6 +113,7 @@ const features = { http: false, http2: false, dgram: false, + dnsPromises: false, watch: false, foreignFfi: false, nodeTest: false, diff --git a/packages/compiler/src/backend/runtime-pack.test.ts b/packages/compiler/src/backend/runtime-pack.test.ts index ffe581366..9e2b26886 100644 --- a/packages/compiler/src/backend/runtime-pack.test.ts +++ b/packages/compiler/src/backend/runtime-pack.test.ts @@ -50,6 +50,7 @@ const BASE: NativeLinkFeatures = { http: false, http2: false, dgram: false, + dnsPromises: false, watch: false, foreignFfi: false, nodeTest: false, diff --git a/packages/compiler/src/backend/warm-cache.ts b/packages/compiler/src/backend/warm-cache.ts index 67e627c91..07192adcb 100644 --- a/packages/compiler/src/backend/warm-cache.ts +++ b/packages/compiler/src/backend/warm-cache.ts @@ -56,6 +56,7 @@ export async function warmNativeCaches( http: false, http2: false, dgram: false, + dnsPromises: false, watch: false, foreignFfi: false, nodeTest: false, diff --git a/packages/compiler/src/diagnostics/diagnostic.ts b/packages/compiler/src/diagnostics/diagnostic.ts index eaae1b531..9f314eafa 100644 --- a/packages/compiler/src/diagnostics/diagnostic.ts +++ b/packages/compiler/src/diagnostics/diagnostic.ts @@ -282,11 +282,11 @@ export const UNSUPPORTED: Record = { "imports (#alias via the imports field, self-name references via " + "exports), statically compiled npm packages, " + "and the built-in fs, fs/promises, path, os, url, crypto, zlib, " + - "child_process, net, http, tls, https, http2, dgram, dns, util, " + + "child_process, net, http, tls, https, http2, dgram, dns, dns/promises, util, " + "util/types, string_decoder, querystring, readline, events, stream, " + - "stream/promises, stream/consumers, buffer, assert, assert/strict, worker_threads, " + + "stream/promises, stream/consumers, stream/web, buffer, assert, assert/strict, worker_threads, " + "cluster, tty, async_hooks, timers, timers/promises, " + - "diagnostics_channel, perf_hooks, module, and console modules (bare or " + + "diagnostics_channel, perf_hooks, module, v8, and console modules (bare or " + "node:-prefixed) and node:test (node:-prefixed only, like in Node) " + "are supported", }, diff --git a/packages/compiler/src/executable/executable-cache.test.ts b/packages/compiler/src/executable/executable-cache.test.ts index e5887e855..5a29d1264 100644 --- a/packages/compiler/src/executable/executable-cache.test.ts +++ b/packages/compiler/src/executable/executable-cache.test.ts @@ -51,6 +51,7 @@ const native: EarlyExecutableNativeFeatures = { http: false, http2: false, dgram: false, + dnsPromises: false, watch: false, foreignFfi: false, nodeTest: false, diff --git a/packages/compiler/src/executable/executable-cache.ts b/packages/compiler/src/executable/executable-cache.ts index fbd7e4edd..3259dccc7 100644 --- a/packages/compiler/src/executable/executable-cache.ts +++ b/packages/compiler/src/executable/executable-cache.ts @@ -73,6 +73,7 @@ export interface EarlyExecutableNativeFeatures { http: boolean; http2: boolean; dgram: boolean; + dnsPromises: boolean; watch: boolean; foreignFfi: boolean; nodeTest: boolean; @@ -184,6 +185,7 @@ const BOOLEAN_NATIVE_KEYS = [ "http", "http2", "dgram", + "dnsPromises", "watch", "foreignFfi", "nodeTest", diff --git a/packages/compiler/src/frontend/builtin-modules.ts b/packages/compiler/src/frontend/builtin-modules.ts index b45dce6d7..2947122d1 100644 --- a/packages/compiler/src/frontend/builtin-modules.ts +++ b/packages/compiler/src/frontend/builtin-modules.ts @@ -42,6 +42,9 @@ export const SUPPORTED_BUILTIN_MODULES = [ "module", "console", "process", + "v8", + "stream/web", + "dns/promises", ] as const; /** Builtins Node itself serves ONLY under the node: prefix — @@ -74,14 +77,33 @@ export function builtinDefaultImportModule(spec: string): string | null { export function canonicalBuiltinModule(spec: string): string | null { const bare = spec.startsWith("node:") ? spec.slice(5) : spec; if (bare === spec && PREFIX_ONLY_BUILTIN_MODULES.has(bare)) return null; - return (SUPPORTED_BUILTIN_MODULES as readonly string[]).includes(bare) ? bare : null; + if ((SUPPORTED_BUILTIN_MODULES as readonly string[]).includes(bare)) return bare; + return bare === spec ? npmBuiltinAliasOf(spec) : null; +} + +/** npm packages served as a builtin module instead of compiling their + * JavaScript (maintainer decision): graceful-fs monkeypatches the fs + * module object and queues retries on EMFILE/ENFILE, which statically + * lowered fs calls cannot honor, and it behaves as fs everywhere else. Its + * import and require resolve to node:fs; gracefulify is a no-op statement. + * The one documented difference: no retry queueing under file-descriptor + * exhaustion. */ +const NPM_BUILTIN_ALIASES: Record = { "graceful-fs": "fs" }; + +/** The builtin module an npm package specifier is served as, or null. */ +export function npmBuiltinAliasOf(spec: string): string | null { + return Object.hasOwn(NPM_BUILTIN_ALIASES, spec) ? (NPM_BUILTIN_ALIASES[spec] ?? null) : null; } /** Both specifier spellings of every supported builtin (prefix-only ones - * keep just the node: form) — the ambient-module allowlist preflight uses. */ -export const SUPPORTED_NODE_MODULES: readonly string[] = SUPPORTED_BUILTIN_MODULES.flatMap((m) => - PREFIX_ONLY_BUILTIN_MODULES.has(m) ? [`node:${m}`] : [m, `node:${m}`], -); + * keep just the node: form) — the ambient-module allowlist preflight uses + * — plus the npm packages served as builtins. */ +export const SUPPORTED_NODE_MODULES: readonly string[] = [ + ...SUPPORTED_BUILTIN_MODULES.flatMap((m) => + PREFIX_ONLY_BUILTIN_MODULES.has(m) ? [`node:${m}`] : [m, `node:${m}`], + ), + ...Object.keys(NPM_BUILTIN_ALIASES), +]; /* ── out-of-scope builtins ─────────────────────────────────────────────── * Builtin modules a compiled binary is not going to serve — not "yet", @@ -90,8 +112,8 @@ export const SUPPORTED_NODE_MODULES: readonly string[] = SUPPORTED_BUILTIN_MODUL * so 'node:sqlite' keys as 'sqlite'. Exact-match only: an npm package * whose name collides with none of these keys keeps the generic wording. * Everything absent from this table stays the plain "not supported yet" - * story — genuinely pending surface (console's Console class, stream/web) - * must not read as permanently refused. */ + * story — genuinely pending surface (console's Console class) must not + * read as permanently refused. */ /** Node's own HTTP/TLS/stream implementation internals, requireable by * legacy convention (underscore-prefixed lib/ modules). They expose @@ -100,11 +122,15 @@ export const SUPPORTED_NODE_MODULES: readonly string[] = SUPPORTED_BUILTIN_MODUL const NODE_INTERNAL_REASON = "a Node-internal module: it exposes Node's own implementation objects, which the scriptc runtime does not replicate"; -const V8_REASON = - "it observes V8 engine internals — heap statistics and snapshots, GC and CPU profiles, serialize/deserialize — and a compiled binary embeds no V8 engine to observe"; +/** node:v8 is importable (packages require it at top level and touch it + * only on optional paths), and getHeapStatistics answers allocator-derived + * figures; every other member observes the engine itself. Member fences + * carry this reason instead of a module-level refusal. */ +export const V8_MEMBER_REASON = + "by design: this member observes V8 engine internals — heap snapshots and spaces, GC and CPU profiles, flags, serialize/deserialize — and a compiled binary embeds no V8 engine to observe"; const OUT_OF_SCOPE_BUILTIN_REASONS: Record = { - v8: V8_REASON, + vm: "it compiles and runs JavaScript source at runtime, and a compiled binary embeds no JavaScript engine to run it", inspector: "it drives the V8 inspector protocol — debugger, profiler, heap access — and a compiled binary embeds no V8 engine to inspect", "inspector/promises": @@ -140,3 +166,10 @@ export function unsupportedModuleFeatureOf(spec: string): string { : undefined; return reason === undefined ? `the '${spec}' module` : `the '${spec}' module (${reason})`; } + +/** The ESNext temporal global's name. Node 24 does not define it: programs + * that name it type-check against TS 7's lib slice (program.ts adds it only + * for them) and lower to Node's answers. Spelled in two pieces so the + * compiler's own sources never trip program.ts's scan when scriptc + * compiles itself. */ +export const TEMPORAL_GLOBAL = "Tempo" + "ral"; diff --git a/packages/compiler/src/frontend/lowering/builtin-calls.ts b/packages/compiler/src/frontend/lowering/builtin-calls.ts index 7bb5f3e62..b58e82c0d 100644 --- a/packages/compiler/src/frontend/lowering/builtin-calls.ts +++ b/packages/compiler/src/frontend/lowering/builtin-calls.ts @@ -14,6 +14,8 @@ import { lowerTimersMemberCall } from "./lower-timers.js"; import { lowerStreamModuleCall } from "./lower-stream.js"; import { lowerNodeModuleCall } from "./builtins/module-resolution.js"; import { builtinModuleFnOf, builtinFenceHintOf } from "./surfaces.js"; +import { resultIsDiscarded } from "./call-position.js"; +import { locOf, requireSpecOf } from "../program.js"; /** `events.addAbortListener(signal, listener)`: the runtime registers the * once/resist-stop-propagation abort listener (or queues the call for an @@ -53,6 +55,38 @@ export function lowerAddAbortListener( return { kind: "dynObjLit", fields: [{ key, value: dispose }], type: DYN, loc }; } +/** graceful-fs's `gracefulify(fsModule)` (graceful-fs is served as node:fs — + * builtin-modules.ts): it patches a module object with the retry queue, + * which statically lowered fs calls never consult, so the call statement + * is a no-op. The argument still evaluates unless it is a plain reference + * (the patched module, typically `require("fs")` or a namespace binding). + * The returned module object has no representation; other positions + * fence. */ +function lowerGracefulify(lowerer: Lowerer, expr: ts.CallExpression, loc: SrcLoc): IrExpr { + const args = expr.arguments; + if (!resultIsDiscarded(expr) || args.length !== 1 || ts.isSpreadElement(args[0]!)) { + lowerer.noLowering( + "gracefulify", + expr, + "graceful-fs compiles as node:fs; gracefulify(fsModule) lowers as a statement whose result is unused", + ); + } + let arg: ts.Expression = args[0]!; + while (ts.isParenthesizedExpression(arg)) arg = arg.expression; + const plain = + ts.isIdentifier(arg) || + (ts.isCallExpression(arg) && requireSpecOf(arg) !== null) || + (ts.isPropertyAccessExpression(arg) && ts.isIdentifier(arg.expression)); + if (plain) return { kind: "seqExpr", stmts: [], result: dynUndefinedExpr(loc), type: DYN, loc }; + return { + kind: "seqExpr", + stmts: [{ kind: "exprStmt", expr: lowerer.lowerExpr(arg), loc: locOf(arg) }], + result: dynUndefinedExpr(loc), + type: DYN, + loc, + }; +} + /** Named imports, namespace members and callable aliases share validation * and dispatch. Resolve diagnostic provenance only if no handler accepts. */ export function lowerBuiltinCall( @@ -121,6 +155,8 @@ export function lowerBuiltinCall( if (bi.module === "worker_threads" && bi.member === "markAsUncloneable") { return lowerUncloneable(lowerer, expr); } + if (bi.module === "fs" && bi.member === "gracefulify") + return lowerGracefulify(lowerer, expr, loc); const builtinFn = builtinModuleFnOf(lowerer, bi.module, bi.member); if (!builtinFn) { lowerer.noLowering( diff --git a/packages/compiler/src/frontend/lowering/builtins/module-bindings.ts b/packages/compiler/src/frontend/lowering/builtins/module-bindings.ts index 9c3a8c0cf..3d47dfdc6 100644 --- a/packages/compiler/src/frontend/lowering/builtins/module-bindings.ts +++ b/packages/compiler/src/frontend/lowering/builtins/module-bindings.ts @@ -23,6 +23,17 @@ import { stripTypeCasts } from "./arguments.js"; export function builtinImportOf( lowerer: Lowerer, ident: ts.Identifier, +): { module: string; member: string } | null { + const binding = builtinModuleBindingOf(lowerer, ident); + // node:stream/web's exports ARE the web stream globals: its bindings + // lower through the globals' own paths (stdlibGlobalNameOf resolves the + // alias to the same name), not through module-member tables. + return binding?.module === "stream/web" ? null : binding; +} + +function builtinModuleBindingOf( + lowerer: Lowerer, + ident: ts.Identifier, ): { module: string; member: string } | null { const symbol = ts.isShorthandPropertyAssignment(ident.parent) && ident.parent.name === ident diff --git a/packages/compiler/src/frontend/lowering/builtins/module-calls.ts b/packages/compiler/src/frontend/lowering/builtins/module-calls.ts index 58ac85aba..ae3bc4495 100644 --- a/packages/compiler/src/frontend/lowering/builtins/module-calls.ts +++ b/packages/compiler/src/frontend/lowering/builtins/module-calls.ts @@ -48,6 +48,7 @@ import { } from "./operating-system.js"; import { lowerQuerystringParseCall, lowerQuerystringStringifyCall } from "./querystring.js"; import { lowerFsGlobCallbackCall, lowerFsGlobSyncCall, lowerFspGlobCall } from "./fs-glob.js"; +import { lowerV8HeapStatisticsCall } from "./v8.js"; export function lowerBuiltinModuleCall( lowerer: Lowerer, @@ -134,6 +135,9 @@ export function lowerBuiltinModuleCall( if (bi.module === "child_process" && (bi.member === "execFileSync" || bi.member === "execSync")) { return lowerer.lowerExecSyncCall(expr, bi.member === "execSync", loc); } + if (bi.module === "v8" && bi.member === "getHeapStatistics") { + return lowerV8HeapStatisticsCall(lowerer, expr, loc); + } if (bi.module === "os" && bi.member === "networkInterfaces") { return lowerOsNetworkInterfacesCall(lowerer, expr, loc); } diff --git a/packages/compiler/src/frontend/lowering/builtins/v8.ts b/packages/compiler/src/frontend/lowering/builtins/v8.ts new file mode 100644 index 000000000..25be8de13 --- /dev/null +++ b/packages/compiler/src/frontend/lowering/builtins/v8.ts @@ -0,0 +1,111 @@ +/* node:v8 — importable, with one lowered member. Packages require it at + * top level and touch it on optional paths (dd-trace's runtime metrics), + * so the module itself is not refused. getHeapStatistics answers + * allocator-derived figures in Node's shape and field order, in the + * spirit of process.memoryUsage(): approximate values, not V8 heap + * figures (a compiled binary embeds no V8 engine). Every other member + * fences at its use with the by-design reason (builtinFenceHintOf). */ +import { numLit } from "../../../ir/build.js"; +import * as ts from "../../ts7/adapter.js"; +import { type Lowerer } from "../lowerer.js"; +import { F64, type IrExpr, type SrcLoc, VOID } from "../../../ir/ir.js"; + +/** v8.getHeapStatistics()'s fields, in Node's order. */ +const HEAP_STATISTICS_FIELDS = [ + "total_heap_size", + "total_heap_size_executable", + "total_physical_size", + "total_available_size", + "used_heap_size", + "heap_size_limit", + "malloced_memory", + "peak_malloced_memory", + "does_zap_garbage", + "number_of_native_contexts", + "number_of_detached_contexts", + "total_global_handles_size", + "used_global_handles_size", + "external_memory", +]; + +/** `v8.getHeapStatistics()`: one memoryUsage sample (the runtime heap's + * total and used bytes), with the machine's memory as the heap limit (the + * runtime heap has no fixed ceiling) and the available size the limit + * minus used bytes. V8-only figures (executable code space, malloced + * memory, global handles, external memory, zapping) read 0, and one + * native context exists. The record is the call site's interned HeapInfo + * shape, built by a shared helper per shape. */ +export function lowerV8HeapStatisticsCall( + lowerer: Lowerer, + call: ts.CallExpression, + loc: SrcLoc, +): IrExpr { + if (call.arguments.length !== 0) { + lowerer.noLowering(`v8.getHeapStatistics with ${call.arguments.length} arguments`, call); + } + const t = lowerer.mapTypeOf(lowerer.typeOf(call)); + if (t?.kind !== "record") lowerer.badType(call, lowerer.typeOf(call)); + const shape = lowerer.shapes.get(t.shapeId); + if ( + !shape || + !shape.fields.every((f) => HEAP_STATISTICS_FIELDS.includes(f.name) && f.type.kind === "f64") + ) { + lowerer.badType(call, lowerer.typeOf(call)); + } + const key = `v8.getHeapStatistics:${t.shapeId}`; + let helper = lowerer.valueHelpers.get(key); + if (!helper) { + helper = `%v8.getHeapStatistics.${lowerer.valueHelpers.size}`; + lowerer.valueHelpers.set(key, helper); + const sample = (index: number): IrExpr => ({ + kind: "libCall", + fn: "process.memoryUsageField", + args: [numLit(index, loc)], + type: F64, + loc, + }); + const limit = (): IrExpr => ({ kind: "libCall", fn: "os.totalmem", args: [], type: F64, loc }); + const value = (name: string): IrExpr => { + switch (name) { + case "total_heap_size": + case "total_physical_size": + return sample(1); + case "used_heap_size": + return sample(2); + case "heap_size_limit": + return limit(); + case "total_available_size": + return { kind: "bin", op: "-", left: limit(), right: sample(2), type: F64, loc }; + case "number_of_native_contexts": + return numLit(1, loc); + default: + return numLit(0, loc); + } + }; + lowerer.liftedFns.push({ + name: helper, + params: [], + returnType: t, + locals: [], + body: [ + { + kind: "exprStmt", + expr: { kind: "libCall", fn: "process.memoryUsageTake", args: [], type: VOID, loc }, + loc, + }, + { + kind: "return", + value: { + kind: "recordLit", + fields: shape.fields.map((f) => ({ name: f.name, value: value(f.name) })), + type: t, + loc, + }, + loc, + }, + ], + loc, + }); + } + return { kind: "call", callee: helper, args: [], type: t, loc }; +} diff --git a/packages/compiler/src/frontend/lowering/code-generation.ts b/packages/compiler/src/frontend/lowering/code-generation.ts new file mode 100644 index 000000000..34c4bcfe5 --- /dev/null +++ b/packages/compiler/src/frontend/lowering/code-generation.ts @@ -0,0 +1,134 @@ +/* Code generation from strings: `eval(...)`, `new Function(...)` and + * `Function(...)`. A compiled binary embeds no JavaScript engine, so scriptc + * answers exactly as `node --disallow-code-generation-from-strings` does: + * every call throws the catchable EvalError "Code generation from strings + * disallowed for this context". Packages that probe for code generation + * (zod 4's `allowsEval`, template engines, CSP-aware JITs) then take their + * interpreted paths. The arguments still evaluate first, in order, and the + * Function constructor converts each to a string before it refuses, as + * V8's CreateDynamicFunction does; eval refuses without converting. */ +import * as ts from "../ts7/adapter.js"; +import type { Lowerer } from "./lowerer.js"; +import { locOf } from "../program.js"; +import { DYN, F64, type IrExpr, type IrStmt, type IrType, STRING } from "../../ir/ir.js"; +import { nodeThrowExpr } from "../../ir/build.js"; +import { ambientUndefReadType, contextualUndefReadType } from "./lower-namespaces.js"; +import { stdlibGlobalNameOf } from "./surfaces.js"; + +export const CODE_GENERATION_DISALLOWED = + "Code generation from strings disallowed for this context"; + +/** The code-generation entry point a callee names: the stdlib `eval` or + * `Function` global (directly, through `globalThis.`, or through a stable + * alias), or the indirect-eval spelling `(0, eval)`. Null otherwise. */ +export function codeGenerationCalleeOf( + lowerer: Lowerer, + callee: ts.Expression, +): "eval" | "Function" | null { + let e = callee; + while (ts.isParenthesizedExpression(e)) e = e.expression; + if (ts.isBinaryExpression(e) && e.operatorToken.kind === ts.SyntaxKind.CommaToken) { + return codeGenerationCalleeOf(lowerer, e.right) === "eval" ? "eval" : null; + } + // Syntactic pre-check first: every call and construction asks, and only + // these spellings can name the globals (aliases are stdlib-resolved + // symbols, checked below only when the spelling could match). + const text = ts.isIdentifier(e) ? e.text : ts.isPropertyAccessExpression(e) ? e.name.text : null; + if (text === null) return null; + if (text !== "eval" && text !== "Function" && !hasCodeGenerationAlias(lowerer)) return null; + const name = stdlibGlobalNameOf(lowerer, e); + return name === "eval" || name === "Function" ? name : null; +} + +/** Some stable binding aliases `eval` or `Function` (`const F = Function`, + * zod 4's spelling). */ +function hasCodeGenerationAlias(lowerer: Lowerer): boolean { + for (const name of lowerer.stdlibGlobalAliases.values()) { + if (name === "eval" || name === "Function") return true; + } + return false; +} + +/** The comma operands an indirect eval evaluates before the call: `a` in + * `(a, eval)(...)`, outermost first. */ +function indirectEvalPrefix(callee: ts.Expression): ts.Expression[] { + let e = callee; + while (ts.isParenthesizedExpression(e)) e = e.expression; + if (!ts.isBinaryExpression(e) || e.operatorToken.kind !== ts.SyntaxKind.CommaToken) return []; + return [...indirectEvalPrefix(e.left), e.left, ...indirectEvalPrefix(e.right)].filter( + (operand) => { + let o = operand; + while (ts.isParenthesizedExpression(o)) o = o.expression; + return !(ts.isBinaryExpression(o) && o.operatorToken.kind === ts.SyntaxKind.CommaToken); + }, + ); +} + +/** Lowers a code-generation call or construction to Node's refusal under + * --disallow-code-generation-from-strings: the callee's own operands, then + * the arguments in order (converted to strings for the Function + * constructor), then the EvalError. The result carries the use site's type + * (the throw always fires, so the value is never observed). */ +export function lowerCodeGenerationCall( + lowerer: Lowerer, + expr: ts.CallExpression | ts.NewExpression, + kind: "eval" | "Function", +): IrExpr { + const loc = locOf(expr); + const stmts: IrStmt[] = []; + for (const operand of indirectEvalPrefix(expr.expression)) { + stmts.push({ kind: "exprStmt", expr: lowerer.lowerExpr(operand), loc: locOf(operand) }); + } + for (const arg of expr.arguments ?? []) { + if (ts.isSpreadElement(arg)) { + lowerer.noLowering( + `${kind === "eval" ? "eval" : "the Function constructor"} with spread arguments`, + arg, + "code generation from strings throws EvalError in compiled programs, as under node --disallow-code-generation-from-strings; pass the arguments directly", + ); + } + const value = lowerer.lowerExpr(arg); + const argLoc = locOf(arg); + if (kind === "Function" && !isPrimitiveIrType(value.type)) { + stmts.push({ + kind: "exprStmt", + expr: { + kind: "libCall", + fn: "dyn.toStringCoerce", + args: [lowerer.coerceToExpected(value, DYN)], + type: STRING, + loc: argLoc, + }, + loc: argLoc, + }); + } else { + stmts.push({ kind: "exprStmt", expr: value, loc: argLoc }); + } + } + const type = useSiteType(lowerer, expr); + const refusal = nodeThrowExpr(6, "", CODE_GENERATION_DISALLOWED, type, loc); + return stmts.length === 0 ? refusal : { kind: "seqExpr", stmts, result: refusal, type, loc }; +} + +function isPrimitiveIrType(t: IrType): boolean { + return ( + t.kind === "string" || + t.kind === "f64" || + t.kind === "bool" || + t.kind === "void" || + t.kind === "bigint" + ); +} + +function useSiteType(lowerer: Lowerer, expr: ts.Expression): IrType { + const mapped = lowerer.mapTypeOf(lowerer.typeOf(expr)); + if ( + mapped && + mapped.kind !== "void" && + mapped.kind !== "func" && + !lowerer.typeNamesUnregisteredClass(mapped) + ) { + return mapped; + } + return ambientUndefReadType(lowerer, expr) ?? contextualUndefReadType(lowerer, expr) ?? F64; +} diff --git a/packages/compiler/src/frontend/lowering/lower-calls.ts b/packages/compiler/src/frontend/lowering/lower-calls.ts index b3094d5c7..4e595aa0b 100644 --- a/packages/compiler/src/frontend/lowering/lower-calls.ts +++ b/packages/compiler/src/frontend/lowering/lower-calls.ts @@ -208,6 +208,8 @@ import { genericCallInstance, } from "./generic-functions.js"; +import { codeGenerationCalleeOf, lowerCodeGenerationCall } from "./code-generation.js"; + const ZLIB_STREAM_METHODS: ReadonlySet = new Set(["flush", "params", "close", "reset"]); export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { @@ -228,6 +230,13 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { const json = lowerJsonRequireCall(lowerer, expr); if (json) return json; + // eval(...) and Function(...): Node's EvalError under + // --disallow-code-generation-from-strings (code-generation.ts). + { + const codeGeneration = codeGenerationCalleeOf(lowerer, expr.expression); + if (codeGeneration !== null) return lowerCodeGenerationCall(lowerer, expr, codeGeneration); + } + // A call whose chain ROOTS at an ambient-undefined name (`declare // const value: Y | undefined; value?.foo("a")`, `declare function // chain...; chain(o).mapValues(f).value()`, a trap binding's read): diff --git a/packages/compiler/src/frontend/lowering/lower-classes.ts b/packages/compiler/src/frontend/lowering/lower-classes.ts index 7a98096b7..54bb69dce 100644 --- a/packages/compiler/src/frontend/lowering/lower-classes.ts +++ b/packages/compiler/src/frontend/lowering/lower-classes.ts @@ -9,6 +9,8 @@ import { InternalCompilerError } from "../../errors.js"; * accessor access, upcasts, `new` expressions, and the builtin Error * hierarchy registration. */ import { fsStreamClassOf, lowerFsStreamNew } from "./builtins/fs-streams.js"; +import { codeGenerationCalleeOf, lowerCodeGenerationCall } from "./code-generation.js"; +import { TEMPORAL_GLOBAL } from "../builtin-modules.js"; import * as ts from "../ts7/adapter.js"; import { lowerZlibStreamNew, zlibStreamModeOf } from "./builtins/compression.js"; import type { FnCtx, Lowerer } from "./lowerer.js"; @@ -128,6 +130,7 @@ import { ambientUndefReadType, ambientUndefVarRootOf, ambientUndefinedFnSymbolOf, + contextualUndefReadType, fenceEarlyAliasUse, fenceEarlyNsMemberRef, nsMemberIdentOf, @@ -8491,6 +8494,20 @@ function assignedThisFieldType(lowerer: Lowerer, expr: ts.NewExpression): IrType export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { const loc = locOf(expr); + // new Function(...): Node's EvalError under + // --disallow-code-generation-from-strings (code-generation.ts). + if (codeGenerationCalleeOf(lowerer, expr.expression) === "Function") + return lowerCodeGenerationCall(lowerer, expr, "Function"); + // `new .X(...)`: Node 24 has no such global, so the callee's + // root read throws the ReferenceError before any argument evaluates. + { + const root = ambientUndefVarRootOf(lowerer, expr.expression); + if (root !== null && root.text === TEMPORAL_GLOBAL) { + const t = + ambientUndefReadType(lowerer, expr) ?? contextualUndefReadType(lowerer, expr) ?? F64; + return nsUndefRead(lowerer, root.text, expr, t); + } + } const worker = lowerWorkerNew(lowerer, expr); if (worker) return worker; const readlineUi = lowerReadlineUiNew(lowerer, expr); diff --git a/packages/compiler/src/frontend/lowering/lower-dgram.ts b/packages/compiler/src/frontend/lowering/lower-dgram.ts index 17dab01ab..903397f39 100644 --- a/packages/compiler/src/frontend/lowering/lower-dgram.ts +++ b/packages/compiler/src/frontend/lowering/lower-dgram.ts @@ -14,6 +14,8 @@ import { isJsSourceFile, locOf } from "../program.js"; import { BOOL, canBoxFuncIntoDyn, + canDynCheckTo, + typeKey, DGRAMSOCK_T, DYN, F64, @@ -130,6 +132,7 @@ export function lowerDgramDnsModuleCall( loc: SrcLoc, ): IrExpr | null { if (bi.module === "dns") return lowerDnsModuleCall(lowerer, expr, bi, loc); + if (bi.module === "dns/promises") return lowerDnsPromisesCall(lowerer, expr, bi, loc); if (bi.module !== "dgram") return null; const args = expr.arguments; if (bi.member === "createSocket") { @@ -277,6 +280,124 @@ function lowerDnsCallback(lowerer: Lowerer, node: ts.Expression, what: string): ); } +/** node:dns/promises calls (and dns.promises members): lookup(hostname[, + * options]) and lookupService(address, port) as promises over the callback + * forms' runtime (argument errors throw synchronously, as Node validates + * before it creates the promise), plus the shared getServers and result + * order functions. A typed result (Promise) checks the + * settled value through the checked-dynamic boundary. */ +function lowerDnsPromisesCall( + lowerer: Lowerer, + expr: ts.CallExpression, + bi: { module: string; member: string }, + loc: SrcLoc, +): IrExpr { + const args = expr.arguments; + const dyn = (node: ts.Expression): IrExpr => + lowerer.coerceToExpected(lowerer.lowerExpr(node), DYN); + if (bi.member === "lookup" || bi.member === "lookupService") { + const service = bi.member === "lookupService"; + if (service ? args.length !== 2 : args.length !== 1 && args.length !== 2) { + lowerer.noLowering( + `dnsPromises.${bi.member} with ${args.length} arguments`, + expr, + service + ? "the form is lookupService(address, port)" + : "the supported forms are lookup(hostname) and lookup(hostname, options)", + ); + } + const promise: IrExpr = { + kind: "libCall", + fn: service ? "dns.lookupServicePromise" : "dns.lookupPromise", + args: [dyn(args[0]!), args.length === 2 ? dyn(args[1]!) : dynUndefinedExpr(loc)], + type: { kind: "promise", inner: DYN }, + loc, + }; + const declared = lowerer.mapTypeOf(lowerer.typeOf(expr)); + if (declared === null || declared.kind !== "promise" || declared.inner.kind === "dyn") { + return promise; + } + return checkedDynPromise(lowerer, promise, declared, expr, loc); + } + if (["getServers", "getDefaultResultOrder", "setDefaultResultOrder"].includes(bi.member)) { + return lowerDnsModuleCall(lowerer, expr, { module: "dns", member: bi.member }, loc); + } + lowerer.noLowering( + `dnsPromises.${bi.member}`, + expr, + "lookup, lookupService, getServers, getDefaultResultOrder, and setDefaultResultOrder are the lowered dns/promises functions", + ts.isIdentifier(expr.expression) ? lowerer.resolveValueSymbol(expr.expression) : undefined, + ); +} + +/** A promise as the call site's typed promise: an interned async + * helper awaits it and checks the settled value into the declared inner + * type (a mismatch rejects with the checked-dynamic TypeError). */ +function checkedDynPromise( + lowerer: Lowerer, + promise: IrExpr, + declared: IrType & { kind: "promise" }, + node: ts.Node, + loc: SrcLoc, +): IrExpr { + const inner = declared.inner; + if ( + !canDynCheckTo( + inner, + (id) => lowerer.shapes.get(id), + (id) => lowerer.unions.get(id), + ) + ) { + lowerer.noLowering( + `a dns/promises result typed '${lowerer.fmt(declared)}'`, + node, + "the settled value must have a checked-dynamic conversion (type it as the documented LookupAddress shape or unknown)", + ); + } + const key = `dns.promise:${typeKey(declared)}`; + let helper = lowerer.valueHelpers.get(key); + if (!helper) { + helper = `%dns.promise.${lowerer.valueHelpers.size}`; + lowerer.valueHelpers.set(key, helper); + const promiseT: IrType = { kind: "promise", inner: DYN }; + lowerer.liftedFns.push({ + name: helper, + params: [{ localId: "promise", name: "promise", type: promiseT }], + returnType: inner, + locals: [ + { id: "promise", name: "promise", type: promiseT, mutable: false }, + { id: "settled", name: "settled", type: DYN, mutable: false }, + ], + body: [ + { + kind: "varDecl", + localId: "settled", + init: { + kind: "awaitExpr", + value: { kind: "varRef", localId: "promise", type: promiseT, loc }, + type: DYN, + loc, + }, + loc, + }, + { + kind: "return", + value: { + kind: "dynCheck", + value: { kind: "varRef", localId: "settled", type: DYN, loc }, + type: inner, + loc, + }, + loc, + }, + ], + loc, + async: true, + }); + } + return { kind: "call", callee: helper, args: [promise], type: declared, loc }; +} + /** dns module calls: lookup(hostname[, options], callback) with Node's * option forms, lookupService(address, port, callback), getServers() and * get/setDefaultResultOrder(). The runtime validates the arguments as diff --git a/packages/compiler/src/frontend/lowering/lower-exprs.ts b/packages/compiler/src/frontend/lowering/lower-exprs.ts index c8874fe34..7131e62c5 100644 --- a/packages/compiler/src/frontend/lowering/lower-exprs.ts +++ b/packages/compiler/src/frontend/lowering/lower-exprs.ts @@ -134,7 +134,7 @@ import { strCharsCall } from "./containers/array-construction.js"; import { lowerOptionalStringIndex } from "./string-index.js"; import { tryLowerIndexedComparison } from "./indexed-comparison.js"; import { npmStaticPackageOfPath } from "../npm-static.js"; -import { unsupportedModuleFeatureOf } from "../builtin-modules.js"; +import { TEMPORAL_GLOBAL, unsupportedModuleFeatureOf } from "../builtin-modules.js"; import { fenceEnumObjectValue, lowerEnumAccess } from "./lower-enums.js"; import { ambientNsRootOf, @@ -144,6 +144,7 @@ import { contextualUndefReadType, fenceEarlyAliasUse, fenceEarlyNsMemberRef, + isTemporalGlobalRoot, lowerNsIdentifierValue, nsMemberIdentOf, nsUndefRead, @@ -674,11 +675,25 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr { } } } + // `typeof globalThis.`: the global object has no such + // property in Node 24. + if ( + ts.isPropertyAccessExpression(tested) && + tested.name.text === TEMPORAL_GLOBAL && + stdlibGlobalNameOf(lowerer, tested.expression) === "globalThis" + ) { + return { kind: "strLit", value: "undefined", type: STRING, loc }; + } if (ts.isIdentifier(tested)) { const sym = lowerer.checker.getSymbolAtLocation(tested); if (!sym) { return { kind: "strLit", value: "undefined", type: STRING, loc }; } + // Node 24 defines no temporal global: typeof answers "undefined" + // without reading it, like any undeclared name. + if (isTemporalGlobalRoot(lowerer, tested, sym)) { + return { kind: "strLit", value: "undefined", type: STRING, loc }; + } if (lowerer.isStdlibSymbol(sym)) { const t = lowerer.typeOf(expr.expression); if ( @@ -1223,7 +1238,12 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr { if (isJsSourceFile(expr.getSourceFile())) { return { kind: "strLit", - value: `[builtin ${bi.module}.${bi.member}]`, + // node:stream/web exports the web stream globals themselves: + // the same identity token as the global spelling. + value: + bi.module === "stream/web" + ? `[builtin ${bi.member}]` + : `[builtin ${bi.module}.${bi.member}]`, type: STRING, loc, }; @@ -1402,6 +1422,13 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr { } { const sym = lowerer.checker.getSymbolAtLocation(expr); + // The temporal global is typed (TS 7's lib slice) but Node 24 does + // not define it: the read throws Node's ReferenceError. + if (isTemporalGlobalRoot(lowerer, expr, sym)) { + const t = + ambientUndefReadType(lowerer, expr) ?? contextualUndefReadType(lowerer, expr) ?? F64; + return nsUndefRead(lowerer, expr.text, expr, t); + } if (lowerer.isStdlibSymbol(sym) || expr.text === "globalThis") { if (isJsSourceFile(expr.getSourceFile())) { const canonical = stdlibGlobalNameOf(lowerer, expr) ?? expr.text; @@ -2282,6 +2309,8 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr { const canonical = stdlibGlobalNameOf(lowerer, expr); if (canonical !== null) { if (canonical === "globalThis") return lowerGlobalValue(lowerer, expr); + // Typed for the programs that name it, absent in Node 24. + if (canonical === TEMPORAL_GLOBAL) return dynUndefinedExpr(loc); if (canonical === "ArrayBuffer") return { kind: "libCall", fn: "arrayBuffer.constructor", args: [], type: DYN, loc }; if (canonical === "fetch") diff --git a/packages/compiler/src/frontend/lowering/lower-global-value.ts b/packages/compiler/src/frontend/lowering/lower-global-value.ts index a5f516e86..d0b251e84 100644 --- a/packages/compiler/src/frontend/lowering/lower-global-value.ts +++ b/packages/compiler/src/frontend/lowering/lower-global-value.ts @@ -3,15 +3,19 @@ import { DYN, STRING, arrayOf, type IrExpr } from "../../ir/ir.js"; import { strLit } from "../../ir/build.js"; import { locOf } from "../program.js"; import type { Lowerer } from "./lowerer.js"; +import { TEMPORAL_GLOBAL } from "../builtin-modules.js"; /** A stored global object has identity and survives module/function boundaries. * Known globals without a value implementation refuse at runtime; absent host * capabilities answer undefined. The checker supplies names, never values. */ export function lowerGlobalValue(lowerer: Lowerer, expr: ts.Expression): IrExpr { const loc = locOf(expr); + // The temporal global is typed for the programs that name it (TS 7's + // lib slice) but absent in Node 24: reads answer undefined. const names = lowerer.checker .getPropertiesOfType(lowerer.typeOf(expr)) - .map((property) => property.name); + .map((property) => property.name) + .filter((name) => name !== TEMPORAL_GLOBAL); return { kind: "libCall", fn: "global.native", diff --git a/packages/compiler/src/frontend/lowering/lower-namespaces.ts b/packages/compiler/src/frontend/lowering/lower-namespaces.ts index 69d050510..8908151fd 100644 --- a/packages/compiler/src/frontend/lowering/lower-namespaces.ts +++ b/packages/compiler/src/frontend/lowering/lower-namespaces.ts @@ -42,6 +42,7 @@ import { boundIdentifiersOf } from "./lowerer.js"; import type { FileParts } from "./lower-modules.js"; import { locOf, resolveImport } from "../program.js"; import { F64, type IrExpr, type IrStmt, type IrType, STRING } from "../../ir/ir.js"; +import { TEMPORAL_GLOBAL } from "../builtin-modules.js"; /** True when this module declaration produces NO runtime construct at all: * ambient (`declare namespace/module`, `declare global`, string-named @@ -425,7 +426,7 @@ export function ambientUndefVarRootOf(lowerer: Lowerer, e: ts.Expression): ts.Id const sym = lowerer.resolveValueSymbol(root); if (!sym) return null; if (lowerer.trapBindings.has(sym)) return root; - if (lowerer.isStdlibSymbol(sym)) return null; + if (lowerer.isStdlibSymbol(sym)) return isTemporalGlobalRoot(lowerer, root, sym) ? root : null; if (ambientUndefinedFnSymbolOf(lowerer, root) !== null) return root; const decls = lowerer.checker.declarationsOf(sym); if (decls.length === 0) return null; @@ -440,6 +441,24 @@ export function ambientUndefVarRootOf(lowerer: Lowerer, e: ts.Expression): ts.Id } } +/** True when `ident` reads the ESNext temporal global, which the program + * type-checks against TS 7's lib slice (program.ts adds it only to programs + * that name it) but Node 24 does not define: Node's read throws the + * catchable ReferenceError " is not defined", so every chain rooted + * here lowers to that throw (typeof answers "undefined" in lower-exprs). */ +export function isTemporalGlobalRoot( + lowerer: Lowerer, + ident: ts.Identifier, + sym: ts.Symbol | undefined | null, +): boolean { + return ( + ident.text === TEMPORAL_GLOBAL && + sym !== undefined && + sym !== null && + lowerer.isStdlibSymbol(sym) + ); +} + /** The declaration-classification form of ambientUndefVarRootOf: the root * that makes `decl` a TRAP BINDING — its initializer provably throws the * root's ReferenceError, so the binding never holds a value and needs no diff --git a/packages/compiler/src/frontend/lowering/lower-stdlib.ts b/packages/compiler/src/frontend/lowering/lower-stdlib.ts index 20caf184b..7a224a4d9 100644 --- a/packages/compiler/src/frontend/lowering/lower-stdlib.ts +++ b/packages/compiler/src/frontend/lowering/lower-stdlib.ts @@ -1465,7 +1465,8 @@ export function lowerStaticFetchCompanionCall( } const root = call.expression.expression; const symbol = lowerer.resolveValueSymbol(root); - if (!symbol || !lowerer.isStdlibSymbol(symbol)) return null; + if ((!symbol || !lowerer.isStdlibSymbol(symbol)) && !isStreamWebReadableStream(lowerer, root)) + return null; const loc = locOf(call); if (root.text === "AbortSignal") { noteEventListeners(lowerer, loc); @@ -1874,6 +1875,12 @@ export function lowerAbortControllerNew(lowerer: Lowerer, expr: ts.NewExpression }; } +/** An import binding of node:stream/web's ReadableStream, which IS the + * global class (`import { ReadableStream } from "node:stream/web"`). */ +function isStreamWebReadableStream(lowerer: Lowerer, ident: ts.Identifier): boolean { + return ident.text === "ReadableStream" && lowerer.isStdlibGlobal(ident, "ReadableStream"); +} + export function lowerStaticReadableStreamNew( lowerer: Lowerer, expr: ts.NewExpression, @@ -1882,7 +1889,11 @@ export function lowerStaticReadableStreamNew( return null; } const symbol = lowerer.resolveValueSymbol(expr.expression); - if (!symbol || !lowerer.isStdlibSymbol(symbol)) return null; + if ( + (!symbol || !lowerer.isStdlibSymbol(symbol)) && + !isStreamWebReadableStream(lowerer, expr.expression) + ) + return null; const args = expr.arguments ?? []; if (args.length > 1) return null; const loc = locOf(expr); diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index b4520d3be..dcb2e08d5 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -11538,6 +11538,12 @@ export class Lowerer { const callable = this.lowerBuiltinCallableValue(bi, loc); if (callable) return callable; } + // node:stream/web's exports are the web stream globals themselves: in + // JavaScript sources the same identity token as the global spelling + // (`require("stream/web").ReadableStream === globalThis.ReadableStream`). + if (bi.module === "stream/web" && isJsSourceFile(expr.getSourceFile())) { + return { kind: "strLit", value: `[builtin ${bi.member}]`, type: STRING, loc }; + } if (bi.member === "constants" && bi.module === "fs") { // A bare `fs.constants` read (not one of the baked bits above). this.noLowering( diff --git a/packages/compiler/src/frontend/lowering/surfaces.ts b/packages/compiler/src/frontend/lowering/surfaces.ts index d50fc8be3..4ff99f309 100644 --- a/packages/compiler/src/frontend/lowering/surfaces.ts +++ b/packages/compiler/src/frontend/lowering/surfaces.ts @@ -24,6 +24,7 @@ import { } from "../../ir/ir.js"; import { isNodeTypesPath, requireSpecOf } from "../program.js"; import { ZLIB_CONSTANTS } from "./zlib-constants.js"; +import { V8_MEMBER_REASON } from "../builtin-modules.js"; /** zlib stream instances: runtime Transform streams (scr_zlib_stream.c). */ const ZLIB_STREAM: IrType = { kind: "object", className: "%Transform" }; @@ -1873,6 +1874,18 @@ export const BUILTIN_MODULE_FNS: Record< // special-cased); the keys exist for the same reason net/http's do. dgram: {}, dns: {}, + // node:dns/promises rides the same spoke (lookup's promise form). + "dns/promises": {}, + // node:v8: getHeapStatistics answers allocator-derived figures in + // Node's shape (builtins/v8.ts — the entry carries the canonical + // shape); every other member fences with the by-design reason + // (builtinFenceHintOf). + v8: { + getHeapStatistics: { fn: "process.memoryUsageTake", params: [], result: VOID }, + }, + // node:stream/web re-exports the web stream globals (the same objects); + // its members lower through the globals' own paths. + "stream/web": {}, // node:assert lowers ENTIRELY through the assert spoke (lower-assert.ts // — every call shape is special-cased: optional messages complete, the // comparisons pick per-type libCalls, deep equality synthesizes @@ -2708,7 +2721,11 @@ export const FS_CALLBACK_MEMBERS = new Set([ * inherited function into the diagnostic text). */ export function builtinFenceHintOf(module: string, member: string): string | undefined { const mod = ownEntry(BUILTIN_MODULE_FENCE_HINTS, module); - return mod ? ownEntry(mod, member) : undefined; + const hint = mod ? ownEntry(mod, member) : undefined; + // node:v8's unlowered members observe the engine itself: the by-design + // reason, not a pending-surface hint (kept out of the hint table so the + // compatibility rows stay by-design rather than refused). + return hint === undefined && module === "v8" ? V8_MEMBER_REASON : hint; } /** BUILTIN_MODULE_CONSTS with the win32-target overrides applied — the @@ -3323,6 +3340,10 @@ export function stdlibGlobalAliasNameOf( "Number", "String", "Boolean", + // Code generation from strings throws Node's EvalError at every call + // (code-generation.ts); zod 4 probes `const F = Function; new F("")`. + "Function", + "eval", ].includes(name) ? name : null; diff --git a/packages/compiler/src/frontend/npm-static.ts b/packages/compiler/src/frontend/npm-static.ts index 1f83a12d4..87cc2a0d7 100644 --- a/packages/compiler/src/frontend/npm-static.ts +++ b/packages/compiler/src/frontend/npm-static.ts @@ -14,6 +14,7 @@ import { workspacePackageOfPath, } from "./workspace-registry.js"; import { trackedExists, trackedReadFile, trackedRealpath } from "./input-tracker.js"; +import { npmBuiltinAliasOf } from "./builtin-modules.js"; let activePackages: ReadonlySet = new Set(); let declarationOverloads: ReadonlyMap = new Map(); @@ -29,7 +30,9 @@ const offenders = new Map(); const rewriteCache = new Map(); export function setNpmStaticPackages>(packages: T): void { - activePackages = new Set(packages); + // npm packages served as builtins (graceful-fs as fs) never compile + // their JavaScript, even when named. + activePackages = new Set([...packages].filter((name) => npmBuiltinAliasOf(name) === null)); declarationOverloads = new Map(); declarationProperties = new Map(); offenders.clear(); diff --git a/packages/compiler/src/frontend/pipeline.ts b/packages/compiler/src/frontend/pipeline.ts index 2ec9553f6..c0afe58a5 100644 --- a/packages/compiler/src/frontend/pipeline.ts +++ b/packages/compiler/src/frontend/pipeline.ts @@ -7,6 +7,7 @@ import { entryFunctionExports, type EntryExportInfo } from "./lib-exports.js"; import { entryContractFacts, type ContractFacts } from "./lib-contract.js"; import { canonicalBuiltinModule, + npmBuiltinAliasOf, checkPreflight, isNodeTypesPath, locOf, @@ -132,6 +133,9 @@ function detectAutoPackages( // never npm candidates. Auto keeps its original path (the // @types/node answer skips them below), unchanged. if (mode === "lib" && canonicalBuiltinModule(spec) !== null) continue; + // npm packages served as builtins (graceful-fs as fs) never compile + // their JavaScript. + if (npmBuiltinAliasOf(spec) !== null) continue; // Explicit attempts name the runtime package. Types-first resolution // may instead identify its @types twin, which has no runtime to admit. if (judged?.has(packageNameOfSpecifier(spec))) continue; diff --git a/packages/compiler/src/frontend/program.ts b/packages/compiler/src/frontend/program.ts index df0cb673e..899d56604 100644 --- a/packages/compiler/src/frontend/program.ts +++ b/packages/compiler/src/frontend/program.ts @@ -99,8 +99,10 @@ import { import { builtinDefaultImportModule, canonicalBuiltinModule, + npmBuiltinAliasOf, SUPPORTED_NODE_MODULES, unsupportedModuleFeatureOf, + TEMPORAL_GLOBAL, } from "./builtin-modules.js"; import { clearWorkspacePackages, @@ -661,6 +663,42 @@ function entryPackageProgramRoots7(program: ts.Program, entryPath: string): stri return [...roots].sort(); } +/** TS 7's ESNext temporal declarations: a lib slice scriptc adds only for + * programs whose sources name the global (programNamesTemporal7). */ +const TEMPORAL_LIB = "lib.esnext.temporal.d.ts"; + +/** True when a program source (user code or compiled npm JavaScript) + * contains the temporal global's name as a whole word. A cheap syntactic + * scan of the implementation files root discovery already materialized; a + * mention in a comment or string also counts, which only adds the lib + * slice. */ +function programNamesTemporal7(program: ts.Program): boolean { + for (const sf of program.getImplementationSourceFiles()) { + const text = sf.text; + for ( + let at = text.indexOf(TEMPORAL_GLOBAL); + at >= 0; + at = text.indexOf(TEMPORAL_GLOBAL, at + 8) + ) { + if (!isIdentifierCharAt(text, at - 1) && !isIdentifierCharAt(text, at + 8)) return true; + } + } + return false; +} + +function isIdentifierCharAt(text: string, index: number): boolean { + if (index < 0 || index >= text.length) return false; + const c = text.charCodeAt(index); + return ( + (c >= 48 && c <= 57) || + (c >= 65 && c <= 90) || + (c >= 97 && c <= 122) || + c === 36 || + c === 95 || + c > 127 + ); +} + function loadProgram7( host: ts.Ts7Host, entryPath: string, @@ -726,6 +764,20 @@ function loadProgram7( program = host.createProgram([...programRoots, overridesDtsPath()], options); if (pass === 31) throw new Error("static program-root discovery did not converge"); } + // Node 24 ships no temporal global, but TS 7 types it in an ESNext + // lib slice. Programs that name it (feature probes such as smol-toml's + // typeof check) get that slice so they type-check, and lower to Node's + // answers (typeof "undefined", ReferenceError on use). Every other + // program keeps the identical lib set: the slice would add globals to + // the checker's world for everyone. + if (programNamesTemporal7(program)) { + program.dispose(); + options = { + ...options, + lib: [...((options.lib as string[] | undefined) ?? []), TEMPORAL_LIB], + }; + program = host.createProgram([...programRoots, overridesDtsPath()], options); + } const entry = program.getSourceFile(entryPath); if (!entry) throw new Error(`could not load ${entryPath}`); const externalTypeSpecifiersByFile = externalTypeFileClosure7(program, externalTypes); @@ -4880,6 +4932,9 @@ function preflight7(load: LoadResult): { continue; } } + // An npm package served as a builtin (graceful-fs as fs) is never + // a program-module edge, even when --npm-static names it. + if (npmBuiltinAliasOf(req.spec) !== null) continue; const isRelative = isRelativeSpecifier(req.spec); let dep = resolveImport7(program, sf, req.spec, "require"); if (dep === null && !isRelative) { @@ -4943,6 +4998,7 @@ function preflight7(load: LoadResult): { if (load.externalTypes.has(spec)) { continue; } + if (npmBuiltinAliasOf(spec) !== null) continue; const projectDep = resolveImport7(program, sf, spec, "require"); if (projectDep !== null) { // A bare require of a JSON document only parses it: nothing to @@ -5669,6 +5725,7 @@ export { ambientDtsPath, fallbackDtsPath, isNodeTypesPath, overridesDtsPath } fr export { builtinDefaultImportModule, canonicalBuiltinModule, + npmBuiltinAliasOf, SUPPORTED_BUILTIN_MODULES, } from "./builtin-modules.js"; diff --git a/packages/compiler/src/frontend/type-mapper.ts b/packages/compiler/src/frontend/type-mapper.ts index 8157d8c04..1cb496866 100644 --- a/packages/compiler/src/frontend/type-mapper.ts +++ b/packages/compiler/src/frontend/type-mapper.ts @@ -3674,6 +3674,34 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { ), }; } + // v8.getHeapStatistics()'s record, in Node's key order (builtins/v8.ts). + if (isStdlibInterface("HeapInfo")) { + const names = [ + "total_heap_size", + "total_heap_size_executable", + "total_physical_size", + "total_available_size", + "used_heap_size", + "heap_size_limit", + "malloced_memory", + "peak_malloced_memory", + "does_zap_garbage", + "number_of_native_contexts", + "number_of_detached_contexts", + "total_global_handles_size", + "used_global_handles_size", + "external_memory", + ]; + return { + kind: "record", + shapeId: ctx.shapes.intern( + [...names].sort().map((name) => ({ name, type: F64 })), + false, + undefined, + names, + ), + }; + } // process.cpuUsage()/threadCpuUsage()'s {user, system} microsecond // record and resourceUsage()'s getrusage record — stdlib data // interfaces interned explicitly (the RemoteInfo pattern; declared diff --git a/packages/compiler/src/index.ts b/packages/compiler/src/index.ts index c3009665a..dcb0db9ce 100644 --- a/packages/compiler/src/index.ts +++ b/packages/compiler/src/index.ts @@ -527,6 +527,7 @@ async function compileExecutableNative( http: features.http, http2: features.http2, dgram: features.dgram, + dnsPromises: features.dnsPromises, watch: features.watch, foreignFfi: features.foreignFfi, ...(features.workers ? { workers: true } : {}), diff --git a/packages/compiler/src/ir/build.ts b/packages/compiler/src/ir/build.ts index adb7366cc..77259450c 100644 --- a/packages/compiler/src/ir/build.ts +++ b/packages/compiler/src/ir/build.ts @@ -58,10 +58,10 @@ export function dynUndefinedExpr(loc: SrcLoc): IrExpr { } /** An always-throwing Node error with the replaced expression's type. - * Kinds: 0 Error, 1 TypeError, 2 RangeError, 5 ReferenceError. An empty + * Kinds: 0 Error, 1 TypeError, 2 RangeError, 5 ReferenceError, 6 EvalError. An empty * code omits the error code property. */ export function nodeThrowExpr( - kind: 0 | 1 | 2 | 5, + kind: 0 | 1 | 2 | 5 | 6, code: string, message: string, type: IrType, diff --git a/packages/compiler/src/ir/builtin-effects.ts b/packages/compiler/src/ir/builtin-effects.ts index 1bb9cbd14..4b28a75d3 100644 --- a/packages/compiler/src/ir/builtin-effects.ts +++ b/packages/compiler/src/ir/builtin-effects.ts @@ -754,6 +754,8 @@ export const MAY_THROW_LIB_FNS: ReadonlySet = new Set([ // Node's argument checks (ERR_INVALID_ARG_TYPE/VALUE, ERR_SOCKET_BAD_PORT). "dns.lookupDyn", "dns.lookupService", + "dns.lookupPromise", + "dns.lookupServicePromise", "dns.setDefaultResultOrder", "net.connect", "net.connectCb", diff --git a/packages/compiler/src/ir/builtin-signatures.ts b/packages/compiler/src/ir/builtin-signatures.ts index ca2749591..e192c529b 100644 --- a/packages/compiler/src/ir/builtin-signatures.ts +++ b/packages/compiler/src/ir/builtin-signatures.ts @@ -1406,6 +1406,12 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ * callback) and lookupService(address, port, callback). */ "dns.lookupDyn": { argTypes: [DYN, DYN, DYN, BOOL], result: VOID }, "dns.lookupService": { argTypes: [DYN, DYN, DYN, BOOL], result: VOID }, + /** node:dns/promises: lookup(hostname, options) and lookupService(address, + * port) over the callback forms; the promise fulfills with the dyn + * { address, family } (array for all: true) / { hostname, service }. + * Argument errors throw synchronously (may-throw). */ + "dns.lookupPromise": { argTypes: [DYN, DYN], result: { kind: "promise", inner: DYN } }, + "dns.lookupServicePromise": { argTypes: [DYN, DYN], result: { kind: "promise", inner: DYN } }, "dns.getServers": { argTypes: [], result: DYN }, "dns.getDefaultResultOrder": { argTypes: [], result: STRING }, "dns.setDefaultResultOrder": { argTypes: [DYN], result: VOID }, diff --git a/packages/compiler/src/ir/capture-values.ts b/packages/compiler/src/ir/capture-values.ts index d7a1516bb..779ca1b76 100644 --- a/packages/compiler/src/ir/capture-values.ts +++ b/packages/compiler/src/ir/capture-values.ts @@ -238,7 +238,7 @@ interface FunctionFacts { hoisted: Map; /** Bindings called through `callValue` without a receiver. */ callees: Set; - /** Temporal-dead-zone bindings: the statement list holding both the + /** TDZ (temporal dead zone) bindings: the statement list holding both the * empty `varDecl` and the initializing `assign`, with their indexes. */ tdzInits: Map; classCaptured: Set; diff --git a/packages/compiler/src/ir/runtime-features.ts b/packages/compiler/src/ir/runtime-features.ts index d16edfabd..04e34c45e 100644 --- a/packages/compiler/src/ir/runtime-features.ts +++ b/packages/compiler/src/ir/runtime-features.ts @@ -38,6 +38,8 @@ export interface RuntimeFeatures { * scr_test.c's mock-aware entry points. */ mockTimers: boolean; dgram: boolean; + /** node:dns/promises: scr_dns_promises.c over the dgram unit. */ + dnsPromises: boolean; http: boolean; http2: boolean; tls: boolean; @@ -210,6 +212,7 @@ function scanRuntimeFeatures(mod: IrModule, stopAt?: keyof RuntimeFeatures): Run nodeTest: false, mockTimers: false, dgram: false, + dnsPromises: false, http: false, http2: false, tls: false, @@ -421,6 +424,8 @@ function scanRuntimeFeatures(mod: IrModule, stopAt?: keyof RuntimeFeatures): Run } } if (fn.startsWith("dgram.") || fn.startsWith("dns.")) features.dgram = true; + if (fn === "dns.lookupPromise" || fn === "dns.lookupServicePromise") + features.dnsPromises = true; if (fn.startsWith("tlsca.")) features.tlsCa = true; // scr_cipher.c over the mbedTLS archive; DEP0182 reports through the // process-warning unit, and unknown output encodings render through diff --git a/packages/compiler/src/native/cli.ts b/packages/compiler/src/native/cli.ts index c0d89bbbf..f567f576a 100644 --- a/packages/compiler/src/native/cli.ts +++ b/packages/compiler/src/native/cli.ts @@ -95,6 +95,7 @@ async function main(): Promise { http: false, http2: false, dgram: false, + dnsPromises: false, watch: false, foreignFfi: false, nodeTest: false, diff --git a/packages/compiler/src/native/prepare.ts b/packages/compiler/src/native/prepare.ts index 18635c8c7..fc28ef7b5 100644 --- a/packages/compiler/src/native/prepare.ts +++ b/packages/compiler/src/native/prepare.ts @@ -147,6 +147,7 @@ function validFeatures(value: unknown): value is NativeLinkFeatures { "http", "http2", "dgram", + "dnsPromises", "watch", "foreignFfi", "nodeTest", diff --git a/packages/compiler/src/native/sanitizer.test.ts b/packages/compiler/src/native/sanitizer.test.ts index 9047f3376..104402385 100644 --- a/packages/compiler/src/native/sanitizer.test.ts +++ b/packages/compiler/src/native/sanitizer.test.ts @@ -97,6 +97,7 @@ test.skipIf(process.platform !== "linux" && process.platform !== "darwin")( http: false, http2: false, dgram: false, + dnsPromises: false, watch: false, foreignFfi: false, nodeTest: false, diff --git a/packages/compiler/surface-manifest.json b/packages/compiler/surface-manifest.json index c01101f6f..153b4b628 100644 --- a/packages/compiler/surface-manifest.json +++ b/packages/compiler/surface-manifest.json @@ -20,7 +20,7 @@ "name": "package imports", "status": "unsupported", "code": "SC1010", - "note": "relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported" + "note": "relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, dns/promises, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, stream/web, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, v8, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported" }, { "id": "diagnostic.sc1012", @@ -810,6 +810,13 @@ "status": "static", "note": "recognized module (bare and node:-prefixed specifiers)" }, + { + "id": "node-builtin.dns.promises", + "kind": "node-builtin", + "name": "dns/promises", + "status": "static", + "note": "recognized module (bare and node:-prefixed specifiers)" + }, { "id": "node-builtin.events", "kind": "node-builtin", @@ -2205,6 +2212,13 @@ "status": "static", "note": "recognized module (bare and node:-prefixed specifiers)" }, + { + "id": "node-builtin.stream.web", + "kind": "node-builtin", + "name": "stream/web", + "status": "static", + "note": "recognized module (bare and node:-prefixed specifiers)" + }, { "id": "node-builtin.string_decoder", "kind": "node-builtin", @@ -2735,6 +2749,19 @@ "name": "util/types.isWeakSet", "status": "static" }, + { + "id": "node-builtin.v8", + "kind": "node-builtin", + "name": "v8", + "status": "static", + "note": "recognized module (bare and node:-prefixed specifiers)" + }, + { + "id": "node-builtin.v8.getHeapStatistics", + "kind": "node-builtin", + "name": "v8.getHeapStatistics", + "status": "static" + }, { "id": "node-builtin.worker_threads", "kind": "node-builtin", diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/code-generation-disallowed.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/code-generation-disallowed.cjs.json new file mode 100644 index 000000000..04ce82284 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/code-generation-disallowed.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/code-generation-disallowed.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/code-generation-disallowed.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/code-generation-disallowed.ts.json new file mode 100644 index 000000000..919dee207 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/code-generation-disallowed.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/code-generation-disallowed.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/dns-promises-lookup.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/dns-promises-lookup.ts.json new file mode 100644 index 000000000..ef705d6a8 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/dns-promises-lookup.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/dns-promises-lookup.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/dns-promises-untyped.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/dns-promises-untyped.cjs.json new file mode 100644 index 000000000..010755d6b --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/dns-promises-untyped.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/dns-promises-untyped.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/graceful-fs-alias/main.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/graceful-fs-alias/main.cjs.json new file mode 100644 index 000000000..7551f04a5 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/graceful-fs-alias/main.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/graceful-fs-alias/main.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/stream-web-readable.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/stream-web-readable.ts.json new file mode 100644 index 000000000..f511fabff --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/stream-web-readable.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/stream-web-readable.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/stream-web-require.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/stream-web-require.cjs.json new file mode 100644 index 000000000..e29438aa8 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/stream-web-require.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/stream-web-require.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/temporal-absent-probe.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/temporal-absent-probe.js.json new file mode 100644 index 000000000..8ab7b473c --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/temporal-absent-probe.js.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/temporal-absent-probe.js" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/temporal-absent.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/temporal-absent.ts.json new file mode 100644 index 000000000..9c7899eca --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/temporal-absent.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/temporal-absent.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/v8-heap-statistics.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/v8-heap-statistics.ts.json new file mode 100644 index 000000000..6dddee82c --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/v8-heap-statistics.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/v8-heap-statistics.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/v8-require-optional.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/v8-require-optional.cjs.json new file mode 100644 index 000000000..ca65df774 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/v8-require-optional.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/v8-require-optional.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/imports.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/imports.ts.json index 7b18f03a0..980cd1fd7 100644 --- a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/imports.ts.json +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/imports.ts.json @@ -21,7 +21,7 @@ "end": 41 }, "milestone": "M4", - "hint": "relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported" + "hint": "relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, dns/promises, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, stream/web, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, v8, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported" } ] } diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/module-out-of-scope.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/module-out-of-scope.ts.json index 1937e357b..8ec8127e2 100644 --- a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/module-out-of-scope.ts.json +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/module-out-of-scope.ts.json @@ -5,11 +5,11 @@ "diags": [ { "code": "SC0001", - "message": "Cannot find name 'node:v8'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node` and then add 'node' to the types field in your tsconfig.", + "message": "Cannot find name 'node:vm'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node` and then add 'node' to the types field in your tsconfig.", "loc": { "file": "/tests/diagnostics/module-out-of-scope.ts", - "start": 381, - "end": 390 + "start": 384, + "end": 393 } }, { @@ -17,8 +17,8 @@ "message": "Cannot find name 'domain'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node` and then add 'node' to the types field in your tsconfig.", "loc": { "file": "/tests/diagnostics/module-out-of-scope.ts", - "start": 415, - "end": 423 + "start": 418, + "end": 426 } }, { @@ -26,8 +26,8 @@ "message": "Cannot find name 'node:sqlite'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node` and then add 'node' to the types field in your tsconfig.", "loc": { "file": "/tests/diagnostics/module-out-of-scope.ts", - "start": 454, - "end": 467 + "start": 457, + "end": 470 } }, { @@ -35,8 +35,8 @@ "message": "Cannot find module '_http_common' or its corresponding type declarations.", "loc": { "file": "/tests/diagnostics/module-out-of-scope.ts", - "start": 496, - "end": 510 + "start": 499, + "end": 513 } }, { @@ -44,84 +44,84 @@ "message": "Cannot find name 'node:inspector'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node` and then add 'node' to the types field in your tsconfig.", "loc": { "file": "/tests/diagnostics/module-out-of-scope.ts", - "start": 533, - "end": 549 + "start": 536, + "end": 552 } }, { "code": "SC0001", - "message": "Cannot find name 'node:stream/web'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node` and then add 'node' to the types field in your tsconfig.", + "message": "Cannot find name 'node:repl'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node` and then add 'node' to the types field in your tsconfig.", "loc": { "file": "/tests/diagnostics/module-out-of-scope.ts", - "start": 582, - "end": 599 + "start": 576, + "end": 587 } }, { "code": "SC1010", - "message": "the 'node:v8' module (it observes V8 engine internals — heap statistics and snapshots, GC and CPU profiles, serialize/deserialize — and a compiled binary embeds no V8 engine to observe) is not supported yet", + "message": "the 'node:vm' module (it compiles and runs JavaScript source at runtime, and a compiled binary embeds no JavaScript engine to run it) is not supported yet", "loc": { "file": "/tests/diagnostics/module-out-of-scope.ts", - "start": 347, - "end": 391 + "start": 352, + "end": 394 }, "milestone": "M4", - "hint": "relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported" + "hint": "relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, dns/promises, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, stream/web, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, v8, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported" }, { "code": "SC1010", "message": "the 'domain' module (deprecated in Node and slated for removal; its implicit error interception hooks every async callback at engine level, which is not modeled) is not supported yet", "loc": { "file": "/tests/diagnostics/module-out-of-scope.ts", - "start": 392, - "end": 424 + "start": 395, + "end": 427 }, "milestone": "M4", - "hint": "relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported" + "hint": "relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, dns/promises, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, stream/web, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, v8, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported" }, { "code": "SC1010", "message": "the 'node:sqlite' module (it wraps the SQLite library bundled into the node executable, and scriptc binaries bundle no SQLite engine) is not supported yet", "loc": { "file": "/tests/diagnostics/module-out-of-scope.ts", - "start": 425, - "end": 468 + "start": 428, + "end": 471 }, "milestone": "M4", - "hint": "relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported" + "hint": "relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, dns/promises, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, stream/web, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, v8, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported" }, { "code": "SC1010", "message": "the '_http_common' module (a Node-internal module: it exposes Node's own implementation objects, which the scriptc runtime does not replicate) is not supported yet", "loc": { "file": "/tests/diagnostics/module-out-of-scope.ts", - "start": 469, - "end": 511 + "start": 472, + "end": 514 }, "milestone": "M4", - "hint": "relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported" + "hint": "relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, dns/promises, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, stream/web, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, v8, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported" }, { "code": "SC1010", "message": "the 'node:inspector' module (it drives the V8 inspector protocol — debugger, profiler, heap access — and a compiled binary embeds no V8 engine to inspect) is not supported yet", "loc": { "file": "/tests/diagnostics/module-out-of-scope.ts", - "start": 512, - "end": 550 + "start": 515, + "end": 553 }, "milestone": "M4", - "hint": "relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported" + "hint": "relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, dns/promises, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, stream/web, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, v8, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported" }, { "code": "SC1010", - "message": "the 'node:stream/web' module is not supported yet", + "message": "the 'node:repl' module is not supported yet", "loc": { "file": "/tests/diagnostics/module-out-of-scope.ts", - "start": 551, - "end": 600 + "start": 554, + "end": 588 }, "milestone": "M4", - "hint": "relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported" + "hint": "relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, dns/promises, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, stream/web, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, v8, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported" } ] } diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/pkg-import-fences/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/pkg-import-fences/main.ts.json index 50b586123..cfe16cdb3 100644 --- a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/pkg-import-fences/main.ts.json +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/pkg-import-fences/main.ts.json @@ -12,7 +12,7 @@ "end": 476 }, "milestone": "M4", - "hint": "relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported" + "hint": "relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, dns/promises, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, stream/web, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, v8, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported" }, { "code": "SC1010", @@ -23,7 +23,7 @@ "end": 415 }, "milestone": "M4", - "hint": "relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported" + "hint": "relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, dns/promises, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, stream/web, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, v8, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported" }, { "code": "SC1010", @@ -34,7 +34,7 @@ "end": 448 }, "milestone": "M4", - "hint": "relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported" + "hint": "relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, dns/promises, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, stream/web, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, v8, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported" } ] } diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/import-fences.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/import-fences.ts.json index 52a865199..05b272052 100644 --- a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/import-fences.ts.json +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/import-fences.ts.json @@ -5,14 +5,14 @@ "diags": [ { "code": "SC1010", - "message": "the 'v8' module (it observes V8 engine internals — heap statistics and snapshots, GC and CPU profiles, serialize/deserialize — and a compiled binary embeds no V8 engine to observe) is not supported yet", + "message": "the 'inspector' module (it drives the V8 inspector protocol — debugger, profiler, heap access — and a compiled binary embeds no V8 engine to inspect) is not supported yet", "loc": { "file": "/tests/fixtures/node-types/import-fences.ts", - "start": 422, - "end": 461 + "start": 432, + "end": 465 }, "milestone": "M4", - "hint": "relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported" + "hint": "relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, dns/promises, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, stream/web, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, v8, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported" } ] } diff --git a/packages/runtime-darwin-arm64/runtime-pack-matrix.mjs b/packages/runtime-darwin-arm64/runtime-pack-matrix.mjs index 54e789624..f49656049 100644 --- a/packages/runtime-darwin-arm64/runtime-pack-matrix.mjs +++ b/packages/runtime-darwin-arm64/runtime-pack-matrix.mjs @@ -106,6 +106,8 @@ const optional = [ ["scr_http.c", "httpEffective"], ["scr_http2.c", "http2"], ["scr_dgram.c", "dgram"], + // The dns promise forms run over the dgram unit's callback forms. + ["scr_dns_promises.c", all("dgram", "dnsPromises")], ["scr_watch.c", "watch"], ["scr_ffi_queue.c", "foreignFfi"], ["scr_test.c", "nodeTest"], diff --git a/packages/runtime-pack-common/runtime-pack-matrix.mjs b/packages/runtime-pack-common/runtime-pack-matrix.mjs index 6b5052482..c9683f6a5 100644 --- a/packages/runtime-pack-common/runtime-pack-matrix.mjs +++ b/packages/runtime-pack-common/runtime-pack-matrix.mjs @@ -96,6 +96,8 @@ const OPTIONAL = [ ["scr_http.c", "httpEffective"], ["scr_http2.c", "http2"], ["scr_dgram.c", "dgram"], + // The dns promise forms run over the dgram unit's callback forms. + ["scr_dns_promises.c", all("dgram", "dnsPromises")], ["scr_watch.c", "watch"], ["scr_ffi_queue.c", "foreignFfi"], ["scr_test.c", "nodeTest"], diff --git a/packages/runtime-wasm32-wasi/scripts/build.mjs b/packages/runtime-wasm32-wasi/scripts/build.mjs index ca0786607..a5a30dae0 100644 --- a/packages/runtime-wasm32-wasi/scripts/build.mjs +++ b/packages/runtime-wasm32-wasi/scripts/build.mjs @@ -16,6 +16,7 @@ process.env.SCRIPTC_RUNTIME_PACK_CONFIG = JSON.stringify({ "scr_http.c", "scr_http2.c", "scr_dgram.c", + "scr_dns_promises.c", "scr_watch.c", "scr_tls_ca.c", "scr_tls.c", diff --git a/packages/runtime/src/scr_dgram.h b/packages/runtime/src/scr_dgram.h index ccdfcbf77..a8e08ec1d 100644 --- a/packages/runtime/src/scr_dgram.h +++ b/packages/runtime/src/scr_dgram.h @@ -83,6 +83,11 @@ void scr_dns_lookup_dyn(const ScrDyn *hostname, const ScrDyn *options, const Scr bool typed); void scr_dns_lookup_service(const ScrDyn *address, const ScrDyn *port, const ScrDyn *callback, bool typed); +/* node:dns/promises (scr_dns_promises.c, linked only for the promise + * forms): the promise (+1), or NULL with the argument error pending. + * Arguments borrowed. */ +ScrPromise *scr_dns_lookup_promise(const ScrDyn *hostname, const ScrDyn *options); +ScrPromise *scr_dns_lookup_service_promise(const ScrDyn *address, const ScrDyn *port); ScrDyn *scr_dns_get_servers(void); /* +1 */ ScrStr *scr_dns_get_default_result_order(void); /* +1 */ void scr_dns_set_default_result_order(const ScrDyn *order); diff --git a/packages/runtime/src/scr_dns_promises.c b/packages/runtime/src/scr_dns_promises.c new file mode 100644 index 000000000..87f5db9e3 --- /dev/null +++ b/packages/runtime/src/scr_dns_promises.c @@ -0,0 +1,312 @@ +/* node:dns/promises — lookup(hostname[, options]) and lookupService(address, + * port) as promises over scr_dgram.c's callback forms. LINK-GATED: compiled + * into the binary only when the IR uses the promise forms (the dnsPromises + * runtime feature), so callback-only and dns-free programs keep their exact + * executables. This unit uses only scr_dgram.c's public surface. + * + * lookup follows lib/internal/dns/promises.js: its argument checks are + * stricter than the callback form's (family is a number only) and throw + * synchronously, before any promise exists (a NUL byte in the hostname + * throws first, as in the callback form). An empty hostname (with the + * shared DEP0118 warning) and an IP literal settle at once, as Node resolves + * them inside the promise executor; other names resolve through the + * callback form's getaddrinfo and deliveries, with normalized options. + * lookup fulfills with { address, family } (an array of them for all: + * true), lookupService with { hostname, service }; failures reject with the + * callback forms' errors. */ +#include "scr_platform.h" +#include "scr_runtime.h" + +#include +#ifdef _WIN32 +#include +#include +#else +#include +#include +#endif + +/* caps[0]: the destination promise. */ +static ScrPromise *scr_dnsp_promise_of(ScrClosure *clo) { + return (ScrPromise *)scr_box_get_ref(clo->caps[0]); +} + +static void scr_dnsp_fulfill(ScrPromise *p, ScrDyn *value /* moves */) { + scr_promise_fulfill_ref(p, value, scr_dyn_retain_v, scr_dyn_release_v, scr_dyn_trace_v); +} + +/* A callback whose first argument is an error rejects the promise with it. */ +static bool scr_dnsp_rejects(ScrPromise *p, ScrDyn *const *args, size_t argc) { + if (argc == 0 || args[0]->kind == SCR_DYN_NULL || args[0]->kind == SCR_DYN_UNDEF) return false; + scr_dyn_throw(scr_dyn_retain(args[0])); + scr_promise_reject_pending(p); + return true; +} + +static ScrDyn *scr_dnsp_arg(ScrDyn *const *args, size_t argc, size_t i) { + return i < argc ? scr_dyn_retain(args[i]) : scr_dyn_undefined(); +} + +static ScrDyn *scr_dnsp_pair(const char *a, size_t alen, ScrDyn *av, const char *b, size_t blen, + ScrDyn *bv) { + ScrDyn *entry = scr_dyn_new_obj(); + scr_dyn_obj_set(entry, a, alen, av); + scr_dyn_obj_set(entry, b, blen, bv); + return entry; +} + +/* (err, address, family) -> { address, family } */ +static ScrDyn *scr_dnsp_one_settle(ScrClosure *clo, ScrDyn *const *args, size_t argc) { + ScrPromise *p = scr_dnsp_promise_of(clo); + if (!scr_dnsp_rejects(p, args, argc)) { + scr_dnsp_fulfill(p, scr_dnsp_pair("address", 7, scr_dnsp_arg(args, argc, 1), "family", 6, + scr_dnsp_arg(args, argc, 2))); + } + scr_promise_release(p); + return scr_dyn_undefined(); +} + +/* (err, addresses) -> addresses */ +static ScrDyn *scr_dnsp_all_settle(ScrClosure *clo, ScrDyn *const *args, size_t argc) { + ScrPromise *p = scr_dnsp_promise_of(clo); + if (!scr_dnsp_rejects(p, args, argc)) scr_dnsp_fulfill(p, scr_dnsp_arg(args, argc, 1)); + scr_promise_release(p); + return scr_dyn_undefined(); +} + +/* (err, hostname, service) -> { hostname, service } */ +static ScrDyn *scr_dnsp_service_settle(ScrClosure *clo, ScrDyn *const *args, size_t argc) { + ScrPromise *p = scr_dnsp_promise_of(clo); + if (!scr_dnsp_rejects(p, args, argc)) { + scr_dnsp_fulfill(p, scr_dnsp_pair("hostname", 8, scr_dnsp_arg(args, argc, 1), "service", 7, + scr_dnsp_arg(args, argc, 2))); + } + scr_promise_release(p); + return scr_dyn_undefined(); +} + +/* The empty-hostname path only needs the callback form's DEP0118 warning. */ +static ScrDyn *scr_dnsp_ignore(ScrClosure *clo, ScrDyn *const *args, size_t argc) { + (void)clo; + (void)args; + (void)argc; + return scr_dyn_undefined(); +} + +static ScrDyn *scr_dnsp_callback(ScrPromise *p, ScrDynThunk thunk, uint32_t arity) { + ScrClosure *clo = scr_closure_new((void *)thunk, 1); + ScrBox *box = scr_box_new_obj(&scr_promise_retain_v, &scr_promise_release_v, NULL); + scr_box_set_ref(box, scr_promise_retain(p)); /* the box owns this reference */ + clo->caps[0] = box; + return scr_dyn_new_func(clo, thunk, arity, "", ""); +} + +static bool scr_dnsp_given(const ScrDyn *v) { + return v != NULL && v->kind != SCR_DYN_UNDEF && v->kind != SCR_DYN_NULL; +} + +static const char *const scr_dnsp_orders[] = { "verbatim", "ipv4first", "ipv6first" }; + +static int scr_dnsp_order_of(const ScrDyn *v) { + if (v->kind != SCR_DYN_STR) return -1; + for (int i = 0; i < 3; i++) { + size_t n = strlen(scr_dnsp_orders[i]); + if (v->v.str->len == n && memcmp(v->v.str->data, scr_dnsp_orders[i], n) == 0) return i; + } + return -1; +} + +static int scr_dnsp_default_order(void) { + ScrStr *name = scr_dns_get_default_result_order(); + ScrDyn *d = scr_dyn_new_str(name); + scr_str_release(name); + int order = scr_dnsp_order_of(d); + scr_dyn_release(d); + return order < 0 ? 0 : order; +} + +/* 4 or 6 for an IP literal (net.isIP), 0 otherwise. */ +static double scr_dnsp_literal_family(const ScrStr *h) { + unsigned char buf[16]; + char tmp[64]; + if (h->len == 0 || h->len >= sizeof tmp) return 0; + memcpy(tmp, h->data, h->len); + tmp[h->len] = '\0'; + if (inet_pton(AF_INET, tmp, buf) == 1) return 4; + if (strchr(tmp, ':') != NULL) { + char *pct = strchr(tmp, '%'); + if (pct) *pct = '\0'; + if (inet_pton(AF_INET6, tmp, buf) == 1) return 6; + } + return 0; +} + +/* validateStringWithoutNullBytes(hostname, 'hostname'): the callback + * form's error. False with it pending. */ +static bool scr_dnsp_host_without_nul(const ScrStr *host) { + if (memchr(host->data, '\0', host->len) == NULL) return true; + ScrStr *shown = scr_str_inspect_quoted((ScrStr *)host, true); + ScrJsonBuf msg; + scr_jb_init(&msg); + scr_jb_puts(&msg, "The argument 'hostname' must be a string without null bytes. Received "); + scr_jb_put_str(&msg, shown); + scr_str_release(shown); + ScrStr *text = scr_jb_finish(&msg); + scr_throw_error_msg_code(SCR_ERR_TYPE, text->data, text->len, "ERR_INVALID_ARG_VALUE"); + scr_str_release(text); + return false; +} + +/* lookup's options, validated as lib/internal/dns/promises.js validates + * them. False with the argument error pending. */ +static bool scr_dnsp_options(const ScrDyn *opts, double *family, bool *all, int *order) { + if (opts->kind == SCR_DYN_NUM) { + double f = opts->v.num; + if (f != 0 && f != 4 && f != 6) { + scr_dyn_arg_value_fail("family", "must be one of: 0, 4, 6", opts); + return false; + } + *family = f + 0.0; /* Node coerces -0 to +0 */ + return true; + } + if (opts->kind != SCR_DYN_UNDEF && opts->kind != SCR_DYN_OBJ && opts->kind != SCR_DYN_NULL) { + scr_dyn_arg_type_fail("options", "of type object or integer", opts); + return false; + } + if (opts->kind != SCR_DYN_OBJ) return true; + const ScrDyn *v = scr_dyn_obj_get(opts, "hints", 5); + if (scr_dnsp_given(v)) { + if (v->kind != SCR_DYN_NUM) { + scr_dyn_prop_type_fail("options.hints", "of type number", v); + return false; + } + if (v->v.num != 0) { + scr_throw_error_msg_code(SCR_ERR_ERROR, "dns.lookup hints are not supported yet", 38, "SC2020"); + return false; + } + } + v = scr_dyn_obj_get(opts, "family", 6); + if (scr_dnsp_given(v)) { + if (v->kind != SCR_DYN_NUM || (v->v.num != 0 && v->v.num != 4 && v->v.num != 6)) { + scr_dyn_arg_value_fail("options.family", "must be one of: 0, 4, 6", v); + return false; + } + *family = v->v.num + 0.0; + } + v = scr_dyn_obj_get(opts, "all", 3); + if (scr_dnsp_given(v)) { + if (v->kind != SCR_DYN_BOOL) { + scr_dyn_prop_type_fail("options.all", "of type boolean", v); + return false; + } + *all = v->v.b; + } + v = scr_dyn_obj_get(opts, "verbatim", 8); + if (scr_dnsp_given(v)) { + if (v->kind != SCR_DYN_BOOL) { + scr_dyn_prop_type_fail("options.verbatim", "of type boolean", v); + return false; + } + *order = v->v.b ? 0 : 1; + } + v = scr_dyn_obj_get(opts, "order", 5); + if (scr_dnsp_given(v)) { + int o = scr_dnsp_order_of(v); + if (o < 0) { + scr_dyn_arg_value_fail("options.order", "must be one of: 'verbatim', 'ipv4first', 'ipv6first'", + v); + return false; + } + *order = o; + } + return true; +} + +/* The callback form's options for a validated promise lookup. +1 dyn. */ +static ScrDyn *scr_dnsp_normalized(double family, bool all, int order) { + ScrDyn *o = scr_dyn_new_obj(); + scr_dyn_obj_set(o, "family", 6, scr_dyn_new_num(family)); + scr_dyn_obj_set(o, "all", 3, scr_dyn_new_bool(all)); + const char *name = scr_dnsp_orders[order]; + ScrStr *s = scr_str_new(name, strlen(name)); + scr_dyn_obj_set(o, "order", 5, scr_dyn_new_str(s)); + scr_str_release(s); + return o; +} + +static ScrPromise *scr_dnsp_lookup(const ScrDyn *hostname, const ScrDyn *opts) { + bool host_given = scr_dyn_truthy(hostname); + if (host_given && hostname->kind != SCR_DYN_STR) { + scr_dyn_arg_type_fail("hostname", "of type string", hostname); + return NULL; + } + if (host_given && !scr_dnsp_host_without_nul(hostname->v.str)) return NULL; + double family = 0; + bool all = false; + int order = scr_dnsp_default_order(); + if (!scr_dnsp_options(opts, &family, &all, &order)) return NULL; + ScrPromise *p = scr_promise_new(); + if (!host_given) { + /* The callback form emits the shared DEP0118 warning; its own answer + * goes to a callback that ignores it. */ + ScrDyn *normalized = scr_dnsp_normalized(family, all, order); + ScrDyn *ignore = scr_dyn_new_func(scr_closure_new((void *)scr_dnsp_ignore, 0), scr_dnsp_ignore, + 0, "", ""); + scr_dns_lookup_dyn(hostname, normalized, ignore, false); + scr_dyn_release(ignore); + scr_dyn_release(normalized); + if (all) { + scr_dnsp_fulfill(p, scr_dyn_new_arr()); + } else { + scr_dnsp_fulfill(p, scr_dnsp_pair("address", 7, scr_dyn_new_null(), "family", 6, + scr_dyn_new_num(family == 6 ? 6 : 4))); + } + return p; + } + const ScrStr *host = hostname->v.str; + double literal = scr_dnsp_literal_family(host); + if (literal != 0) { + ScrDyn *entry = scr_dnsp_pair("address", 7, scr_dyn_new_str((ScrStr *)host), "family", 6, + scr_dyn_new_num(literal)); + if (all) { + ScrDyn *list = scr_dyn_new_arr(); + scr_dyn_arr_push(list, entry); + entry = list; + } + scr_dnsp_fulfill(p, entry); + return p; + } + ScrDyn *cb = scr_dnsp_callback(p, all ? scr_dnsp_all_settle : scr_dnsp_one_settle, all ? 2 : 3); + { + ScrDyn *normalized = scr_dnsp_normalized(family, all, order); + scr_dns_lookup_dyn(hostname, normalized, cb, false); + scr_dyn_release(normalized); + } + scr_dyn_release(cb); + if (scr_exc_pending()) { + scr_promise_release(p); + return NULL; + } + return p; +} + +ScrPromise *scr_dns_lookup_promise(const ScrDyn *hostname, const ScrDyn *options) { + if (options->kind != SCR_DYN_TYPED_REF) return scr_dnsp_lookup(hostname, options); + /* a typed options record arrives by reference */ + ScrDyn *view = scr_dyn_typed_ref_materialize(options); + ScrPromise *p = scr_exc_pending() ? NULL : scr_dnsp_lookup(hostname, view); + scr_dyn_release(view); + return p; +} + +ScrPromise *scr_dns_lookup_service_promise(const ScrDyn *address, const ScrDyn *port) { + ScrPromise *p = scr_promise_new(); + ScrDyn *cb = scr_dnsp_callback(p, scr_dnsp_service_settle, 3); + scr_dns_lookup_service(address, port, cb, false); + scr_dyn_release(cb); + if (scr_exc_pending()) { + scr_promise_release(p); + return NULL; + } + return p; +} diff --git a/tests/corpus/code-generation-disallowed.cjs b/tests/corpus/code-generation-disallowed.cjs new file mode 100644 index 000000000..b98f902f7 --- /dev/null +++ b/tests/corpus/code-generation-disallowed.cjs @@ -0,0 +1,36 @@ +// @disallow-code-generation +// The probe packages ship (zod 4's allowsEval): an aliased Function +// constructor inside try/catch decides between a generated fast path and +// an interpreted fallback. Arguments convert to strings before the +// refusal, calling an object's toString. +const F = Function; +function allowsEval() { + try { + new F(""); + return true; + } catch (e) { + console.log("probe:", e.constructor === EvalError, e.message); + return false; + } +} +const parse = allowsEval() + ? new F("input", "return input.trim()") + : (input) => input.trim(); +console.log(JSON.stringify(parse(" value "))); + +try { + new Function({ toString() { console.log("toString called"); return "x"; } }, "return x"); +} catch (e) { + console.log(String(e)); +} +try { + eval({ toString() { console.log("never called"); return "1"; } }); +} catch (e) { + console.log(String(e)); +} +const indirect = eval; +try { + indirect("1"); +} catch (e) { + console.log(e.name); +} diff --git a/tests/corpus/code-generation-disallowed.ts b/tests/corpus/code-generation-disallowed.ts new file mode 100644 index 000000000..d19a30964 --- /dev/null +++ b/tests/corpus/code-generation-disallowed.ts @@ -0,0 +1,44 @@ +// @disallow-code-generation +// eval and the Function constructor throw Node's EvalError, exactly as +// under node --disallow-code-generation-from-strings (the harness runs the +// Node oracle with that flag): catchable, at call time, after the +// arguments evaluate. The Function constructor converts its arguments to +// strings before it refuses; eval refuses without converting. +const log: string[] = []; +const arg = (text: string): string => { + log.push(text); + return text; +}; + +let allowsEval = true; +try { + new Function(""); +} catch (e) { + allowsEval = false; + const err = e as Error; + console.log(err.name, err.message, err instanceof EvalError, err instanceof Error); +} +console.log("allowsEval", allowsEval); + +try { + eval(arg("1 + 1")); +} catch (e) { + log.push((e as Error).name); +} +try { + Function(arg("a"), arg("return a")); +} catch (e) { + log.push((e as Error).name); +} +try { + (0, eval)(arg("2")); +} catch (e) { + log.push((e as Error).name); +} +try { + globalThis.eval(arg("3")); +} catch (e) { + log.push((e as Error).name); +} +console.log(log.join(",")); +console.log(typeof eval, typeof Function); diff --git a/tests/corpus/dns-promises-lookup.ts b/tests/corpus/dns-promises-lookup.ts new file mode 100644 index 000000000..4dbde911a --- /dev/null +++ b/tests/corpus/dns-promises-lookup.ts @@ -0,0 +1,40 @@ +// node:dns/promises and dns.promises: lookup and lookupService as promises +// over the same resolution as the callback forms. Literal addresses settle +// without a resolver; argument errors throw synchronously, as Node +// validates before it creates the promise; failures reject with the +// callback form's errors. +import { lookup } from "node:dns/promises"; +import * as dns from "node:dns"; + +function attempt(label: string, f: () => unknown): void { + try { + f(); + console.log(label, "ok"); + } catch (err) { + const e = err as NodeJS.ErrnoException; + console.log(label, e.name, e.code, e.message); + } +} + +attempt("family 5", () => lookup("localhost", 5)); +attempt("options.family", () => lookup("localhost", { family: 3 })); +attempt("hostname", () => lookup(42 as unknown as string)); + +(async () => { + const one = await lookup("127.0.0.1"); + console.log("literal", one.address, one.family); + const all = await dns.promises.lookup("::1", { all: true }); + console.log("literal all", JSON.stringify(all)); + const four = await lookup("localhost", 4); + console.log("family 4", four.address, four.family); + const v4 = await lookup("localhost", { family: 4, all: true }); + console.log("all", JSON.stringify(v4)); + try { + await lookup("no-such-host.invalid"); + } catch (err) { + const e = err as NodeJS.ErrnoException; + console.log("missing", e.name, e.code, e.syscall, e.message); + } + console.log("order", dns.promises.getDefaultResultOrder()); +})(); +process.nextTick(() => console.log("tick before the first settlement")); diff --git a/tests/corpus/dns-promises-untyped.cjs b/tests/corpus/dns-promises-untyped.cjs new file mode 100644 index 000000000..de25f1404 --- /dev/null +++ b/tests/corpus/dns-promises-untyped.cjs @@ -0,0 +1,34 @@ +// The promise forms from CommonJS, with untyped results: lookup's +// { address, family } objects and arrays, the promise form's stricter +// options (family is a number only), a rejected lookup, and +// lookupService's argument checks. +const dnsPromises = require("dns/promises"); +const { promises } = require("node:dns"); + +async function main() { + console.log(await dnsPromises.lookup("127.0.0.1")); + console.log(await promises.lookup("::1", { all: true })); + console.log(await dnsPromises.lookup("localhost", { family: 4 })); + try { + dnsPromises.lookup("localhost", { family: "IPv4" }); + } catch (e) { + console.log(e.code, e.message); + } + try { + dnsPromises.lookup("localhost", "4"); + } catch (e) { + console.log(e.code, e.message); + } + await dnsPromises.lookup("no-such-host.invalid").catch((e) => console.log("rejected", e.code)); + try { + dnsPromises.lookupService("not-an-ip", 80); + } catch (e) { + console.log(e.code, e.message); + } + try { + dnsPromises.lookupService("127.0.0.1", 70000); + } catch (e) { + console.log(e.code, e.message); + } +} +main(); diff --git a/tests/corpus/graceful-fs-alias/main.cjs b/tests/corpus/graceful-fs-alias/main.cjs new file mode 100644 index 000000000..874cab8c2 --- /dev/null +++ b/tests/corpus/graceful-fs-alias/main.cjs @@ -0,0 +1,34 @@ +// graceful-fs compiles as node:fs (its retry queue for EMFILE/ENFILE is +// the one documented difference): the same file operations, errors and +// callback order as under Node with the real package (vendored here, +// 4.2.11, ISC). gracefulify(fs) is a no-op statement in compiled programs. +const fs = require("graceful-fs"); +const { readFileSync, writeFileSync } = require("graceful-fs"); +const path = require("node:path"); +const os = require("node:os"); + +fs.gracefulify(require("fs")); +const dir = fs.mkdtempSync(path.join(os.tmpdir(), "graceful-fs-alias-")); +const file = path.join(dir, "a.txt"); +writeFileSync(file, "hello"); +fs.appendFileSync(file, " world"); +console.log(readFileSync(file, "utf8"), fs.existsSync(file), fs.statSync(file).size); +console.log(fs.readdirSync(dir)); +try { + fs.readFileSync(path.join(dir, "missing.txt")); +} catch (e) { + console.log("sync", e.code, e.syscall); +} + +fs.readFile(path.join(dir, "missing.txt"), "utf8", (err) => { + console.log("callback", err.code); + fs.writeFile(file, "rewritten", (werr) => { + console.log("written", werr); + fs.readFile(file, "utf8", (rerr, text) => { + console.log("read", rerr, text); + fs.rmSync(dir, { recursive: true }); + console.log("removed", fs.existsSync(dir)); + }); + }); +}); +console.log("scheduled"); diff --git a/tests/corpus/graceful-fs-alias/node_modules/graceful-fs/LICENSE b/tests/corpus/graceful-fs-alias/node_modules/graceful-fs/LICENSE new file mode 100644 index 000000000..e906a25ac --- /dev/null +++ b/tests/corpus/graceful-fs-alias/node_modules/graceful-fs/LICENSE @@ -0,0 +1,15 @@ +The ISC License + +Copyright (c) 2011-2022 Isaac Z. Schlueter, Ben Noordhuis, and Contributors + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/tests/corpus/graceful-fs-alias/node_modules/graceful-fs/clone.js b/tests/corpus/graceful-fs-alias/node_modules/graceful-fs/clone.js new file mode 100644 index 000000000..dff3cc8c5 --- /dev/null +++ b/tests/corpus/graceful-fs-alias/node_modules/graceful-fs/clone.js @@ -0,0 +1,23 @@ +'use strict' + +module.exports = clone + +var getPrototypeOf = Object.getPrototypeOf || function (obj) { + return obj.__proto__ +} + +function clone (obj) { + if (obj === null || typeof obj !== 'object') + return obj + + if (obj instanceof Object) + var copy = { __proto__: getPrototypeOf(obj) } + else + var copy = Object.create(null) + + Object.getOwnPropertyNames(obj).forEach(function (key) { + Object.defineProperty(copy, key, Object.getOwnPropertyDescriptor(obj, key)) + }) + + return copy +} diff --git a/tests/corpus/graceful-fs-alias/node_modules/graceful-fs/graceful-fs.js b/tests/corpus/graceful-fs-alias/node_modules/graceful-fs/graceful-fs.js new file mode 100644 index 000000000..8d5b89e4f --- /dev/null +++ b/tests/corpus/graceful-fs-alias/node_modules/graceful-fs/graceful-fs.js @@ -0,0 +1,448 @@ +var fs = require('fs') +var polyfills = require('./polyfills.js') +var legacy = require('./legacy-streams.js') +var clone = require('./clone.js') + +var util = require('util') + +/* istanbul ignore next - node 0.x polyfill */ +var gracefulQueue +var previousSymbol + +/* istanbul ignore else - node 0.x polyfill */ +if (typeof Symbol === 'function' && typeof Symbol.for === 'function') { + gracefulQueue = Symbol.for('graceful-fs.queue') + // This is used in testing by future versions + previousSymbol = Symbol.for('graceful-fs.previous') +} else { + gracefulQueue = '___graceful-fs.queue' + previousSymbol = '___graceful-fs.previous' +} + +function noop () {} + +function publishQueue(context, queue) { + Object.defineProperty(context, gracefulQueue, { + get: function() { + return queue + } + }) +} + +var debug = noop +if (util.debuglog) + debug = util.debuglog('gfs4') +else if (/\bgfs4\b/i.test(process.env.NODE_DEBUG || '')) + debug = function() { + var m = util.format.apply(util, arguments) + m = 'GFS4: ' + m.split(/\n/).join('\nGFS4: ') + console.error(m) + } + +// Once time initialization +if (!fs[gracefulQueue]) { + // This queue can be shared by multiple loaded instances + var queue = global[gracefulQueue] || [] + publishQueue(fs, queue) + + // Patch fs.close/closeSync to shared queue version, because we need + // to retry() whenever a close happens *anywhere* in the program. + // This is essential when multiple graceful-fs instances are + // in play at the same time. + fs.close = (function (fs$close) { + function close (fd, cb) { + return fs$close.call(fs, fd, function (err) { + // This function uses the graceful-fs shared queue + if (!err) { + resetQueue() + } + + if (typeof cb === 'function') + cb.apply(this, arguments) + }) + } + + Object.defineProperty(close, previousSymbol, { + value: fs$close + }) + return close + })(fs.close) + + fs.closeSync = (function (fs$closeSync) { + function closeSync (fd) { + // This function uses the graceful-fs shared queue + fs$closeSync.apply(fs, arguments) + resetQueue() + } + + Object.defineProperty(closeSync, previousSymbol, { + value: fs$closeSync + }) + return closeSync + })(fs.closeSync) + + if (/\bgfs4\b/i.test(process.env.NODE_DEBUG || '')) { + process.on('exit', function() { + debug(fs[gracefulQueue]) + require('assert').equal(fs[gracefulQueue].length, 0) + }) + } +} + +if (!global[gracefulQueue]) { + publishQueue(global, fs[gracefulQueue]); +} + +module.exports = patch(clone(fs)) +if (process.env.TEST_GRACEFUL_FS_GLOBAL_PATCH && !fs.__patched) { + module.exports = patch(fs) + fs.__patched = true; +} + +function patch (fs) { + // Everything that references the open() function needs to be in here + polyfills(fs) + fs.gracefulify = patch + + fs.createReadStream = createReadStream + fs.createWriteStream = createWriteStream + var fs$readFile = fs.readFile + fs.readFile = readFile + function readFile (path, options, cb) { + if (typeof options === 'function') + cb = options, options = null + + return go$readFile(path, options, cb) + + function go$readFile (path, options, cb, startTime) { + return fs$readFile(path, options, function (err) { + if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) + enqueue([go$readFile, [path, options, cb], err, startTime || Date.now(), Date.now()]) + else { + if (typeof cb === 'function') + cb.apply(this, arguments) + } + }) + } + } + + var fs$writeFile = fs.writeFile + fs.writeFile = writeFile + function writeFile (path, data, options, cb) { + if (typeof options === 'function') + cb = options, options = null + + return go$writeFile(path, data, options, cb) + + function go$writeFile (path, data, options, cb, startTime) { + return fs$writeFile(path, data, options, function (err) { + if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) + enqueue([go$writeFile, [path, data, options, cb], err, startTime || Date.now(), Date.now()]) + else { + if (typeof cb === 'function') + cb.apply(this, arguments) + } + }) + } + } + + var fs$appendFile = fs.appendFile + if (fs$appendFile) + fs.appendFile = appendFile + function appendFile (path, data, options, cb) { + if (typeof options === 'function') + cb = options, options = null + + return go$appendFile(path, data, options, cb) + + function go$appendFile (path, data, options, cb, startTime) { + return fs$appendFile(path, data, options, function (err) { + if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) + enqueue([go$appendFile, [path, data, options, cb], err, startTime || Date.now(), Date.now()]) + else { + if (typeof cb === 'function') + cb.apply(this, arguments) + } + }) + } + } + + var fs$copyFile = fs.copyFile + if (fs$copyFile) + fs.copyFile = copyFile + function copyFile (src, dest, flags, cb) { + if (typeof flags === 'function') { + cb = flags + flags = 0 + } + return go$copyFile(src, dest, flags, cb) + + function go$copyFile (src, dest, flags, cb, startTime) { + return fs$copyFile(src, dest, flags, function (err) { + if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) + enqueue([go$copyFile, [src, dest, flags, cb], err, startTime || Date.now(), Date.now()]) + else { + if (typeof cb === 'function') + cb.apply(this, arguments) + } + }) + } + } + + var fs$readdir = fs.readdir + fs.readdir = readdir + var noReaddirOptionVersions = /^v[0-5]\./ + function readdir (path, options, cb) { + if (typeof options === 'function') + cb = options, options = null + + var go$readdir = noReaddirOptionVersions.test(process.version) + ? function go$readdir (path, options, cb, startTime) { + return fs$readdir(path, fs$readdirCallback( + path, options, cb, startTime + )) + } + : function go$readdir (path, options, cb, startTime) { + return fs$readdir(path, options, fs$readdirCallback( + path, options, cb, startTime + )) + } + + return go$readdir(path, options, cb) + + function fs$readdirCallback (path, options, cb, startTime) { + return function (err, files) { + if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) + enqueue([ + go$readdir, + [path, options, cb], + err, + startTime || Date.now(), + Date.now() + ]) + else { + if (files && files.sort) + files.sort() + + if (typeof cb === 'function') + cb.call(this, err, files) + } + } + } + } + + if (process.version.substr(0, 4) === 'v0.8') { + var legStreams = legacy(fs) + ReadStream = legStreams.ReadStream + WriteStream = legStreams.WriteStream + } + + var fs$ReadStream = fs.ReadStream + if (fs$ReadStream) { + ReadStream.prototype = Object.create(fs$ReadStream.prototype) + ReadStream.prototype.open = ReadStream$open + } + + var fs$WriteStream = fs.WriteStream + if (fs$WriteStream) { + WriteStream.prototype = Object.create(fs$WriteStream.prototype) + WriteStream.prototype.open = WriteStream$open + } + + Object.defineProperty(fs, 'ReadStream', { + get: function () { + return ReadStream + }, + set: function (val) { + ReadStream = val + }, + enumerable: true, + configurable: true + }) + Object.defineProperty(fs, 'WriteStream', { + get: function () { + return WriteStream + }, + set: function (val) { + WriteStream = val + }, + enumerable: true, + configurable: true + }) + + // legacy names + var FileReadStream = ReadStream + Object.defineProperty(fs, 'FileReadStream', { + get: function () { + return FileReadStream + }, + set: function (val) { + FileReadStream = val + }, + enumerable: true, + configurable: true + }) + var FileWriteStream = WriteStream + Object.defineProperty(fs, 'FileWriteStream', { + get: function () { + return FileWriteStream + }, + set: function (val) { + FileWriteStream = val + }, + enumerable: true, + configurable: true + }) + + function ReadStream (path, options) { + if (this instanceof ReadStream) + return fs$ReadStream.apply(this, arguments), this + else + return ReadStream.apply(Object.create(ReadStream.prototype), arguments) + } + + function ReadStream$open () { + var that = this + open(that.path, that.flags, that.mode, function (err, fd) { + if (err) { + if (that.autoClose) + that.destroy() + + that.emit('error', err) + } else { + that.fd = fd + that.emit('open', fd) + that.read() + } + }) + } + + function WriteStream (path, options) { + if (this instanceof WriteStream) + return fs$WriteStream.apply(this, arguments), this + else + return WriteStream.apply(Object.create(WriteStream.prototype), arguments) + } + + function WriteStream$open () { + var that = this + open(that.path, that.flags, that.mode, function (err, fd) { + if (err) { + that.destroy() + that.emit('error', err) + } else { + that.fd = fd + that.emit('open', fd) + } + }) + } + + function createReadStream (path, options) { + return new fs.ReadStream(path, options) + } + + function createWriteStream (path, options) { + return new fs.WriteStream(path, options) + } + + var fs$open = fs.open + fs.open = open + function open (path, flags, mode, cb) { + if (typeof mode === 'function') + cb = mode, mode = null + + return go$open(path, flags, mode, cb) + + function go$open (path, flags, mode, cb, startTime) { + return fs$open(path, flags, mode, function (err, fd) { + if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) + enqueue([go$open, [path, flags, mode, cb], err, startTime || Date.now(), Date.now()]) + else { + if (typeof cb === 'function') + cb.apply(this, arguments) + } + }) + } + } + + return fs +} + +function enqueue (elem) { + debug('ENQUEUE', elem[0].name, elem[1]) + fs[gracefulQueue].push(elem) + retry() +} + +// keep track of the timeout between retry() calls +var retryTimer + +// reset the startTime and lastTime to now +// this resets the start of the 60 second overall timeout as well as the +// delay between attempts so that we'll retry these jobs sooner +function resetQueue () { + var now = Date.now() + for (var i = 0; i < fs[gracefulQueue].length; ++i) { + // entries that are only a length of 2 are from an older version, don't + // bother modifying those since they'll be retried anyway. + if (fs[gracefulQueue][i].length > 2) { + fs[gracefulQueue][i][3] = now // startTime + fs[gracefulQueue][i][4] = now // lastTime + } + } + // call retry to make sure we're actively processing the queue + retry() +} + +function retry () { + // clear the timer and remove it to help prevent unintended concurrency + clearTimeout(retryTimer) + retryTimer = undefined + + if (fs[gracefulQueue].length === 0) + return + + var elem = fs[gracefulQueue].shift() + var fn = elem[0] + var args = elem[1] + // these items may be unset if they were added by an older graceful-fs + var err = elem[2] + var startTime = elem[3] + var lastTime = elem[4] + + // if we don't have a startTime we have no way of knowing if we've waited + // long enough, so go ahead and retry this item now + if (startTime === undefined) { + debug('RETRY', fn.name, args) + fn.apply(null, args) + } else if (Date.now() - startTime >= 60000) { + // it's been more than 60 seconds total, bail now + debug('TIMEOUT', fn.name, args) + var cb = args.pop() + if (typeof cb === 'function') + cb.call(null, err) + } else { + // the amount of time between the last attempt and right now + var sinceAttempt = Date.now() - lastTime + // the amount of time between when we first tried, and when we last tried + // rounded up to at least 1 + var sinceStart = Math.max(lastTime - startTime, 1) + // backoff. wait longer than the total time we've been retrying, but only + // up to a maximum of 100ms + var desiredDelay = Math.min(sinceStart * 1.2, 100) + // it's been long enough since the last retry, do it again + if (sinceAttempt >= desiredDelay) { + debug('RETRY', fn.name, args) + fn.apply(null, args.concat([startTime])) + } else { + // if we can't do this job yet, push it to the end of the queue + // and let the next iteration check again + fs[gracefulQueue].push(elem) + } + } + + // schedule our next run if one isn't already scheduled + if (retryTimer === undefined) { + retryTimer = setTimeout(retry, 0) + } +} diff --git a/tests/corpus/graceful-fs-alias/node_modules/graceful-fs/legacy-streams.js b/tests/corpus/graceful-fs-alias/node_modules/graceful-fs/legacy-streams.js new file mode 100644 index 000000000..d617b50fc --- /dev/null +++ b/tests/corpus/graceful-fs-alias/node_modules/graceful-fs/legacy-streams.js @@ -0,0 +1,118 @@ +var Stream = require('stream').Stream + +module.exports = legacy + +function legacy (fs) { + return { + ReadStream: ReadStream, + WriteStream: WriteStream + } + + function ReadStream (path, options) { + if (!(this instanceof ReadStream)) return new ReadStream(path, options); + + Stream.call(this); + + var self = this; + + this.path = path; + this.fd = null; + this.readable = true; + this.paused = false; + + this.flags = 'r'; + this.mode = 438; /*=0666*/ + this.bufferSize = 64 * 1024; + + options = options || {}; + + // Mixin options into this + var keys = Object.keys(options); + for (var index = 0, length = keys.length; index < length; index++) { + var key = keys[index]; + this[key] = options[key]; + } + + if (this.encoding) this.setEncoding(this.encoding); + + if (this.start !== undefined) { + if ('number' !== typeof this.start) { + throw TypeError('start must be a Number'); + } + if (this.end === undefined) { + this.end = Infinity; + } else if ('number' !== typeof this.end) { + throw TypeError('end must be a Number'); + } + + if (this.start > this.end) { + throw new Error('start must be <= end'); + } + + this.pos = this.start; + } + + if (this.fd !== null) { + process.nextTick(function() { + self._read(); + }); + return; + } + + fs.open(this.path, this.flags, this.mode, function (err, fd) { + if (err) { + self.emit('error', err); + self.readable = false; + return; + } + + self.fd = fd; + self.emit('open', fd); + self._read(); + }) + } + + function WriteStream (path, options) { + if (!(this instanceof WriteStream)) return new WriteStream(path, options); + + Stream.call(this); + + this.path = path; + this.fd = null; + this.writable = true; + + this.flags = 'w'; + this.encoding = 'binary'; + this.mode = 438; /*=0666*/ + this.bytesWritten = 0; + + options = options || {}; + + // Mixin options into this + var keys = Object.keys(options); + for (var index = 0, length = keys.length; index < length; index++) { + var key = keys[index]; + this[key] = options[key]; + } + + if (this.start !== undefined) { + if ('number' !== typeof this.start) { + throw TypeError('start must be a Number'); + } + if (this.start < 0) { + throw new Error('start must be >= zero'); + } + + this.pos = this.start; + } + + this.busy = false; + this._queue = []; + + if (this.fd === null) { + this._open = fs.open; + this._queue.push([this._open, this.path, this.flags, this.mode, undefined]); + this.flush(); + } + } +} diff --git a/tests/corpus/graceful-fs-alias/node_modules/graceful-fs/package.json b/tests/corpus/graceful-fs-alias/node_modules/graceful-fs/package.json new file mode 100644 index 000000000..87babf024 --- /dev/null +++ b/tests/corpus/graceful-fs-alias/node_modules/graceful-fs/package.json @@ -0,0 +1,53 @@ +{ + "name": "graceful-fs", + "description": "A drop-in replacement for fs, making various improvements.", + "version": "4.2.11", + "repository": { + "type": "git", + "url": "https://github.com/isaacs/node-graceful-fs" + }, + "main": "graceful-fs.js", + "directories": { + "test": "test" + }, + "scripts": { + "preversion": "npm test", + "postversion": "npm publish", + "postpublish": "git push origin --follow-tags", + "test": "nyc --silent node test.js | tap -c -", + "posttest": "nyc report" + }, + "keywords": [ + "fs", + "module", + "reading", + "retry", + "retries", + "queue", + "error", + "errors", + "handling", + "EMFILE", + "EAGAIN", + "EINVAL", + "EPERM", + "EACCESS" + ], + "license": "ISC", + "devDependencies": { + "import-fresh": "^2.0.0", + "mkdirp": "^0.5.0", + "rimraf": "^2.2.8", + "tap": "^16.3.4" + }, + "files": [ + "fs.js", + "graceful-fs.js", + "legacy-streams.js", + "polyfills.js", + "clone.js" + ], + "tap": { + "reporter": "classic" + } +} diff --git a/tests/corpus/graceful-fs-alias/node_modules/graceful-fs/polyfills.js b/tests/corpus/graceful-fs-alias/node_modules/graceful-fs/polyfills.js new file mode 100644 index 000000000..453f1a9e7 --- /dev/null +++ b/tests/corpus/graceful-fs-alias/node_modules/graceful-fs/polyfills.js @@ -0,0 +1,355 @@ +var constants = require('constants') + +var origCwd = process.cwd +var cwd = null + +var platform = process.env.GRACEFUL_FS_PLATFORM || process.platform + +process.cwd = function() { + if (!cwd) + cwd = origCwd.call(process) + return cwd +} +try { + process.cwd() +} catch (er) {} + +// This check is needed until node.js 12 is required +if (typeof process.chdir === 'function') { + var chdir = process.chdir + process.chdir = function (d) { + cwd = null + chdir.call(process, d) + } + if (Object.setPrototypeOf) Object.setPrototypeOf(process.chdir, chdir) +} + +module.exports = patch + +function patch (fs) { + // (re-)implement some things that are known busted or missing. + + // lchmod, broken prior to 0.6.2 + // back-port the fix here. + if (constants.hasOwnProperty('O_SYMLINK') && + process.version.match(/^v0\.6\.[0-2]|^v0\.5\./)) { + patchLchmod(fs) + } + + // lutimes implementation, or no-op + if (!fs.lutimes) { + patchLutimes(fs) + } + + // https://github.com/isaacs/node-graceful-fs/issues/4 + // Chown should not fail on einval or eperm if non-root. + // It should not fail on enosys ever, as this just indicates + // that a fs doesn't support the intended operation. + + fs.chown = chownFix(fs.chown) + fs.fchown = chownFix(fs.fchown) + fs.lchown = chownFix(fs.lchown) + + fs.chmod = chmodFix(fs.chmod) + fs.fchmod = chmodFix(fs.fchmod) + fs.lchmod = chmodFix(fs.lchmod) + + fs.chownSync = chownFixSync(fs.chownSync) + fs.fchownSync = chownFixSync(fs.fchownSync) + fs.lchownSync = chownFixSync(fs.lchownSync) + + fs.chmodSync = chmodFixSync(fs.chmodSync) + fs.fchmodSync = chmodFixSync(fs.fchmodSync) + fs.lchmodSync = chmodFixSync(fs.lchmodSync) + + fs.stat = statFix(fs.stat) + fs.fstat = statFix(fs.fstat) + fs.lstat = statFix(fs.lstat) + + fs.statSync = statFixSync(fs.statSync) + fs.fstatSync = statFixSync(fs.fstatSync) + fs.lstatSync = statFixSync(fs.lstatSync) + + // if lchmod/lchown do not exist, then make them no-ops + if (fs.chmod && !fs.lchmod) { + fs.lchmod = function (path, mode, cb) { + if (cb) process.nextTick(cb) + } + fs.lchmodSync = function () {} + } + if (fs.chown && !fs.lchown) { + fs.lchown = function (path, uid, gid, cb) { + if (cb) process.nextTick(cb) + } + fs.lchownSync = function () {} + } + + // on Windows, A/V software can lock the directory, causing this + // to fail with an EACCES or EPERM if the directory contains newly + // created files. Try again on failure, for up to 60 seconds. + + // Set the timeout this long because some Windows Anti-Virus, such as Parity + // bit9, may lock files for up to a minute, causing npm package install + // failures. Also, take care to yield the scheduler. Windows scheduling gives + // CPU to a busy looping process, which can cause the program causing the lock + // contention to be starved of CPU by node, so the contention doesn't resolve. + if (platform === "win32") { + fs.rename = typeof fs.rename !== 'function' ? fs.rename + : (function (fs$rename) { + function rename (from, to, cb) { + var start = Date.now() + var backoff = 0; + fs$rename(from, to, function CB (er) { + if (er + && (er.code === "EACCES" || er.code === "EPERM" || er.code === "EBUSY") + && Date.now() - start < 60000) { + setTimeout(function() { + fs.stat(to, function (stater, st) { + if (stater && stater.code === "ENOENT") + fs$rename(from, to, CB); + else + cb(er) + }) + }, backoff) + if (backoff < 100) + backoff += 10; + return; + } + if (cb) cb(er) + }) + } + if (Object.setPrototypeOf) Object.setPrototypeOf(rename, fs$rename) + return rename + })(fs.rename) + } + + // if read() returns EAGAIN, then just try it again. + fs.read = typeof fs.read !== 'function' ? fs.read + : (function (fs$read) { + function read (fd, buffer, offset, length, position, callback_) { + var callback + if (callback_ && typeof callback_ === 'function') { + var eagCounter = 0 + callback = function (er, _, __) { + if (er && er.code === 'EAGAIN' && eagCounter < 10) { + eagCounter ++ + return fs$read.call(fs, fd, buffer, offset, length, position, callback) + } + callback_.apply(this, arguments) + } + } + return fs$read.call(fs, fd, buffer, offset, length, position, callback) + } + + // This ensures `util.promisify` works as it does for native `fs.read`. + if (Object.setPrototypeOf) Object.setPrototypeOf(read, fs$read) + return read + })(fs.read) + + fs.readSync = typeof fs.readSync !== 'function' ? fs.readSync + : (function (fs$readSync) { return function (fd, buffer, offset, length, position) { + var eagCounter = 0 + while (true) { + try { + return fs$readSync.call(fs, fd, buffer, offset, length, position) + } catch (er) { + if (er.code === 'EAGAIN' && eagCounter < 10) { + eagCounter ++ + continue + } + throw er + } + } + }})(fs.readSync) + + function patchLchmod (fs) { + fs.lchmod = function (path, mode, callback) { + fs.open( path + , constants.O_WRONLY | constants.O_SYMLINK + , mode + , function (err, fd) { + if (err) { + if (callback) callback(err) + return + } + // prefer to return the chmod error, if one occurs, + // but still try to close, and report closing errors if they occur. + fs.fchmod(fd, mode, function (err) { + fs.close(fd, function(err2) { + if (callback) callback(err || err2) + }) + }) + }) + } + + fs.lchmodSync = function (path, mode) { + var fd = fs.openSync(path, constants.O_WRONLY | constants.O_SYMLINK, mode) + + // prefer to return the chmod error, if one occurs, + // but still try to close, and report closing errors if they occur. + var threw = true + var ret + try { + ret = fs.fchmodSync(fd, mode) + threw = false + } finally { + if (threw) { + try { + fs.closeSync(fd) + } catch (er) {} + } else { + fs.closeSync(fd) + } + } + return ret + } + } + + function patchLutimes (fs) { + if (constants.hasOwnProperty("O_SYMLINK") && fs.futimes) { + fs.lutimes = function (path, at, mt, cb) { + fs.open(path, constants.O_SYMLINK, function (er, fd) { + if (er) { + if (cb) cb(er) + return + } + fs.futimes(fd, at, mt, function (er) { + fs.close(fd, function (er2) { + if (cb) cb(er || er2) + }) + }) + }) + } + + fs.lutimesSync = function (path, at, mt) { + var fd = fs.openSync(path, constants.O_SYMLINK) + var ret + var threw = true + try { + ret = fs.futimesSync(fd, at, mt) + threw = false + } finally { + if (threw) { + try { + fs.closeSync(fd) + } catch (er) {} + } else { + fs.closeSync(fd) + } + } + return ret + } + + } else if (fs.futimes) { + fs.lutimes = function (_a, _b, _c, cb) { if (cb) process.nextTick(cb) } + fs.lutimesSync = function () {} + } + } + + function chmodFix (orig) { + if (!orig) return orig + return function (target, mode, cb) { + return orig.call(fs, target, mode, function (er) { + if (chownErOk(er)) er = null + if (cb) cb.apply(this, arguments) + }) + } + } + + function chmodFixSync (orig) { + if (!orig) return orig + return function (target, mode) { + try { + return orig.call(fs, target, mode) + } catch (er) { + if (!chownErOk(er)) throw er + } + } + } + + + function chownFix (orig) { + if (!orig) return orig + return function (target, uid, gid, cb) { + return orig.call(fs, target, uid, gid, function (er) { + if (chownErOk(er)) er = null + if (cb) cb.apply(this, arguments) + }) + } + } + + function chownFixSync (orig) { + if (!orig) return orig + return function (target, uid, gid) { + try { + return orig.call(fs, target, uid, gid) + } catch (er) { + if (!chownErOk(er)) throw er + } + } + } + + function statFix (orig) { + if (!orig) return orig + // Older versions of Node erroneously returned signed integers for + // uid + gid. + return function (target, options, cb) { + if (typeof options === 'function') { + cb = options + options = null + } + function callback (er, stats) { + if (stats) { + if (stats.uid < 0) stats.uid += 0x100000000 + if (stats.gid < 0) stats.gid += 0x100000000 + } + if (cb) cb.apply(this, arguments) + } + return options ? orig.call(fs, target, options, callback) + : orig.call(fs, target, callback) + } + } + + function statFixSync (orig) { + if (!orig) return orig + // Older versions of Node erroneously returned signed integers for + // uid + gid. + return function (target, options) { + var stats = options ? orig.call(fs, target, options) + : orig.call(fs, target) + if (stats) { + if (stats.uid < 0) stats.uid += 0x100000000 + if (stats.gid < 0) stats.gid += 0x100000000 + } + return stats; + } + } + + // ENOSYS means that the fs doesn't support the op. Just ignore + // that, because it doesn't matter. + // + // if there's no getuid, or if getuid() is something other + // than 0, and the error is EINVAL or EPERM, then just ignore + // it. + // + // This specific case is a silent failure in cp, install, tar, + // and most other unix tools that manage permissions. + // + // When running as root, or if other types of errors are + // encountered, then it's strict. + function chownErOk (er) { + if (!er) + return true + + if (er.code === "ENOSYS") + return true + + var nonroot = !process.getuid || process.getuid() !== 0 + if (nonroot) { + if (er.code === "EINVAL" || er.code === "EPERM") + return true + } + + return false + } +} diff --git a/tests/corpus/stream-web-readable.ts b/tests/corpus/stream-web-readable.ts new file mode 100644 index 000000000..5117a1738 --- /dev/null +++ b/tests/corpus/stream-web-readable.ts @@ -0,0 +1,22 @@ +// node:stream/web exports the web stream globals themselves: its +// ReadableStream is globalThis.ReadableStream, with the same behavior +// (identity is pinned by stream-web-require.cjs). +import { ReadableStream } from "node:stream/web"; +import type { ReadableStreamDefaultController } from "node:stream/web"; + +const stream = new ReadableStream({ + start(controller: ReadableStreamDefaultController) { + controller.enqueue("a"); + controller.enqueue("b"); + controller.close(); + }, +}); +const reader = stream.getReader(); +(async () => { + for (;;) { + const { done, value } = await reader.read(); + if (done) break; + console.log("chunk", value); + } + for await (const n of ReadableStream.from([1, 2, 3])) console.log("from", n); +})(); diff --git a/tests/corpus/stream-web-require.cjs b/tests/corpus/stream-web-require.cjs new file mode 100644 index 000000000..9c0308010 --- /dev/null +++ b/tests/corpus/stream-web-require.cjs @@ -0,0 +1,10 @@ +// The undici shape: CommonJS code reads ReadableStream off +// require("stream/web") (lazily, and as a fallback for the global); the +// value is the global class. +let ReadableStream; +function getStream() { + if (!ReadableStream) ReadableStream = require("stream/web").ReadableStream; + return ReadableStream; +} +const Fallback = globalThis.ReadableStream || require("node:stream/web").ReadableStream; +console.log(getStream() === globalThis.ReadableStream, Fallback === getStream()); diff --git a/tests/corpus/temporal-absent-probe.js b/tests/corpus/temporal-absent-probe.js new file mode 100644 index 000000000..79312bbcd --- /dev/null +++ b/tests/corpus/temporal-absent-probe.js @@ -0,0 +1,17 @@ +// The feature-probe shape packages ship (smol-toml's date handling): a +// JavaScript module checks for Temporal and falls back to Date, reading +// the global object too. +const temporal = typeof Temporal === "object" ? Temporal : null; +console.log(temporal, globalThis.Temporal, "Temporal" in globalThis); + +function toDate(value) { + if (temporal !== null) return temporal.Instant.from(value).toString(); + return new Date(value).toISOString(); +} +console.log(toDate("1979-05-27T07:32:00Z")); + +try { + Temporal.Now.plainDateISO(); +} catch (e) { + console.log(e.name + ": " + e.message); +} diff --git a/tests/corpus/temporal-absent.ts b/tests/corpus/temporal-absent.ts new file mode 100644 index 000000000..102044f31 --- /dev/null +++ b/tests/corpus/temporal-absent.ts @@ -0,0 +1,35 @@ +// Node 24 defines no Temporal global. Programs that name it type-check +// against TS 7's Temporal declarations and see Node's answers: typeof is +// "undefined", the global object has no such property, and any use throws +// the catchable ReferenceError at the root read, before arguments run. +console.log(typeof Temporal, typeof globalThis.Temporal); +const hasTemporal = typeof Temporal !== "undefined"; +console.log("hasTemporal", hasTemporal); + +function parseDate(text: string): string { + if (hasTemporal) return Temporal.PlainDate.from(text).toString(); + return new Date(text).toISOString().slice(0, 10); +} +console.log(parseDate("2024-02-29")); + +let evaluated = false; +const argument = (): string => { + evaluated = true; + return "2024-01-01"; +}; +try { + Temporal.PlainDate.from(argument()); +} catch (e) { + const err = e as Error; + console.log(err.name, err.message, err instanceof ReferenceError, "argument evaluated:", evaluated); +} +try { + console.log(Temporal.Now.instant().epochMilliseconds); +} catch (e) { + console.log((e as Error).message); +} +try { + new Temporal.Duration(1); +} catch (e) { + console.log((e as Error).message); +} diff --git a/tests/corpus/v8-heap-statistics.ts b/tests/corpus/v8-heap-statistics.ts new file mode 100644 index 000000000..9586f60a2 --- /dev/null +++ b/tests/corpus/v8-heap-statistics.ts @@ -0,0 +1,16 @@ +// node:v8 is importable. getHeapStatistics answers allocator-derived +// figures in Node's shape and key order; the values are approximate (no +// V8 heap exists in a compiled binary), so this pins the shape and the +// relations between fields, not the numbers. +import * as v8 from "node:v8"; + +const warm: string[] = []; +for (let i = 0; i < 1000; i++) warm.push("item " + i); + +const stats = v8.getHeapStatistics(); +console.log(Object.keys(stats).join(",")); +console.log(Object.values(stats).every((value) => typeof value === "number")); +console.log(stats.used_heap_size <= stats.total_heap_size); +console.log(stats.heap_size_limit > 0, stats.total_available_size <= stats.heap_size_limit); +console.log(stats.does_zap_garbage === 0 || stats.does_zap_garbage === 1); +console.log(stats.number_of_native_contexts >= 1, warm.length); diff --git a/tests/corpus/v8-require-optional.cjs b/tests/corpus/v8-require-optional.cjs new file mode 100644 index 000000000..339e0af5f --- /dev/null +++ b/tests/corpus/v8-require-optional.cjs @@ -0,0 +1,18 @@ +// @deferred-fences: 2 +// The dd-trace shape: a module requires v8 at top level and touches its +// engine-observation members only on optional paths. Requiring it and +// destructuring members succeeds; getHeapStatistics answers. +const v8 = require("v8"); +const { writeHeapSnapshot, setFlagsFromString } = require("node:v8"); + +console.log(typeof v8, typeof v8.getHeapStatistics); +const stats = v8.getHeapStatistics(); +console.log(stats.heap_size_limit > 0, "used_heap_size" in stats); + +function snapshot(enabled) { + if (enabled) return writeHeapSnapshot(); + return "skipped"; +} +console.log(snapshot(process.env.SCRIPTC_NEVER_SET === "1")); +if (process.env.SCRIPTC_NEVER_SET === "1") setFlagsFromString("--expose-gc"); +console.log("done"); diff --git a/tests/diagnostics/module-out-of-scope.ts b/tests/diagnostics/module-out-of-scope.ts index 35fd3ee2d..e947a077d 100644 --- a/tests/diagnostics/module-out-of-scope.ts +++ b/tests/diagnostics/module-out-of-scope.ts @@ -1,13 +1,13 @@ // The out-of-scope builtin fences: modules a compiled binary is not going -// to serve say WHY — V8 introspection, the inspector protocol, the bundled -// SQLite engine, the deprecated domain module, and Node's underscore- -// prefixed implementation internals — while genuinely pending surface -// (stream/web here) keeps the plain not-yet wording. -import { getHeapStatistics } from "node:v8"; +// to serve say WHY — runtime code evaluation (vm), the inspector protocol, +// the bundled SQLite engine, the deprecated domain module, and Node's +// underscore-prefixed implementation internals — while genuinely pending +// surface (repl here) keeps the plain not-yet wording. +import { runInNewContext } from "node:vm"; import { create } from "domain"; import { DatabaseSync } from "node:sqlite"; import { HTTPParser } from "_http_common"; import { open } from "node:inspector"; -import { ReadableStream } from "node:stream/web"; +import { start } from "node:repl"; -console.log(getHeapStatistics, create, DatabaseSync, HTTPParser, open, ReadableStream); +console.log(runInNewContext, create, DatabaseSync, HTTPParser, open, start); diff --git a/tests/fixtures/node-types/import-fences.ts b/tests/fixtures/node-types/import-fences.ts index 1109a2fa4..15cd1f005 100644 --- a/tests/fixtures/node-types/import-fences.ts +++ b/tests/fixtures/node-types/import-fences.ts @@ -2,12 +2,12 @@ * fence joins the blockers, the imported bindings poison at their use * sites (grouping with the import line), and every other statement still * counts — the report shows a percentage. Builds still fail on the import - * exactly as before. (net, then dgram, graduated to supported modules, so - * the out-of-scope "v8" carries this test now.) */ -import { getHeapStatistics } from "v8"; + * exactly as before. (net, dgram, then v8 graduated to supported modules, + * so the out-of-scope "inspector" carries this test now.) */ +import { open } from "inspector"; import { join } from "node:path"; const cmd = join("/usr", "bin", "afplay"); console.log(cmd); -getHeapStatistics(); +open(); console.log("after"); diff --git a/tests/fixtures/self-hosting/frontend-collections.ts b/tests/fixtures/self-hosting/frontend-collections.ts index 6b6654bb2..f52e1ecaa 100644 --- a/tests/fixtures/self-hosting/frontend-collections.ts +++ b/tests/fixtures/self-hosting/frontend-collections.ts @@ -8,7 +8,7 @@ import { isNpmStaticPackage, npmStaticActive, npmStaticOffenders, npmStaticPacka for (const specifier of SUPPORTED_NODE_MODULES) { console.log(specifier, canonicalBuiltinModule(specifier), builtinDefaultImportModule(specifier)); } -for (const specifier of ["test", "node:test", "node:missing", "missing", "v8", "node:inspector"]) { +for (const specifier of ["test", "node:test", "node:missing", "missing", "vm", "node:inspector", "graceful-fs", "node:graceful-fs"]) { console.log(canonicalBuiltinModule(specifier), unsupportedModuleFeatureOf(specifier)); } clearWorkspacePackages(); diff --git a/tests/harness/__snapshots__/coverage-import-fences.txt b/tests/harness/__snapshots__/coverage-import-fences.txt index 388647c55..790c36c86 100644 --- a/tests/harness/__snapshots__/coverage-import-fences.txt +++ b/tests/harness/__snapshots__/coverage-import-fences.txt @@ -4,6 +4,6 @@ scriptc coverage tests/fixtures/node-types/import-fences.ts compile statically 3 (75%) blockers: - ×2 SC1010 the 'v8' module (it observes V8 engine internals — heap statistics and snapshots, GC and CPU profiles, serialize/deserialize — and a compiled binary embeds no V8 engine to observe) + ×2 SC1010 the 'inspector' module (it drives the V8 inspector protocol — debugger, profiler, heap access — and a compiled binary embeds no V8 engine to inspect) at tests/fixtures/node-types/import-fences.ts:7:1, tests/fixtures/node-types/import-fences.ts:12:1 - hint: relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported \ No newline at end of file + hint: relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, dns/promises, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, stream/web, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, v8, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported \ No newline at end of file diff --git a/tests/harness/__snapshots__/imports.ts.txt b/tests/harness/__snapshots__/imports.ts.txt index ef48d5031..9db4b4f6f 100644 --- a/tests/harness/__snapshots__/imports.ts.txt +++ b/tests/harness/__snapshots__/imports.ts.txt @@ -4,7 +4,7 @@ imports.ts:1:1 - error SC1010: the 'some-package' package (nothing installed res | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 2 | import { join } from "node:path"; - hint: relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported + hint: relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, dns/promises, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, stream/web, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, v8, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported imports.ts:1:27 - error SC0001: Cannot find module 'some-package' or its corresponding type declarations. diff --git a/tests/harness/__snapshots__/module-out-of-scope.ts.txt b/tests/harness/__snapshots__/module-out-of-scope.ts.txt index 47e241459..a5ef44c6b 100644 --- a/tests/harness/__snapshots__/module-out-of-scope.ts.txt +++ b/tests/harness/__snapshots__/module-out-of-scope.ts.txt @@ -1,31 +1,31 @@ -module-out-of-scope.ts:6:1 - error SC1010: the 'node:v8' module (it observes V8 engine internals — heap statistics and snapshots, GC and CPU profiles, serialize/deserialize — and a compiled binary embeds no V8 engine to observe) is not supported yet +module-out-of-scope.ts:6:1 - error SC1010: the 'node:vm' module (it compiles and runs JavaScript source at runtime, and a compiled binary embeds no JavaScript engine to run it) is not supported yet - 5 | // (stream/web here) keeps the plain not-yet wording. - 6 | import { getHeapStatistics } from "node:v8"; - | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 5 | // surface (repl here) keeps the plain not-yet wording. + 6 | import { runInNewContext } from "node:vm"; + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 7 | import { create } from "domain"; - hint: relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported + hint: relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, dns/promises, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, stream/web, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, v8, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported -module-out-of-scope.ts:6:35 - error SC0001: Cannot find name 'node:v8'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node` and then add 'node' to the types field in your tsconfig. +module-out-of-scope.ts:6:33 - error SC0001: Cannot find name 'node:vm'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node` and then add 'node' to the types field in your tsconfig. - 5 | // (stream/web here) keeps the plain not-yet wording. - 6 | import { getHeapStatistics } from "node:v8"; - | ^~~~~~~~~ + 5 | // surface (repl here) keeps the plain not-yet wording. + 6 | import { runInNewContext } from "node:vm"; + | ^~~~~~~~~ 7 | import { create } from "domain"; module-out-of-scope.ts:7:1 - error SC1010: the 'domain' module (deprecated in Node and slated for removal; its implicit error interception hooks every async callback at engine level, which is not modeled) is not supported yet - 6 | import { getHeapStatistics } from "node:v8"; + 6 | import { runInNewContext } from "node:vm"; 7 | import { create } from "domain"; | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 8 | import { DatabaseSync } from "node:sqlite"; - hint: relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported + hint: relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, dns/promises, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, stream/web, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, v8, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported module-out-of-scope.ts:7:24 - error SC0001: Cannot find name 'domain'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node` and then add 'node' to the types field in your tsconfig. - 6 | import { getHeapStatistics } from "node:v8"; + 6 | import { runInNewContext } from "node:vm"; 7 | import { create } from "domain"; | ^~~~~~~~ 8 | import { DatabaseSync } from "node:sqlite"; @@ -37,7 +37,7 @@ module-out-of-scope.ts:8:1 - error SC1010: the 'node:sqlite' module (it wraps th | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 9 | import { HTTPParser } from "_http_common"; - hint: relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported + hint: relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, dns/promises, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, stream/web, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, v8, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported module-out-of-scope.ts:8:30 - error SC0001: Cannot find name 'node:sqlite'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node` and then add 'node' to the types field in your tsconfig. @@ -53,7 +53,7 @@ module-out-of-scope.ts:9:1 - error SC1010: the '_http_common' module (a Node-int | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 10 | import { open } from "node:inspector"; - hint: relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported + hint: relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, dns/promises, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, stream/web, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, v8, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported module-out-of-scope.ts:9:28 - error SC0001: Cannot find module '_http_common' or its corresponding type declarations. @@ -67,29 +67,29 @@ module-out-of-scope.ts:10:1 - error SC1010: the 'node:inspector' module (it driv 9 | import { HTTPParser } from "_http_common"; 10 | import { open } from "node:inspector"; | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - 11 | import { ReadableStream } from "node:stream/web"; + 11 | import { start } from "node:repl"; - hint: relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported + hint: relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, dns/promises, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, stream/web, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, v8, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported module-out-of-scope.ts:10:22 - error SC0001: Cannot find name 'node:inspector'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node` and then add 'node' to the types field in your tsconfig. 9 | import { HTTPParser } from "_http_common"; 10 | import { open } from "node:inspector"; | ^~~~~~~~~~~~~~~~ - 11 | import { ReadableStream } from "node:stream/web"; + 11 | import { start } from "node:repl"; -module-out-of-scope.ts:11:1 - error SC1010: the 'node:stream/web' module is not supported yet +module-out-of-scope.ts:11:1 - error SC1010: the 'node:repl' module is not supported yet 10 | import { open } from "node:inspector"; - 11 | import { ReadableStream } from "node:stream/web"; - | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 11 | import { start } from "node:repl"; + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 12 | - hint: relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported + hint: relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, dns/promises, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, stream/web, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, v8, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported -module-out-of-scope.ts:11:32 - error SC0001: Cannot find name 'node:stream/web'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node` and then add 'node' to the types field in your tsconfig. +module-out-of-scope.ts:11:23 - error SC0001: Cannot find name 'node:repl'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node` and then add 'node' to the types field in your tsconfig. 10 | import { open } from "node:inspector"; - 11 | import { ReadableStream } from "node:stream/web"; - | ^~~~~~~~~~~~~~~~~ + 11 | import { start } from "node:repl"; + | ^~~~~~~~~~~ 12 | \ No newline at end of file diff --git a/tests/harness/__snapshots__/pkg-import-fences/main.ts.txt b/tests/harness/__snapshots__/pkg-import-fences/main.ts.txt index f99018eb4..280743462 100644 --- a/tests/harness/__snapshots__/pkg-import-fences/main.ts.txt +++ b/tests/harness/__snapshots__/pkg-import-fences/main.ts.txt @@ -5,7 +5,7 @@ pkg-import-fences/main.ts:7:1 - error SC1010: the 'ambientonly' module (it exist | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 8 | import { y } from "patternXmod"; - hint: relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported + hint: relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, dns/promises, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, stream/web, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, v8, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported pkg-import-fences/main.ts:8:1 - error SC1010: the 'patternXmod' module (it exists only as an ambient 'declare module' type surface — there is no runtime module to import) is not supported yet @@ -14,7 +14,7 @@ pkg-import-fences/main.ts:8:1 - error SC1010: the 'patternXmod' module (it exist | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 9 | import { t } from "#types"; - hint: relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported + hint: relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, dns/promises, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, stream/web, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, v8, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported pkg-import-fences/main.ts:9:1 - error SC1010: the '#types' import (it resolves only to type declarations — there is no runtime implementation to compile) is not supported yet @@ -23,4 +23,4 @@ pkg-import-fences/main.ts:9:1 - error SC1010: the '#types' import (it resolves o | ^~~~~~~~~~~~~~~~~~~~~~~~~~~ 10 | - hint: relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported \ No newline at end of file + hint: relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, dns/promises, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, stream/web, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, v8, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported \ No newline at end of file diff --git a/tests/harness/differential.test.ts b/tests/harness/differential.test.ts index a2206fd97..56db773fd 100644 --- a/tests/harness/differential.test.ts +++ b/tests/harness/differential.test.ts @@ -155,15 +155,27 @@ function wantsNoDeprecation(file: string): boolean { /** The Node oracle's argv for a corpus entry (shims + the per-program * transform flag). */ +/** `// @disallow-code-generation` in the directive head: the Node oracle + * runs with --disallow-code-generation-from-strings — the mode compiled + * programs match for eval and the Function constructor, which throw its + * EvalError (code-generation.ts). Programs that never generate code + * behave identically either way. */ +function wantsDisallowCodeGeneration(file: string): boolean { + return directiveHead(file).some((l) => /^\/\/ @disallow-code-generation\s*$/.test(l)); +} + function nodeOracleArgs(file: string): string[] { const transform = wantsTransformTypes(file) ? ["--experimental-transform-types", "--disable-warning=ExperimentalWarning"] : []; const nodep = wantsNoDeprecation(file) ? ["--no-deprecation"] : []; + const codegen = wantsDisallowCodeGeneration(file) + ? ["--disallow-code-generation-from-strings"] + : []; // --import makes Node load even a CJS entry through its ESM loader, // changing an entry throw's uncaughtException origin to unhandledRejection. const shims = directiveHead(file).includes("// @no-node-shims") ? [] : ["--import", comptimeShim]; - return [...transform, ...nodep, ...shims, nodeOracleFile(file)]; + return [...transform, ...nodep, ...codegen, ...shims, nodeOracleFile(file)]; } /** Runs a binary, tolerating an expected nonzero exit (execFile rejects on diff --git a/tests/harness/linux-differential.test.ts b/tests/harness/linux-differential.test.ts index 6b0c7f1bb..9730623cd 100644 --- a/tests/harness/linux-differential.test.ts +++ b/tests/harness/linux-differential.test.ts @@ -199,6 +199,9 @@ async function runLinuxNode(file: string): Promise { ...(directiveHead(file).some((l) => /^\/\/ @no-deprecation\s*$/.test(l)) ? ["--no-deprecation"] : []), + ...(directiveHead(file).some((l) => /^\/\/ @disallow-code-generation\s*$/.test(l)) + ? ["--disallow-code-generation-from-strings"] + : []), ...(directiveHead(file).includes("// @no-node-shims") ? [] : ["--import", inContainer(join(repoRoot, "tests/harness/comptime-shim.mjs")), "--import"]), diff --git a/tests/harness/llvm-differential.test.ts b/tests/harness/llvm-differential.test.ts index 6df4d6f6b..792fdb1d2 100644 --- a/tests/harness/llvm-differential.test.ts +++ b/tests/harness/llvm-differential.test.ts @@ -152,10 +152,14 @@ function nodeOracleArgs(file: string): string[] { ? ["--experimental-transform-types", "--disable-warning=ExperimentalWarning"] : []; const nodep = wantsNoDeprecation(file) ? ["--no-deprecation"] : []; + // differential.test.ts's @disallow-code-generation directive. + const codegen = directiveHead(file).some((l) => /^\/\/ @disallow-code-generation\s*$/.test(l)) + ? ["--disallow-code-generation-from-strings"] + : []; // --import makes Node load even a CJS entry through its ESM loader, // changing an entry throw's uncaughtException origin to unhandledRejection. const shims = directiveHead(file).includes("// @no-node-shims") ? [] : ["--import", comptimeShim]; - return [...transform, ...nodep, ...shims, nodeOracleFile(file)]; + return [...transform, ...nodep, ...codegen, ...shims, nodeOracleFile(file)]; } function programInputs(file: string): string[] { diff --git a/tests/harness/windows-differential.test.ts b/tests/harness/windows-differential.test.ts index dc199d38f..366820420 100644 --- a/tests/harness/windows-differential.test.ts +++ b/tests/harness/windows-differential.test.ts @@ -354,10 +354,13 @@ async function runWindowsNode(file: string): Promise { const nodep = directiveHead(file).some((l) => /^\/\/ @no-deprecation\s*$/.test(l)) ? "--no-deprecation " : ""; + const codegen = directiveHead(file).some((l) => /^\/\/ @disallow-code-generation\s*$/.test(l)) + ? "--disallow-code-generation-from-strings " + : ""; const shims = directiveHead(file).includes("// @no-node-shims") ? "" : "--import ./comptime-shim.mjs "; - return runOnBox(`node ${transform}${nodep}${shims}${entry}`); + return runOnBox(`node ${transform}${nodep}${codegen}${shims}${entry}`); } function programInputs(file: string): string[] { From 5a342ad1a58a61812bfcad36f1664ecfbf9529bb Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 18 Oct 2026 12:17:25 -0700 Subject: [PATCH 002/122] Parse date strings with V8's grammar when the bounded parser rejects them Date.parse and new Date(string) answered NaN natively for every form outside ISO 8601 with an explicit offset and the X509 certificate shape, so RFC 1123 HTTP dates ("Mon, 01 Jan 2024 00:00:00 GMT") never parsed and the fresh/express/send conditional GET never matched. The runtime now falls back to a port of V8's DateParser (the ES5 date-time format, then the Safari-compatible legacy grammar: weekday and month names, RFC 2822 offsets, zone abbreviations, AM/PM, parenthesized comments, slashes) for exactly the strings the bounded grammars reject, so the timestamps they accept cost nothing more. Zone-less dates are local time, converted with the spec's UTC(t) over the host zone (DST gaps read with the offset before the transition, overlaps answer the earlier instant). With native parsing matching Node for every input, the SC6005 literal warning has nothing left to report and is retired rather than extended to non-literal inputs. Co-Authored-By: Claude Opus 5.5 --- docs/content/docs/coverage.mdx | 1 - .../limitations/dates-and-timezones.mdx | 4 +- .../compiler/src/diagnostics/diagnostic.ts | 17 +- .../src/frontend/lowering/builtins/dates.ts | 5 +- .../src/frontend/lowering/divergences.ts | 75 --- .../src/frontend/lowering/surfaces.ts | 2 +- .../compiler/src/ir/builtin-signatures.ts | 11 +- packages/compiler/surface-manifest.json | 2 +- .../corpus/6230-date-parse-legacy.ts.json | 6 + packages/runtime/src/scr_lib.c | 633 +++++++++++++++++- packages/runtime/src/scr_number.h | 5 +- tests/corpus/6230-date-parse-legacy.ts | 112 ++++ tests/coverage-fixtures/divergences.ts | 2 +- .../__snapshots__/coverage-divergences.txt | 13 +- tests/harness/coverage.test.ts | 7 +- 15 files changed, 766 insertions(+), 129 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6230-date-parse-legacy.ts.json create mode 100644 tests/corpus/6230-date-parse-legacy.ts diff --git a/docs/content/docs/coverage.mdx b/docs/content/docs/coverage.mdx index 2d26e684b..9c9ec413f 100644 --- a/docs/content/docs/coverage.mdx +++ b/docs/content/docs/coverage.mdx @@ -68,7 +68,6 @@ Some code compiles but can behave differently than under Node.js. Coverage repor - `SC6002`: an object literal's key order differs from its type's declaration order, and the program observes the order through `JSON.stringify`, `Object.keys`, `Object.values`, `Object.entries`, `for...in`, or console output. - `SC6003`: `localeCompare` compares a string literal outside the built-in Latin-script collation. - `SC6004`: a call passes a class instance to a function that casts it to an unrelated class. -- `SC6005`: a `Date` string literal uses a form the native parser does not accept. ## Gating builds and CI diff --git a/docs/content/limitations/dates-and-timezones.mdx b/docs/content/limitations/dates-and-timezones.mdx index ad830eb9a..eacaf29d0 100644 --- a/docs/content/limitations/dates-and-timezones.mdx +++ b/docs/content/limitations/dates-and-timezones.mdx @@ -1,3 +1,3 @@ -String constructors and `Date.parse` accept ECMAScript date-only forms, date-times with an explicit `Z` or numeric offset, and GMT certificate-validity strings from the supported X509 APIs. Other V8-specific forms and offset-less local date-times produce an Invalid Date or `NaN`; `SC6005` reports string literals in those forms. +String constructors and `Date.parse` use V8's date-string grammar: ECMAScript date-time strings and the legacy forms Node.js accepts, such as RFC 1123 HTTP dates (`Mon, 01 Jan 2024 00:00:00 GMT`), `Date#toString` output, and `Jan 2, 2024`. Strings without a timezone are local time. -Local calendar getters use the operating system's timezone database. Historical results can differ from Node.js when timezone data differs. For valid extreme years outside a platform calendar API's range, scriptc uses a calendar-equivalent year in the 400-year Gregorian cycle to obtain the timezone rule. +Local calendar getters and local-time conversions use the operating system's timezone database. Historical results can differ from Node.js when timezone data differs. For valid extreme years outside a platform calendar API's range, scriptc uses a calendar-equivalent year in the 400-year Gregorian cycle to obtain the timezone rule. diff --git a/packages/compiler/src/diagnostics/diagnostic.ts b/packages/compiler/src/diagnostics/diagnostic.ts index eaae1b531..da4da347a 100644 --- a/packages/compiler/src/diagnostics/diagnostic.ts +++ b/packages/compiler/src/diagnostics/diagnostic.ts @@ -59,8 +59,9 @@ * order that differs from insertion order where the program * observes it (SC6002), localeCompare over text the built-in * collation does not cover (SC6003), a call feeding an instance - * to a cast to an unrelated class (SC6004), and Date strings the native parser does not - * accept (SC6005) + * to a cast to an unrelated class (SC6004); SC6005 (Date strings + * outside the native parser's grammar) is retired — the runtime + * parses date strings with V8's grammar * SC9xxx internal compiler errors (still source-anchored) */ import type { SrcLoc } from "../ir/ir.js"; @@ -200,18 +201,6 @@ export function unrelatedClassCastDivergenceDiag( }; } -/** SC6005 — a Date string literal outside the grammar the native parser - * accepts (ISO 8601 with an explicit offset, date-only forms, and the - * certificate-time shape): Node parses many more forms. */ -export function dateLiteralDivergenceDiag(text: string, loc: SrcLoc): ScrDiagnostic { - return { - code: "SC6005", - message: `the date string '${text}' parses to an invalid date (NaN) natively, while Node accepts it (the native parser takes ISO 8601 date-times with an explicit offset, such as '2024-01-02T03:04:05Z', and date-only forms)`, - loc, - hint: "write the date in ISO 8601 with an explicit offset ('Z' or '+HH:MM'), or build it from numbers with Date.UTC", - }; -} - /* ── SC5xxx: native FFI ───────────────────────────────────────────────── */ /** SC5001 — the outbound native-FFI manifest is malformed or unreadable. */ diff --git a/packages/compiler/src/frontend/lowering/builtins/dates.ts b/packages/compiler/src/frontend/lowering/builtins/dates.ts index fa880bdc9..7c5a276ac 100644 --- a/packages/compiler/src/frontend/lowering/builtins/dates.ts +++ b/packages/compiler/src/frontend/lowering/builtins/dates.ts @@ -52,9 +52,8 @@ export function lowerDateCall( } return { kind: "libCall", fn: "date.now", args: [], type: F64, loc }; } - // Date.parse uses the same bounded date-string parser as - // new Date(dateString).getTime(); keep their NaN and TimeClip behavior - // identical for the ISO timestamps used by CLI applications. + // Date.parse uses the same date-string parser as new Date(dateString) + // (V8's grammar in the runtime), so their NaN and TimeClip behavior agree. if (lowerer.stdlibGlobalMember(access, "Date") === "parse") { if (call.arguments.length !== 1 || ts.isSpreadElement(call.arguments[0]!)) { lowerer.noLowering(`Date.parse with ${call.arguments.length} arguments`, call); diff --git a/packages/compiler/src/frontend/lowering/divergences.ts b/packages/compiler/src/frontend/lowering/divergences.ts index 2a9009aed..004cb55a1 100644 --- a/packages/compiler/src/frontend/lowering/divergences.ts +++ b/packages/compiler/src/frontend/lowering/divergences.ts @@ -14,7 +14,6 @@ import * as ts from "../ts7/adapter.js"; import { locOf } from "../program.js"; import { isNodeModulesPath } from "../resolve.js"; import { - dateLiteralDivergenceDiag, keyOrderDivergenceDiag, localeCompareDivergenceDiag, unrelatedClassCastDivergenceDiag, @@ -56,7 +55,6 @@ export function scanDivergences( const literals: ts.ObjectLiteralExpression[] = []; const observers: ts.Expression[] = []; const casts: (ts.AsExpression | ts.TypeAssertion)[] = []; - const dates: { call: ts.CallExpression | ts.NewExpression; arg: ts.Expression }[] = []; const localeCompares: ts.CallExpression[] = []; /** binding name → member names assigned through it (`q.a = 2`). */ const assignedMembers = new Map>(); @@ -97,19 +95,9 @@ export function scanDivergences( member === "dir") ) { for (const a of node.arguments) if (!isLiteralLike(a)) observers.push(a); - } else if (recv.text === "Date" && member === "parse") { - if (node.arguments[0] !== undefined) dates.push({ call: node, arg: node.arguments[0] }); } } } - } else if (ts.isNewExpression(node)) { - if ( - ts.isIdentifier(node.expression) && - node.expression.text === "Date" && - node.arguments?.length === 1 - ) { - dates.push({ call: node, arg: node.arguments[0]! }); - } } else if (ts.isForInStatement(node)) { observers.push(node.expression); } else if (ts.isVariableDeclaration(node)) { @@ -397,55 +385,9 @@ export function scanDivergences( } } - // ── Date strings the native parser does not accept ──────────────── - for (const { call, arg } of dates) { - const callee = ts.isNewExpression(call) - ? call.expression - : ts.isPropertyAccessExpression(call.expression) - ? call.expression.expression - : undefined; - if (callee === undefined || !lowerer.isStdlibGlobal(callee, "Date")) continue; - for (const literal of dateStringLiterals(checker, arg)) { - const text = stringLiteralText(literal)!; - if (nativeDateParses(text) || !/\d/.test(text)) continue; - out.push(dateLiteralDivergenceDiag(text, locOf(literal))); - } - } - return out; } -/** The string literals a Date argument can be: the literal itself, or - * each element of a const array of literals a for-of binding iterates. */ -function dateStringLiterals(checker: ts.TypeChecker, arg: ts.Expression): ts.Expression[] { - if (stringLiteralText(arg) !== null) return [arg]; - if (!ts.isIdentifier(arg)) return []; - const symbol = safely(() => checker.getSymbolAtLocation(arg)); - const decl = symbol === undefined ? undefined : checker.valueDeclarationOf(symbol); - const list = decl?.parent; - const loop = list?.parent; - if ( - decl === undefined || - !ts.isVariableDeclaration(decl) || - list === undefined || - loop === undefined || - !ts.isForOfStatement(loop) || - loop.initializer !== list - ) - return []; - let iterated: ts.Expression = loop.expression; - if (ts.isIdentifier(iterated)) { - const source = safely(() => checker.getSymbolAtLocation(iterated)); - const init = source === undefined ? undefined : checker.valueDeclarationOf(source); - if (init === undefined || !ts.isVariableDeclaration(init) || init.initializer === undefined) - return []; - if (init.parent === undefined || (init.parent.flags & ts.NodeFlags.Const) === 0) return []; - iterated = init.initializer; - } - if (!ts.isArrayLiteralExpression(iterated)) return []; - return iterated.elements.filter((e) => stringLiteralText(e) !== null); -} - /** A checker query that fails here only loses a warning. */ function safely(f: () => T): T | undefined { try { @@ -857,20 +799,3 @@ function classExtends( function className(decl: ts.ClassLikeDeclaration): string { return decl.name?.text ?? "(anonymous class)"; } - -const MONTHS = ["jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"]; - -/** The date-string grammar the native runtime accepts (scr_lib.c - * scr_date_parse_get_time): ECMAScript's date-time format with an explicit - * offset (date-only forms are UTC) and the certificate-time shape - * "Mon DD HH:MM:SS YYYY GMT". Everything else parses to NaN natively. */ -export function nativeDateParses(text: string): boolean { - const iso = - /^(?:[+-]\d{6}|\d{4})(?:-(\d{2})(?:-(\d{2}))?)?$/.test(text) || - /^(?:[+-]\d{6}|\d{4})-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2}(?:\.\d{3})?)?(?:Z|[+-]\d{2}:\d{2})$/.test( - text, - ); - if (iso) return true; - const cert = /^([A-Za-z]{3}) {1,2}\d{1,2} \d{2}:\d{2}:\d{2} \d{4} GMT$/.exec(text); - return cert !== null && MONTHS.includes(cert[1]!.toLowerCase()); -} diff --git a/packages/compiler/src/frontend/lowering/surfaces.ts b/packages/compiler/src/frontend/lowering/surfaces.ts index d394c1960..561a58953 100644 --- a/packages/compiler/src/frontend/lowering/surfaces.ts +++ b/packages/compiler/src/frontend/lowering/surfaces.ts @@ -2170,7 +2170,7 @@ export const AMBIENT_SURFACE_FNS: readonly AmbientSurfaceRow[] = [ kind: "stdlib", name: "Date.parse", fns: ["date.parse"], - note: "one date string, using the bounded parser shared with new Date(dateString).getTime()", + note: "one date string, using V8's date parser shared with new Date(dateString)", }, { id: "stdlib.date.constructor", diff --git a/packages/compiler/src/ir/builtin-signatures.ts b/packages/compiler/src/ir/builtin-signatures.ts index 08c493ae4..1d14d8732 100644 --- a/packages/compiler/src/ir/builtin-signatures.ts +++ b/packages/compiler/src/ir/builtin-signatures.ts @@ -4187,13 +4187,10 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ "date.getUTCMilliseconds": { argTypes: [DATE_T], result: F64 }, "date.getTimezoneOffset": { argTypes: [DATE_T], result: F64 }, /** The composed `new Date(dateString).getTime()` read: one borrowed - * string, f64 milliseconds since epoch. The parsed grammar is BOUNDED - * (documented divergence — V8's parser accepts far more): the ASN.1 - * validity shape X509Certificate.validFrom/validTo answer ("Jul 1 - * 00:00:00 2026 GMT" — the portless cert-expiry read), and ECMA's own - * date-time string format (YYYY-MM-DD[THH:mm[:ss[.sss]]][Z|±HH:MM] — - * date-only forms are UTC, exactly the spec). Anything else is NaN, - * Node's invalid-date getTime. Never throws. */ + * string, f64 milliseconds since epoch, with V8's date-string grammar + * (the ES5 format, then the legacy forms: RFC 1123/2822, Date#toString, + * "Jan 2, 2024", ...). Zone-less dates are local time; anything V8 + * rejects is NaN, Node's invalid-date getTime. Never throws. */ "date.parseGetTime": { argTypes: [STRING], result: F64 }, /** `Date.UTC(...)` — seven f64 arguments (the frontend completes the * spec's defaults for omitted trailing parts: month 0, date 1, time diff --git a/packages/compiler/surface-manifest.json b/packages/compiler/surface-manifest.json index f5313f12d..471210669 100644 --- a/packages/compiler/surface-manifest.json +++ b/packages/compiler/surface-manifest.json @@ -4150,7 +4150,7 @@ "kind": "stdlib", "name": "Date.parse", "status": "static", - "note": "one date string, using the bounded parser shared with new Date(dateString).getTime()" + "note": "one date string, using V8's date parser shared with new Date(dateString)" }, { "id": "stdlib.date.toISOString", diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6230-date-parse-legacy.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6230-date-parse-legacy.ts.json new file mode 100644 index 000000000..b7cad9619 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6230-date-parse-legacy.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6230-date-parse-legacy.ts" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_lib.c b/packages/runtime/src/scr_lib.c index fd51e9c78..2c232cbea 100644 --- a/packages/runtime/src/scr_lib.c +++ b/packages/runtime/src/scr_lib.c @@ -10298,9 +10298,9 @@ ScrStr *scr_date_to_iso(double ms) { /* ── new Date(dateString).getTime() ────────────────────────────────────── * - * The BOUNDED date-string parse (documented divergence — V8's parser - * accepts far more): two grammars answer a time value, everything else is - * NaN (Node's invalid-date getTime). + * The BOUNDED date-string parse, the fast path: two grammars answer a time + * value directly; a string they reject falls back to V8's full parser + * (scr_date_parse_v8 below), which also answers everything they accept. * * 1. The ASN1_TIME_print shape X509Certificate.validFrom/validTo answer * ("Jul 1 00:00:00 2026 GMT", "Jul 17 17:52:11 2026 GMT") — the @@ -10309,8 +10309,8 @@ ScrStr *scr_date_to_iso(double ms) { * 2. ECMA's own date-time string format with an EXPLICIT offset: * YYYY[-MM[-DD]] date-only forms (UTC, per the spec) and full * date-times YYYY-MM-DDTHH:mm[:ss[.sss]] ending in Z or ±HH:MM. - * Offset-less date-times are LOCAL time in JS; that arm answers NaN - * here rather than guessing a zone (the divergence note). + * Offset-less date-times are LOCAL time in JS; they take the + * fallback, which converts them in the host zone. */ /* Howard Hinnant's days_from_civil (the to_iso walk inverted). */ @@ -10351,7 +10351,9 @@ static bool scr_date_digits(const char **p, const char *end, int n, int *out) { return true; } -double scr_date_parse_get_time(ScrStr *s) { +static double scr_date_parse_v8(ScrStr *s); + +static double scr_date_parse_bounded(ScrStr *s) { const char *p = s->data; const char *end = s->data + s->len; static const char *mon[12] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", @@ -10444,6 +10446,13 @@ double scr_date_parse_get_time(ScrStr *s) { } } +/* The bounded grammars answer first; only a string they reject reaches + * V8's full parser, so the timestamps they accept cost nothing more. */ +double scr_date_parse_get_time(ScrStr *s) { + double t = scr_date_parse_bounded(s); + return isnan(t) ? scr_date_parse_v8(s) : t; +} + /* Date.UTC(year, month, date, hours, minutes, seconds, ms) — the spec's * MakeDay/MakeTime/TimeClip pipeline over ALREADY-NUMBER arguments (tsc * pins them; the frontend completes omitted trailing arguments with the @@ -10647,6 +10656,618 @@ double scr_date_get_timezone_offset(double ms) { return scr_date_local_parts(ms, &p) ? p.timezone_offset : NAN; } +/* ── Local time to UTC ───────────────────────────────────────────────── + * The spec's UTC(t) over the host zone: t is a local wall-clock time + * value. The offset in effect a day before and a day after t brackets any + * transition (none come closer together than that in tzdata). A time in + * a DST overlap has two instants and answers the earlier; a time in a gap + * has none and is read with the offset BEFORE the transition, exactly + * V8's ICU getOffsetFromLocal(FORMER, FORMER). Offsets come from the same + * localtime/surrogate-year walk as the local getters, so a Date built + * from local fields reads those fields back. */ + +/* The host zone's offset (local - UTC) in ms at UTC instant `ms` (an + * integral time value within about ±8.65e15). */ +static bool scr_date_offset_at(double ms, double *out) { + long long cms = (long long)ms; + double secd = (double)(cms / 1000 - (cms % 1000 < 0)); + struct tm tmv; + double basis_secd = secd; + if (!scr_date_localtime(basis_secd, &tmv)) { + ScrDateParts utc; + scr_date_utc_parts_unchecked(ms, &utc); + long long cycle_year = (utc.year - 2000) % 400; + if (cycle_year < 0) cycle_year += 400; + basis_secd = + scr_days_from_civil(2000 + cycle_year, utc.month + 1, utc.date) * 86400.0 + + utc.hours * 3600.0 + utc.minutes * 60.0 + utc.seconds; + if (!scr_date_localtime(basis_secd, &tmv)) return false; + } + double local_as_utc = + scr_days_from_civil((long long)tmv.tm_year + 1900, tmv.tm_mon + 1, tmv.tm_mday) * 86400.0 + + tmv.tm_hour * 3600.0 + tmv.tm_min * 60.0 + tmv.tm_sec; + *out = (local_as_utc - basis_secd) * 1000.0; + return true; +} + +static double scr_date_local_to_utc(double t) { + double before, after, at_first, at_second; + if (!scr_date_offset_at(t - 86400000.0, &before) || + !scr_date_offset_at(t + 86400000.0, &after)) { + return NAN; + } + double first = t - before; + if (!scr_date_offset_at(first, &at_first)) return NAN; + if (before == after) { + if (at_first == before) return first; + /* Two transitions inside the window: settle on the instant's own offset. */ + double again = t - at_first, at_again; + if (scr_date_offset_at(again, &at_again) && at_again == at_first) return again; + return first; + } + double second = t - after; + if (!scr_date_offset_at(second, &at_second)) return NAN; + bool first_ok = at_first == before, second_ok = at_second == after; + if (first_ok && second_ok) return first < second ? first : second; /* overlap */ + if (second_ok && !first_ok) return second; + return first; /* the earlier offset's instant, or a gap */ +} + +/* V8's MakeDay (date.cc): bounds the UNtruncated year and month, then + * truncates (FastD2I) and normalizes the month into the year. */ +static double scr_date_v8_make_day(double year, double month, double date) { + if (!(year >= -1000000.0 && year <= 1000000.0 && month >= -10000000.0 && + month <= 10000000.0 && isfinite(date))) { + return NAN; + } + int y = (int)year, m = (int)month; + y += m / 12; + m %= 12; + if (m < 0) { + m += 12; + y -= 1; + } + static const int year_delta = 399999; + static const int base_day = 365 * (1970 + year_delta) + (1970 + year_delta) / 4 - + (1970 + year_delta) / 100 + (1970 + year_delta) / 400; + int day_from_year = 365 * (y + year_delta) + (y + year_delta) / 4 - + (y + year_delta) / 100 + (y + year_delta) / 400 - base_day; + static const int common[12] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334}; + static const int leap[12] = {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335}; + bool is_leap = !((y % 4 != 0) || (y % 100 == 0 && y % 400 != 0)); + day_from_year += is_leap ? leap[m] : common[m]; + return (double)(day_from_year - 1) + trunc(date); +} + +static double scr_date_v8_make_time(double h, double m, double s, double ms) { + if (!isfinite(h) || !isfinite(m) || !isfinite(s) || !isfinite(ms)) return NAN; + return trunc(h) * 3600000.0 + trunc(m) * 60000.0 + trunc(s) * 1000.0 + trunc(ms); +} + +/* MakeDate over local fields, then UTC(t) and TimeClip. V8 refuses a local + * time value more than ten days past TimeClip's range before converting. */ +static double scr_date_from_local(double day, double time) { + if (!isfinite(day) || !isfinite(time)) return NAN; + double t = time + day * 86400000.0; + if (!(t >= -8640000864000000.0 && t <= 8640000864000000.0)) return NAN; + return scr_date_new_ms(scr_date_local_to_utc(trunc(t))); +} + +/* ── V8's date-string parser, the fallback ───────────────────────────── + * scr_date_parse_get_time answers its bounded grammars directly and asks + * this parser only when they produce NaN. It follows V8's DateParser + * (dateparser.cc, dateparser-inl.h) step for step: the ES5 date-time + * format first, then the Safari-compatible legacy grammar for whatever + * is left (RFC 1123/2822 HTTP dates, Date#toString output, "Jan 2, 2024", + * "2024/01/02 10:00", ...). Strings are read as UTF-16 code units, and a + * NUL unit ends the input, as in V8. A date without a zone is local time. */ + +enum { + SCR_DT_INVALID = -6, SCR_DT_UNKNOWN = -5, SCR_DT_SPACE = -4, + SCR_DT_NUMBER = -3, SCR_DT_SYMBOL = -2, SCR_DT_END = -1, + /* keyword tags are their keyword type (>= 0) */ + SCR_DK_WORD = 0, SCR_DK_MONTH = 1, SCR_DK_ZONE = 2, SCR_DK_T = 3, SCR_DK_AMPM = 4, +}; +#define SCR_DT_NONE INT_MAX + +typedef struct { + int tag, length, value; +} ScrDateTok; + +typedef struct { + const unsigned char *p, *end; + uint32_t ch, low; + ScrDateTok next; +} ScrDateScan; + +static void scr_date_scan_advance(ScrDateScan *in) { + if (in->low) { + in->ch = in->low; + in->low = 0; + return; + } + if (in->p >= in->end) { + in->ch = 0; + return; + } + const unsigned char *p = in->p; + size_t left = (size_t)(in->end - p); + uint32_t cp = p[0]; + size_t n = 1; + if (cp >= 0xF0 && left >= 4) { + cp = ((cp & 0x07u) << 18) | ((p[1] & 0x3Fu) << 12) | ((p[2] & 0x3Fu) << 6) | (p[3] & 0x3Fu); + n = 4; + } else if (cp >= 0xE0 && left >= 3) { + cp = ((cp & 0x0Fu) << 12) | ((p[1] & 0x3Fu) << 6) | (p[2] & 0x3Fu); + n = 3; + } else if (cp >= 0xC0 && left >= 2) { + cp = ((cp & 0x1Fu) << 6) | (p[1] & 0x3Fu); + n = 2; + } else if (cp >= 0x80) { + cp = 0xFFFD; + } + in->p += n; + if (cp > 0xFFFF) { + cp -= 0x10000; + in->ch = 0xD800 + (cp >> 10); + in->low = 0xDC00 + (cp & 0x3FF); + } else { + in->ch = cp; + } +} + +/* ECMAScript WhiteSpace, and WhiteSpace or LineTerminator. */ +static bool scr_date_is_space(uint32_t c) { + return c == 0x09 || c == 0x0B || c == 0x0C || c == 0x20 || c == 0xA0 || c == 0xFEFF || + c == 0x1680 || (c >= 0x2000 && c <= 0x200A) || c == 0x202F || c == 0x205F || + c == 0x3000; +} + +static bool scr_date_is_space_or_newline(uint32_t c) { + return scr_date_is_space(c) || c == 0x0A || c == 0x0D || c == 0x2028 || c == 0x2029; +} + +static const struct { + char prefix[3]; + signed char type, value; +} scr_date_keywords[] = { + {{'j', 'a', 'n'}, SCR_DK_MONTH, 1}, {{'f', 'e', 'b'}, SCR_DK_MONTH, 2}, + {{'m', 'a', 'r'}, SCR_DK_MONTH, 3}, {{'a', 'p', 'r'}, SCR_DK_MONTH, 4}, + {{'m', 'a', 'y'}, SCR_DK_MONTH, 5}, {{'j', 'u', 'n'}, SCR_DK_MONTH, 6}, + {{'j', 'u', 'l'}, SCR_DK_MONTH, 7}, {{'a', 'u', 'g'}, SCR_DK_MONTH, 8}, + {{'s', 'e', 'p'}, SCR_DK_MONTH, 9}, {{'o', 'c', 't'}, SCR_DK_MONTH, 10}, + {{'n', 'o', 'v'}, SCR_DK_MONTH, 11}, {{'d', 'e', 'c'}, SCR_DK_MONTH, 12}, + {{'a', 'm', 0}, SCR_DK_AMPM, 0}, {{'p', 'm', 0}, SCR_DK_AMPM, 12}, + {{'u', 't', 0}, SCR_DK_ZONE, 0}, {{'u', 't', 'c'}, SCR_DK_ZONE, 0}, + {{'z', 0, 0}, SCR_DK_ZONE, 0}, {{'g', 'm', 't'}, SCR_DK_ZONE, 0}, + {{'c', 'd', 't'}, SCR_DK_ZONE, -5}, {{'c', 's', 't'}, SCR_DK_ZONE, -6}, + {{'e', 'd', 't'}, SCR_DK_ZONE, -4}, {{'e', 's', 't'}, SCR_DK_ZONE, -5}, + {{'m', 'd', 't'}, SCR_DK_ZONE, -6}, {{'m', 's', 't'}, SCR_DK_ZONE, -7}, + {{'p', 'd', 't'}, SCR_DK_ZONE, -7}, {{'p', 's', 't'}, SCR_DK_ZONE, -8}, + {{'t', 0, 0}, SCR_DK_T, 0}, +}; + +static ScrDateTok scr_date_scan_token(ScrDateScan *in) { + ScrDateTok t = {SCR_DT_UNKNOWN, 1, -1}; + uint32_t c = in->ch; + if (c == 0) { + t.tag = SCR_DT_END; + return t; + } + if (c >= '0' && c <= '9') { + /* Leading zeros count toward the length; past nine significant + * digits the value stops growing but the digits are consumed. */ + int n = 0, digits = 0, length = 0; + while (in->ch == '0') { + scr_date_scan_advance(in); + length++; + } + while (in->ch >= '0' && in->ch <= '9') { + if (digits < 9) n = n * 10 + (int)(in->ch - '0'); + digits++; + length++; + scr_date_scan_advance(in); + } + t.tag = SCR_DT_NUMBER; + t.value = n; + t.length = length; + return t; + } + if (c == ':' || c == '-' || c == '+' || c == '.' || c == ')') { + scr_date_scan_advance(in); + t.tag = SCR_DT_SYMBOL; + t.value = (int)c; + return t; + } + if (c >= 'A' && !scr_date_is_space(c)) { + uint32_t prefix[3] = {0, 0, 0}; + int length = 0; + while (in->ch >= 'A' && !scr_date_is_space(in->ch)) { + if (length < 3) prefix[length] = in->ch | 0x20; + length++; + scr_date_scan_advance(in); + } + t.tag = SCR_DK_WORD; + t.value = 0; + t.length = length; + for (size_t i = 0; i < sizeof scr_date_keywords / sizeof scr_date_keywords[0]; i++) { + const char *k = scr_date_keywords[i].prefix; + if (prefix[0] == (uint32_t)(unsigned char)k[0] && prefix[1] == (uint32_t)(unsigned char)k[1] && + prefix[2] == (uint32_t)(unsigned char)k[2] && + (length <= 3 || scr_date_keywords[i].type == SCR_DK_MONTH)) { + t.tag = scr_date_keywords[i].type; + t.value = scr_date_keywords[i].value; + break; + } + } + return t; + } + if (scr_date_is_space_or_newline(c)) { + scr_date_scan_advance(in); + t.tag = SCR_DT_SPACE; + return t; + } + if (c == '(') { + int balance = 0; + do { + if (in->ch == ')') balance--; + else if (in->ch == '(') balance++; + scr_date_scan_advance(in); + } while (balance > 0 && in->ch); + return t; + } + scr_date_scan_advance(in); + return t; +} + +static ScrDateTok scr_date_next(ScrDateScan *in) { + ScrDateTok t = in->next; + in->next = scr_date_scan_token(in); + return t; +} + +static bool scr_date_tok_symbol(ScrDateTok t, int c) { + return t.tag == SCR_DT_SYMBOL && t.value == c; +} + +static bool scr_date_skip(ScrDateScan *in, int c) { + if (!scr_date_tok_symbol(in->next, c)) return false; + in->next = scr_date_scan_token(in); + return true; +} + +static bool scr_date_tok_sign(ScrDateTok t) { + return t.tag == SCR_DT_SYMBOL && (t.value == '-' || t.value == '+'); +} + +static bool scr_date_tok_fixed(ScrDateTok t, int length) { + return t.tag == SCR_DT_NUMBER && t.length == length; +} + +static bool scr_date_tok_z(ScrDateTok t) { + return t.tag == SCR_DK_ZONE && t.length == 1 && t.value == 0; +} + +static bool scr_date_between(int x, int lo, int hi) { return x >= lo && x <= hi; } + +typedef struct { + int comp[3], index, named_month; + bool iso; +} ScrDateDay; + +typedef struct { + int comp[4], index, hour_offset; +} ScrDateTime; + +typedef struct { + int sign, hour, minute; +} ScrDateZone; + +static bool scr_date_day_add(ScrDateDay *d, int n) { + if (d->index >= 3) return false; + d->comp[d->index++] = n; + return true; +} + +static bool scr_date_time_add(ScrDateTime *t, int n) { + if (t->index >= 4) return false; + t->comp[t->index++] = n; + return true; +} + +static bool scr_date_time_add_final(ScrDateTime *t, int n) { + if (!scr_date_time_add(t, n)) return false; + while (t->index < 4) t->comp[t->index++] = 0; + return true; +} + +static bool scr_date_time_expecting(const ScrDateTime *t, int n) { + return (t->index == 1 && scr_date_between(n, 0, 59)) || + (t->index == 2 && scr_date_between(n, 0, 59)) || + (t->index == 3 && scr_date_between(n, 0, 999)); +} + +static void scr_date_zone_set(ScrDateZone *z, int hours) { + z->sign = hours < 0 ? -1 : 1; + z->hour = hours * z->sign; + z->minute = 0; +} + +static bool scr_date_zone_expecting(const ScrDateZone *z, int n) { + return z->hour != SCR_DT_NONE && z->minute == SCR_DT_NONE && scr_date_between(n, 0, 59); +} + +/* ReadMilliseconds: the first three significant digits of the numeral. */ +static int scr_date_millis(ScrDateTok t) { + int number = t.value, length = t.length; + if (length == 1) number *= 100; + else if (length == 2) number *= 10; + else if (length > 3) { + if (length > 9) length = 9; + int factor = 1; + do { + factor *= 10; + length--; + } while (length > 3); + number /= factor; + } + return number; +} + +/* ParseES5DateTime: answers the first token it could not place (the + * legacy grammar resumes there), END after a complete ES5 string, or + * INVALID for a malformed ES5 time part. */ +static ScrDateTok scr_date_parse_es5(ScrDateScan *in, ScrDateDay *day, ScrDateTime *time, + ScrDateZone *tz) { + ScrDateTok invalid = {SCR_DT_INVALID, 0, -1}; + if (scr_date_tok_sign(in->next)) { + ScrDateTok sign = scr_date_next(in); + if (!scr_date_tok_fixed(in->next, 6)) return sign; + int s = 44 - sign.value; + int year = scr_date_next(in).value; + if (s < 0 && year == 0) return sign; + scr_date_day_add(day, s * year); + } else if (scr_date_tok_fixed(in->next, 4)) { + scr_date_day_add(day, scr_date_next(in).value); + } else { + return scr_date_next(in); + } + if (scr_date_skip(in, '-')) { + if (!scr_date_tok_fixed(in->next, 2) || !scr_date_between(in->next.value, 1, 12)) { + return scr_date_next(in); + } + scr_date_day_add(day, scr_date_next(in).value); + if (scr_date_skip(in, '-')) { + if (!scr_date_tok_fixed(in->next, 2) || !scr_date_between(in->next.value, 1, 31)) { + return scr_date_next(in); + } + scr_date_day_add(day, scr_date_next(in).value); + } + } + if (in->next.tag != SCR_DK_T) { + if (in->next.tag != SCR_DT_END) return scr_date_next(in); + } else { + scr_date_next(in); + if (!scr_date_tok_fixed(in->next, 2) || !scr_date_between(in->next.value, 0, 24)) return invalid; + bool hour_is_24 = in->next.value == 24; + scr_date_time_add(time, scr_date_next(in).value); + if (!scr_date_skip(in, ':')) return invalid; + if (!scr_date_tok_fixed(in->next, 2) || !scr_date_between(in->next.value, 0, 59) || + (hour_is_24 && in->next.value > 0)) { + return invalid; + } + scr_date_time_add(time, scr_date_next(in).value); + if (scr_date_skip(in, ':')) { + if (!scr_date_tok_fixed(in->next, 2) || !scr_date_between(in->next.value, 0, 59) || + (hour_is_24 && in->next.value > 0)) { + return invalid; + } + scr_date_time_add(time, scr_date_next(in).value); + if (scr_date_skip(in, '.')) { + if (in->next.tag != SCR_DT_NUMBER || (hour_is_24 && in->next.value > 0)) return invalid; + scr_date_time_add(time, scr_date_millis(scr_date_next(in))); + } + } + if (scr_date_tok_z(in->next)) { + scr_date_next(in); + scr_date_zone_set(tz, 0); + } else if (scr_date_tok_sign(in->next)) { + tz->sign = scr_date_next(in).value == '+' ? 1 : -1; + if (scr_date_tok_fixed(in->next, 4)) { + int hourmin = scr_date_next(in).value; + int hour = hourmin / 100, minute = hourmin % 100; + if (!scr_date_between(hour, 0, 23) || !scr_date_between(minute, 0, 59)) return invalid; + tz->hour = hour; + tz->minute = minute; + } else { + if (!scr_date_tok_fixed(in->next, 2) || !scr_date_between(in->next.value, 0, 23)) { + return invalid; + } + tz->hour = scr_date_next(in).value; + if (!scr_date_skip(in, ':')) return invalid; + if (!scr_date_tok_fixed(in->next, 2) || !scr_date_between(in->next.value, 0, 59)) { + return invalid; + } + tz->minute = scr_date_next(in).value; + } + } + if (in->next.tag != SCR_DT_END) return invalid; + } + /* Date-only forms without a zone are UTC; date-times are local. */ + if (tz->hour == SCR_DT_NONE && time->index == 0) scr_date_zone_set(tz, 0); + day->iso = true; + ScrDateTok end = {SCR_DT_END, 0, -1}; + return end; +} + +static bool scr_date_day_write(ScrDateDay *d, double *year_out, double *month_out, double *day_out) { + if (d->index < 1) return false; + while (d->index < 3) d->comp[d->index++] = 1; + int year = 0, month, day; + if (d->named_month == SCR_DT_NONE) { + if (d->iso || !scr_date_between(d->comp[0], 1, 31)) { + year = d->comp[0]; + month = d->comp[1]; + day = d->comp[2]; + } else { + month = d->comp[0]; + day = d->comp[1]; + year = d->comp[2]; + } + } else { + month = d->named_month; + /* V8 pads to three components before choosing an order, so a lone + * day after a month name reads the padding 1 as the year (2001). */ + if (!scr_date_between(d->comp[0], 1, 31)) { + year = d->comp[0]; + day = d->comp[1]; + } else { + day = d->comp[0]; + year = d->comp[1]; + } + } + if (!d->iso) { + if (scr_date_between(year, 0, 49)) year += 2000; + else if (scr_date_between(year, 50, 99)) year += 1900; + } + if (!scr_date_between(month, 1, 12) || !scr_date_between(day, 1, 31)) return false; + *year_out = year; + *month_out = month - 1; + *day_out = day; + return true; +} + +static bool scr_date_time_write(ScrDateTime *t, double out[4]) { + while (t->index < 4) t->comp[t->index++] = 0; + int hour = t->comp[0], minute = t->comp[1], second = t->comp[2], millisecond = t->comp[3]; + if (t->hour_offset != SCR_DT_NONE) { + if (!scr_date_between(hour, 0, 12)) return false; + hour %= 12; + hour += t->hour_offset; + } + if (!scr_date_between(hour, 0, 23) || !scr_date_between(minute, 0, 59) || + !scr_date_between(second, 0, 59) || !scr_date_between(millisecond, 0, 999)) { + if (hour != 24 || minute != 0 || second != 0 || millisecond != 0) return false; + } + out[0] = hour; + out[1] = minute; + out[2] = second; + out[3] = millisecond; + return true; +} + +static bool scr_date_zone_write(ScrDateZone *z, double *offset_seconds) { + if (z->sign == SCR_DT_NONE) { + *offset_seconds = NAN; + return true; + } + if (z->hour == SCR_DT_NONE) z->hour = 0; + if (z->minute == SCR_DT_NONE) z->minute = 0; + unsigned total = (unsigned)z->hour * 3600u + (unsigned)z->minute * 60u; + if (total > (unsigned)INT_MAX) return false; + *offset_seconds = z->sign < 0 ? -(double)total : (double)total; + return true; +} + +static double scr_date_parse_v8(ScrStr *s) { + ScrDateScan in = {(const unsigned char *)s->data, (const unsigned char *)s->data + s->len, 0, 0, {0, 0, 0}}; + scr_date_scan_advance(&in); + in.next = scr_date_scan_token(&in); + ScrDateDay day = {{0, 0, 0}, 0, SCR_DT_NONE, false}; + ScrDateTime time = {{0, 0, 0, 0}, 0, SCR_DT_NONE}; + ScrDateZone tz = {SCR_DT_NONE, SCR_DT_NONE, SCR_DT_NONE}; + ScrDateTok token = scr_date_parse_es5(&in, &day, &time, &tz); + if (token.tag == SCR_DT_INVALID) return NAN; + bool has_read_number = day.index != 0; + for (; token.tag != SCR_DT_END; token = scr_date_next(&in)) { + if (token.tag == SCR_DT_NUMBER) { + has_read_number = true; + int n = token.value; + if (scr_date_skip(&in, ':')) { + if (scr_date_skip(&in, ':')) { + /* n:: */ + if (time.index != 0) return NAN; + scr_date_time_add(&time, n); + scr_date_time_add(&time, 0); + } else { + if (!scr_date_time_add(&time, n)) return NAN; + if (scr_date_tok_symbol(in.next, '.')) scr_date_next(&in); + } + } else if (scr_date_skip(&in, '.') && scr_date_time_expecting(&time, n)) { + scr_date_time_add(&time, n); + if (in.next.tag != SCR_DT_NUMBER) return NAN; + int ms = scr_date_millis(scr_date_next(&in)); + if (ms < 0) return NAN; + scr_date_time_add_final(&time, ms); + } else if (scr_date_zone_expecting(&tz, n)) { + tz.minute = n; + } else if (scr_date_time_expecting(&time, n)) { + scr_date_time_add_final(&time, n); + ScrDateTok peek = in.next; + if (peek.tag != SCR_DT_END && peek.tag != SCR_DT_SPACE && !scr_date_tok_z(peek) && + !scr_date_tok_sign(peek)) { + return NAN; + } + } else { + if (!scr_date_day_add(&day, n)) return NAN; + scr_date_skip(&in, '-'); + } + } else if (token.tag >= 0) { + if (token.tag == SCR_DK_AMPM && time.index != 0) { + time.hour_offset = token.value; + } else if (token.tag == SCR_DK_MONTH) { + day.named_month = token.value; + scr_date_skip(&in, '-'); + } else if (token.tag == SCR_DK_ZONE && has_read_number) { + scr_date_zone_set(&tz, token.value); + } else { + /* Garbage words are illegal once a number has been read, and the + * first number must be separated from them. */ + if (has_read_number) return NAN; + if (in.next.tag == SCR_DT_NUMBER) return NAN; + } + } else if (scr_date_tok_sign(token) && + ((tz.hour == 0 && tz.minute == 0) || time.index != 0)) { + /* A UTC offset, only after UTC or a time. */ + tz.sign = token.value == '-' ? -1 : 1; + int n = 0, length = 0; + if (in.next.tag == SCR_DT_NUMBER) { + ScrDateTok number = scr_date_next(&in); + length = number.length; + n = number.value; + } + has_read_number = true; + if (scr_date_tok_symbol(in.next, ':')) { + tz.hour = n; + tz.minute = SCR_DT_NONE; + } else if (length == 2 || length == 1) { + tz.hour = n; + tz.minute = 0; + } else if (length == 4 || length == 3) { + tz.hour = n / 100; + tz.minute = n % 100; + } else { + return NAN; + } + } else if ((scr_date_tok_sign(token) || scr_date_tok_symbol(token, ')')) && has_read_number) { + return NAN; + } + /* Anything else (whitespace, unknown characters) is ignored. */ + } + double year, month, date, hms[4], offset; + if (!scr_date_day_write(&day, &year, &month, &date) || !scr_date_time_write(&time, hms) || + !scr_date_zone_write(&tz, &offset)) { + return NAN; + } + double dayv = scr_date_v8_make_day(year, month, date); + double timev = scr_date_v8_make_time(hms[0], hms[1], hms[2], hms[3]); + if (isnan(offset)) return scr_date_from_local(dayv, timev); + if (!isfinite(dayv) || !isfinite(timev)) return NAN; + double t = timev + dayv * 86400000.0 - offset * 1000.0; + if (!(t >= -8640000000000000.0 && t <= 8640000000000000.0)) return NAN; + return scr_date_new_ms(t); +} + /* ── Number statics ──────────────────────────────────────────────────── * JS-exact: the ES2015 Number statics never coerce (unlike the global * isNaN/isFinite), and the compiler routes only number-typed arguments diff --git a/packages/runtime/src/scr_number.h b/packages/runtime/src/scr_number.h index 84db27075..80911023e 100644 --- a/packages/runtime/src/scr_number.h +++ b/packages/runtime/src/scr_number.h @@ -101,8 +101,9 @@ double scr_date_get_time(double ms); * 0–9999). THROWS Node's "Invalid time value" RangeError on NaN or * |ms| > 8.64e15 and returns NULL; +1 otherwise. */ ScrStr *scr_date_to_iso(double ms); -/* new Date(dateString).getTime(): the BOUNDED parse (the X509 validity - * shape + ECMA's format with an explicit offset); NaN elsewhere. */ +/* new Date(dateString).getTime() and Date.parse: V8's date parser (the + * ES5 format, then its legacy grammar: RFC 1123/2822, Date#toString, + * "Jan 2, 2024", ...). Zone-less dates are local time. Never throws. */ double scr_date_parse_get_time(ScrStr *s); /* Date.UTC over already-number arguments (frontend completes the spec's * defaults): MakeDay/MakeTime/TimeClip exactly — 0–99 maps to 1900+year, diff --git a/tests/corpus/6230-date-parse-legacy.ts b/tests/corpus/6230-date-parse-legacy.ts new file mode 100644 index 000000000..fdf4a5bcd --- /dev/null +++ b/tests/corpus/6230-date-parse-legacy.ts @@ -0,0 +1,112 @@ +// Date.parse and new Date(string) follow V8's date-string grammar: the ES5 +// date-time format first, then the legacy forms Node accepts (RFC 1123 and +// RFC 2822 HTTP dates, Date#toString output, month names, slashes, AM/PM, +// zone names and numeric offsets). Strings without a zone are local time, +// so every input is parsed under several zones, including DST transitions. +const fixed = [ + "Mon, 01 Jan 2024 00:00:00 GMT", + "Sun, 06 Nov 1994 08:49:37 GMT", + "Sunday, 06-Nov-94 08:49:37 GMT", + "Sun Nov 6 08:49:37 1994", + "Tue, 2 Jan 2024 03:04:05 +0100", + "2 Jan 2024 03:04 -0800", + "Mon Jan 01 2024 00:00:00 GMT+0000 (Coordinated Universal Time)", + "Thu Jan 01 1970 00:00:00 GMT-0500 (EST)", + "Jan 5", + "1/2", + "1/2/3", + "2024/01/02", + "2024/01/02 10:00", + "2024-01-02 10:00:00", + "2024-01-02T10:00:00", + "2024-01-02T10:00:00.5", + "2024-01-02T10:00:00.123456Z", + "2024-01-02T10:00:00+0530", + "2024-01-02T24:00:00Z", + "2024-01-02T24:00:01Z", + "2024-01-02T1:00Z", + " 2024-01-01", + "2024-01-01 ", + "Jan 2, 2024", + "January 2, 2024 10:00 PM", + "12:00 am Jan 1 2024", + "12:00 pm Jan 1 2024", + "13:00 pm Jan 1 2024", + "Jan 1 2024 10:00 GMT+5", + "Jan 1 2024 10:00 UTC+0530", + "Jan 1 2024 10:00 +05:30", + "Jan 1 2024 10:00 PST", + "Jan 1 2024 10:00 edt", + "Jan 1 2024 10:00 XYZ", + "Jan 1 2024 10.30", + "10:20:30 Jan 1 2024", + "Jan 1 2024 10:: GMT", + "Jan 1 2024 (a (nested) comment) 10:00", + "Jan 1 2024 10:00)", + "foo 2024-01-01", + "foo2024", + "May 2024", + "2024 May 3", + "3 May 2024", + "May 3 99", + "May 3 49", + "May 3 50", + "Feb 29 2023", + "Feb 30 2024 GMT", + "Dec 31 1969 23:59:59.999 GMT", + "2024-13-01", + "2024-02-31T00:00:00Z", + "+002024-01-01", + "-000000-01-01", + "-000001-01-01T00:00:00Z", + "+275760-09-13T00:00:00.000Z", + "+275760-09-13T00:00:00.001Z", + "Sat, 13 Sep 275760 00:00:00 GMT", + "Sat, 13 Sep 275760 00:00:01 GMT", + "-271821-04-20T00:00:00Z", + "Jan 1 9999999 GMT", + "2024-01-01T10:00:00\u0000junk", + "Jan 1
2024", + "Jan 1 2024", + "Jan 1 2024 été", + "0", + "12", + "2024", + "20240", + "", + "garbage", +]; +// A deterministic generator of date-like strings over V8's token classes. +let seed = 20240101; +function next(n: number): number { + seed = (seed * 1103515245 + 12345) % 2147483648; + return seed % n; +} +const pieces = ["Mon", "Tue,", "Wednesday", "Jan", "january", "FEB", "Sept", "dec", "ma", "1", "01", "2", "12", "13", "24", "31", "32", "59", "60", "99", "100", "2024", "1970", "0", "000", "0000", "123456", "-", "+", ":", "::", ".", ",", "/", " ", "\t", "(", ")", "(x)", "GMT", "UTC", "UT", "Z", "T", "t", "PST", "edt", "AM", "pm", "+0100", "-0800", "+01:00", "-8", "GMT+2", "GMT-0530", "999", "1000000", "x", "10:00", "23:59:59", "24:00", "12:30:45.123", "2024-01-15", "2024-01-15T10:00", "T10:00:00Z", "Jan1", "1Jan"]; +const generated: string[] = []; +for (let i = 0; i < 1500; i++) { + let s = ""; + const n = 1 + next(6); + for (let j = 0; j < n; j++) s += pieces[next(pieces.length)]! + (next(3) === 0 ? " " : ""); + generated.push(s); +} +for (const zone of ["UTC", "America/New_York", "Asia/Kolkata", "Australia/Lord_Howe"]) { + process.env.TZ = zone; + console.log("zone", zone); + for (const s of fixed) console.log(JSON.stringify(s), Date.parse(s), new Date(s).getTime()); + let valid = 0; + let sum = 0; + let hash = 0; + for (const s of generated) { + const t = Date.parse(s); + if (Number.isNaN(t)) continue; + valid++; + sum = (sum + (t % 1000003)) % 1000000007; + hash = (hash * 31 + (t % 65521)) % 2147483647; + } + console.log("generated", generated.length, "valid", valid, sum, hash); +} +// An HTTP conditional GET (the fresh package's comparison). +const lastModified = "Sun, 31 Dec 2023 00:00:00 GMT"; +const ifModifiedSince = "Mon, 01 Jan 2024 00:00:00 GMT"; +console.log("fresh", Date.parse(lastModified) <= Date.parse(ifModifiedSince)); diff --git a/tests/coverage-fixtures/divergences.ts b/tests/coverage-fixtures/divergences.ts index e13701a93..43c32a738 100644 --- a/tests/coverage-fixtures/divergences.ts +++ b/tests/coverage-fixtures/divergences.ts @@ -47,7 +47,7 @@ function sizeOf(s: Shape): number { } console.log(sizeOf(new Circle())); -// The native date parser takes ISO 8601 with an explicit offset. +// Date strings parse with V8's grammar natively, so none of these diverge. console.log(Date.parse("Jan 2, 2024"), new Date("2024/01/02").getTime(), Date.parse("2024-01-02T03:04:05Z")); const stamps = ["2024-03-04T05:06:07Z", "March 4, 2024"]; for (const stamp of stamps) console.log(Date.parse(stamp)); diff --git a/tests/harness/__snapshots__/coverage-divergences.txt b/tests/harness/__snapshots__/coverage-divergences.txt index 67c9b57e3..0b261f3d0 100644 --- a/tests/harness/__snapshots__/coverage-divergences.txt +++ b/tests/harness/__snapshots__/coverage-divergences.txt @@ -5,7 +5,7 @@ scriptc coverage tests/coverage-fixtures/divergences.ts fully static — all analyzed statements compile to native code. - differs from Node 8 sites (compiles; the native program can behave differently) + differs from Node 5 sites (compiles; the native program can behave differently) ×1 SC6001 passing a 'Tagged' where 'Labeled' is expected copies the object (converting to a record type with other properties compiles as a field copy): the write to 'target.label' updates only the copy, while… at tests/coverage-fixtures/divergences.ts:16:9 hint: declare the parameter or binding with the value's full type ('Tagged'), or make the shape a class — class instances keep their identity when passed as a base class @@ -20,13 +20,4 @@ scriptc coverage tests/coverage-fixtures/divergences.ts hint: results agree with Node for Latin-script text; for other scripts compare explicitly (for example by code point with < and >) so both runtimes agree ×1 SC6004 this 'Circle' reaches a cast to the unrelated class 'Square' in the callee: natively the checked cast throws a TypeError when it executes, while Node reads the members structurally at tests/coverage-fixtures/divergences.ts:48:20 - hint: narrow with 'instanceof Square' before casting, or cast to a base class or interface both classes share - ×1 SC6005 the date string 'Jan 2, 2024' parses to an invalid date (NaN) natively, while Node accepts it (the native parser takes ISO 8601 date-times with an explicit offset, such as '2024-01-02T03:04:05Z', and… - at tests/coverage-fixtures/divergences.ts:51:24 - hint: write the date in ISO 8601 with an explicit offset ('Z' or '+HH:MM'), or build it from numbers with Date.UTC - ×1 SC6005 the date string '2024/01/02' parses to an invalid date (NaN) natively, while Node accepts it (the native parser takes ISO 8601 date-times with an explicit offset, such as '2024-01-02T03:04:05Z', and … - at tests/coverage-fixtures/divergences.ts:51:49 - hint: write the date in ISO 8601 with an explicit offset ('Z' or '+HH:MM'), or build it from numbers with Date.UTC - ×1 SC6005 the date string 'March 4, 2024' parses to an invalid date (NaN) natively, while Node accepts it (the native parser takes ISO 8601 date-times with an explicit offset, such as '2024-01-02T03:04:05Z', a… - at tests/coverage-fixtures/divergences.ts:52:41 - hint: write the date in ISO 8601 with an explicit offset ('Z' or '+HH:MM'), or build it from numbers with Date.UTC \ No newline at end of file + hint: narrow with 'instanceof Square' before casting, or cast to a base class or interface both classes share \ No newline at end of file diff --git a/tests/harness/coverage.test.ts b/tests/harness/coverage.test.ts index 06f0e75a8..c7ba05248 100644 --- a/tests/harness/coverage.test.ts +++ b/tests/harness/coverage.test.ts @@ -46,9 +46,6 @@ test("divergences report as warnings, never as blockers", async () => { "SC6002", "SC6003", "SC6004", - "SC6005", - "SC6005", - "SC6005", ]); expect(coveragePasses(coverage, "blockers")).toBe(true); expect(coveragePasses(coverage, "divergences")).toBe(false); @@ -220,11 +217,11 @@ test("CLI: build --print=diagnostics reports failures and divergence warnings as }; expect(envelope.success).toBe(true); expect(envelope.artifact).toBe(join(outDir, "out.ir.json")); - expect(envelope.diagnostics.map((d) => d.severity)).toEqual(Array(8).fill("warning")); + expect(envelope.diagnostics.map((d) => d.severity)).toEqual(Array(5).fill("warning")); const human = build(fixture("divergences.ts")); expect(human.status).toBe(0); expect(human.stderr).toContain("warning SC6001"); - expect(human.stderr).toContain("8 warnings: the program compiled"); + expect(human.stderr).toContain("5 warnings: the program compiled"); } finally { rmSync(outDir, { recursive: true, force: true }); } From 40f15eb4b3df90d60aacac93d570febdd3a3212e Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 18 Oct 2026 12:17:36 -0700 Subject: [PATCH 003/122] Lower new Date(year, monthIndex, ...) as local time The multi-argument Date constructor was refused (SC2020), which blocked date-fns and consumer code that builds dates from calendar fields. Typed callers now lower to a seven-f64 runtime call (omitted trailing fields complete with the spec's defaults) and JavaScript callers pass their arguments through ToNumber in order. The runtime mirrors V8: MakeDay bounds the untruncated year and month, years 0-99 map to 1900-1999, fields roll over, and the local time value converts to UTC in the host zone with the same UTC(t) the date parser uses, then TimeClip. Spread arguments stay fenced. Co-Authored-By: Claude Opus 5.5 --- .../content/limitations/builtin-apis/date.mdx | 2 +- .../limitations/dates-and-timezones.mdx | 2 +- .../compiler/src/backend/llvm/lib-shared.ts | 2 + .../src/frontend/lowering/lower-classes.ts | 44 +++++++++++++++++-- .../src/frontend/lowering/surfaces.ts | 11 ++++- packages/compiler/src/ir/builtin-effects.ts | 1 + .../compiler/src/ir/builtin-signatures.ts | 10 +++++ packages/compiler/src/ir/int32-slots.ts | 1 + packages/compiler/surface-manifest.json | 2 +- .../corpus/6231-date-local-fields.ts.json | 6 +++ .../tests/corpus/6232-date-fields-js.cjs.json | 6 +++ packages/runtime/src/scr_dyn.h | 1 + packages/runtime/src/scr_lib.c | 29 +++++++++++- packages/runtime/src/scr_number.h | 5 +++ tests/corpus/6231-date-local-fields.ts | 36 +++++++++++++++ tests/corpus/6232-date-fields-js.cjs | 30 +++++++++++++ tests/diagnostics/stdlib-fence.ts | 2 +- .../harness/__snapshots__/stdlib-fence.ts.txt | 8 ++-- 18 files changed, 184 insertions(+), 14 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6231-date-local-fields.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6232-date-fields-js.cjs.json create mode 100644 tests/corpus/6231-date-local-fields.ts create mode 100644 tests/corpus/6232-date-fields-js.cjs diff --git a/docs/content/limitations/builtin-apis/date.mdx b/docs/content/limitations/builtin-apis/date.mdx index db5278c53..8d15ef917 100644 --- a/docs/content/limitations/builtin-apis/date.mdx +++ b/docs/content/limitations/builtin-apis/date.mdx @@ -1 +1 @@ -- **Date:** Date unions, year/month constructors, setters, statically typed TypeScript identity comparisons, thrown Date values, and locale/string formatters are unsupported. Zero-argument and single number/string constructors, storage and passing, `getTime`, `valueOf`, `toISOString`, `toJSON` (a `string | null`: null for an invalid date, as in Node), calendar getters, `getTimezoneOffset`, and single-string `Date.parse` compile. JavaScript Dates retain identity through checked storage and support Date-copy construction and JSON serialization. A TypeScript Date converted to `unknown` becomes a Date with the same time value; each conversion creates a new Date, so two `unknown` values converted from one TypeScript Date are not `===`. See [Dates and timezones](#dates-and-timezones) for parsing restrictions. +- **Date:** Date unions, spread constructor arguments, setters, statically typed TypeScript identity comparisons, thrown Date values, and locale/string formatters are unsupported. Zero-argument, single number/string, and local-time year/month field constructors, storage and passing, `getTime`, `valueOf`, `toISOString`, `toJSON` (a `string | null`: null for an invalid date, as in Node), calendar getters, `getTimezoneOffset`, and single-string `Date.parse` compile. JavaScript Dates retain identity through checked storage and support Date-copy construction and JSON serialization. A TypeScript Date converted to `unknown` becomes a Date with the same time value; each conversion creates a new Date, so two `unknown` values converted from one TypeScript Date are not `===`. See [Dates and timezones](#dates-and-timezones) for timezone data. diff --git a/docs/content/limitations/dates-and-timezones.mdx b/docs/content/limitations/dates-and-timezones.mdx index eacaf29d0..166757627 100644 --- a/docs/content/limitations/dates-and-timezones.mdx +++ b/docs/content/limitations/dates-and-timezones.mdx @@ -1,3 +1,3 @@ -String constructors and `Date.parse` use V8's date-string grammar: ECMAScript date-time strings and the legacy forms Node.js accepts, such as RFC 1123 HTTP dates (`Mon, 01 Jan 2024 00:00:00 GMT`), `Date#toString` output, and `Jan 2, 2024`. Strings without a timezone are local time. +String constructors and `Date.parse` use V8's date-string grammar: ECMAScript date-time strings and the legacy forms Node.js accepts, such as RFC 1123 HTTP dates (`Mon, 01 Jan 2024 00:00:00 GMT`), `Date#toString` output, and `Jan 2, 2024`. Strings without a timezone, and `new Date(year, monthIndex, ...)` fields, are local time. Local calendar getters and local-time conversions use the operating system's timezone database. Historical results can differ from Node.js when timezone data differs. For valid extreme years outside a platform calendar API's range, scriptc uses a calendar-equivalent year in the 400-year Gregorian cycle to obtain the timezone rule. diff --git a/packages/compiler/src/backend/llvm/lib-shared.ts b/packages/compiler/src/backend/llvm/lib-shared.ts index 02bf37192..df7b32889 100644 --- a/packages/compiler/src/backend/llvm/lib-shared.ts +++ b/packages/compiler/src/backend/llvm/lib-shared.ts @@ -306,6 +306,7 @@ export const LIB_FN_SYMS: Record = { "date.valueOf": "scr_date_get_time", "date.parseGetTime": "scr_date_parse_get_time", "date.utc": "scr_date_utc", + "date.newLocal": "scr_date_new_local", "date.getFullYear": "scr_date_get_full_year_local", "date.getUTCFullYear": "scr_date_get_full_year_utc", "date.getMonth": "scr_date_get_month_local", @@ -899,6 +900,7 @@ export const LIB_FN_SYMS: Record = { "dyn.nativeUrlIs": "scr_dyn_native_url_is", "dyn.nativeDateIs": "scr_dyn_native_date_is", "date.nativeNew": "scr_dyn_native_date_new", + "date.nativeNewFields": "scr_dyn_native_date_new_fields", "date.checkedValue": "scr_dyn_native_date_value", "dyn.nativeRegexIs": "scr_dyn_native_regex_is", "dyn.toString": "scr_dyn_to_string_argument", diff --git a/packages/compiler/src/frontend/lowering/lower-classes.ts b/packages/compiler/src/frontend/lowering/lower-classes.ts index 39b3242d6..0fd332217 100644 --- a/packages/compiler/src/frontend/lowering/lower-classes.ts +++ b/packages/compiler/src/frontend/lowering/lower-classes.ts @@ -8962,14 +8962,52 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { // fenced, so copying the scalar cannot create an observable lie. if (symbol && symbol.name === "Date" && lowerer.isStdlibSymbol(symbol)) { const args = expr.arguments ?? []; - if (args.some(ts.isSpreadElement) || args.length > 1) { + if (args.some(ts.isSpreadElement)) { lowerer.noLowering( - `new Date with ${args.length} arguments`, + `new Date with spread arguments`, expr, - "new Date(), new Date(milliseconds), and new Date(dateString) are supported; the local-time year/month field constructor has no lowering", + "new Date(), new Date(milliseconds), new Date(dateString), and new Date(year, monthIndex, ...) with listed arguments are supported", symbol, ); } + // new Date(year, monthIndex[, date[, hours[, minutes[, seconds[, ms]]]]]): + // local-time fields, converted to UTC in the host zone at run time. + // JavaScript callers pass their arguments to the runtime's ToNumber + // (in order, exactly the constructor's coercions); typed callers + // pass numbers, completed with the spec's defaults (date 1, time + // parts 0) for the seven-f64 ABI. + if (args.length > 1) { + if (isJsSourceFile(expr.getSourceFile())) { + return { + kind: "libCall", + fn: "date.nativeNewFields", + args: [ + { + kind: "dynArrLit", + elems: args.map((arg) => lowerer.lowerExprExpecting(arg, DYN)), + type: DYN, + loc, + }, + ], + type: DYN, + loc, + }; + } + if (args.length > 7) { + lowerer.noLowering(`new Date with ${args.length} arguments`, expr, undefined, symbol); + } + const defaults = [0, 0, 1, 0, 0, 0, 0]; + const fields: IrExpr[] = []; + for (let i = 0; i < 7; i++) { + const arg = args[i]; + fields.push( + arg !== undefined + ? lowerer.lowerExprExpecting(arg, F64) + : { kind: "numLit", value: defaults[i]!, type: F64, loc }, + ); + } + return { kind: "libCall", fn: "date.newLocal", args: fields, type: DATE_T, loc }; + } if (isJsSourceFile(expr.getSourceFile())) { return { kind: "libCall", diff --git a/packages/compiler/src/frontend/lowering/surfaces.ts b/packages/compiler/src/frontend/lowering/surfaces.ts index 561a58953..d06180952 100644 --- a/packages/compiler/src/frontend/lowering/surfaces.ts +++ b/packages/compiler/src/frontend/lowering/surfaces.ts @@ -2176,8 +2176,15 @@ export const AMBIENT_SURFACE_FNS: readonly AmbientSurfaceRow[] = [ id: "stdlib.date.constructor", kind: "stdlib", name: "Date constructor", - fns: ["date.newNow", "date.newMs", "date.newString", "date.nativeNew"], - note: "zero arguments, or one milliseconds/date-string argument; values are the read-only TimeClip scalar slice", + fns: [ + "date.newNow", + "date.newMs", + "date.newString", + "date.nativeNew", + "date.newLocal", + "date.nativeNewFields", + ], + note: "zero arguments, one milliseconds/date-string argument, or local-time year/month fields; values are the read-only TimeClip scalar slice", }, { id: "stdlib.date.UTC", diff --git a/packages/compiler/src/ir/builtin-effects.ts b/packages/compiler/src/ir/builtin-effects.ts index 1bb9cbd14..045e624ed 100644 --- a/packages/compiler/src/ir/builtin-effects.ts +++ b/packages/compiler/src/ir/builtin-effects.ts @@ -468,6 +468,7 @@ export const MAY_THROW_LIB_FNS: ReadonlySet = new Set([ "json.stringifyReplacer", "json.stringifyValue", "date.nativeNew", + "date.nativeNewFields", "date.checkedValue", "util.parseArgs", "util.parseEnv", diff --git a/packages/compiler/src/ir/builtin-signatures.ts b/packages/compiler/src/ir/builtin-signatures.ts index 1d14d8732..6119175c7 100644 --- a/packages/compiler/src/ir/builtin-signatures.ts +++ b/packages/compiler/src/ir/builtin-signatures.ts @@ -206,6 +206,10 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ "dyn.nativeUrlIs": { argTypes: [DYN], result: BOOL }, "dyn.nativeDateIs": { argTypes: [DYN], result: BOOL }, "date.nativeNew": { argTypes: [DYN], result: DYN }, + /** `new Date(year, monthIndex, ...)` in JavaScript: the argument array's + * first seven values pass through ToNumber in order (later ones are + * evaluated, never converted), then the local-time field constructor. */ + "date.nativeNewFields": { argTypes: [DYN], result: DYN }, "date.checkedValue": { argTypes: [DYN], result: DATE_T }, "dyn.nativeRegexIs": { argTypes: [DYN], result: BOOL }, /** toString() on a checked-dynamic receiver: runtime kind dispatch @@ -4198,6 +4202,12 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ * map to 1900+year, out-of-range months/dates roll over, non-finite * parts and out-of-range results answer NaN. Never throws. */ "date.utc": { argTypes: [F64, F64, F64, F64, F64, F64, F64], result: F64 }, + /** `new Date(year, monthIndex[, date, hours, minutes, seconds, ms])` — + * the same seven-f64 completion as date.utc, but the fields are LOCAL + * time: V8's MakeDay/MakeTime, then UTC(t) in the host zone (DST gaps + * read with the offset before the transition, overlaps answer the + * earlier instant), then TimeClip. Never throws. */ + "date.newLocal": { argTypes: [F64, F64, F64, F64, F64, F64, F64], result: DATE_T }, /** WHATWG TextDecoder.decode over u8 bytes (scr_bytes.c): utf-8 with * default options — the same maximal-subpart replacement decode as * Buffer.toString("utf8"), with the leading BOM stripped (the one diff --git a/packages/compiler/src/ir/int32-slots.ts b/packages/compiler/src/ir/int32-slots.ts index d7d06db7d..64faf0936 100644 --- a/packages/compiler/src/ir/int32-slots.ts +++ b/packages/compiler/src/ir/int32-slots.ts @@ -347,6 +347,7 @@ const DYN_READ_ONLY = new Set([ "arrayBuffer.viewU8", "arrayBuffer.viewU8C", "date.nativeNew", + "date.nativeNewFields", // The dynamic operators run the ToPrimitive and coercion helpers of // dyn.compare, dyn.numberConstructor and dyn.toStringCoerce, then build a // fresh number, string or bigint. dyn.increment steps dyn.toNumeric's result. diff --git a/packages/compiler/surface-manifest.json b/packages/compiler/surface-manifest.json index 471210669..852d054cc 100644 --- a/packages/compiler/surface-manifest.json +++ b/packages/compiler/surface-manifest.json @@ -4027,7 +4027,7 @@ "kind": "stdlib", "name": "Date constructor", "status": "static", - "note": "zero arguments, or one milliseconds/date-string argument; values are the read-only TimeClip scalar slice" + "note": "zero arguments, one milliseconds/date-string argument, or local-time year/month fields; values are the read-only TimeClip scalar slice" }, { "id": "stdlib.date.getDate", diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6231-date-local-fields.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6231-date-local-fields.ts.json new file mode 100644 index 000000000..6f078d295 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6231-date-local-fields.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6231-date-local-fields.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6232-date-fields-js.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6232-date-fields-js.cjs.json new file mode 100644 index 000000000..10f48c83a --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6232-date-fields-js.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6232-date-fields-js.cjs" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_dyn.h b/packages/runtime/src/scr_dyn.h index 667ceced0..0aeb9938f 100644 --- a/packages/runtime/src/scr_dyn.h +++ b/packages/runtime/src/scr_dyn.h @@ -600,6 +600,7 @@ ScrDyn *scr_dyn_native_search_params(ScrSearchParams *value); bool scr_dyn_native_search_params_is(const ScrDyn *value); bool scr_dyn_native_date_is(const ScrDyn *value); ScrDyn *scr_dyn_native_date_new(const ScrDyn *arguments); +ScrDyn *scr_dyn_native_date_new_fields(const ScrDyn *arguments); ScrDyn *scr_dyn_native_date_from_ms(double ms); double scr_dyn_native_date_value(const ScrDyn *value); ScrDyn *scr_dyn_native_regex(ScrRegex *value); diff --git a/packages/runtime/src/scr_lib.c b/packages/runtime/src/scr_lib.c index 2c232cbea..b4b834b2d 100644 --- a/packages/runtime/src/scr_lib.c +++ b/packages/runtime/src/scr_lib.c @@ -10656,7 +10656,7 @@ double scr_date_get_timezone_offset(double ms) { return scr_date_local_parts(ms, &p) ? p.timezone_offset : NAN; } -/* ── Local time to UTC ───────────────────────────────────────────────── +/* ── Local time to UTC, and the year/month field constructor ─────────── * The spec's UTC(t) over the host zone: t is a local wall-clock time * value. The offset in effect a day before and a day after t brackets any * transition (none come closer together than that in tzdata). A time in @@ -10753,6 +10753,20 @@ static double scr_date_from_local(double day, double time) { return scr_date_new_ms(scr_date_local_to_utc(trunc(t))); } +/* new Date(year, monthIndex[, date[, hours[, minutes[, seconds[, ms]]]]]) + * over already-number arguments (the frontend completes omitted trailing + * arguments with date 1 and time parts 0): years 0–99 map to 1900+year, + * fields roll over through MakeDay/MakeTime, and the local time converts + * to UTC in the host zone. Never throws. */ +double scr_date_new_local(double y, double mo, double d, + double h, double mi, double s, double ms) { + if (!isnan(y)) { + double yi = trunc(y); + if (yi >= 0 && yi <= 99) y = 1900 + yi; + } + return scr_date_from_local(scr_date_v8_make_day(y, mo, d), scr_date_v8_make_time(h, mi, s, ms)); +} + /* ── V8's date-string parser, the fallback ───────────────────────────── * scr_date_parse_get_time answers its bounded grammars directly and asks * this parser only when they produce NaN. It follows V8's DateParser @@ -11596,6 +11610,19 @@ ScrDyn *scr_dyn_native_date_new(const ScrDyn *arguments) { return scr_dyn_native_date_from_ms(ms); } +/* new Date(year, monthIndex, ...) in JavaScript: ToNumber over the first + * seven arguments in order (a throwing conversion stops there), the + * spec's defaults for the rest, then the local-time field constructor. */ +ScrDyn *scr_dyn_native_date_new_fields(const ScrDyn *arguments) { + double fields[7] = {0, 0, 1, 0, 0, 0, 0}; + size_t n = arguments->v.arr.len < 7 ? arguments->v.arr.len : 7; + for (size_t i = 0; i < n; i++) { + if (!scr_dyn_number_coerce_js(arguments->v.arr.items[i], &fields[i])) return NULL; + } + return scr_dyn_native_date_from_ms( + scr_date_new_local(fields[0], fields[1], fields[2], fields[3], fields[4], fields[5], fields[6])); +} + /* The Date.prototype methods scr_native_date_invoke answers. */ static const char *const scr_native_date_methods[] = { "getTime", "valueOf", "toISOString", "toJSON", "getFullYear", "getUTCFullYear", "getMonth", diff --git a/packages/runtime/src/scr_number.h b/packages/runtime/src/scr_number.h index 80911023e..752e50ec0 100644 --- a/packages/runtime/src/scr_number.h +++ b/packages/runtime/src/scr_number.h @@ -105,6 +105,11 @@ ScrStr *scr_date_to_iso(double ms); * ES5 format, then its legacy grammar: RFC 1123/2822, Date#toString, * "Jan 2, 2024", ...). Zone-less dates are local time. Never throws. */ double scr_date_parse_get_time(ScrStr *s); +/* new Date(year, monthIndex, date, hours, minutes, seconds, ms) over + * already-number arguments: V8's MakeDay/MakeTime over LOCAL fields, then + * UTC(t) in the host zone and TimeClip. Never throws. */ +double scr_date_new_local(double y, double mo, double d, + double h, double mi, double s, double ms); /* Date.UTC over already-number arguments (frontend completes the spec's * defaults): MakeDay/MakeTime/TimeClip exactly — 0–99 maps to 1900+year, * month/date rollover, NaN for non-finite parts, out-of-range results, diff --git a/tests/corpus/6231-date-local-fields.ts b/tests/corpus/6231-date-local-fields.ts new file mode 100644 index 000000000..6ff18c964 --- /dev/null +++ b/tests/corpus/6231-date-local-fields.ts @@ -0,0 +1,36 @@ +// new Date(year, monthIndex, date, hours, minutes, seconds, ms) builds a +// LOCAL time: fields roll over through MakeDay/MakeTime, years 0-99 mean +// 1900-1999, and the result converts to UTC in the host zone. A time in a +// DST gap reads with the offset before the transition; a time in an +// overlap answers the earlier instant. +const zones = ["UTC", "America/New_York", "Europe/Berlin", "Australia/Lord_Howe", "Asia/Kolkata", "America/St_Johns", "Pacific/Apia"]; +const years = [1800, 1969, 1970, 2000, 2024, 2038, 2100, 0, 49, 99, 100, -1, -271821, 275760]; +for (const zone of zones) { + process.env.TZ = zone; + const out: string[] = []; + for (const y of years) { + for (const m of [0, 2, 9, 11, 12, -1]) { + for (const d of [1, 31, 0]) out.push(`${y}/${m}/${d}=${new Date(y, m, d, 1, 30, 15, 500).getTime()}`); + } + } + console.log(zone, out.join(" ")); + // Every quarter hour across a spring-forward and a fall-back day. + const y = 2024; + const m = zone === "America/New_York" || zone === "Europe/Berlin" ? 2 : 9; + const d = zone === "America/New_York" ? 10 : zone === "Europe/Berlin" ? 31 : 6; + const day: string[] = []; + for (let minute = 0; minute < 24 * 60; minute += 15) { + const spring = new Date(y, m, d, 0, minute); + const fall = new Date(y, m + 7, d, 0, minute); + day.push(`${spring.getTime()}:${spring.getHours()}:${fall.getTime()}:${fall.getMinutes()}`); + } + console.log(zone, day.join(" ")); + const date = new Date(2020, 0, 15, 12, 30, 45, 123); + console.log(zone, date.getTime(), date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), date.getMinutes(), date.getSeconds(), date.getMilliseconds(), date.getTimezoneOffset()); +} +process.env.TZ = "America/New_York"; +console.log(new Date(2020, 0).getTime(), new Date(2020, 0, 1, 0, 0, 0, 0.9).getTime(), new Date(2020.7, 0.9).getTime(), new Date(-0.5, 0).getTime()); +console.log(new Date(1e6, 0).getTime(), new Date(1e6 + 0.5, 0).getTime(), new Date(2020, 1e7).getTime(), new Date(2020, 1e7 + 1).getTime()); +console.log(new Date(NaN, 0).getTime(), new Date(2020, 0, Infinity).getTime(), new Date(2020, 0, 1, -Infinity).getTime()); +console.log(new Date(275760, 8, 13).getTime(), new Date(275760, 8, 12, 20).getTime(), new Date(-271821, 3, 19, 20).getTime()); +console.log(new Date(2021, 6, 4, 9, 5).toISOString(), new Date(99, 11, 31, 23, 59, 59, 999).toISOString()); diff --git a/tests/corpus/6232-date-fields-js.cjs b/tests/corpus/6232-date-fields-js.cjs new file mode 100644 index 000000000..a9cef51fa --- /dev/null +++ b/tests/corpus/6232-date-fields-js.cjs @@ -0,0 +1,30 @@ +// JavaScript callers of the Date field constructor and the date-string +// parser: arguments pass through ToNumber in order (valueOf runs, strings +// and booleans convert, a throwing conversion stops the rest), arguments +// past the seventh are evaluated but never converted, and RFC 1123 dates +// parse the way HTTP caching middleware reads them. +process.env.TZ = "Europe/Berlin"; +const log = []; +const arg = (name, value) => ({ valueOf() { log.push(name); return value; } }); +const d = new Date(arg("y", 2024), arg("m", 1), arg("d", 29), arg("h", 13), arg("mi", 5), arg("s", 6), arg("ms", 7), arg("extra", 8)); +console.log(d.getTime(), d.toISOString(), log.join(",")); +console.log(new Date("2024", "0", "15").getTime(), new Date(2024, true, null).getTime(), new Date(2024, undefined).getTime()); +console.log(new Date(2024, 0, "x").getTime(), new Date(99, 0).getFullYear(), new Date(2024, 0, 1, 2, 3).getHours()); +try { + new Date(2024, arg("m", 0), { valueOf() { throw new Error("conversion stopped"); } }, arg("h", 1)); +} catch (e) { + console.log(e.message, log.slice(-1).join(",")); +} +function isFresh(reqHeaders, resHeaders) { + const modifiedSince = reqHeaders["if-modified-since"]; + const lastModified = resHeaders["last-modified"]; + if (!modifiedSince || !lastModified) return false; + const lm = Date.parse(lastModified); + const ms = Date.parse(modifiedSince); + return !(isNaN(lm) || isNaN(ms) || lm > ms); +} +console.log(isFresh({ "if-modified-since": "Mon, 01 Jan 2024 00:00:00 GMT" }, { "last-modified": "Sun, 31 Dec 2023 00:00:00 GMT" })); +console.log(isFresh({ "if-modified-since": "Sun, 31 Dec 2023 00:00:00 GMT" }, { "last-modified": "Mon, 01 Jan 2024 00:00:00 GMT" })); +console.log(isFresh({ "if-modified-since": "not a date" }, { "last-modified": "Mon, 01 Jan 2024 00:00:00 GMT" })); +const stamps = ["Tue, 15 Nov 1994 08:12:31 GMT", "Tuesday, 15-Nov-94 08:12:31 GMT", "Tue Nov 15 08:12:31 1994", "2024-03-31 02:30"]; +for (const s of stamps) console.log(s, Date.parse(s), new Date(s).getTime()); diff --git a/tests/diagnostics/stdlib-fence.ts b/tests/diagnostics/stdlib-fence.ts index 11b516b99..ae456af90 100644 --- a/tests/diagnostics/stdlib-fence.ts +++ b/tests/diagnostics/stdlib-fence.ts @@ -20,7 +20,7 @@ const parsedMs = Date.parse; const fmt = new Intl.NumberFormat(); // Constructors. -const d = new Date(2024, 0); // the local-time year/month field constructor stays fenced +const d = new Date(...([2024, 0] as const)); // spread field arguments stay fenced const mutableDate = new Date(0); mutableDate.setUTCFullYear(2024); // mutation is outside the read-only Date slice const sameDate = mutableDate === mutableDate; // scalar storage must not fake object identity diff --git a/tests/harness/__snapshots__/stdlib-fence.ts.txt b/tests/harness/__snapshots__/stdlib-fence.ts.txt index 337354eec..908ab1cff 100644 --- a/tests/harness/__snapshots__/stdlib-fence.ts.txt +++ b/tests/harness/__snapshots__/stdlib-fence.ts.txt @@ -50,14 +50,14 @@ stdlib-fence.ts:20:13 - error SC2020: 'new Intl.NumberFormat' is part of the sta hint: the type checker sees the full standard library, but only the supported surface compiles (https://scriptc.dev/limitations) -stdlib-fence.ts:23:11 - error SC2020: 'new Date with 2 arguments' is part of the standard library types but has no scriptc lowering yet +stdlib-fence.ts:23:11 - error SC2020: 'new Date with spread arguments' is part of the standard library types but has no scriptc lowering yet 22 | // Constructors. - 23 | const d = new Date(2024, 0); // the local-time year/month field constructor stays fenced - | ^~~~~~~~~~~~~~~~~ + 23 | const d = new Date(...([2024, 0] as const)); // spread field arguments stay fenced + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 24 | const mutableDate = new Date(0); - hint: new Date(), new Date(milliseconds), and new Date(dateString) are supported; the local-time year/month field constructor has no lowering + hint: new Date(), new Date(milliseconds), new Date(dateString), and new Date(year, monthIndex, ...) with listed arguments are supported stdlib-fence.ts:25:1 - error SC2020: 'Date.prototype.setUTCFullYear with arguments' is part of the standard library types but has no scriptc lowering yet From 1144e3cea421e3ac322799a4f0abd0093d6caeb6 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 18 Oct 2026 12:40:20 -0700 Subject: [PATCH 004/122] Make plain-object constructor and Object.prototype method identity exact fast-deep-equal and every isPlainObject idiom compare `o.constructor === Object` and `o.valueOf === Object.prototype.valueOf`. Natively a dynamic read of an inherited Object.prototype member answered undefined and the JavaScript identifier `Object` was a string identity token, so equal({a: 1}, {a: 1}) was false with no diagnostic. The builtin constructors Object, Function, Date, RegExp, Map, Set, Promise, WeakMap and WeakSet now read as interned function values (in TypeScript too, where they were refused), and dynamic values answer `.constructor` with the same functions: plain objects (including Object.create chains), arrays, functions, class instances and the native Date/RegExp/Map/Set/WeakMap/WeakSet/Promise values. A member no nearer prototype defines falls back to the interned Object.prototype method `Object.prototype.` reads (valueOf, toString, hasOwnProperty, propertyIsEnumerable), respecting the types that define their own toString or valueOf. Typed records and class instances read these members through their dynamic view instead of hitting the stdlib fence. `instanceof` accepts the constructors, and `Object(v)` / `new Object()` work through a stored value; calling the other constructors through one stays fenced. Co-Authored-By: Claude Opus 5.5 --- .../limitations/builtin-apis/prototypes.mdx | 2 +- .../compiler/src/backend/llvm/lib-shared.ts | 1 + .../lowering/class-dynamic-dispatch.ts | 103 ++++++-- .../src/frontend/lowering/lower-exprs.ts | 107 +++++++- .../src/frontend/lowering/lower-stmts.ts | 8 + .../compiler/src/ir/builtin-signatures.ts | 5 + packages/compiler/src/ir/ir.ts | 7 +- .../6233-object-identity-dynamic.cjs.json | 6 + .../corpus/6235-object-identity-typed.ts.json | 6 + packages/runtime/src/scr_dyn.h | 3 + packages/runtime/src/scr_dyn_invoke.c | 6 + packages/runtime/src/scr_json.c | 237 ++++++++++++++++++ tests/corpus/6233-object-identity-dynamic.cjs | 86 +++++++ tests/corpus/6235-object-identity-typed.ts | 24 ++ 14 files changed, 577 insertions(+), 24 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6233-object-identity-dynamic.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6235-object-identity-typed.ts.json create mode 100644 tests/corpus/6233-object-identity-dynamic.cjs create mode 100644 tests/corpus/6235-object-identity-typed.ts diff --git a/docs/content/limitations/builtin-apis/prototypes.mdx b/docs/content/limitations/builtin-apis/prototypes.mdx index 9cfc041ed..441884bfc 100644 --- a/docs/content/limitations/builtin-apis/prototypes.mdx +++ b/docs/content/limitations/builtin-apis/prototypes.mdx @@ -1 +1 @@ -- **Prototypes:** Named properties and methods on program class prototypes support replacement, inheritance, and deletion. Bare prototype reflection, accessor replacement, and prototype storage on local, generic, mixin, or runtime-provided classes are unsupported. +- **Prototypes:** Named properties and methods on program class prototypes support replacement, inheritance, and deletion. Bare prototype reflection, accessor replacement, and prototype storage on local, generic, mixin, or runtime-provided classes are unsupported. Plain objects, arrays, functions, class instances, and Date, RegExp, Map, Set, WeakMap, WeakSet, and Promise values inherit `constructor`, `valueOf`, `toString`, `hasOwnProperty`, and `propertyIsEnumerable` from `Object.prototype` as in Node, so `value.constructor === Object` and `value.valueOf === Object.prototype.valueOf` compare identities exactly. The constructors `Object`, `Function`, `Date`, `RegExp`, `Map`, `Set`, `WeakMap`, `WeakSet`, and `Promise` read as function values; through a stored value only `Object` can be called or constructed. diff --git a/packages/compiler/src/backend/llvm/lib-shared.ts b/packages/compiler/src/backend/llvm/lib-shared.ts index df7b32889..3f36e0d7c 100644 --- a/packages/compiler/src/backend/llvm/lib-shared.ts +++ b/packages/compiler/src/backend/llvm/lib-shared.ts @@ -899,6 +899,7 @@ export const LIB_FN_SYMS: Record = { "dyn.nativeMapIs": "scr_dyn_native_map_is", "dyn.nativeUrlIs": "scr_dyn_native_url_is", "dyn.nativeDateIs": "scr_dyn_native_date_is", + "dyn.globalConstructor": "scr_dyn_global_constructor", "date.nativeNew": "scr_dyn_native_date_new", "date.nativeNewFields": "scr_dyn_native_date_new_fields", "date.checkedValue": "scr_dyn_native_date_value", diff --git a/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts b/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts index 120032bf5..0477e5c3e 100644 --- a/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts +++ b/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts @@ -2062,6 +2062,49 @@ export class ClassDynamicDispatch { // A read compares the bag with the receiver and then reads it: // evaluate it once, so each read retains the receiver once. const bagRead = varRef("bag.0", DYN, loc); + const bagGet: IrExpr | null = read + ? { + kind: "libCall", + fn: "dyn.bagGet", + args: [bagRead, read.key, varRef("p.0", DYN, loc)], + type: DYN, + loc, + } + : null; + // A class instance whose class chain declares no member of an + // Object.prototype method's name (classes that do answer in + // their own branches) inherits it unless an own property + // shadows it; a class constructor's static bag reads as before. + const instanceRead: IrExpr | null = + read && INHERITED_OBJECT_METHODS.has(name) + ? { + kind: "ternary", + cond: { + kind: "dynTest", + test: "function", + value: varRef("p.0", DYN, loc), + type: BOOL, + loc, + }, + then: bagGet!, + else_: { + kind: "ternary", + cond: this.readBagHas(lowerer, "dyn.hasKey", bagRead, read.key, loc), + then: bagGet!, + else_: { + kind: "libCall", + fn: "dyn.builtinMethod", + args: [{ kind: "strLit", value: "Object", type: STRING, loc }, read.key], + type: DYN, + loc, + }, + type: DYN, + loc, + }, + type: DYN, + loc, + } + : bagGet; const fallback: IrExpr = read ? { kind: "ternary", @@ -2073,13 +2116,7 @@ export class ClassDynamicDispatch { loc, }, then: { ...read, value: bagRead }, - else_: { - kind: "libCall", - fn: "dyn.bagGet", - args: [bagRead, read.key, varRef("p.0", DYN, loc)], - type: DYN, - loc, - }, + else_: instanceRead!, type: DYN, loc, } @@ -4681,19 +4718,40 @@ export class ClassDynamicDispatch { if (!field && !accessor) { const instance = lowerer.coerceToExpected(receiver, DYN); const bag = this.readPropertyBagOrEmpty(lowerer, instance, loc); - return [ - { - kind: "return", - value: { - kind: "libCall", - fn: "dyn.bagGet", - args: [bag, { kind: "strLit", value: name, type: STRING, loc }, instance], - type: DYN, + const key: IrExpr = { kind: "strLit", value: name, type: STRING, loc }; + const own: IrExpr = { + kind: "libCall", + fn: "dyn.bagGet", + args: [bag, key, instance], + type: DYN, + loc, + }; + // An Object.prototype method no class in the chain declares: an + // own property shadows it, otherwise the read answers the same + // interned value `Object.prototype.` reads. + if (INHERITED_OBJECT_METHODS.has(name) && !findMethodOn(lowerer, info, name)) { + return [ + { + kind: "return", + value: { + kind: "ternary", + cond: this.readBagHas(lowerer, "dyn.hasKey", bag, key, loc), + then: own, + else_: { + kind: "libCall", + fn: "dyn.builtinMethod", + args: [{ kind: "strLit", value: "Object", type: STRING, loc }, key], + type: DYN, + loc, + }, + type: DYN, + loc, + }, loc, }, - loc, - }, - ]; + ]; + } + return [{ kind: "return", value: own, loc }]; } const type = field ?? accessor!.sig.ret; if (!canConvertToDyn(type, getRecord, getUnion)) return fence(); @@ -5216,3 +5274,12 @@ export class ClassDynamicDispatch { return body; } } + +/** Object.prototype methods every class instance inherits unless its class + * chain declares them. */ +const INHERITED_OBJECT_METHODS: ReadonlySet = new Set([ + "valueOf", + "toString", + "hasOwnProperty", + "propertyIsEnumerable", +]); diff --git a/packages/compiler/src/frontend/lowering/lower-exprs.ts b/packages/compiler/src/frontend/lowering/lower-exprs.ts index d4d03591d..d3f1f4676 100644 --- a/packages/compiler/src/frontend/lowering/lower-exprs.ts +++ b/packages/compiler/src/frontend/lowering/lower-exprs.ts @@ -1450,8 +1450,12 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr { { const sym = lowerer.checker.getSymbolAtLocation(expr); if (lowerer.isStdlibSymbol(sym) || expr.text === "globalThis") { + const canonical = stdlibGlobalNameOf(lowerer, expr) ?? expr.text; + // Builtin constructors have real function values in both source + // languages (TypeScript reads them as dynamic values). + const constructorValue = builtinGlobalValue(canonical, loc); + if (constructorValue !== null) return constructorValue; if (isJsSourceFile(expr.getSourceFile())) { - const canonical = stdlibGlobalNameOf(lowerer, expr) ?? expr.text; return { kind: "strLit", value: `[builtin ${canonical}]`, type: STRING, loc }; } // The families with a WHY: each hint states what makes the @@ -2349,7 +2353,14 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr { type: DYN, loc, }; - return { kind: "strLit", value: `[builtin ${canonical}]`, type: STRING, loc }; + return ( + builtinGlobalValue(canonical, loc) ?? { + kind: "strLit", + value: `[builtin ${canonical}]`, + type: STRING, + loc, + } + ); } } // A JS receiver whose CHECKER type has no mapping (`mustCallChecks @@ -2428,6 +2439,45 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr { }; } } + // A plain record's inherited Object.prototype members READ as values + // (`o.constructor`, `o.valueOf`, `({}).toString`, the isPlainObject and + // deep-equal idioms): the record boxes into its dynamic view, whose + // read answers the interned Object constructor and Object.prototype + // methods — the same values `Object` and `Object.prototype.valueOf` + // read, so identity comparisons are exact. Calls keep their own + // lowerings; a record declaring the name reads its field. + if ( + OBJECT_PROTOTYPE_VALUE_READS.has(expr.name.text) && + !isCalleeOf(expr) && + // `X.prototype.toString` keeps the builtin prototype-method lowering. + !(ts.isPropertyAccessExpression(expr.expression) && expr.expression.name.text === "prototype") + ) { + const receiver = tryLowerExpression(lowerer, expr.expression); + const name = expr.name.text; + const shape = + receiver?.type.kind === "record" ? lowerer.shapes.get(receiver.type.shapeId) : undefined; + const info = + receiver?.type.kind === "object" ? lowerer.classes.get(receiver.type.className) : undefined; + const inherits = + receiver?.type.kind === "dyn" || + (shape !== undefined && + !shape.tuple && + !shape.fields.some((field) => field.name === name)) || + (info !== undefined && + name !== "constructor" && + !info.fields.has(name) && + !lowerer.findMethodOn(info, name) && + !lowerer.findMethodOn(info, `get:${name}`)); + if (receiver !== null && receiver !== undefined && inherits) { + return { + kind: "dynKeyGet", + value: lowerer.coerceToExpected(receiver, DYN), + key: { kind: "strLit", value: name, type: STRING, loc }, + type: DYN, + loc, + }; + } + } // `f.name` / `f.length` / own properties on a FUNCTION-typed JS // value (the mustCall wrapper's function-instance members): read // through the dyn box — the closure's own-property table answers @@ -16263,3 +16313,56 @@ function isBuiltinModuleValueDecl(decl: ts.Node): boolean { ts.isVariableDeclaration(decl)) ); } + +/** The builtin constructors JavaScript sources read as real function + * values rather than identity tokens: the runtime interns one function per + * constructor, and dynamic values answer `.constructor` with the same one, + * so `o.constructor === Object` is an exact identity test. */ +const GLOBAL_CONSTRUCTOR_VALUES: ReadonlySet = new Set([ + "Object", + "Function", + "Date", + "RegExp", + "Map", + "Set", + "Promise", + "WeakMap", + "WeakSet", +]); + +/** The value a JavaScript source reads for the stdlib global `canonical`, + * or null when it stays an identity token. */ +export function builtinGlobalValue(canonical: string, loc: SrcLoc): IrExpr | null { + if (GLOBAL_CONSTRUCTOR_VALUES.has(canonical)) { + return { + kind: "libCall", + fn: "dyn.globalConstructor", + args: [{ kind: "strLit", value: canonical, type: STRING, loc }], + type: DYN, + loc, + }; + } + return null; +} + +/** Object.prototype members a plain record inherits, read as values. */ +const OBJECT_PROTOTYPE_VALUE_READS: ReadonlySet = new Set([ + "constructor", + "valueOf", + "toString", + "hasOwnProperty", + "propertyIsEnumerable", +]); + +/** Whether `expr` is the callee of a call (`o.toString()`), which keeps its + * call lowering rather than reading the member as a value. */ +function isCalleeOf(expr: ts.PropertyAccessExpression): boolean { + let node: ts.Node = expr; + while (node.parent !== undefined && ts.isParenthesizedExpression(node.parent)) node = node.parent; + const parent = node.parent; + return ( + parent !== undefined && + (ts.isCallExpression(parent) || ts.isNewExpression(parent)) && + parent.expression === node + ); +} diff --git a/packages/compiler/src/frontend/lowering/lower-stmts.ts b/packages/compiler/src/frontend/lowering/lower-stmts.ts index 0f1b606ef..78c3e76d3 100644 --- a/packages/compiler/src/frontend/lowering/lower-stmts.ts +++ b/packages/compiler/src/frontend/lowering/lower-stmts.ts @@ -169,6 +169,7 @@ import { import { globalSymbolKey } from "./expressions/global-symbols.js"; import { lowerShortCircuitAssignment } from "./expressions/nullish-assignment.js"; import { + builtinGlobalValue, lowerEnvironmentKey, lowerNativeFunctionAssignment, lowerStrictEquality, @@ -3334,6 +3335,9 @@ function stdlibGlobalTokenDestructure( const canonical = member.name === "global" ? "globalThis" : member.name; token = `[builtin ${canonical}]`; alias = canonical; + // Builtin constructors read as their interned function values, the + // same values the bare and `globalThis.X` spellings read. + value = builtinGlobalValue(canonical, locOf(el)); } else if (globalName === "console" && !CONSOLE_CALL_MEMBERS.has(propName)) { token = `[builtin console.${propName}]`; } else if (TOKEN_OPAQUE_GLOBALS.has(globalName)) { @@ -3377,6 +3381,10 @@ function stdlibGlobalTokenDestructure( const out: IrStmt[] = []; for (const b of binds) { const loc = locOf(b.name); + if (b.value !== undefined && b.alias !== null) { + const symbol = lowerer.checker.getSymbolAtLocation(b.name); + if (symbol) lowerer.stdlibGlobalAliases.set(symbol, b.alias); + } if (b.value !== undefined) { if (b.g !== undefined) { out.push({ diff --git a/packages/compiler/src/ir/builtin-signatures.ts b/packages/compiler/src/ir/builtin-signatures.ts index 6119175c7..74956d161 100644 --- a/packages/compiler/src/ir/builtin-signatures.ts +++ b/packages/compiler/src/ir/builtin-signatures.ts @@ -205,6 +205,11 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ "dyn.nativeMapIs": { argTypes: [DYN], result: BOOL }, "dyn.nativeUrlIs": { argTypes: [DYN], result: BOOL }, "dyn.nativeDateIs": { argTypes: [DYN], result: BOOL }, + /** A builtin constructor (`Object`, `Date`, `RegExp`, `Map`, `Set`, + * `Promise`, `WeakMap`, `WeakSet`) read as a JavaScript value: one + * interned function per name, the same one dynamic values answer for + * `.constructor`. The name is a compiler-chosen literal. */ + "dyn.globalConstructor": { argTypes: [STRING], result: DYN }, "date.nativeNew": { argTypes: [DYN], result: DYN }, /** `new Date(year, monthIndex, ...)` in JavaScript: the argument array's * first seven values pass through ToNumber in order (later ones are diff --git a/packages/compiler/src/ir/ir.ts b/packages/compiler/src/ir/ir.ts index 5382a6735..21c14a60b 100644 --- a/packages/compiler/src/ir/ir.ts +++ b/packages/compiler/src/ir/ir.ts @@ -3214,9 +3214,10 @@ export type IrExpr = /** Keyed read on a dyn value — `pkg.name` / `pkg["k"]` / the * `pkg?.scripts` chain step on a JSON.parse result. `key` is * string-typed (a strLit for the dot form); `type` is always dyn. An - * OBJ receiver answers the member (+1) or the undefined singleton (the - * own-property answer — prototype members like `toString` answer - * undefined, SEMANTICS.md); ARR answers `length` and canonical + * OBJ receiver answers the member (+1), an inherited Object.prototype + * member (the Object constructor and the interned valueOf/toString/ + * hasOwnProperty/propertyIsEnumerable values), or the undefined singleton; + * ARR answers `length` and canonical * in-range indices, STR answers `length` (UTF-16-exact), both * undefined otherwise; NUM/BOOL/BYTES answer undefined. An * undefined/null receiver THROWS the catchable Node-shaped TypeError diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6233-object-identity-dynamic.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6233-object-identity-dynamic.cjs.json new file mode 100644 index 000000000..a5dd87184 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6233-object-identity-dynamic.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6233-object-identity-dynamic.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6235-object-identity-typed.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6235-object-identity-typed.ts.json new file mode 100644 index 000000000..c48f4164b --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6235-object-identity-typed.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6235-object-identity-typed.ts" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_dyn.h b/packages/runtime/src/scr_dyn.h index 0aeb9938f..738c5d487 100644 --- a/packages/runtime/src/scr_dyn.h +++ b/packages/runtime/src/scr_dyn.h @@ -652,6 +652,9 @@ ScrDyn *scr_dyn_symbol_ref(ScrSym *value, void (*release)(ScrSym *), ScrStr *(*r ScrDyn *scr_dyn_new_str(ScrStr *s); ScrDyn *scr_dyn_new_arr(void); ScrDyn *scr_dyn_array_constructor(void); +/* The interned builtin constructor `name` (Object, Date, RegExp, Map, Set, + * Promise, WeakMap, WeakSet) as a dynamic function value: +1. */ +ScrDyn *scr_dyn_global_constructor(const ScrStr *name); bool scr_dyn_arr_has_index(const ScrDyn *arr, size_t index); void scr_dyn_arr_push_hole(ScrDyn *arr); ScrDyn *scr_dyn_new_obj(void); diff --git a/packages/runtime/src/scr_dyn_invoke.c b/packages/runtime/src/scr_dyn_invoke.c index a3085399f..086555975 100644 --- a/packages/runtime/src/scr_dyn_invoke.c +++ b/packages/runtime/src/scr_dyn_invoke.c @@ -1712,6 +1712,12 @@ static ScrDyn *dyn_builtin_method_call(ScrClosure *closure, ScrDyn *const *args, } ScrDyn *scr_dyn_builtin_method(const ScrStr *prototype, const ScrStr *method) { + /* Object.prototype's shared methods: the interned values dynamic reads + * of inherited members answer too. */ + if (!strcmp(prototype->data, "Object")) { + ScrDyn *shared = scr_dyn_object_proto_method(method->data); + if (shared) return shared; + } if (!dyn_builtin_methods) { dyn_builtin_methods = scr_dyn_new_obj_null_proto(); scr_atexit(dyn_builtin_methods_cleanup); diff --git a/packages/runtime/src/scr_json.c b/packages/runtime/src/scr_json.c index b23e78241..2d38f54e5 100644 --- a/packages/runtime/src/scr_json.c +++ b/packages/runtime/src/scr_json.c @@ -1533,6 +1533,13 @@ ScrDyn *scr_dyn_map_seed_entry(const ScrDyn *entry) { double scr_dyn_arr_len(const ScrDyn *d) { return d->kind == SCR_DYN_ARR ? (double)d->v.arr.len : 0; } +enum { + SCR_INHERIT_VALUE_OF = 1, SCR_INHERIT_TO_STRING = 2, SCR_INHERIT_OWN = 4, + SCR_INHERIT_ALL = 7, +}; +static ScrDyn *scr_dyn_object_proto_inherited(const char *key, size_t key_len, int methods); +static ScrDyn *scr_dyn_global_ctor_named(const char *name, size_t len); + ScrDyn *scr_dyn_arr_at(const ScrDyn *d, double i) { if (d->kind != SCR_DYN_ARR || i < 0 || i >= (double)d->v.arr.len) { return scr_dyn_retain(scr_dyn_undefined()); @@ -1557,6 +1564,15 @@ ScrDyn *scr_dyn_arr_named_get(const ScrDyn *d, const ScrStr *key) { scr_dyn_release(value); return scr_dyn_array_constructor(); } + /* Array.prototype defines its own toString; valueOf, hasOwnProperty and + * propertyIsEnumerable come from Object.prototype. */ + if (value && value->kind == SCR_DYN_UNDEF && !d->null_proto && !d->prototype && key->len != 11) { + ScrDyn *inherited = scr_dyn_object_proto_inherited(key->data, key->len, SCR_INHERIT_VALUE_OF | SCR_INHERIT_OWN); + if (inherited) { + scr_dyn_release(value); + return inherited; + } + } return value; } @@ -3375,6 +3391,163 @@ ScrDyn *scr_dyn_array_constructor(void) { } return scr_dyn_retain(scr_array_ctor); } +/* ── The builtin constructors as dynamic values ──────────────────────── + * JavaScript sources read `Object`, `Date`, `RegExp`, `Map`, `Set`, + * `Promise`, `WeakMap` and `WeakSet` as these interned functions, and the + * same functions answer `.constructor` on the matching dynamic values, so + * `o.constructor === Object` and `a.constructor === b.constructor` are + * exact identity tests. `Object(v)` and `new Object()` work through the + * stored value; calling the others through it is fenced. */ +static const char *const scr_global_ctor_names[] = { + "Object", "Date", "RegExp", "Map", "Set", "Promise", "WeakMap", "WeakSet", +}; +#define SCR_GLOBAL_CTORS (sizeof scr_global_ctor_names / sizeof scr_global_ctor_names[0]) +static SCR_TL ScrDyn *scr_global_ctors[SCR_GLOBAL_CTORS]; +static SCR_TL bool scr_global_ctors_registered; + +static void scr_global_ctors_cleanup(void) { + for (size_t i = 0; i < SCR_GLOBAL_CTORS; i++) { + scr_dyn_release(scr_global_ctors[i]); + scr_global_ctors[i] = NULL; + } +} + +static ScrDyn *scr_global_ctor_call(ScrClosure *closure, ScrDyn *const *args, size_t argc) { + const char *name = closure->fn; + if (strcmp(name, "Object") == 0) { + const ScrDyn *value = argc ? args[0] : scr_dyn_undefined(); + if (value->kind == SCR_DYN_UNDEF || value->kind == SCR_DYN_NULL) return scr_dyn_new_obj(); + if (value->kind != SCR_DYN_NUM && value->kind != SCR_DYN_STR && value->kind != SCR_DYN_BOOL && + value->kind != SCR_DYN_BIGINT && value->kind != SCR_DYN_SYMBOL) { + return scr_dyn_retain((ScrDyn *)value); + } + } + ScrJsonBuf message; + scr_jb_init(&message); + scr_jb_puts(&message, "calling the "); + scr_jb_puts(&message, name); + scr_jb_puts(&message, strcmp(name, "Object") == 0 ? " constructor on a primitive" : " constructor"); + scr_jb_puts(&message, " through a stored value is not supported yet"); + ScrStr *text = scr_jb_finish(&message); + scr_throw_error_msg_code(SCR_ERR_ERROR, text->data, text->len, "SC2020"); + scr_str_release(text); + return NULL; +} + +static ScrDyn *scr_dyn_global_ctor_at(size_t index) { + if (!scr_global_ctors_registered) { + scr_global_ctors_registered = true; + scr_atexit(scr_global_ctors_cleanup); + } + if (!scr_global_ctors[index]) { + const char *name = scr_global_ctor_names[index]; + scr_global_ctors[index] = scr_dyn_new_func(scr_closure_new((void *)name, 0), scr_global_ctor_call, + index == 0 ? 1 : 0, "native:constructor", name); + } + return scr_dyn_retain(scr_global_ctors[index]); +} + +static ScrDyn *scr_dyn_global_ctor_named(const char *name, size_t len) { + for (size_t i = 0; i < SCR_GLOBAL_CTORS; i++) { + if (strlen(scr_global_ctor_names[i]) == len && memcmp(scr_global_ctor_names[i], name, len) == 0) + return scr_dyn_global_ctor_at(i); + } + return NULL; +} + +static ScrDyn *scr_function_constructor(uint32_t kind); + +ScrDyn *scr_dyn_global_constructor(const ScrStr *name) { + if (name->len == 8 && memcmp(name->data, "Function", 8) == 0) return scr_function_constructor(0); + ScrDyn *ctor = scr_dyn_global_ctor_named(name->data, name->len); + if (!ctor) scr_trap("scriptc: invalid global constructor name\n"); + return ctor; +} + +/* The four Object.prototype methods as interned function values. They live + * in this always-linked unit so every dynamic read can answer them; the + * `Object.prototype.` reads (scr_dyn_builtin_method) return the same + * functions, so identity comparisons hold. Calls take the receiver from the + * ambient `this`. */ +static const char *const scr_object_proto_method_names[] = { + "valueOf", "toString", "hasOwnProperty", "propertyIsEnumerable", +}; +static SCR_TL ScrDyn *scr_object_proto_methods[4]; +static SCR_TL bool scr_object_proto_methods_registered; + +static void scr_object_proto_methods_cleanup(void) { + for (size_t i = 0; i < 4; i++) { + scr_dyn_release(scr_object_proto_methods[i]); + scr_object_proto_methods[i] = NULL; + } +} + +static ScrDyn *scr_object_proto_method_call(ScrClosure *closure, ScrDyn *const *args, size_t argc) { + const char *name = closure->fn; + ScrDyn *receiver = scr_dyn_this_get(); + ScrDyn *result = NULL; + ScrDyn *key = argc ? args[0] : scr_dyn_undefined(); + if (strcmp(name, "toString") == 0) { + ScrStr *tag = scr_dyn_object_tag(receiver); + if (tag) { + result = scr_dyn_new_str(tag); + scr_str_release(tag); + } + } else if (strcmp(name, "hasOwnProperty") == 0) { + bool own = scr_dyn_has_own_computed(receiver, key); + if (!scr_exc_pending()) result = scr_dyn_new_bool(own); + } else if (strcmp(name, "propertyIsEnumerable") == 0) { + bool enumerable = scr_dyn_property_is_enumerable_computed(receiver, key); + if (!scr_exc_pending()) result = scr_dyn_new_bool(enumerable); + } else if (receiver->kind == SCR_DYN_NULL || receiver->kind == SCR_DYN_UNDEF) { + static const char message[] = "Cannot convert undefined or null to object"; + scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1); + } else if (receiver->kind == SCR_DYN_OBJ || receiver->kind == SCR_DYN_ARR || + receiver->kind == SCR_DYN_FUNC || receiver->kind == SCR_DYN_HANDLE || + receiver->kind == SCR_DYN_TYPED_REF || receiver->kind == SCR_DYN_PROXY || + receiver->kind == SCR_DYN_PROMISE || receiver->kind == SCR_DYN_BYTES) { + result = scr_dyn_retain(receiver); + } else { + static const char message[] = "Object.prototype.valueOf on a primitive receiver is not supported yet"; + scr_throw_error_msg_code(SCR_ERR_ERROR, message, sizeof message - 1, "SC2020"); + } + scr_dyn_release(receiver); + return result; +} + +ScrDyn *scr_dyn_object_proto_method(const char *name) { + for (size_t i = 0; i < 4; i++) { + if (strcmp(scr_object_proto_method_names[i], name) != 0) continue; + if (!scr_object_proto_methods_registered) { + scr_object_proto_methods_registered = true; + scr_atexit(scr_object_proto_methods_cleanup); + } + if (!scr_object_proto_methods[i]) { + const char *method = scr_object_proto_method_names[i]; + scr_object_proto_methods[i] = scr_dyn_new_func(scr_closure_new((void *)method, 0), + scr_object_proto_method_call, i >= 2 ? 1 : 0, "native:prototype", method); + } + return scr_dyn_retain(scr_object_proto_methods[i]); + } + return NULL; +} + +/* An Object.prototype member a value inherits when nothing nearer defines + * it: the Object constructor, or the interned Object.prototype method that + * `Object.prototype.` also reads. `methods` selects which inherited + * methods apply (types that define their own toString/valueOf exclude them); + * NULL when the name is not one of these. */ +static ScrDyn *scr_dyn_object_proto_inherited(const char *key, size_t key_len, int methods) { + if (key_len == 11 && memcmp(key, "constructor", 11) == 0) return scr_dyn_global_ctor_at(0); + const char *name = NULL; + if ((methods & SCR_INHERIT_VALUE_OF) && key_len == 7 && memcmp(key, "valueOf", 7) == 0) name = "valueOf"; + else if ((methods & SCR_INHERIT_TO_STRING) && key_len == 8 && memcmp(key, "toString", 8) == 0) name = "toString"; + else if ((methods & SCR_INHERIT_OWN) && key_len == 14 && memcmp(key, "hasOwnProperty", 14) == 0) name = "hasOwnProperty"; + else if ((methods & SCR_INHERIT_OWN) && key_len == 20 && memcmp(key, "propertyIsEnumerable", 20) == 0) name = "propertyIsEnumerable"; + if (!name) return NULL; + return scr_dyn_object_proto_method(name); +} + static void scr_array_buffer_ctor_cleanup(void) { scr_dyn_release(scr_array_buffer_ctor); scr_array_buffer_ctor = NULL; @@ -3405,6 +3578,28 @@ bool scr_bytes_instanceof(const ScrDyn *value, const ScrDyn *callee) { if (scr_bytes_ctors[i] && callee->v.fn.clo == scr_bytes_ctors[i]->v.fn.clo) return scr_dyn_typed_array_is(value, (int)i); } + for (size_t i = 0; i < SCR_GLOBAL_CTORS; i++) { + if (!scr_global_ctors[i] || callee->v.fn.clo != scr_global_ctors[i]->v.fn.clo) continue; + if (value->kind == SCR_DYN_TYPED_REF) { + ScrDyn *view = scr_dyn_typed_ref_materialize(value); + bool result = view && !scr_exc_pending() && scr_bytes_instanceof(view, callee); + scr_dyn_release(view); + return result; + } + switch (i) { + case 0: /* Object: any object whose chain reaches Object.prototype */ + if (value->kind == SCR_DYN_OBJ) return scr_dyn_has_object_prototype(value); + return value->kind == SCR_DYN_ARR || value->kind == SCR_DYN_FUNC || value->kind == SCR_DYN_HANDLE || + value->kind == SCR_DYN_PROMISE || value->kind == SCR_DYN_BYTES; + case 1: return value->kind == SCR_DYN_HANDLE && value->v.handle.tag == SCR_DYNH_DATE; + case 2: return value->kind == SCR_DYN_HANDLE && value->v.handle.tag == SCR_DYNH_REGEXP; + case 3: return value->kind == SCR_DYN_HANDLE && value->v.handle.tag == SCR_DYNH_MAP; + case 4: return value->kind == SCR_DYN_HANDLE && value->v.handle.tag == SCR_DYNH_SET; + case 5: return value->kind == SCR_DYN_PROMISE; + case 6: return value->kind == SCR_DYN_HANDLE && value->v.handle.tag == SCR_DYNH_WEAK_MAP; + case 7: return value->kind == SCR_DYN_HANDLE && value->v.handle.tag == SCR_DYNH_WEAK_SET; + } + } static const char message[] = "instanceof against this native constructor is not supported yet"; scr_throw_error_msg_code(SCR_ERR_ERROR, message, sizeof message - 1, "SC1090"); return false; @@ -3414,6 +3609,8 @@ ScrDyn *scr_bytes_construct(const ScrDyn *callee, const ScrDyn *args, const ScrS return scr_array_ctor_call(callee->v.fn.clo, args->v.arr.items, args->v.arr.len); if (callee->kind == SCR_DYN_FUNC && scr_array_buffer_ctor && callee->v.fn.clo == scr_array_buffer_ctor->v.fn.clo) return scr_array_buffer_new(args->v.arr.len ? args->v.arr.items[0] : scr_dyn_undefined()); + if (callee->kind == SCR_DYN_FUNC && scr_global_ctors[0] && callee->v.fn.clo == scr_global_ctors[0]->v.fn.clo) + return scr_global_ctor_call(callee->v.fn.clo, args->v.arr.items, args->v.arr.len); for (size_t i = 0; i < SCR_BYTES_ELEM_COUNT; i++) { if (callee->kind != SCR_DYN_FUNC || !scr_bytes_ctors[i] || callee->v.fn.clo != scr_bytes_ctors[i]->v.fn.clo) continue; const ScrDyn *input = args->v.arr.len > 0 ? args->v.arr.items[0] : scr_dyn_undefined(); @@ -4085,9 +4282,32 @@ SCR_TL ScrDyn *(*scr_dyn_promise_member_fn)(const char *key, size_t len) = NULL; ScrDyn *scr_dyn_promise_key_get(const ScrDyn *d, const ScrStr *k) { (void)d; ScrDyn *method = scr_dyn_promise_member_fn ? scr_dyn_promise_member_fn(k->data, k->len) : NULL; + if (!method && !scr_exc_pending()) { + method = k->len == 11 && memcmp(k->data, "constructor", 11) == 0 + ? scr_dyn_global_ctor_named("Promise", 7) + : scr_dyn_object_proto_inherited(k->data, k->len, SCR_INHERIT_ALL); + } return method ? method : scr_dyn_retain(scr_dyn_undefined()); } +/* The builtin classes whose dynamic values answer `.constructor` and the + * Object.prototype members their own prototypes do not shadow. */ +static ScrDyn *scr_dyn_handle_inherited(ScrDynHandleTag tag, const ScrStr *k) { + const char *ctor; + int methods; + switch (tag) { + case SCR_DYNH_DATE: ctor = "Date"; methods = SCR_INHERIT_OWN; break; + case SCR_DYNH_REGEXP: ctor = "RegExp"; methods = SCR_INHERIT_VALUE_OF | SCR_INHERIT_OWN; break; + case SCR_DYNH_MAP: ctor = "Map"; methods = SCR_INHERIT_ALL; break; + case SCR_DYNH_SET: ctor = "Set"; methods = SCR_INHERIT_ALL; break; + case SCR_DYNH_WEAK_MAP: ctor = "WeakMap"; methods = SCR_INHERIT_ALL; break; + case SCR_DYNH_WEAK_SET: ctor = "WeakSet"; methods = SCR_INHERIT_ALL; break; + default: return NULL; + } + if (k->len == 11 && memcmp(k->data, "constructor", 11) == 0) return scr_dyn_global_ctor_named(ctor, strlen(ctor)); + return scr_dyn_object_proto_inherited(k->data, k->len, methods); +} + ScrDyn *scr_dyn_handle_key_get(const ScrDyn *d, const ScrStr *k) { const ScrDynHandleOps *ops = scr_dyn_handle_ops(d->v.handle.tag); ScrDyn *r = ops->get(d->v.handle.ptr, k->data, k->len); @@ -4095,6 +4315,7 @@ ScrDyn *scr_dyn_handle_key_get(const ScrDyn *d, const ScrStr *k) { scr_dyn_release(r); return NULL; } + if (!r) r = scr_dyn_handle_inherited(d->v.handle.tag, k); /* Unmodeled names answer undefined — the checked-dynamic tree's own-property stance * (real-but-unmodeled members throw "not supported yet" inside ops->get instead; * SEMANTICS.md documents the remainder). */ @@ -9710,7 +9931,11 @@ ScrDyn *scr_dyn_fn_get(const ScrDyn *d, const char *key, size_t key_len) { if (d->v.fn.class_obj && !d->v.fn.class_obj->static_data) { static const char message[] = "scriptc: class properties through unknown other than name and length are not supported"; scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1); + return NULL; } + /* Function.prototype defines its own toString; valueOf, hasOwnProperty + * and propertyIsEnumerable come from Object.prototype. */ + if (key_len != 11) return scr_dyn_object_proto_inherited(key, key_len, SCR_INHERIT_VALUE_OF | SCR_INHERIT_OWN); return NULL; } @@ -10049,6 +10274,18 @@ static ScrDyn *scr_dyn_obj_read_receiver(const ScrDyn *d, const char *key, size_ return result; } } + /* A plain object read as itself (not a function's property table or a + * class's prototype bag read for another receiver) inherits the + * Object.prototype members its chain does not define. Errors keep their + * own story (Error.prototype.toString and the constructor tokens). */ + if (receiver == d && d->kind == SCR_DYN_OBJ && scr_dyn_has_object_prototype(d)) { + bool error = scr_errdyn_find(d, false) != SIZE_MAX; + if (!(error && key_len == 11)) { + ScrDyn *inherited = scr_dyn_object_proto_inherited( + key, key_len, error ? SCR_INHERIT_VALUE_OF | SCR_INHERIT_OWN : SCR_INHERIT_ALL); + if (inherited) return inherited; + } + } return scr_dyn_retain(scr_dyn_undefined()); } diff --git a/tests/corpus/6233-object-identity-dynamic.cjs b/tests/corpus/6233-object-identity-dynamic.cjs new file mode 100644 index 000000000..b40a8493f --- /dev/null +++ b/tests/corpus/6233-object-identity-dynamic.cjs @@ -0,0 +1,86 @@ +// Plain objects, arrays, functions and the builtin collections inherit +// Object.prototype's constructor and methods: `o.constructor === Object`, +// `o.valueOf === Object.prototype.valueOf` and the isPlainObject and +// deep-equal idioms built on them (fast-deep-equal, is-plain-obj, lodash) +// answer exactly what Node answers. +function equal(a, b) { + if (a === b) return true; + if (a && b && typeof a == "object" && typeof b == "object") { + if (a.constructor !== b.constructor) return false; + var length, i, keys; + if (Array.isArray(a)) { + length = a.length; + if (length != b.length) return false; + for (i = length; i-- !== 0; ) if (!equal(a[i], b[i])) return false; + return true; + } + if (a.constructor === RegExp) return a.source === b.source && a.flags === b.flags; + if (a.valueOf !== Object.prototype.valueOf) return a.valueOf() === b.valueOf(); + if (a.toString !== Object.prototype.toString) return a.toString() === b.toString(); + keys = Object.keys(a); + length = keys.length; + if (length !== Object.keys(b).length) return false; + for (i = length; i-- !== 0; ) if (!Object.prototype.hasOwnProperty.call(b, keys[i])) return false; + for (i = length; i-- !== 0; ) { + var key = keys[i]; + if (!equal(a[key], b[key])) return false; + } + return true; + } + return a !== a && b !== b; +} +class Point { + constructor(x) { + this.x = x; + } +} +console.log(equal({ a: [1, { b: 2 }] }, { a: [1, { b: 2 }] }), equal({ a: 1 }, { a: "1" }), equal(new Date(0), new Date(0)), equal(new Date(0), new Date(1))); +console.log(equal(/a/g, /a/g), equal(/a/g, /a/i), equal([1, 2], [1, 2, 3]), equal(new Map(), new Set()), equal(new Point(1), new Point(1)), equal(new Point(1), { x: 1 })); +console.log(equal(JSON.parse('{"x":{"y":[1,2]}}'), { x: { y: [1, 2] } }), equal(NaN, NaN), equal(null, {})); + +const values = { + plain: { a: 1 }, + parsed: JSON.parse('{"x":{"y":1}}').x, + array: [1, 2], + date: new Date(0), + regexp: /a/g, + map: new Map([[1, 2]]), + set: new Set([1]), + fn: function () {}, + arrow: () => 1, + error: new TypeError("x"), + promise: Promise.resolve(1), + nullProto: Object.create(null), + created: Object.create({ inherited: 1 }), + instance: new Point(2), +}; +const ctors = { Object: Object, Array: Array, Date: Date, RegExp: RegExp, Map: Map, Set: Set, Function: Function, TypeError: TypeError, Promise: Promise, Point: Point }; +for (const name of Object.keys(values)) { + const v = values[name]; + const c = v.constructor; + const hits = Object.keys(ctors).filter((k) => c === ctors[k]); + console.log(name, hits.join("|") || "-", v.valueOf === Object.prototype.valueOf, + name === "regexp" ? "-" : v.toString === Object.prototype.toString, + v.hasOwnProperty === Object.prototype.hasOwnProperty, + v.propertyIsEnumerable === Object.prototype.propertyIsEnumerable); +} +const o = { a: 1 }; +console.log(typeof Object, typeof Date, typeof Map, Object.name, Date.name, o.constructor.name, typeof o.constructor); +console.log(o.toString(), o.valueOf() === o, new o.constructor().constructor === Object, Object(o) === o, typeof Object(), Object(null).constructor === Object); +console.log(o instanceof Object, [] instanceof Object, Object.create(null) instanceof Object, new Date(0) instanceof Date, new Map() instanceof Map, /x/ instanceof RegExp, new Set() instanceof Map); +const { Object: O, Map: M } = globalThis; +console.log(O === Object, M === Map, globalThis.Object === Object, O.keys({ z: 1 }).join()); +const shadow = { constructor: "mine", valueOf() { return 7; }, toString: 3 }; +console.log(shadow.constructor, shadow.valueOf === Object.prototype.valueOf, shadow.toString, shadow.valueOf()); + +function isPlainObj(value) { + if (typeof value !== "object" || value === null) return false; + const prototype = Object.getPrototypeOf(value); + return (prototype === null || prototype === Object.prototype || Object.getPrototypeOf(prototype) === null) && !(Symbol.toStringTag in value) && !(Symbol.iterator in value); +} +function isPlainCtor(value) { + return !!value && typeof value === "object" && (value.constructor === Object || value.constructor === undefined); +} +for (const name of ["plain", "parsed", "array", "nullProto", "created", "instance"]) { + console.log(name, isPlainObj(values[name]), isPlainCtor(values[name])); +} diff --git a/tests/corpus/6235-object-identity-typed.ts b/tests/corpus/6235-object-identity-typed.ts new file mode 100644 index 000000000..70386d17f --- /dev/null +++ b/tests/corpus/6235-object-identity-typed.ts @@ -0,0 +1,24 @@ +// TypeScript records and class instances read their inherited +// Object.prototype members as values, so the identity tests agree with +// Node: `o.constructor === Object`, `o.valueOf === Object.prototype.valueOf`. +interface Shape { + kind: string; + size: number; +} +class Box { + width = 2; + describe(): string { + return `box ${this.width}`; + } +} +const record: Shape = { kind: "square", size: 3 }; +const box = new Box(); +const empty = {}; +console.log(record.constructor === Object, record.valueOf === Object.prototype.valueOf, record.toString === Object.prototype.toString); +console.log(record.hasOwnProperty === Object.prototype.hasOwnProperty, empty.valueOf === Object.prototype.valueOf, typeof record.constructor); +console.log(box.valueOf === Object.prototype.valueOf, box.toString === Object.prototype.toString, box.describe()); +const parsed: unknown = JSON.parse('{"a":1}'); +if (typeof parsed === "object" && parsed !== null) { + console.log(parsed.constructor === Object, Object.getPrototypeOf(parsed) === Object.prototype); +} +console.log(Object.getPrototypeOf(record) === Object.prototype, record.toString(), record.constructor === empty.constructor); From 9fab4348d3463263bdc591b2efb6cd1e8967fda3 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 18 Oct 2026 12:40:25 -0700 Subject: [PATCH 005/122] Read Object.prototype's own members as values `const { hasOwnProperty } = Object.prototype`, `Object.prototype.constructor` and `hasOwnProperty.call(Object.prototype, "constructor")` hit the runtime "Object.prototype reflection and mutation" fence (acorn, ini, safe-stable-stringify and qs read the prototype this way). Reads of the prototype's constructor and shared methods now answer the same interned values the inherited reads answer, own-property checks see its twelve own members (none enumerable), and Object.prototype.toString.call(error) answers "[object Error]" instead of "[object Object]". Mutation and the remaining reflection (isPrototypeOf, __proto__, accessors) stay fenced. Co-Authored-By: Claude Opus 5.5 --- .../limitations/builtin-apis/prototypes.mdx | 2 +- .../6234-object-prototype-values.cjs.json | 6 +++++ packages/runtime/src/scr_json.c | 12 ++++++++- tests/corpus/6233-object-identity-dynamic.cjs | 12 +++++++-- tests/corpus/6234-object-prototype-values.cjs | 26 +++++++++++++++++++ 5 files changed, 54 insertions(+), 4 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6234-object-prototype-values.cjs.json create mode 100644 tests/corpus/6234-object-prototype-values.cjs diff --git a/docs/content/limitations/builtin-apis/prototypes.mdx b/docs/content/limitations/builtin-apis/prototypes.mdx index 441884bfc..b349db3fb 100644 --- a/docs/content/limitations/builtin-apis/prototypes.mdx +++ b/docs/content/limitations/builtin-apis/prototypes.mdx @@ -1 +1 @@ -- **Prototypes:** Named properties and methods on program class prototypes support replacement, inheritance, and deletion. Bare prototype reflection, accessor replacement, and prototype storage on local, generic, mixin, or runtime-provided classes are unsupported. Plain objects, arrays, functions, class instances, and Date, RegExp, Map, Set, WeakMap, WeakSet, and Promise values inherit `constructor`, `valueOf`, `toString`, `hasOwnProperty`, and `propertyIsEnumerable` from `Object.prototype` as in Node, so `value.constructor === Object` and `value.valueOf === Object.prototype.valueOf` compare identities exactly. The constructors `Object`, `Function`, `Date`, `RegExp`, `Map`, `Set`, `WeakMap`, `WeakSet`, and `Promise` read as function values; through a stored value only `Object` can be called or constructed. +- **Prototypes:** Named properties and methods on program class prototypes support replacement, inheritance, and deletion. Bare prototype reflection, accessor replacement, and prototype storage on local, generic, mixin, or runtime-provided classes are unsupported. Plain objects, arrays, functions, class instances, and Date, RegExp, Map, Set, WeakMap, WeakSet, and Promise values inherit `constructor`, `valueOf`, `toString`, `hasOwnProperty`, and `propertyIsEnumerable` from `Object.prototype` as in Node, so `value.constructor === Object` and `value.valueOf === Object.prototype.valueOf` compare identities exactly. `Object.prototype`'s own members read as values; its mutation and other reflection, such as `isPrototypeOf` and `__proto__`, are unsupported. The constructors `Object`, `Function`, `Date`, `RegExp`, `Map`, `Set`, `WeakMap`, `WeakSet`, and `Promise` read as function values; through a stored value only `Object` can be called or constructed. diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6234-object-prototype-values.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6234-object-prototype-values.cjs.json new file mode 100644 index 000000000..bce305944 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6234-object-prototype-values.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6234-object-prototype-values.cjs" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_json.c b/packages/runtime/src/scr_json.c index 2d38f54e5..6d3b6bfc6 100644 --- a/packages/runtime/src/scr_json.c +++ b/packages/runtime/src/scr_json.c @@ -5713,6 +5713,8 @@ void scr_dyn_throw(ScrDyn *d) { scr_dyn_trace_v, scr_dyn_is_object(d)); } +static size_t scr_errdyn_find(const void *key, bool by_err); + ScrStr *scr_dyn_object_tag(const ScrDyn *d) { if (d->kind == SCR_DYN_PROXY) { scr_dyn_proxy_unsupported("Symbol.toStringTag lookup"); @@ -5736,7 +5738,7 @@ ScrStr *scr_dyn_object_tag(const ScrDyn *d) { case SCR_DYN_NUM: tag = "[object Number]"; break; case SCR_DYN_STR: tag = "[object String]"; break; case SCR_DYN_ARR: tag = "[object Array]"; break; - case SCR_DYN_OBJ: tag = "[object Object]"; break; + case SCR_DYN_OBJ: tag = scr_errdyn_find(d, false) != SIZE_MAX ? "[object Error]" : "[object Object]"; break; case SCR_DYN_HANDLE: if ((d->v.handle.tag >= SCR_DYNH_ABORT_SIGNAL && d->v.handle.tag <= SCR_DYNH_ABORT_CONTROLLER) || d->v.handle.tag == SCR_DYNH_EVENT_TARGET || @@ -10223,6 +10225,10 @@ bool scr_dyn_err_instanceof(const ScrDyn *d, double kind) { static ScrDyn *scr_dyn_obj_read_receiver(const ScrDyn *d, const char *key, size_t key_len, const ScrDyn *receiver) { for (const ScrDyn *current = d; current; current = scr_dyn_proto_next(current)) { if (current == scr_builtin_object_prototype) { + /* Object.prototype's own constructor and the methods its values + * share read as those values; other reflection stays fenced. */ + ScrDyn *member = scr_dyn_object_proto_inherited(key, key_len, SCR_INHERIT_ALL); + if (member) return member; scr_dyn_class_reflection_fence(current); return NULL; } @@ -11704,6 +11710,8 @@ bool scr_dyn_has_own(const ScrDyn *v, const ScrStr *key) { if (v->kind == SCR_DYN_OBJ) { + /* Object.prototype's own members (its value carries no entries). */ + if (v == scr_builtin_object_prototype) return scr_dyn_object_proto_name(key); return scr_dyn_obj_get(v, key->data, key->len) != NULL; } if (v->kind == SCR_DYN_ARR) { @@ -11736,6 +11744,8 @@ bool scr_dyn_has_own(const ScrDyn *v, const ScrStr *key) { } bool scr_dyn_property_is_enumerable(const ScrDyn *value, const ScrStr *key) { + /* Object.prototype's own members are all non-enumerable. */ + if (value == scr_builtin_object_prototype) return false; if (scr_dyn_class_reflection_fence(value)) return false; if (value->kind == SCR_DYN_TYPED_REF) { ScrDyn *view = scr_dyn_typed_ref_materialize(value); diff --git a/tests/corpus/6233-object-identity-dynamic.cjs b/tests/corpus/6233-object-identity-dynamic.cjs index b40a8493f..372e141b7 100644 --- a/tests/corpus/6233-object-identity-dynamic.cjs +++ b/tests/corpus/6233-object-identity-dynamic.cjs @@ -62,7 +62,8 @@ for (const name of Object.keys(values)) { console.log(name, hits.join("|") || "-", v.valueOf === Object.prototype.valueOf, name === "regexp" ? "-" : v.toString === Object.prototype.toString, v.hasOwnProperty === Object.prototype.hasOwnProperty, - v.propertyIsEnumerable === Object.prototype.propertyIsEnumerable); + v.propertyIsEnumerable === Object.prototype.propertyIsEnumerable, + Object.prototype.toString.call(v)); } const o = { a: 1 }; console.log(typeof Object, typeof Date, typeof Map, Object.name, Date.name, o.constructor.name, typeof o.constructor); @@ -78,9 +79,16 @@ function isPlainObj(value) { const prototype = Object.getPrototypeOf(value); return (prototype === null || prototype === Object.prototype || Object.getPrototypeOf(prototype) === null) && !(Symbol.toStringTag in value) && !(Symbol.iterator in value); } +function isPlainLodash(value) { + if (value == null || Object.prototype.toString.call(value) != "[object Object]") return false; + const proto = Object.getPrototypeOf(value); + if (proto === null) return true; + const Ctor = Object.prototype.hasOwnProperty.call(proto, "constructor") && proto.constructor; + return typeof Ctor == "function" && Ctor instanceof Ctor && Ctor === Object; +} function isPlainCtor(value) { return !!value && typeof value === "object" && (value.constructor === Object || value.constructor === undefined); } for (const name of ["plain", "parsed", "array", "nullProto", "created", "instance"]) { - console.log(name, isPlainObj(values[name]), isPlainCtor(values[name])); + console.log(name, isPlainObj(values[name]), isPlainLodash(values[name]), isPlainCtor(values[name])); } diff --git a/tests/corpus/6234-object-prototype-values.cjs b/tests/corpus/6234-object-prototype-values.cjs new file mode 100644 index 000000000..8e9cde0f3 --- /dev/null +++ b/tests/corpus/6234-object-prototype-values.cjs @@ -0,0 +1,26 @@ +// Object.prototype as a value: destructured and aliased methods, the +// prototype's own constructor and members, `{}.valueOf`, and the +// `Object.prototype.toString.call(x)` tag idiom (acorn, ini, qs, +// safe-stable-stringify, rfdc). +const { hasOwnProperty, propertyIsEnumerable } = Object.prototype; +const toStr = Object.prototype.toString; +const has = Object.prototype.hasOwnProperty; +const o = { a: 1 }; +console.log(hasOwnProperty.call(o, "a"), hasOwnProperty.call(o, "b"), has.call(o, "a"), propertyIsEnumerable.call(o, "a")); +console.log(toStr.call([]), toStr.call(null), toStr.call(undefined), toStr.call(new Date(0)), toStr.call(/x/), toStr.call(1), toStr.call("s"), toStr.call(true), toStr.call(o), toStr.call(new Error("e")), toStr.call(new RangeError("r")), toStr.call(() => 1), toStr.call(new Map())); +console.log({}.valueOf === Object.prototype.valueOf, ({}).toString === toStr, typeof Object.prototype.toString, Object.prototype.constructor === Object); +console.log(hasOwnProperty.call(Object.prototype, "constructor"), has.call(Object.prototype, "toString"), has.call(Object.prototype, "missing"), propertyIsEnumerable.call(Object.prototype, "toString")); +console.log(hasOwnProperty === Object.prototype.hasOwnProperty, o.hasOwnProperty === hasOwnProperty, [].hasOwnProperty === has); +function kind(value) { + switch (toStr.call(value)) { + case "[object Date]": return "date:" + value.getTime(); + case "[object Array]": return "array:" + value.length; + case "[object RegExp]": return "regexp:" + value.source; + case "[object Error]": return "error:" + value.message; + case "[object Object]": return "object:" + Object.keys(value).join(","); + default: return "other"; + } +} +console.log([new Date(5), [1, 2], /ab/, new Error("boom"), { p: 1, q: 2 }, 3].map(kind).join(" ")); +const config = JSON.parse('{"name":"app","db":{"host":"h"}}'); +for (const key in config) if (has.call(config, key)) console.log(key, typeof config[key], toStr.call(config[key])); From acfdc7fe13e217e48a9993fe700ef9bc522c7538 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 18 Oct 2026 12:49:31 -0700 Subject: [PATCH 006/122] Store builtin timer functions as real function values in JavaScript `var nextTick = typeof setImmediate === "function" ? setImmediate : setTimeout` (bcryptjs) and `cond ? setImmediate : process.nextTick` stored the builtin as an identity token string, so typeof answered "string" and calling it threw "nextTick is not a function". The JavaScript identifier, `globalThis.X` and destructuring spellings of setTimeout, setInterval, setImmediate, clearTimeout, clearInterval, clearImmediate and queueMicrotask, and a stored process.nextTick, now read one interned runtime function per name. Conditional, logical and assigned values carry it like any function: typeof is "function", identity holds across spellings, and calls validate the callback (ERR_INVALID_ARG_TYPE), coerce the delay, pass the extra arguments and return the same numeric handles the direct calls return, which the stored clear functions accept. Co-Authored-By: Claude Opus 5.5 --- .../limitations/builtin-function-values.mdx | 2 + .../compiler/src/backend/llvm/lib-shared.ts | 2 + .../src/frontend/lowering/builtins/process.ts | 6 ++ .../src/frontend/lowering/lower-exprs.ts | 43 +++++++-- .../src/frontend/lowering/lower-stmts.ts | 2 +- .../compiler/src/ir/builtin-signatures.ts | 6 ++ .../6236-builtin-function-values.cjs.json | 6 ++ packages/runtime/src/scr_async.c | 96 +++++++++++++++++++ packages/runtime/src/scr_dyn.h | 6 ++ tests/corpus/6236-builtin-function-values.cjs | 52 ++++++++++ 10 files changed, 214 insertions(+), 7 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6236-builtin-function-values.cjs.json create mode 100644 tests/corpus/6236-builtin-function-values.cjs diff --git a/docs/content/limitations/builtin-function-values.mdx b/docs/content/limitations/builtin-function-values.mdx index 4fd093e47..e87f86e13 100644 --- a/docs/content/limitations/builtin-function-values.mdx +++ b/docs/content/limitations/builtin-function-values.mdx @@ -5,6 +5,8 @@ Some builtins can be stored and passed as functions: - `util.isDeepStrictEqual`, `styleText`, `stripVTControlCharacters`, `toUSVString`, `inherits`, `deprecate`, `callbackify`, `debuglog`, and `debug`. - `fs.existsSync`, `unlinkSync`, `chmodSync`, `chownSync`, `renameSync`, and `closeSync`, and `fs/promises.unlink`, `chmod`, and `rename`. +In JavaScript, `setTimeout`, `setInterval`, `setImmediate`, `clearTimeout`, `clearInterval`, `clearImmediate`, `queueMicrotask`, and `process.nextTick` are function values wherever they are stored, including conditional and logical expressions and assignments: `typeof` answers `"function"`, identity holds across spellings, and calls schedule as the direct calls do, returning the same numeric timer handles. Calls through these values bypass `node:test` mock timers. Other builtin globals stored in JavaScript remain opaque identity values. + Other immutable aliases of table-backed Node builtins support direct calls, but cannot be passed elsewhere. `util.promisify` supports compile-time conversions of `child_process.execFile`, `child_process.exec`, `setTimeout` and `setImmediate` (to the `timers/promises` functions, as in Node), `stream.finished` and `stream.pipeline` (to the `stream/promises` functions, as in Node), the `zlib` callback one-shots `deflate`, `inflate`, `deflateRaw`, `inflateRaw`, `gzip`, `gunzip` and `unzip` (with or without options), the `crypto` callback functions `randomBytes`, `pbkdf2`, `scrypt`, `hkdf`, `randomFill`, `randomInt` and `generateKey`, `dns.lookup` (settling with `{ address, family }`, or the address array with `all: true`) and `dns.lookupService` (settling with `{ hostname, service }`), and the `fs` callback functions that have an `fs/promises` counterpart (`access`, `appendFile`, `chmod`, `chown`, `copyFile`, `lchown`, `link`, `lstat`, `lutimes`, `mkdir`, `mkdtemp`, `readFile`, `readdir`, `readlink`, `realpath`, `rename`, `rm`, `rmdir`, `stat`, `statfs`, `symlink`, `truncate`, `unlink`, `utimes`, and `writeFile`); calls through the converted function lower as the `fs/promises` call. `util.styleText` supports standard formats and aliases, `#RGB` and `#RRGGBB` hex colors, arrays of formats, nested ANSI resets, and explicit color enabling with `{ validateStream: false }`. Native calls accept standard process streams; custom native streams and mutations to `util.inspect.colors` remain unsupported. diff --git a/packages/compiler/src/backend/llvm/lib-shared.ts b/packages/compiler/src/backend/llvm/lib-shared.ts index 3f36e0d7c..45662d3cc 100644 --- a/packages/compiler/src/backend/llvm/lib-shared.ts +++ b/packages/compiler/src/backend/llvm/lib-shared.ts @@ -900,6 +900,7 @@ export const LIB_FN_SYMS: Record = { "dyn.nativeUrlIs": "scr_dyn_native_url_is", "dyn.nativeDateIs": "scr_dyn_native_date_is", "dyn.globalConstructor": "scr_dyn_global_constructor", + "dyn.builtinFunction": "scr_dyn_builtin_function", "date.nativeNew": "scr_dyn_native_date_new", "date.nativeNewFields": "scr_dyn_native_date_new_fields", "date.checkedValue": "scr_dyn_native_date_value", @@ -1612,4 +1613,5 @@ export const USES_TIMERS_LIB_FNS = new Set([ "timers.queueMicrotaskDyn", "timers.setImmediateFnValue", "timers.immediatePromise", + "dyn.builtinFunction", ]); diff --git a/packages/compiler/src/frontend/lowering/builtins/process.ts b/packages/compiler/src/frontend/lowering/builtins/process.ts index 94c2cdba7..c9935a744 100644 --- a/packages/compiler/src/frontend/lowering/builtins/process.ts +++ b/packages/compiler/src/frontend/lowering/builtins/process.ts @@ -26,6 +26,7 @@ import { funcOf, } from "../../../ir/ir.js"; import { lowerBuiltinLoaderValue } from "../lower-builtin-values.js"; +import { builtinFunctionValue } from "../lower-exprs.js"; import { lowerProcessIpcSend, ipcMessageListener, ipcDisconnectListener } from "./ipc.js"; import { lowerDiagnosticsSubscriber, lowerTracingArguments } from "./async-context.js"; @@ -385,6 +386,11 @@ export function lowerProcessProperty( if (member === "hrtime") { return { kind: "libCall", fn: "process.hrtimeValue", args: [], type: DYN, loc }; } + // A stored process.nextTick in JavaScript (`cond ? setImmediate : + // process.nextTick`): the interned builtin function value. + if (member === "nextTick" && isJsSourceFile(expr.getSourceFile())) { + return builtinFunctionValue("process.nextTick", loc); + } if (member === "getBuiltinModule") { // Keep the native loader's argument validation when the function is // stored. The declaration's string signature must not insert a checked diff --git a/packages/compiler/src/frontend/lowering/lower-exprs.ts b/packages/compiler/src/frontend/lowering/lower-exprs.ts index d3f1f4676..02565e547 100644 --- a/packages/compiler/src/frontend/lowering/lower-exprs.ts +++ b/packages/compiler/src/frontend/lowering/lower-exprs.ts @@ -1453,8 +1453,12 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr { const canonical = stdlibGlobalNameOf(lowerer, expr) ?? expr.text; // Builtin constructors have real function values in both source // languages (TypeScript reads them as dynamic values). - const constructorValue = builtinGlobalValue(canonical, loc); - if (constructorValue !== null) return constructorValue; + const builtinValue = builtinGlobalValue( + canonical, + loc, + isJsSourceFile(expr.getSourceFile()), + ); + if (builtinValue !== null) return builtinValue; if (isJsSourceFile(expr.getSourceFile())) { return { kind: "strLit", value: `[builtin ${canonical}]`, type: STRING, loc }; } @@ -2354,7 +2358,7 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr { loc, }; return ( - builtinGlobalValue(canonical, loc) ?? { + builtinGlobalValue(canonical, loc, true) ?? { kind: "strLit", value: `[builtin ${canonical}]`, type: STRING, @@ -16330,9 +16334,36 @@ const GLOBAL_CONSTRUCTOR_VALUES: ReadonlySet = new Set([ "WeakSet", ]); -/** The value a JavaScript source reads for the stdlib global `canonical`, - * or null when it stays an identity token. */ -export function builtinGlobalValue(canonical: string, loc: SrcLoc): IrExpr | null { +/** The builtin global functions JavaScript sources read as interned + * function values (the `cond ? setImmediate : process.nextTick` feature + * probe of bcryptjs and async libraries): typeof answers "function", + * identity holds across spellings, and calls run the timer APIs. */ +const GLOBAL_FUNCTION_VALUES: ReadonlySet = new Set([ + "setTimeout", + "setInterval", + "setImmediate", + "clearTimeout", + "clearInterval", + "clearImmediate", + "queueMicrotask", +]); + +/** A builtin function value: one interned runtime function per name. */ +export function builtinFunctionValue(name: string, loc: SrcLoc): IrExpr { + return { + kind: "libCall", + fn: "dyn.builtinFunction", + args: [{ kind: "strLit", value: name, type: STRING, loc }], + type: DYN, + loc, + }; +} + +/** The value a source reads for the stdlib global `canonical` (builtin + * constructors in both languages, builtin functions in JavaScript), or + * null when it stays an identity token (JavaScript) or fenced. */ +export function builtinGlobalValue(canonical: string, loc: SrcLoc, js = false): IrExpr | null { + if (js && GLOBAL_FUNCTION_VALUES.has(canonical)) return builtinFunctionValue(canonical, loc); if (GLOBAL_CONSTRUCTOR_VALUES.has(canonical)) { return { kind: "libCall", diff --git a/packages/compiler/src/frontend/lowering/lower-stmts.ts b/packages/compiler/src/frontend/lowering/lower-stmts.ts index 78c3e76d3..315464289 100644 --- a/packages/compiler/src/frontend/lowering/lower-stmts.ts +++ b/packages/compiler/src/frontend/lowering/lower-stmts.ts @@ -3337,7 +3337,7 @@ function stdlibGlobalTokenDestructure( alias = canonical; // Builtin constructors read as their interned function values, the // same values the bare and `globalThis.X` spellings read. - value = builtinGlobalValue(canonical, locOf(el)); + value = builtinGlobalValue(canonical, locOf(el), true); } else if (globalName === "console" && !CONSOLE_CALL_MEMBERS.has(propName)) { token = `[builtin console.${propName}]`; } else if (TOKEN_OPAQUE_GLOBALS.has(globalName)) { diff --git a/packages/compiler/src/ir/builtin-signatures.ts b/packages/compiler/src/ir/builtin-signatures.ts index 74956d161..3709ae4e1 100644 --- a/packages/compiler/src/ir/builtin-signatures.ts +++ b/packages/compiler/src/ir/builtin-signatures.ts @@ -210,6 +210,12 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ * interned function per name, the same one dynamic values answer for * `.constructor`. The name is a compiler-chosen literal. */ "dyn.globalConstructor": { argTypes: [STRING], result: DYN }, + /** A builtin global function (setTimeout, setInterval, setImmediate, + * the clear functions, queueMicrotask, process.nextTick) read as a + * JavaScript value: one interned function per name whose calls run the + * timer APIs (it pulls in the event loop). The name is a compiler-chosen + * literal. */ + "dyn.builtinFunction": { argTypes: [STRING], result: DYN }, "date.nativeNew": { argTypes: [DYN], result: DYN }, /** `new Date(year, monthIndex, ...)` in JavaScript: the argument array's * first seven values pass through ToNumber in order (later ones are diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6236-builtin-function-values.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6236-builtin-function-values.cjs.json new file mode 100644 index 000000000..21b9f85c2 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6236-builtin-function-values.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6236-builtin-function-values.cjs" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_async.c b/packages/runtime/src/scr_async.c index 48f3a5329..58c7f653e 100644 --- a/packages/runtime/src/scr_async.c +++ b/packages/runtime/src/scr_async.c @@ -3717,6 +3717,102 @@ ScrDyn *scr_set_immediate_dyn_value(void) { 1, "(cb,...)", "setImmediate"); } +/* ── Builtin global functions as dynamic values ───────────────────────── + * A JavaScript source that stores a builtin global function (the + * `var nextTick = typeof setImmediate === "function" ? setImmediate : + * setTimeout` feature probe) reads one interned function per name, so + * typeof answers "function" and `===` is identity. Calls through it run + * the timer and tick APIs with Node's argument contract: a function + * callback (else ERR_INVALID_ARG_TYPE), the delay through ToNumber, the + * remaining arguments passed to the callback. Timer functions answer the + * same numeric handles the direct calls answer, which the clear functions + * accept. A name without a dynamic call path throws a named fence. */ +static SCR_TL ScrDyn *scr_builtin_fn_table; + +static void scr_builtin_fn_cleanup(void) { + scr_dyn_release(scr_builtin_fn_table); + scr_builtin_fn_table = NULL; +} + +static bool scr_builtin_fn_callback(ScrDyn *const *args, size_t argc) { + if (argc > 0 && args[0]->kind == SCR_DYN_FUNC) return true; + static const char msg[] = "The \"callback\" argument must be of type function."; + scr_throw_error_msg_code(SCR_ERR_TYPE, msg, sizeof msg - 1, "ERR_INVALID_ARG_TYPE"); + return false; +} + +/* The closure that calls args[0] with args[first..] when the loop fires it. */ +static ScrClosure *scr_builtin_fn_deferred(ScrDyn *const *args, size_t argc, size_t first) { + ScrDyn *rest = scr_dyn_new_arr(); + for (size_t i = first; i < argc; i++) scr_dyn_arr_push(rest, scr_dyn_retain(args[i])); + ScrClosure *c = scr_closure_new((void *)scr_imm_dyn_fire, 2); + c->caps[0] = scr_box_new_obj(scr_dyn_retain_v, scr_dyn_release_v, NULL); + scr_box_set_ref(c->caps[0], scr_dyn_retain(args[0])); + c->caps[1] = scr_box_new_obj(scr_dyn_retain_v, scr_dyn_release_v, NULL); + scr_box_set_ref(c->caps[1], rest); /* moves */ + return c; +} + +static ScrDyn *scr_builtin_fn_call(ScrClosure *clo, ScrDyn *const *args, size_t argc) { + ScrStr *name = scr_box_get_ref(clo->caps[0]); + ScrDyn *result = NULL; + bool timeout = strcmp(name->data, "setTimeout") == 0; + if (timeout || strcmp(name->data, "setInterval") == 0) { + double ms = 0; + if (scr_builtin_fn_callback(args, argc) && (argc < 2 || scr_dyn_number_coerce_js(args[1], &ms))) { + ScrClosure *c = scr_builtin_fn_deferred(args, argc, 2); + result = scr_dyn_new_num(timeout ? scr_set_timeout_handle(c, ms) : scr_set_interval(c, ms)); + } + } else if (strcmp(name->data, "setImmediate") == 0) { + if (scr_builtin_fn_callback(args, argc)) + result = scr_dyn_new_num(scr_set_immediate(scr_builtin_fn_deferred(args, argc, 1))); + } else if (strcmp(name->data, "process.nextTick") == 0) { + if (scr_builtin_fn_callback(args, argc)) { + scr_next_tick(scr_builtin_fn_deferred(args, argc, 1)); + result = scr_dyn_retain(scr_dyn_undefined()); + } + } else if (strcmp(name->data, "queueMicrotask") == 0) { + if (scr_builtin_fn_callback(args, argc)) { + scr_queue_microtask_dyn(args[0]); + result = scr_dyn_retain(scr_dyn_undefined()); + } + } else if (strcmp(name->data, "clearTimeout") == 0 || strcmp(name->data, "clearInterval") == 0 || + strcmp(name->data, "clearImmediate") == 0) { + /* Node ignores anything that is not a live handle. */ + if (argc > 0 && args[0]->kind == SCR_DYN_NUM) { + if (name->data[5] == 'I' && name->data[6] == 'm') scr_clear_immediate(args[0]->v.num); + else scr_clear_interval(args[0]->v.num); + } + result = scr_dyn_retain(scr_dyn_undefined()); + } else { + char message[160]; + int length = snprintf(message, sizeof message, "calling '%s' through a stored value is not supported yet", name->data); + scr_throw_error_msg_code(SCR_ERR_ERROR, message, (size_t)length, "SC2020"); + } + scr_str_release(name); + return result; +} + +ScrDyn *scr_dyn_builtin_function(const ScrStr *name) { + if (!scr_builtin_fn_table) { + scr_builtin_fn_table = scr_dyn_new_obj_null_proto(); + scr_atexit(scr_builtin_fn_cleanup); + } + ScrDyn *value = scr_dyn_obj_get(scr_builtin_fn_table, name->data, name->len); + if (!value) { + ScrClosure *closure = scr_closure_new(NULL, 1); + closure->caps[0] = scr_box_new(SCR_BOX_STR); + scr_box_set_ref(closure->caps[0], scr_str_retain((ScrStr *)name)); + const char *dot = memchr(name->data, '.', name->len); + size_t arity = strcmp(name->data, "queueMicrotask") == 0 || strncmp(name->data, "clear", 5) == 0 ? 1 : + strcmp(name->data, "setImmediate") == 0 || strcmp(name->data, "process.nextTick") == 0 ? 1 : 2; + value = scr_dyn_new_func(closure, scr_builtin_fn_call, arity, "native:builtin", + dot ? dot + 1 : name->data); + scr_dyn_obj_set(scr_builtin_fn_table, name->data, name->len, value); /* the table owns it */ + } + return scr_dyn_retain(value); +} + /* ── queueMicrotask ────────────────────────────────────────────────── * The READY queue's FIFO is the microtask queue (promise continuations * ride it as parked fibers), so a queueMicrotask callback enters the SAME diff --git a/packages/runtime/src/scr_dyn.h b/packages/runtime/src/scr_dyn.h index 738c5d487..d497b45ac 100644 --- a/packages/runtime/src/scr_dyn.h +++ b/packages/runtime/src/scr_dyn.h @@ -655,6 +655,12 @@ ScrDyn *scr_dyn_array_constructor(void); /* The interned builtin constructor `name` (Object, Date, RegExp, Map, Set, * Promise, WeakMap, WeakSet) as a dynamic function value: +1. */ ScrDyn *scr_dyn_global_constructor(const ScrStr *name); +/* A builtin global function (setTimeout, setImmediate, process.nextTick, ...) + * as an interned dynamic function value: +1. */ +ScrDyn *scr_dyn_builtin_function(const ScrStr *name); +/* Object.prototype's valueOf/toString/hasOwnProperty/propertyIsEnumerable as + * interned function values (+1), or NULL for other names. */ +ScrDyn *scr_dyn_object_proto_method(const char *name); bool scr_dyn_arr_has_index(const ScrDyn *arr, size_t index); void scr_dyn_arr_push_hole(ScrDyn *arr); ScrDyn *scr_dyn_new_obj(void); diff --git a/tests/corpus/6236-builtin-function-values.cjs b/tests/corpus/6236-builtin-function-values.cjs new file mode 100644 index 000000000..b8abee311 --- /dev/null +++ b/tests/corpus/6236-builtin-function-values.cjs @@ -0,0 +1,52 @@ +// Builtin timer functions stored as values (bcryptjs's `nextTick` feature +// probe, async libraries' scheduler selection): conditional, logical and +// assigned values are real functions — typeof answers "function", identity +// holds across spellings, and calls schedule with Node's argument contract +// and ordering. +var nextTick = + typeof setImmediate === "function" + ? setImmediate + : typeof queueMicrotask === "function" + ? queueMicrotask + : setTimeout; +const pick = process.argv.length > 100; +const later = pick ? setImmediate : process.nextTick; +const timeout = pick ? setImmediate : setTimeout; +const micro = pick || queueMicrotask; +let assigned = setTimeout; +assigned = pick ? assigned : setImmediate; +const { setInterval: every, clearInterval: stop } = globalThis; +console.log(typeof nextTick, typeof later, typeof timeout, typeof micro, typeof assigned, typeof every); +console.log(nextTick === setImmediate, later === process.nextTick, timeout === setTimeout, micro === queueMicrotask, assigned === globalThis.setImmediate, every === setInterval, stop === clearInterval); +console.log(nextTick.name, later.name, timeout.name, setTimeout.length, queueMicrotask.length); + +const order = []; +nextTick(function () { order.push("immediate:" + arguments.length); }); +later((a, b) => order.push("tick:" + a + b), 1, 2); +// Microtask and nextTick queues order differently in CommonJS and ESM +// entry evaluation, so the microtask reports through its own flag. +let microRan = false; +micro(() => { microRan = true; }); +timeout((x) => order.push("timeout:" + x), 60, "x"); +assigned(() => order.push("assigned")); +const cancelled = timeout(() => order.push("cancelled"), 1); +(pick ? clearImmediate : clearTimeout)(cancelled); +let count = 0; +const handle = every(() => { + count++; + order.push("interval:" + count); + if (count === 2) stop(handle); +}, 20); +timeout(() => console.log(order.join(" "), "micro:" + microRan), 120); + +for (const bad of [undefined, 1, "fn"]) { + try { + later(bad); + } catch (e) { + console.log("rejected", typeof bad, e.code, e instanceof TypeError); + } +} +const log = pick ? console.error : console.log; +log("log", typeof log); +const mx = pick ? Math.min : Math.max; +console.log(mx(1, 3, 2), typeof mx); From ae531a06ce0b572e07356f1540905917dc90d77c Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 18 Oct 2026 14:27:05 -0700 Subject: [PATCH 007/122] Keep the compatibility paths out of the always-linked runtime units The zero-cost gate showed every benchmark executable changing: the new string literals and floating-point constants grew the merged constant pools of scr_json.o and scr_lib.o, which every program linking those units carries, and edits to scr_dyn_fn_get and scr_dyn_has_own changed code the typed benchmarks reach. - The builtin constructor values, the inherited Object.prototype members and the stored timer functions move to scr_dyn_invoke.c, which links only into programs that use them: the frontend now selects that unit for dyn.globalConstructor, dyn.builtinFunction, dyn.objectTag and constant-key reads of constructor/valueOf/toString/hasOwnProperty/ propertyIsEnumerable. Those reads call scr_dyn_inherited_read after the ordinary read (and scr_dyn_object_prototype_member before it, since the ordinary read fences Object.prototype itself); computed-key reads keep their old answer. The unit's new strings live in their own section so programs linking it for other reasons keep their bytes. - scr_date_parse_get_time keeps its exact body; Date.parse and new Date(string) call the new scr_date_parse (bounded grammars, then V8's parser), and JavaScript `new Date(value)` calls scr_dyn_native_date_new_value, so fs.Stats dates are untouched. The parser and UTC(t) avoid new pooled constants. - Object.prototype.toString's "[object Error]" moves to scr_dyn_object_tag_of; scr_json.c is unchanged. - Calling or constructing through stored constructor values and own-property checks on Object.prototype itself are not supported in this change (they would touch shared code). Also pins the IMF-fixdate (cookie Expires) parse and Date#toUTCString round-trip in the corpus. Co-Authored-By: Claude Opus 5.5 --- .../limitations/builtin-apis/prototypes.mdx | 2 +- .../compiler/src/backend/llvm/expr-dynamic.ts | 53 ++- .../compiler/src/backend/llvm/lib-shared.ts | 9 +- .../src/frontend/lowering/lower-classes.ts | 3 +- .../src/frontend/lowering/lower-exprs.ts | 7 + .../src/frontend/lowering/surfaces.ts | 1 + packages/compiler/src/ir/builtin-effects.ts | 1 + .../compiler/src/ir/builtin-signatures.ts | 3 + packages/compiler/src/ir/int32-slots.ts | 1 + packages/compiler/src/ir/runtime-features.ts | 21 +- packages/runtime/src/scr_async.c | 96 ----- packages/runtime/src/scr_dyn.h | 12 +- packages/runtime/src/scr_dyn_invoke.c | 345 +++++++++++++++++- packages/runtime/src/scr_json.c | 249 +------------ packages/runtime/src/scr_lib.c | 113 ++++-- packages/runtime/src/scr_number.h | 9 +- tests/corpus/6230-date-parse-legacy.ts | 1 + tests/corpus/6232-date-fields-js.cjs | 20 + tests/corpus/6233-object-identity-dynamic.cjs | 3 +- tests/corpus/6234-object-prototype-values.cjs | 1 - 20 files changed, 543 insertions(+), 407 deletions(-) diff --git a/docs/content/limitations/builtin-apis/prototypes.mdx b/docs/content/limitations/builtin-apis/prototypes.mdx index b349db3fb..5f6687082 100644 --- a/docs/content/limitations/builtin-apis/prototypes.mdx +++ b/docs/content/limitations/builtin-apis/prototypes.mdx @@ -1 +1 @@ -- **Prototypes:** Named properties and methods on program class prototypes support replacement, inheritance, and deletion. Bare prototype reflection, accessor replacement, and prototype storage on local, generic, mixin, or runtime-provided classes are unsupported. Plain objects, arrays, functions, class instances, and Date, RegExp, Map, Set, WeakMap, WeakSet, and Promise values inherit `constructor`, `valueOf`, `toString`, `hasOwnProperty`, and `propertyIsEnumerable` from `Object.prototype` as in Node, so `value.constructor === Object` and `value.valueOf === Object.prototype.valueOf` compare identities exactly. `Object.prototype`'s own members read as values; its mutation and other reflection, such as `isPrototypeOf` and `__proto__`, are unsupported. The constructors `Object`, `Function`, `Date`, `RegExp`, `Map`, `Set`, `WeakMap`, `WeakSet`, and `Promise` read as function values; through a stored value only `Object` can be called or constructed. +- **Prototypes:** Named properties and methods on program class prototypes support replacement, inheritance, and deletion. Bare prototype reflection, accessor replacement, and prototype storage on local, generic, mixin, or runtime-provided classes are unsupported. Plain objects, arrays, functions, class instances, and Date, RegExp, Map, Set, WeakMap, WeakSet, and Promise values inherit `constructor`, `valueOf`, `toString`, `hasOwnProperty`, and `propertyIsEnumerable` from `Object.prototype` as in Node, so `value.constructor === Object` and `value.valueOf === Object.prototype.valueOf` compare identities exactly. Named reads such as `value.valueOf` answer these inherited members; computed-key reads such as `value[key]` do not. `Object.prototype`'s own members read as values; its mutation and other reflection, such as `isPrototypeOf` and `__proto__`, are unsupported. The constructors `Object`, `Function`, `Date`, `RegExp`, `Map`, `Set`, `WeakMap`, `WeakSet`, and `Promise` read as function values; through a stored value only `Object` can be called, and `instanceof` and `new` against them remain unsupported. diff --git a/packages/compiler/src/backend/llvm/expr-dynamic.ts b/packages/compiler/src/backend/llvm/expr-dynamic.ts index 76df9795d..3ce21a1ad 100644 --- a/packages/compiler/src/backend/llvm/expr-dynamic.ts +++ b/packages/compiler/src/backend/llvm/expr-dynamic.ts @@ -535,6 +535,27 @@ export function emitDynamicExpr( e.key.type.kind === "dyn" ? host.dyn.dynComputedKeyGetHelper() : host.dyn.dynKeyGetHelper(); const t = B.tmp(); const generic = `call ptr @${helper}(ptr ${d.name}, ptr ${k.name}, i1 ${e.optional ? "true" : "false"})`; + const inherited = e.key.kind === "strLit" && INHERITED_OBJECT_MEMBERS.has(e.key.value); + // Object.prototype itself answers its own members before the ordinary + // read (which fences reflection on the prototype). + let prototypeJoin: { member: string; from: string; join: string } | null = null; + if (inherited) { + host.declare(`declare ptr @scr_dyn_object_prototype_member(ptr, ptr)`); + const member = B.tmp(); + B.line( + `${member} = call ptr @scr_dyn_object_prototype_member(ptr ${d.name}, ptr ${k.name})`, + ); + const isMember = B.tmp(); + B.line(`${isMember} = icmp ne ptr ${member}, null`); + const from = B.newLabel("dkg.op"); + const read = B.newLabel("dkg.rd"); + const join = B.newLabel("dkg.opj"); + B.condBr(isMember, from, read); + B.startBlock(from); + B.br(join); + B.startBlock(read); + prototypeJoin = { member, from, join }; + } if (e.key.kind === "strLit") { // A constant key on an ordinary object reads through the site's // cache; every other receiver keeps the shared helper. @@ -560,7 +581,27 @@ export function emitDynamicExpr( } else { B.line(`${t} = ${generic}`); } - const out = host.own({ name: t, type: e.type }); + let result = t; + if (prototypeJoin) { + // An undefined read of an Object.prototype member name answers the + // member the receiver inherits (scr_dyn_invoke.c; the frontend + // links that unit for these reads). + host.declare(`declare ptr @scr_dyn_inherited_read(ptr, ptr, ptr)`); + const read = B.tmp(); + B.line( + `${read} = call ptr @scr_dyn_inherited_read(ptr ${d.name}, ptr ${k.name}, ptr ${t})`, + ); + const readEnd = B.newLabel("dkg.rde"); + B.br(readEnd); + B.startBlock(readEnd); + B.br(prototypeJoin.join); + B.startBlock(prototypeJoin.join); + result = B.tmp(); + B.line( + `${result} = phi ptr [ ${prototypeJoin.member}, %${prototypeJoin.from} ], [ ${read}, %${readEnd} ]`, + ); + } + const out = host.own({ name: result, type: e.type }); host.emitPendingCheck(); return out; } @@ -1204,3 +1245,13 @@ function emitUnionEqWrappedScalar( function wrappedStringLiteral(e: IrExpr): string | null { return e.kind === "unionWrap" && e.value.kind === "strLit" ? e.value.value : null; } + +/** Object.prototype member names whose constant-key dynamic reads fall back + * to the inherited member (runtime-features.ts links the unit). */ +export const INHERITED_OBJECT_MEMBERS: ReadonlySet = new Set([ + "constructor", + "valueOf", + "toString", + "hasOwnProperty", + "propertyIsEnumerable", +]); diff --git a/packages/compiler/src/backend/llvm/lib-shared.ts b/packages/compiler/src/backend/llvm/lib-shared.ts index 45662d3cc..377f1909a 100644 --- a/packages/compiler/src/backend/llvm/lib-shared.ts +++ b/packages/compiler/src/backend/llvm/lib-shared.ts @@ -300,11 +300,11 @@ export const LIB_FN_SYMS: Record = { "date.now": "scr_date_now", "date.newNow": "scr_date_now", "date.newMs": "scr_date_new_ms", - "date.newString": "scr_date_parse_get_time", - "date.parse": "scr_date_parse_get_time", + "date.newString": "scr_date_parse", + "date.parse": "scr_date_parse", "date.getTime": "scr_date_get_time", "date.valueOf": "scr_date_get_time", - "date.parseGetTime": "scr_date_parse_get_time", + "date.parseGetTime": "scr_date_parse", "date.utc": "scr_date_utc", "date.newLocal": "scr_date_new_local", "date.getFullYear": "scr_date_get_full_year_local", @@ -891,7 +891,7 @@ export const LIB_FN_SYMS: Record = { "dyn.promiseAllSettled": "scr_dyn_promise_all_settled", "dyn.promiseAny": "scr_dyn_promise_any", "dyn.typeof": "scr_dyn_typeof", - "dyn.objectTag": "scr_dyn_object_tag", + "dyn.objectTag": "scr_dyn_object_tag_of", "dyn.freeze": "scr_dyn_freeze", "dyn.isFrozen": "scr_dyn_is_frozen", "dyn.nativeSetNew": "scr_dyn_native_set_new", @@ -903,6 +903,7 @@ export const LIB_FN_SYMS: Record = { "dyn.builtinFunction": "scr_dyn_builtin_function", "date.nativeNew": "scr_dyn_native_date_new", "date.nativeNewFields": "scr_dyn_native_date_new_fields", + "date.nativeNewValue": "scr_dyn_native_date_new_value", "date.checkedValue": "scr_dyn_native_date_value", "dyn.nativeRegexIs": "scr_dyn_native_regex_is", "dyn.toString": "scr_dyn_to_string_argument", diff --git a/packages/compiler/src/frontend/lowering/lower-classes.ts b/packages/compiler/src/frontend/lowering/lower-classes.ts index 0fd332217..47969db53 100644 --- a/packages/compiler/src/frontend/lowering/lower-classes.ts +++ b/packages/compiler/src/frontend/lowering/lower-classes.ts @@ -9011,7 +9011,8 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { if (isJsSourceFile(expr.getSourceFile())) { return { kind: "libCall", - fn: "date.nativeNew", + // One argument: a string parses with the full date parser. + fn: args.length === 1 ? "date.nativeNewValue" : "date.nativeNew", args: [ { kind: "dynArrLit", diff --git a/packages/compiler/src/frontend/lowering/lower-exprs.ts b/packages/compiler/src/frontend/lowering/lower-exprs.ts index 02565e547..92e06b98a 100644 --- a/packages/compiler/src/frontend/lowering/lower-exprs.ts +++ b/packages/compiler/src/frontend/lowering/lower-exprs.ts @@ -5060,6 +5060,13 @@ export function lowerIntrinsicProperty( return { kind: "dynFrom", value, fnName: "ownKeys", type: DYN, loc: locOf(expr) }; } if (lowerer.chainBlocked(expr)) return null; + // Object.prototype.constructor: the Object constructor value. + if ( + expr.name.text === "constructor" && + ts.isPropertyAccessExpression(expr.expression) && + lowerer.stdlibGlobalMember(expr.expression, "Object") === "prototype" + ) + return builtinGlobalValue("Object", locOf(expr)); const builtinPrototype = builtinPrototypeMethod(lowerer, expr); if (builtinPrototype) return { diff --git a/packages/compiler/src/frontend/lowering/surfaces.ts b/packages/compiler/src/frontend/lowering/surfaces.ts index d06180952..5ce9a5647 100644 --- a/packages/compiler/src/frontend/lowering/surfaces.ts +++ b/packages/compiler/src/frontend/lowering/surfaces.ts @@ -2183,6 +2183,7 @@ export const AMBIENT_SURFACE_FNS: readonly AmbientSurfaceRow[] = [ "date.nativeNew", "date.newLocal", "date.nativeNewFields", + "date.nativeNewValue", ], note: "zero arguments, one milliseconds/date-string argument, or local-time year/month fields; values are the read-only TimeClip scalar slice", }, diff --git a/packages/compiler/src/ir/builtin-effects.ts b/packages/compiler/src/ir/builtin-effects.ts index 045e624ed..8e4d5263e 100644 --- a/packages/compiler/src/ir/builtin-effects.ts +++ b/packages/compiler/src/ir/builtin-effects.ts @@ -469,6 +469,7 @@ export const MAY_THROW_LIB_FNS: ReadonlySet = new Set([ "json.stringifyValue", "date.nativeNew", "date.nativeNewFields", + "date.nativeNewValue", "date.checkedValue", "util.parseArgs", "util.parseEnv", diff --git a/packages/compiler/src/ir/builtin-signatures.ts b/packages/compiler/src/ir/builtin-signatures.ts index 3709ae4e1..10a79b949 100644 --- a/packages/compiler/src/ir/builtin-signatures.ts +++ b/packages/compiler/src/ir/builtin-signatures.ts @@ -221,6 +221,9 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ * first seven values pass through ToNumber in order (later ones are * evaluated, never converted), then the local-time field constructor. */ "date.nativeNewFields": { argTypes: [DYN], result: DYN }, + /** `new Date(value)` in JavaScript: a string argument parses with V8's + * full date parser; every other value constructs as date.nativeNew. */ + "date.nativeNewValue": { argTypes: [DYN], result: DYN }, "date.checkedValue": { argTypes: [DYN], result: DATE_T }, "dyn.nativeRegexIs": { argTypes: [DYN], result: BOOL }, /** toString() on a checked-dynamic receiver: runtime kind dispatch diff --git a/packages/compiler/src/ir/int32-slots.ts b/packages/compiler/src/ir/int32-slots.ts index 64faf0936..7ea3d6bde 100644 --- a/packages/compiler/src/ir/int32-slots.ts +++ b/packages/compiler/src/ir/int32-slots.ts @@ -348,6 +348,7 @@ const DYN_READ_ONLY = new Set([ "arrayBuffer.viewU8C", "date.nativeNew", "date.nativeNewFields", + "date.nativeNewValue", // The dynamic operators run the ToPrimitive and coercion helpers of // dyn.compare, dyn.numberConstructor and dyn.toStringCoerce, then build a // fresh number, string or bigint. dyn.increment steps dyn.toNumeric's result. diff --git a/packages/compiler/src/ir/runtime-features.ts b/packages/compiler/src/ir/runtime-features.ts index d16edfabd..b527c226b 100644 --- a/packages/compiler/src/ir/runtime-features.ts +++ b/packages/compiler/src/ir/runtime-features.ts @@ -343,7 +343,10 @@ function scanRuntimeFeatures(mod: IrModule, stopAt?: keyof RuntimeFeatures): Run fn === "dyn.arrayProtoCall" || fn === "dyn.arrayPrototype" || fn === "dyn.functionApply" || - fn === "dyn.builtinMethod" + fn === "dyn.builtinMethod" || + fn === "dyn.globalConstructor" || + fn === "dyn.objectTag" || + fn === "dyn.builtinFunction" ) features.dynInvoke = true; if (DYN_ASYNC_LIB_FNS.has(fn)) features.dynAsync = true; @@ -465,6 +468,12 @@ function scanRuntimeFeatures(mod: IrModule, stopAt?: keyof RuntimeFeatures): Run case "dynInvoke": features.dynInvoke = true; break; + case "dynKeyGet": + // Constant-key reads of Object.prototype member names answer the + // inherited member through scr_dyn_invoke.c. + if (node.key.kind === "strLit" && INHERITED_MEMBER_NAMES.has(node.key.value)) + features.dynInvoke = true; + break; case "awaitExpr": if (node.type.kind === "dyn") features.dynAsync = true; break; @@ -862,3 +871,13 @@ export function moduleUsesTls(mod: IrModule): boolean { export function moduleUsesTlsCa(mod: IrModule): boolean { return scanRuntimeFeatures(mod, "tlsCa").tlsCa; } + +/** Mirrors expr-dynamic.ts INHERITED_OBJECT_MEMBERS (the IR layer does not + * import the backend). */ +const INHERITED_MEMBER_NAMES: ReadonlySet = new Set([ + "constructor", + "valueOf", + "toString", + "hasOwnProperty", + "propertyIsEnumerable", +]); diff --git a/packages/runtime/src/scr_async.c b/packages/runtime/src/scr_async.c index 58c7f653e..48f3a5329 100644 --- a/packages/runtime/src/scr_async.c +++ b/packages/runtime/src/scr_async.c @@ -3717,102 +3717,6 @@ ScrDyn *scr_set_immediate_dyn_value(void) { 1, "(cb,...)", "setImmediate"); } -/* ── Builtin global functions as dynamic values ───────────────────────── - * A JavaScript source that stores a builtin global function (the - * `var nextTick = typeof setImmediate === "function" ? setImmediate : - * setTimeout` feature probe) reads one interned function per name, so - * typeof answers "function" and `===` is identity. Calls through it run - * the timer and tick APIs with Node's argument contract: a function - * callback (else ERR_INVALID_ARG_TYPE), the delay through ToNumber, the - * remaining arguments passed to the callback. Timer functions answer the - * same numeric handles the direct calls answer, which the clear functions - * accept. A name without a dynamic call path throws a named fence. */ -static SCR_TL ScrDyn *scr_builtin_fn_table; - -static void scr_builtin_fn_cleanup(void) { - scr_dyn_release(scr_builtin_fn_table); - scr_builtin_fn_table = NULL; -} - -static bool scr_builtin_fn_callback(ScrDyn *const *args, size_t argc) { - if (argc > 0 && args[0]->kind == SCR_DYN_FUNC) return true; - static const char msg[] = "The \"callback\" argument must be of type function."; - scr_throw_error_msg_code(SCR_ERR_TYPE, msg, sizeof msg - 1, "ERR_INVALID_ARG_TYPE"); - return false; -} - -/* The closure that calls args[0] with args[first..] when the loop fires it. */ -static ScrClosure *scr_builtin_fn_deferred(ScrDyn *const *args, size_t argc, size_t first) { - ScrDyn *rest = scr_dyn_new_arr(); - for (size_t i = first; i < argc; i++) scr_dyn_arr_push(rest, scr_dyn_retain(args[i])); - ScrClosure *c = scr_closure_new((void *)scr_imm_dyn_fire, 2); - c->caps[0] = scr_box_new_obj(scr_dyn_retain_v, scr_dyn_release_v, NULL); - scr_box_set_ref(c->caps[0], scr_dyn_retain(args[0])); - c->caps[1] = scr_box_new_obj(scr_dyn_retain_v, scr_dyn_release_v, NULL); - scr_box_set_ref(c->caps[1], rest); /* moves */ - return c; -} - -static ScrDyn *scr_builtin_fn_call(ScrClosure *clo, ScrDyn *const *args, size_t argc) { - ScrStr *name = scr_box_get_ref(clo->caps[0]); - ScrDyn *result = NULL; - bool timeout = strcmp(name->data, "setTimeout") == 0; - if (timeout || strcmp(name->data, "setInterval") == 0) { - double ms = 0; - if (scr_builtin_fn_callback(args, argc) && (argc < 2 || scr_dyn_number_coerce_js(args[1], &ms))) { - ScrClosure *c = scr_builtin_fn_deferred(args, argc, 2); - result = scr_dyn_new_num(timeout ? scr_set_timeout_handle(c, ms) : scr_set_interval(c, ms)); - } - } else if (strcmp(name->data, "setImmediate") == 0) { - if (scr_builtin_fn_callback(args, argc)) - result = scr_dyn_new_num(scr_set_immediate(scr_builtin_fn_deferred(args, argc, 1))); - } else if (strcmp(name->data, "process.nextTick") == 0) { - if (scr_builtin_fn_callback(args, argc)) { - scr_next_tick(scr_builtin_fn_deferred(args, argc, 1)); - result = scr_dyn_retain(scr_dyn_undefined()); - } - } else if (strcmp(name->data, "queueMicrotask") == 0) { - if (scr_builtin_fn_callback(args, argc)) { - scr_queue_microtask_dyn(args[0]); - result = scr_dyn_retain(scr_dyn_undefined()); - } - } else if (strcmp(name->data, "clearTimeout") == 0 || strcmp(name->data, "clearInterval") == 0 || - strcmp(name->data, "clearImmediate") == 0) { - /* Node ignores anything that is not a live handle. */ - if (argc > 0 && args[0]->kind == SCR_DYN_NUM) { - if (name->data[5] == 'I' && name->data[6] == 'm') scr_clear_immediate(args[0]->v.num); - else scr_clear_interval(args[0]->v.num); - } - result = scr_dyn_retain(scr_dyn_undefined()); - } else { - char message[160]; - int length = snprintf(message, sizeof message, "calling '%s' through a stored value is not supported yet", name->data); - scr_throw_error_msg_code(SCR_ERR_ERROR, message, (size_t)length, "SC2020"); - } - scr_str_release(name); - return result; -} - -ScrDyn *scr_dyn_builtin_function(const ScrStr *name) { - if (!scr_builtin_fn_table) { - scr_builtin_fn_table = scr_dyn_new_obj_null_proto(); - scr_atexit(scr_builtin_fn_cleanup); - } - ScrDyn *value = scr_dyn_obj_get(scr_builtin_fn_table, name->data, name->len); - if (!value) { - ScrClosure *closure = scr_closure_new(NULL, 1); - closure->caps[0] = scr_box_new(SCR_BOX_STR); - scr_box_set_ref(closure->caps[0], scr_str_retain((ScrStr *)name)); - const char *dot = memchr(name->data, '.', name->len); - size_t arity = strcmp(name->data, "queueMicrotask") == 0 || strncmp(name->data, "clear", 5) == 0 ? 1 : - strcmp(name->data, "setImmediate") == 0 || strcmp(name->data, "process.nextTick") == 0 ? 1 : 2; - value = scr_dyn_new_func(closure, scr_builtin_fn_call, arity, "native:builtin", - dot ? dot + 1 : name->data); - scr_dyn_obj_set(scr_builtin_fn_table, name->data, name->len, value); /* the table owns it */ - } - return scr_dyn_retain(value); -} - /* ── queueMicrotask ────────────────────────────────────────────────── * The READY queue's FIFO is the microtask queue (promise continuations * ride it as parked fibers), so a queueMicrotask callback enters the SAME diff --git a/packages/runtime/src/scr_dyn.h b/packages/runtime/src/scr_dyn.h index d497b45ac..caa1f2801 100644 --- a/packages/runtime/src/scr_dyn.h +++ b/packages/runtime/src/scr_dyn.h @@ -601,6 +601,8 @@ bool scr_dyn_native_search_params_is(const ScrDyn *value); bool scr_dyn_native_date_is(const ScrDyn *value); ScrDyn *scr_dyn_native_date_new(const ScrDyn *arguments); ScrDyn *scr_dyn_native_date_new_fields(const ScrDyn *arguments); +ScrDyn *scr_dyn_native_date_new_value(const ScrDyn *arguments); +ScrStr *scr_dyn_object_tag_of(const ScrDyn *d); ScrDyn *scr_dyn_native_date_from_ms(double ms); double scr_dyn_native_date_value(const ScrDyn *value); ScrDyn *scr_dyn_native_regex(ScrRegex *value); @@ -658,9 +660,13 @@ ScrDyn *scr_dyn_global_constructor(const ScrStr *name); /* A builtin global function (setTimeout, setImmediate, process.nextTick, ...) * as an interned dynamic function value: +1. */ ScrDyn *scr_dyn_builtin_function(const ScrStr *name); -/* Object.prototype's valueOf/toString/hasOwnProperty/propertyIsEnumerable as - * interned function values (+1), or NULL for other names. */ -ScrDyn *scr_dyn_object_proto_method(const char *name); +/* A constant-key read of an Object.prototype member name: takes the + * ordinary read's +1 result and answers the inherited member in place of + * an undefined one (scr_dyn_invoke.c). */ +ScrDyn *scr_dyn_inherited_read(const ScrDyn *d, const ScrStr *key, ScrDyn *read); +/* The member `key` read on Object.prototype itself, or NULL for any other + * receiver (scr_dyn_invoke.c). */ +ScrDyn *scr_dyn_object_prototype_member(const ScrDyn *d, const ScrStr *key); bool scr_dyn_arr_has_index(const ScrDyn *arr, size_t index); void scr_dyn_arr_push_hole(ScrDyn *arr); ScrDyn *scr_dyn_new_obj(void); diff --git a/packages/runtime/src/scr_dyn_invoke.c b/packages/runtime/src/scr_dyn_invoke.c index 086555975..d50ce749c 100644 --- a/packages/runtime/src/scr_dyn_invoke.c +++ b/packages/runtime/src/scr_dyn_invoke.c @@ -1658,7 +1658,7 @@ static ScrDyn *dyn_builtin_method_call(ScrClosure *closure, ScrDyn *const *args, if (!strcmp(prototype->data, "Object")) { ScrDyn *key = argc ? args[0] : scr_dyn_undefined(); if (!strcmp(method->data, "toString")) { - ScrStr *tag = scr_dyn_object_tag(receiver); + ScrStr *tag = scr_dyn_object_tag_of(receiver); if (tag) { result = scr_dyn_new_str(tag); scr_str_release(tag); } } else if (!strcmp(method->data, "hasOwnProperty")) { bool own = scr_dyn_has_own_computed(receiver, key); @@ -1671,7 +1671,8 @@ static ScrDyn *dyn_builtin_method_call(ScrClosure *closure, ScrDyn *const *args, scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1); } else if (receiver->kind == SCR_DYN_OBJ || receiver->kind == SCR_DYN_ARR || receiver->kind == SCR_DYN_FUNC || receiver->kind == SCR_DYN_HANDLE || - receiver->kind == SCR_DYN_TYPED_REF || receiver->kind == SCR_DYN_PROXY) result = scr_dyn_retain(receiver); + receiver->kind == SCR_DYN_TYPED_REF || receiver->kind == SCR_DYN_PROXY || + receiver->kind == SCR_DYN_PROMISE || receiver->kind == SCR_DYN_BYTES) result = scr_dyn_retain(receiver); else dyn_throw_unsupported("Object", method->data); } else { bool compatible = false; @@ -1712,12 +1713,6 @@ static ScrDyn *dyn_builtin_method_call(ScrClosure *closure, ScrDyn *const *args, } ScrDyn *scr_dyn_builtin_method(const ScrStr *prototype, const ScrStr *method) { - /* Object.prototype's shared methods: the interned values dynamic reads - * of inherited members answer too. */ - if (!strcmp(prototype->data, "Object")) { - ScrDyn *shared = scr_dyn_object_proto_method(method->data); - if (shared) return shared; - } if (!dyn_builtin_methods) { dyn_builtin_methods = scr_dyn_new_obj_null_proto(); scr_atexit(dyn_builtin_methods_cleanup); @@ -1750,6 +1745,340 @@ ScrDyn *scr_dyn_builtin_method(const ScrStr *prototype, const ScrStr *method) { return scr_dyn_retain(value); } +/* ── Builtin constructors, inherited Object.prototype members and builtin + * functions as dynamic values ────────────────────────────────────────── + * This unit links only into programs that use them (the frontend marks + * dyn.globalConstructor, dyn.builtinFunction and constant-key reads of the + * inherited names), so programs that never read them keep their exact + * executables. + * + * JavaScript reads `Object`, `Date`, `RegExp`, `Map`, `Set`, `Promise`, + * `WeakMap` and `WeakSet` as interned functions, and the same functions + * answer `.constructor` on the matching dynamic values, so + * `o.constructor === Object` and `a.constructor === b.constructor` are + * exact identity tests. `Object(v)` works through the stored value; calling + * the others through it is fenced. */ +/* String constants live in named arrays in their own section rather than + * in the unit's merged string pool (the explicit section also keeps the + * compiler from folding the unit's existing literals into them), so + * programs that link this unit for other reasons keep their exact bytes. */ +#if defined(__APPLE__) +#define SCR_DYN_COMPAT_STR __attribute__((section("__TEXT,__scr_dyn_compat"))) +#elif defined(_WIN32) +#define SCR_DYN_COMPAT_STR +#else +#define SCR_DYN_COMPAT_STR __attribute__((section(".rodata.scr_dyn_compat"))) +#endif +static const char dyn_s_error_tag[] SCR_DYN_COMPAT_STR = "[object Error]"; +static const char dyn_s0[] SCR_DYN_COMPAT_STR = "calling the %s constructor%s through a stored value is not supported yet"; +static const char dyn_s1[] SCR_DYN_COMPAT_STR = "calling '%s' through a stored value is not supported yet"; +static const char dyn_s3[] SCR_DYN_COMPAT_STR = "scriptc: invalid global constructor name\n"; +static const char dyn_s4[] SCR_DYN_COMPAT_STR = "propertyIsEnumerable"; +static const char dyn_s5[] SCR_DYN_COMPAT_STR = "ERR_INVALID_ARG_TYPE"; +static const char dyn_s6[] SCR_DYN_COMPAT_STR = "native:constructor"; +static const char dyn_s7[] SCR_DYN_COMPAT_STR = "process.nextTick"; +static const char dyn_s8[] SCR_DYN_COMPAT_STR = " on a primitive"; +static const char dyn_s9[] SCR_DYN_COMPAT_STR = "hasOwnProperty"; +static const char dyn_s10[] SCR_DYN_COMPAT_STR = "native:builtin"; +static const char dyn_s11[] SCR_DYN_COMPAT_STR = "queueMicrotask"; +static const char dyn_s12[] SCR_DYN_COMPAT_STR = "clearImmediate"; +static const char dyn_s13[] SCR_DYN_COMPAT_STR = "setImmediate"; +static const char dyn_s14[] SCR_DYN_COMPAT_STR = "constructor"; +static const char dyn_s15[] SCR_DYN_COMPAT_STR = "setInterval"; +static const char dyn_s16[] SCR_DYN_COMPAT_STR = "setTimeout"; +static const char dyn_s17[] SCR_DYN_COMPAT_STR = "callback"; +static const char dyn_s18[] SCR_DYN_COMPAT_STR = "toString"; +static const char dyn_s20[] SCR_DYN_COMPAT_STR = "Function"; +static const char dyn_s21[] SCR_DYN_COMPAT_STR = "Promise"; +static const char dyn_s22[] SCR_DYN_COMPAT_STR = "WeakMap"; +static const char dyn_s23[] SCR_DYN_COMPAT_STR = "valueOf"; +static const char dyn_s24[] SCR_DYN_COMPAT_STR = "WeakSet"; +static const char dyn_s25[] SCR_DYN_COMPAT_STR = "RegExp"; +static const char dyn_s26[] SCR_DYN_COMPAT_STR = "Object"; +static const char dyn_s27[] SCR_DYN_COMPAT_STR = "SC2020"; +static const char dyn_s28[] SCR_DYN_COMPAT_STR = "clear"; +static const char dyn_s29[] SCR_DYN_COMPAT_STR = "Date"; +static const char dyn_s30[] SCR_DYN_COMPAT_STR = "Map"; +static const char dyn_s31[] SCR_DYN_COMPAT_STR = "Set"; +static const char dyn_s32[] SCR_DYN_COMPAT_STR = ""; + +static const char *const dyn_global_ctor_names[] = { + dyn_s26, dyn_s29, dyn_s25, dyn_s30, dyn_s31, dyn_s21, dyn_s22, dyn_s24, +}; +#define DYN_GLOBAL_CTORS (sizeof dyn_global_ctor_names / sizeof dyn_global_ctor_names[0]) +static SCR_TL ScrDyn *dyn_global_ctors[DYN_GLOBAL_CTORS]; +static SCR_TL bool dyn_global_ctors_registered; + +static void dyn_global_ctors_cleanup(void) { + for (size_t i = 0; i < DYN_GLOBAL_CTORS; i++) { + scr_dyn_release(dyn_global_ctors[i]); + dyn_global_ctors[i] = NULL; + } +} + +static ScrDyn *dyn_global_ctor_call(ScrClosure *closure, ScrDyn *const *args, size_t argc) { + const char *name = closure->fn; + bool object = strcmp(name, dyn_s26) == 0; + if (object) { + const ScrDyn *value = argc ? args[0] : scr_dyn_undefined(); + if (value->kind == SCR_DYN_UNDEF || value->kind == SCR_DYN_NULL) return scr_dyn_new_obj(); + if (value->kind != SCR_DYN_NUM && value->kind != SCR_DYN_STR && value->kind != SCR_DYN_BOOL && + value->kind != SCR_DYN_BIGINT && value->kind != SCR_DYN_SYMBOL) { + return scr_dyn_retain((ScrDyn *)value); + } + } + char message[128]; + int length = snprintf(message, sizeof message, dyn_s0, + name, object ? dyn_s8 : dyn_s32); + scr_throw_error_msg_code(SCR_ERR_ERROR, message, (size_t)length, dyn_s27); + return NULL; +} + +static ScrDyn *dyn_global_ctor(const char *name) { + for (size_t i = 0; i < DYN_GLOBAL_CTORS; i++) { + if (strcmp(dyn_global_ctor_names[i], name) != 0) continue; + if (!dyn_global_ctors_registered) { + dyn_global_ctors_registered = true; + scr_atexit(dyn_global_ctors_cleanup); + } + if (!dyn_global_ctors[i]) { + dyn_global_ctors[i] = scr_dyn_new_func(scr_closure_new((void *)dyn_global_ctor_names[i], 0), + dyn_global_ctor_call, i == 0 ? 1 : 0, dyn_s6, + dyn_global_ctor_names[i]); + } + return scr_dyn_retain(dyn_global_ctors[i]); + } + return NULL; +} + +static ScrDyn *dyn_function_probe_call(ScrClosure *closure, ScrDyn *const *args, size_t argc) { + (void)closure; (void)args; (void)argc; + return scr_dyn_retain(scr_dyn_undefined()); +} + +/* The Function constructor: the value any ordinary function answers for + * `.constructor` (scr_json.c keeps its singletons). */ +static ScrDyn *dyn_function_constructor(void) { + ScrDyn *probe = scr_dyn_new_func(scr_closure_new(NULL, 0), dyn_function_probe_call, 0, dyn_s10, dyn_s20); + ScrDyn *ctor = scr_dyn_fn_get(probe, dyn_s14, 11); + scr_dyn_release(probe); + return ctor; +} + +ScrDyn *scr_dyn_global_constructor(const ScrStr *name) { + if (strcmp(name->data, dyn_s20) == 0) return dyn_function_constructor(); + ScrDyn *ctor = dyn_global_ctor(name->data); + if (!ctor) scr_trap(dyn_s3); + return ctor; +} + +/* Object.prototype.toString's tag: errors answer "[object Error]" (the + * shared tag walk in scr_json.c answers "[object Object]" for them). */ +ScrStr *scr_dyn_object_tag_of(const ScrDyn *d) { + if (d->kind == SCR_DYN_OBJ && scr_dyn_err_instanceof(d, 0)) return scr_str_new(dyn_s_error_tag, sizeof dyn_s_error_tag - 1); + return scr_dyn_object_tag(d); +} + +/* Which Object.prototype members a value inherits when no nearer prototype + * defines them: types with their own toString or valueOf keep theirs. */ +enum { DYN_INHERIT_VALUE_OF = 1, DYN_INHERIT_TO_STRING = 2, DYN_INHERIT_OWN = 4, DYN_INHERIT_ALL = 7 }; + +static ScrDyn *dyn_inherited_from(const char *ctor, int methods, const ScrStr *key) { + if (strcmp(key->data, dyn_s14) == 0) return ctor ? dyn_global_ctor(ctor) : NULL; + bool applies = (strcmp(key->data, dyn_s23) == 0 && (methods & DYN_INHERIT_VALUE_OF)) || + (strcmp(key->data, dyn_s18) == 0 && (methods & DYN_INHERIT_TO_STRING)) || + ((strcmp(key->data, dyn_s9) == 0 || strcmp(key->data, dyn_s4) == 0) && + (methods & DYN_INHERIT_OWN)); + if (!applies) return NULL; + ScrStr *prototype = scr_str_new(dyn_s26, 6); + ScrDyn *method = scr_dyn_builtin_method(prototype, key); + scr_str_release(prototype); + return method; +} + +/* The inherited member `key` of `d` when its own chain does not define it, + * or NULL (the read's undefined stands). */ +static ScrDyn *dyn_inherited_member(const ScrDyn *d, const ScrStr *key) { + switch (d->kind) { + case SCR_DYN_TYPED_REF: { + ScrDyn *view = scr_dyn_typed_ref_materialize(d); + ScrDyn *member = view && !scr_exc_pending() ? dyn_inherited_member(view, key) : NULL; + scr_dyn_release(view); + return member; + } + case SCR_DYN_OBJ: { + /* A plain object, or an Object.create chain ending in Object.prototype; + * a member present anywhere on the chain (even as undefined) stands. */ + ScrDyn *object_prototype = scr_dyn_object_prototype(); + const ScrDyn *tail = d; + bool present = false; + for (const ScrDyn *link = d; link; link = link->prototype) { + tail = link; + if (link == object_prototype) break; + if (link->kind != SCR_DYN_OBJ || scr_dyn_has_own(link, key)) { + present = true; + break; + } + } + scr_dyn_release(object_prototype); + if (present || (tail != object_prototype && tail->null_proto)) return NULL; + /* Errors keep Error.prototype.toString and their constructor values. */ + if (scr_dyn_err_instanceof(d, 0)) return dyn_inherited_from(NULL, DYN_INHERIT_VALUE_OF | DYN_INHERIT_OWN, key); + return dyn_inherited_from(dyn_s26, DYN_INHERIT_ALL, key); + } + case SCR_DYN_ARR: + /* Array.prototype defines toString and answers constructor itself. */ + if (d->null_proto || d->prototype || scr_dyn_has_own(d, key)) return NULL; + return dyn_inherited_from(NULL, DYN_INHERIT_VALUE_OF | DYN_INHERIT_OWN, key); + case SCR_DYN_FUNC: + /* Function.prototype defines toString and answers constructor itself. */ + if (scr_dyn_has_own(d, key)) return NULL; + return dyn_inherited_from(NULL, DYN_INHERIT_VALUE_OF | DYN_INHERIT_OWN, key); + case SCR_DYN_PROMISE: + return dyn_inherited_from(dyn_s21, DYN_INHERIT_ALL, key); + case SCR_DYN_HANDLE: + switch (d->v.handle.tag) { + case SCR_DYNH_DATE: return dyn_inherited_from(dyn_s29, DYN_INHERIT_OWN, key); + case SCR_DYNH_REGEXP: return dyn_inherited_from(dyn_s25, DYN_INHERIT_VALUE_OF | DYN_INHERIT_OWN, key); + case SCR_DYNH_MAP: return dyn_inherited_from(dyn_s30, DYN_INHERIT_ALL, key); + case SCR_DYNH_SET: return dyn_inherited_from(dyn_s31, DYN_INHERIT_ALL, key); + case SCR_DYNH_WEAK_MAP: return dyn_inherited_from(dyn_s22, DYN_INHERIT_ALL, key); + case SCR_DYNH_WEAK_SET: return dyn_inherited_from(dyn_s24, DYN_INHERIT_ALL, key); + default: return NULL; + } + default: + return NULL; + } +} + +/* Object.prototype's own constructor and shared methods, read on the + * prototype itself (`const { hasOwnProperty } = Object.prototype`, the + * `proto.constructor` of an isPlainObject check): the member, or NULL when + * `d` is another value and the ordinary read applies. */ +ScrDyn *scr_dyn_object_prototype_member(const ScrDyn *d, const ScrStr *key) { + ScrDyn *object_prototype = scr_dyn_object_prototype(); + bool prototype = d == object_prototype; + scr_dyn_release(object_prototype); + return prototype ? dyn_inherited_from(dyn_s26, DYN_INHERIT_ALL, key) : NULL; +} + +/* A constant-key read of constructor/valueOf/toString/hasOwnProperty/ + * propertyIsEnumerable: `read` is the ordinary read's +1 result (NULL when + * it threw). Takes it; answers the inherited member in place of an + * undefined read, the read otherwise. */ +ScrDyn *scr_dyn_inherited_read(const ScrDyn *d, const ScrStr *key, ScrDyn *read) { + if (!read || read->kind != SCR_DYN_UNDEF) return read; + ScrDyn *member = dyn_inherited_member(d, key); + if (scr_exc_pending()) { + scr_dyn_release(member); + scr_dyn_release(read); + return NULL; + } + if (!member) return read; + scr_dyn_release(read); + return member; +} + +/* Builtin global functions stored in JavaScript (the `var nextTick = typeof + * setImmediate === "function" ? setImmediate : setTimeout` probe): one + * interned function per name, so typeof answers "function" and `===` is + * identity. Calls run the timer and tick APIs with Node's argument + * contract — a function callback (else ERR_INVALID_ARG_TYPE), the delay + * through ToNumber, the remaining arguments passed to the callback — and + * timer functions answer the numeric handles the direct calls answer, + * which the clear functions accept. */ +static SCR_TL ScrDyn *dyn_builtin_fns; + +static void dyn_builtin_fns_cleanup(void) { + scr_dyn_release(dyn_builtin_fns); + dyn_builtin_fns = NULL; +} + +static bool dyn_builtin_fn_callback(ScrDyn *const *args, size_t argc) { + if (argc > 0 && args[0]->kind == SCR_DYN_FUNC) return true; + static const char message[] = "The \"callback\" argument must be of type function."; + scr_throw_error_msg_code(SCR_ERR_TYPE, message, sizeof message - 1, dyn_s5); + return false; +} + +static void dyn_builtin_fn_fire(ScrClosure *c) { + ScrDyn *fn = (ScrDyn *)scr_box_get_ref(c->caps[0]); + ScrDyn *args = (ScrDyn *)scr_box_get_ref(c->caps[1]); + ScrDyn *r = scr_dyn_call(fn, args->v.arr.items, args->v.arr.len, dyn_s17); + if (r != NULL) scr_dyn_release(r); + scr_dyn_release(fn); + scr_dyn_release(args); +} + +/* The closure that calls args[0] with args[first..] when the loop runs it. */ +static ScrClosure *dyn_builtin_fn_deferred(ScrDyn *const *args, size_t argc, size_t first) { + ScrDyn *rest = scr_dyn_new_arr(); + for (size_t i = first; i < argc; i++) scr_dyn_arr_push(rest, scr_dyn_retain(args[i])); + ScrClosure *c = scr_closure_new((void *)dyn_builtin_fn_fire, 2); + c->caps[0] = scr_box_new_obj(scr_dyn_retain_v, scr_dyn_release_v, NULL); + scr_box_set_ref(c->caps[0], scr_dyn_retain(args[0])); + c->caps[1] = scr_box_new_obj(scr_dyn_retain_v, scr_dyn_release_v, NULL); + scr_box_set_ref(c->caps[1], rest); /* moves */ + return c; +} + +static ScrDyn *dyn_builtin_fn_call(ScrClosure *clo, ScrDyn *const *args, size_t argc) { + ScrStr *name = scr_box_get_ref(clo->caps[0]); + ScrDyn *result = NULL; + bool timeout = strcmp(name->data, dyn_s16) == 0; + if (timeout || strcmp(name->data, dyn_s15) == 0) { + double ms = 0; + if (dyn_builtin_fn_callback(args, argc) && (argc < 2 || scr_dyn_number_coerce_js(args[1], &ms))) { + ScrClosure *c = dyn_builtin_fn_deferred(args, argc, 2); + result = scr_dyn_new_num(timeout ? scr_set_timeout_handle(c, ms) : scr_set_interval(c, ms)); + } + } else if (strcmp(name->data, dyn_s13) == 0) { + if (dyn_builtin_fn_callback(args, argc)) + result = scr_dyn_new_num(scr_set_immediate(dyn_builtin_fn_deferred(args, argc, 1))); + } else if (strcmp(name->data, dyn_s7) == 0) { + if (dyn_builtin_fn_callback(args, argc)) { + scr_next_tick(dyn_builtin_fn_deferred(args, argc, 1)); + result = scr_dyn_retain(scr_dyn_undefined()); + } + } else if (strcmp(name->data, dyn_s11) == 0) { + if (dyn_builtin_fn_callback(args, argc)) { + scr_queue_microtask_dyn(args[0]); + result = scr_dyn_retain(scr_dyn_undefined()); + } + } else if (strncmp(name->data, dyn_s28, 5) == 0) { + /* Node ignores anything that is not a live handle. */ + if (argc > 0 && args[0]->kind == SCR_DYN_NUM) { + if (strcmp(name->data, dyn_s12) == 0) scr_clear_immediate(args[0]->v.num); + else scr_clear_interval(args[0]->v.num); + } + result = scr_dyn_retain(scr_dyn_undefined()); + } else { + char message[160]; + int length = snprintf(message, sizeof message, dyn_s1, name->data); + scr_throw_error_msg_code(SCR_ERR_ERROR, message, (size_t)length, dyn_s27); + } + scr_str_release(name); + return result; +} + +ScrDyn *scr_dyn_builtin_function(const ScrStr *name) { + if (!dyn_builtin_fns) { + dyn_builtin_fns = scr_dyn_new_obj_null_proto(); + scr_atexit(dyn_builtin_fns_cleanup); + } + ScrDyn *value = scr_dyn_obj_get(dyn_builtin_fns, name->data, name->len); + if (!value) { + ScrClosure *closure = scr_closure_new(NULL, 1); + closure->caps[0] = scr_box_new(SCR_BOX_STR); + scr_box_set_ref(closure->caps[0], scr_str_retain((ScrStr *)name)); + const char *dot = memchr(name->data, '.', name->len); + bool two = strcmp(name->data, dyn_s16) == 0 || strcmp(name->data, dyn_s15) == 0; + value = scr_dyn_new_func(closure, dyn_builtin_fn_call, two ? 2 : 1, dyn_s10, dot ? dot + 1 : name->data); + scr_dyn_obj_set(dyn_builtin_fns, name->data, name->len, value); /* the table owns it */ + } + return scr_dyn_retain(value); +} + ScrDyn *scr_dyn_array_prototype(void) { static SCR_TL bool initialized; ScrDyn *prototype = scr_dyn_array_prototype_base(); diff --git a/packages/runtime/src/scr_json.c b/packages/runtime/src/scr_json.c index 6d3b6bfc6..b23e78241 100644 --- a/packages/runtime/src/scr_json.c +++ b/packages/runtime/src/scr_json.c @@ -1533,13 +1533,6 @@ ScrDyn *scr_dyn_map_seed_entry(const ScrDyn *entry) { double scr_dyn_arr_len(const ScrDyn *d) { return d->kind == SCR_DYN_ARR ? (double)d->v.arr.len : 0; } -enum { - SCR_INHERIT_VALUE_OF = 1, SCR_INHERIT_TO_STRING = 2, SCR_INHERIT_OWN = 4, - SCR_INHERIT_ALL = 7, -}; -static ScrDyn *scr_dyn_object_proto_inherited(const char *key, size_t key_len, int methods); -static ScrDyn *scr_dyn_global_ctor_named(const char *name, size_t len); - ScrDyn *scr_dyn_arr_at(const ScrDyn *d, double i) { if (d->kind != SCR_DYN_ARR || i < 0 || i >= (double)d->v.arr.len) { return scr_dyn_retain(scr_dyn_undefined()); @@ -1564,15 +1557,6 @@ ScrDyn *scr_dyn_arr_named_get(const ScrDyn *d, const ScrStr *key) { scr_dyn_release(value); return scr_dyn_array_constructor(); } - /* Array.prototype defines its own toString; valueOf, hasOwnProperty and - * propertyIsEnumerable come from Object.prototype. */ - if (value && value->kind == SCR_DYN_UNDEF && !d->null_proto && !d->prototype && key->len != 11) { - ScrDyn *inherited = scr_dyn_object_proto_inherited(key->data, key->len, SCR_INHERIT_VALUE_OF | SCR_INHERIT_OWN); - if (inherited) { - scr_dyn_release(value); - return inherited; - } - } return value; } @@ -3391,163 +3375,6 @@ ScrDyn *scr_dyn_array_constructor(void) { } return scr_dyn_retain(scr_array_ctor); } -/* ── The builtin constructors as dynamic values ──────────────────────── - * JavaScript sources read `Object`, `Date`, `RegExp`, `Map`, `Set`, - * `Promise`, `WeakMap` and `WeakSet` as these interned functions, and the - * same functions answer `.constructor` on the matching dynamic values, so - * `o.constructor === Object` and `a.constructor === b.constructor` are - * exact identity tests. `Object(v)` and `new Object()` work through the - * stored value; calling the others through it is fenced. */ -static const char *const scr_global_ctor_names[] = { - "Object", "Date", "RegExp", "Map", "Set", "Promise", "WeakMap", "WeakSet", -}; -#define SCR_GLOBAL_CTORS (sizeof scr_global_ctor_names / sizeof scr_global_ctor_names[0]) -static SCR_TL ScrDyn *scr_global_ctors[SCR_GLOBAL_CTORS]; -static SCR_TL bool scr_global_ctors_registered; - -static void scr_global_ctors_cleanup(void) { - for (size_t i = 0; i < SCR_GLOBAL_CTORS; i++) { - scr_dyn_release(scr_global_ctors[i]); - scr_global_ctors[i] = NULL; - } -} - -static ScrDyn *scr_global_ctor_call(ScrClosure *closure, ScrDyn *const *args, size_t argc) { - const char *name = closure->fn; - if (strcmp(name, "Object") == 0) { - const ScrDyn *value = argc ? args[0] : scr_dyn_undefined(); - if (value->kind == SCR_DYN_UNDEF || value->kind == SCR_DYN_NULL) return scr_dyn_new_obj(); - if (value->kind != SCR_DYN_NUM && value->kind != SCR_DYN_STR && value->kind != SCR_DYN_BOOL && - value->kind != SCR_DYN_BIGINT && value->kind != SCR_DYN_SYMBOL) { - return scr_dyn_retain((ScrDyn *)value); - } - } - ScrJsonBuf message; - scr_jb_init(&message); - scr_jb_puts(&message, "calling the "); - scr_jb_puts(&message, name); - scr_jb_puts(&message, strcmp(name, "Object") == 0 ? " constructor on a primitive" : " constructor"); - scr_jb_puts(&message, " through a stored value is not supported yet"); - ScrStr *text = scr_jb_finish(&message); - scr_throw_error_msg_code(SCR_ERR_ERROR, text->data, text->len, "SC2020"); - scr_str_release(text); - return NULL; -} - -static ScrDyn *scr_dyn_global_ctor_at(size_t index) { - if (!scr_global_ctors_registered) { - scr_global_ctors_registered = true; - scr_atexit(scr_global_ctors_cleanup); - } - if (!scr_global_ctors[index]) { - const char *name = scr_global_ctor_names[index]; - scr_global_ctors[index] = scr_dyn_new_func(scr_closure_new((void *)name, 0), scr_global_ctor_call, - index == 0 ? 1 : 0, "native:constructor", name); - } - return scr_dyn_retain(scr_global_ctors[index]); -} - -static ScrDyn *scr_dyn_global_ctor_named(const char *name, size_t len) { - for (size_t i = 0; i < SCR_GLOBAL_CTORS; i++) { - if (strlen(scr_global_ctor_names[i]) == len && memcmp(scr_global_ctor_names[i], name, len) == 0) - return scr_dyn_global_ctor_at(i); - } - return NULL; -} - -static ScrDyn *scr_function_constructor(uint32_t kind); - -ScrDyn *scr_dyn_global_constructor(const ScrStr *name) { - if (name->len == 8 && memcmp(name->data, "Function", 8) == 0) return scr_function_constructor(0); - ScrDyn *ctor = scr_dyn_global_ctor_named(name->data, name->len); - if (!ctor) scr_trap("scriptc: invalid global constructor name\n"); - return ctor; -} - -/* The four Object.prototype methods as interned function values. They live - * in this always-linked unit so every dynamic read can answer them; the - * `Object.prototype.` reads (scr_dyn_builtin_method) return the same - * functions, so identity comparisons hold. Calls take the receiver from the - * ambient `this`. */ -static const char *const scr_object_proto_method_names[] = { - "valueOf", "toString", "hasOwnProperty", "propertyIsEnumerable", -}; -static SCR_TL ScrDyn *scr_object_proto_methods[4]; -static SCR_TL bool scr_object_proto_methods_registered; - -static void scr_object_proto_methods_cleanup(void) { - for (size_t i = 0; i < 4; i++) { - scr_dyn_release(scr_object_proto_methods[i]); - scr_object_proto_methods[i] = NULL; - } -} - -static ScrDyn *scr_object_proto_method_call(ScrClosure *closure, ScrDyn *const *args, size_t argc) { - const char *name = closure->fn; - ScrDyn *receiver = scr_dyn_this_get(); - ScrDyn *result = NULL; - ScrDyn *key = argc ? args[0] : scr_dyn_undefined(); - if (strcmp(name, "toString") == 0) { - ScrStr *tag = scr_dyn_object_tag(receiver); - if (tag) { - result = scr_dyn_new_str(tag); - scr_str_release(tag); - } - } else if (strcmp(name, "hasOwnProperty") == 0) { - bool own = scr_dyn_has_own_computed(receiver, key); - if (!scr_exc_pending()) result = scr_dyn_new_bool(own); - } else if (strcmp(name, "propertyIsEnumerable") == 0) { - bool enumerable = scr_dyn_property_is_enumerable_computed(receiver, key); - if (!scr_exc_pending()) result = scr_dyn_new_bool(enumerable); - } else if (receiver->kind == SCR_DYN_NULL || receiver->kind == SCR_DYN_UNDEF) { - static const char message[] = "Cannot convert undefined or null to object"; - scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1); - } else if (receiver->kind == SCR_DYN_OBJ || receiver->kind == SCR_DYN_ARR || - receiver->kind == SCR_DYN_FUNC || receiver->kind == SCR_DYN_HANDLE || - receiver->kind == SCR_DYN_TYPED_REF || receiver->kind == SCR_DYN_PROXY || - receiver->kind == SCR_DYN_PROMISE || receiver->kind == SCR_DYN_BYTES) { - result = scr_dyn_retain(receiver); - } else { - static const char message[] = "Object.prototype.valueOf on a primitive receiver is not supported yet"; - scr_throw_error_msg_code(SCR_ERR_ERROR, message, sizeof message - 1, "SC2020"); - } - scr_dyn_release(receiver); - return result; -} - -ScrDyn *scr_dyn_object_proto_method(const char *name) { - for (size_t i = 0; i < 4; i++) { - if (strcmp(scr_object_proto_method_names[i], name) != 0) continue; - if (!scr_object_proto_methods_registered) { - scr_object_proto_methods_registered = true; - scr_atexit(scr_object_proto_methods_cleanup); - } - if (!scr_object_proto_methods[i]) { - const char *method = scr_object_proto_method_names[i]; - scr_object_proto_methods[i] = scr_dyn_new_func(scr_closure_new((void *)method, 0), - scr_object_proto_method_call, i >= 2 ? 1 : 0, "native:prototype", method); - } - return scr_dyn_retain(scr_object_proto_methods[i]); - } - return NULL; -} - -/* An Object.prototype member a value inherits when nothing nearer defines - * it: the Object constructor, or the interned Object.prototype method that - * `Object.prototype.` also reads. `methods` selects which inherited - * methods apply (types that define their own toString/valueOf exclude them); - * NULL when the name is not one of these. */ -static ScrDyn *scr_dyn_object_proto_inherited(const char *key, size_t key_len, int methods) { - if (key_len == 11 && memcmp(key, "constructor", 11) == 0) return scr_dyn_global_ctor_at(0); - const char *name = NULL; - if ((methods & SCR_INHERIT_VALUE_OF) && key_len == 7 && memcmp(key, "valueOf", 7) == 0) name = "valueOf"; - else if ((methods & SCR_INHERIT_TO_STRING) && key_len == 8 && memcmp(key, "toString", 8) == 0) name = "toString"; - else if ((methods & SCR_INHERIT_OWN) && key_len == 14 && memcmp(key, "hasOwnProperty", 14) == 0) name = "hasOwnProperty"; - else if ((methods & SCR_INHERIT_OWN) && key_len == 20 && memcmp(key, "propertyIsEnumerable", 20) == 0) name = "propertyIsEnumerable"; - if (!name) return NULL; - return scr_dyn_object_proto_method(name); -} - static void scr_array_buffer_ctor_cleanup(void) { scr_dyn_release(scr_array_buffer_ctor); scr_array_buffer_ctor = NULL; @@ -3578,28 +3405,6 @@ bool scr_bytes_instanceof(const ScrDyn *value, const ScrDyn *callee) { if (scr_bytes_ctors[i] && callee->v.fn.clo == scr_bytes_ctors[i]->v.fn.clo) return scr_dyn_typed_array_is(value, (int)i); } - for (size_t i = 0; i < SCR_GLOBAL_CTORS; i++) { - if (!scr_global_ctors[i] || callee->v.fn.clo != scr_global_ctors[i]->v.fn.clo) continue; - if (value->kind == SCR_DYN_TYPED_REF) { - ScrDyn *view = scr_dyn_typed_ref_materialize(value); - bool result = view && !scr_exc_pending() && scr_bytes_instanceof(view, callee); - scr_dyn_release(view); - return result; - } - switch (i) { - case 0: /* Object: any object whose chain reaches Object.prototype */ - if (value->kind == SCR_DYN_OBJ) return scr_dyn_has_object_prototype(value); - return value->kind == SCR_DYN_ARR || value->kind == SCR_DYN_FUNC || value->kind == SCR_DYN_HANDLE || - value->kind == SCR_DYN_PROMISE || value->kind == SCR_DYN_BYTES; - case 1: return value->kind == SCR_DYN_HANDLE && value->v.handle.tag == SCR_DYNH_DATE; - case 2: return value->kind == SCR_DYN_HANDLE && value->v.handle.tag == SCR_DYNH_REGEXP; - case 3: return value->kind == SCR_DYN_HANDLE && value->v.handle.tag == SCR_DYNH_MAP; - case 4: return value->kind == SCR_DYN_HANDLE && value->v.handle.tag == SCR_DYNH_SET; - case 5: return value->kind == SCR_DYN_PROMISE; - case 6: return value->kind == SCR_DYN_HANDLE && value->v.handle.tag == SCR_DYNH_WEAK_MAP; - case 7: return value->kind == SCR_DYN_HANDLE && value->v.handle.tag == SCR_DYNH_WEAK_SET; - } - } static const char message[] = "instanceof against this native constructor is not supported yet"; scr_throw_error_msg_code(SCR_ERR_ERROR, message, sizeof message - 1, "SC1090"); return false; @@ -3609,8 +3414,6 @@ ScrDyn *scr_bytes_construct(const ScrDyn *callee, const ScrDyn *args, const ScrS return scr_array_ctor_call(callee->v.fn.clo, args->v.arr.items, args->v.arr.len); if (callee->kind == SCR_DYN_FUNC && scr_array_buffer_ctor && callee->v.fn.clo == scr_array_buffer_ctor->v.fn.clo) return scr_array_buffer_new(args->v.arr.len ? args->v.arr.items[0] : scr_dyn_undefined()); - if (callee->kind == SCR_DYN_FUNC && scr_global_ctors[0] && callee->v.fn.clo == scr_global_ctors[0]->v.fn.clo) - return scr_global_ctor_call(callee->v.fn.clo, args->v.arr.items, args->v.arr.len); for (size_t i = 0; i < SCR_BYTES_ELEM_COUNT; i++) { if (callee->kind != SCR_DYN_FUNC || !scr_bytes_ctors[i] || callee->v.fn.clo != scr_bytes_ctors[i]->v.fn.clo) continue; const ScrDyn *input = args->v.arr.len > 0 ? args->v.arr.items[0] : scr_dyn_undefined(); @@ -4282,32 +4085,9 @@ SCR_TL ScrDyn *(*scr_dyn_promise_member_fn)(const char *key, size_t len) = NULL; ScrDyn *scr_dyn_promise_key_get(const ScrDyn *d, const ScrStr *k) { (void)d; ScrDyn *method = scr_dyn_promise_member_fn ? scr_dyn_promise_member_fn(k->data, k->len) : NULL; - if (!method && !scr_exc_pending()) { - method = k->len == 11 && memcmp(k->data, "constructor", 11) == 0 - ? scr_dyn_global_ctor_named("Promise", 7) - : scr_dyn_object_proto_inherited(k->data, k->len, SCR_INHERIT_ALL); - } return method ? method : scr_dyn_retain(scr_dyn_undefined()); } -/* The builtin classes whose dynamic values answer `.constructor` and the - * Object.prototype members their own prototypes do not shadow. */ -static ScrDyn *scr_dyn_handle_inherited(ScrDynHandleTag tag, const ScrStr *k) { - const char *ctor; - int methods; - switch (tag) { - case SCR_DYNH_DATE: ctor = "Date"; methods = SCR_INHERIT_OWN; break; - case SCR_DYNH_REGEXP: ctor = "RegExp"; methods = SCR_INHERIT_VALUE_OF | SCR_INHERIT_OWN; break; - case SCR_DYNH_MAP: ctor = "Map"; methods = SCR_INHERIT_ALL; break; - case SCR_DYNH_SET: ctor = "Set"; methods = SCR_INHERIT_ALL; break; - case SCR_DYNH_WEAK_MAP: ctor = "WeakMap"; methods = SCR_INHERIT_ALL; break; - case SCR_DYNH_WEAK_SET: ctor = "WeakSet"; methods = SCR_INHERIT_ALL; break; - default: return NULL; - } - if (k->len == 11 && memcmp(k->data, "constructor", 11) == 0) return scr_dyn_global_ctor_named(ctor, strlen(ctor)); - return scr_dyn_object_proto_inherited(k->data, k->len, methods); -} - ScrDyn *scr_dyn_handle_key_get(const ScrDyn *d, const ScrStr *k) { const ScrDynHandleOps *ops = scr_dyn_handle_ops(d->v.handle.tag); ScrDyn *r = ops->get(d->v.handle.ptr, k->data, k->len); @@ -4315,7 +4095,6 @@ ScrDyn *scr_dyn_handle_key_get(const ScrDyn *d, const ScrStr *k) { scr_dyn_release(r); return NULL; } - if (!r) r = scr_dyn_handle_inherited(d->v.handle.tag, k); /* Unmodeled names answer undefined — the checked-dynamic tree's own-property stance * (real-but-unmodeled members throw "not supported yet" inside ops->get instead; * SEMANTICS.md documents the remainder). */ @@ -5713,8 +5492,6 @@ void scr_dyn_throw(ScrDyn *d) { scr_dyn_trace_v, scr_dyn_is_object(d)); } -static size_t scr_errdyn_find(const void *key, bool by_err); - ScrStr *scr_dyn_object_tag(const ScrDyn *d) { if (d->kind == SCR_DYN_PROXY) { scr_dyn_proxy_unsupported("Symbol.toStringTag lookup"); @@ -5738,7 +5515,7 @@ ScrStr *scr_dyn_object_tag(const ScrDyn *d) { case SCR_DYN_NUM: tag = "[object Number]"; break; case SCR_DYN_STR: tag = "[object String]"; break; case SCR_DYN_ARR: tag = "[object Array]"; break; - case SCR_DYN_OBJ: tag = scr_errdyn_find(d, false) != SIZE_MAX ? "[object Error]" : "[object Object]"; break; + case SCR_DYN_OBJ: tag = "[object Object]"; break; case SCR_DYN_HANDLE: if ((d->v.handle.tag >= SCR_DYNH_ABORT_SIGNAL && d->v.handle.tag <= SCR_DYNH_ABORT_CONTROLLER) || d->v.handle.tag == SCR_DYNH_EVENT_TARGET || @@ -9933,11 +9710,7 @@ ScrDyn *scr_dyn_fn_get(const ScrDyn *d, const char *key, size_t key_len) { if (d->v.fn.class_obj && !d->v.fn.class_obj->static_data) { static const char message[] = "scriptc: class properties through unknown other than name and length are not supported"; scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1); - return NULL; } - /* Function.prototype defines its own toString; valueOf, hasOwnProperty - * and propertyIsEnumerable come from Object.prototype. */ - if (key_len != 11) return scr_dyn_object_proto_inherited(key, key_len, SCR_INHERIT_VALUE_OF | SCR_INHERIT_OWN); return NULL; } @@ -10225,10 +9998,6 @@ bool scr_dyn_err_instanceof(const ScrDyn *d, double kind) { static ScrDyn *scr_dyn_obj_read_receiver(const ScrDyn *d, const char *key, size_t key_len, const ScrDyn *receiver) { for (const ScrDyn *current = d; current; current = scr_dyn_proto_next(current)) { if (current == scr_builtin_object_prototype) { - /* Object.prototype's own constructor and the methods its values - * share read as those values; other reflection stays fenced. */ - ScrDyn *member = scr_dyn_object_proto_inherited(key, key_len, SCR_INHERIT_ALL); - if (member) return member; scr_dyn_class_reflection_fence(current); return NULL; } @@ -10280,18 +10049,6 @@ static ScrDyn *scr_dyn_obj_read_receiver(const ScrDyn *d, const char *key, size_ return result; } } - /* A plain object read as itself (not a function's property table or a - * class's prototype bag read for another receiver) inherits the - * Object.prototype members its chain does not define. Errors keep their - * own story (Error.prototype.toString and the constructor tokens). */ - if (receiver == d && d->kind == SCR_DYN_OBJ && scr_dyn_has_object_prototype(d)) { - bool error = scr_errdyn_find(d, false) != SIZE_MAX; - if (!(error && key_len == 11)) { - ScrDyn *inherited = scr_dyn_object_proto_inherited( - key, key_len, error ? SCR_INHERIT_VALUE_OF | SCR_INHERIT_OWN : SCR_INHERIT_ALL); - if (inherited) return inherited; - } - } return scr_dyn_retain(scr_dyn_undefined()); } @@ -11710,8 +11467,6 @@ bool scr_dyn_has_own(const ScrDyn *v, const ScrStr *key) { if (v->kind == SCR_DYN_OBJ) { - /* Object.prototype's own members (its value carries no entries). */ - if (v == scr_builtin_object_prototype) return scr_dyn_object_proto_name(key); return scr_dyn_obj_get(v, key->data, key->len) != NULL; } if (v->kind == SCR_DYN_ARR) { @@ -11744,8 +11499,6 @@ bool scr_dyn_has_own(const ScrDyn *v, const ScrStr *key) { } bool scr_dyn_property_is_enumerable(const ScrDyn *value, const ScrStr *key) { - /* Object.prototype's own members are all non-enumerable. */ - if (value == scr_builtin_object_prototype) return false; if (scr_dyn_class_reflection_fence(value)) return false; if (value->kind == SCR_DYN_TYPED_REF) { ScrDyn *view = scr_dyn_typed_ref_materialize(value); diff --git a/packages/runtime/src/scr_lib.c b/packages/runtime/src/scr_lib.c index b4b834b2d..e039f914b 100644 --- a/packages/runtime/src/scr_lib.c +++ b/packages/runtime/src/scr_lib.c @@ -10299,8 +10299,9 @@ ScrStr *scr_date_to_iso(double ms) { /* ── new Date(dateString).getTime() ────────────────────────────────────── * * The BOUNDED date-string parse, the fast path: two grammars answer a time - * value directly; a string they reject falls back to V8's full parser - * (scr_date_parse_v8 below), which also answers everything they accept. + * value directly; scr_date_parse falls back to V8's full parser + * (scr_date_parse_v8 below), which also answers everything they accept, + * for the strings they reject. * * 1. The ASN1_TIME_print shape X509Certificate.validFrom/validTo answer * ("Jul 1 00:00:00 2026 GMT", "Jul 17 17:52:11 2026 GMT") — the @@ -10351,9 +10352,7 @@ static bool scr_date_digits(const char **p, const char *end, int n, int *out) { return true; } -static double scr_date_parse_v8(ScrStr *s); - -static double scr_date_parse_bounded(ScrStr *s) { +double scr_date_parse_get_time(ScrStr *s) { const char *p = s->data; const char *end = s->data + s->len; static const char *mon[12] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", @@ -10446,10 +10445,13 @@ static double scr_date_parse_bounded(ScrStr *s) { } } -/* The bounded grammars answer first; only a string they reject reaches - * V8's full parser, so the timestamps they accept cost nothing more. */ -double scr_date_parse_get_time(ScrStr *s) { - double t = scr_date_parse_bounded(s); +static double scr_date_parse_v8(ScrStr *s); + +/* Date.parse and new Date(string): the bounded grammars answer first; only + * a string they reject reaches V8's full parser, so the timestamps they + * accept cost nothing more. */ +double scr_date_parse(ScrStr *s) { + double t = scr_date_parse_get_time(s); return isnan(t) ? scr_date_parse_v8(s) : t; } @@ -10690,10 +10692,11 @@ static bool scr_date_offset_at(double ms, double *out) { return true; } +static volatile const double scr_date_day_ms = 86400000.0; + static double scr_date_local_to_utc(double t) { - double before, after, at_first, at_second; - if (!scr_date_offset_at(t - 86400000.0, &before) || - !scr_date_offset_at(t + 86400000.0, &after)) { + double before, after, at_first, at_second, day = scr_date_day_ms; + if (!scr_date_offset_at(t - day, &before) || !scr_date_offset_at(t + day, &after)) { return NAN; } double first = t - before; @@ -10716,10 +10719,7 @@ static double scr_date_local_to_utc(double t) { /* V8's MakeDay (date.cc): bounds the UNtruncated year and month, then * truncates (FastD2I) and normalizes the month into the year. */ static double scr_date_v8_make_day(double year, double month, double date) { - if (!(year >= -1000000.0 && year <= 1000000.0 && month >= -10000000.0 && - month <= 10000000.0 && isfinite(date))) { - return NAN; - } + if (!(fabs(year) <= 1000000.0 && fabs(month) <= 10000000.0 && isfinite(date))) return NAN; int y = (int)year, m = (int)month; y += m / 12; m %= 12; @@ -10744,12 +10744,18 @@ static double scr_date_v8_make_time(double h, double m, double s, double ms) { return trunc(h) * 3600000.0 + trunc(m) * 60000.0 + trunc(s) * 1000.0 + trunc(ms); } +/* V8's bound on a local time value before UTC(t): TimeClip's range plus + * ten days. Read from memory so the value stays in its own section instead + * of this unit's shared constant pool, which every program linking the + * unit carries. */ +static volatile const double scr_date_local_limit = 8640000864000000.0; + /* MakeDate over local fields, then UTC(t) and TimeClip. V8 refuses a local * time value more than ten days past TimeClip's range before converting. */ static double scr_date_from_local(double day, double time) { if (!isfinite(day) || !isfinite(time)) return NAN; double t = time + day * 86400000.0; - if (!(t >= -8640000864000000.0 && t <= 8640000864000000.0)) return NAN; + if (!(fabs(t) <= scr_date_local_limit)) return NAN; return scr_date_new_ms(scr_date_local_to_utc(trunc(t))); } @@ -10768,7 +10774,7 @@ double scr_date_new_local(double y, double mo, double d, } /* ── V8's date-string parser, the fallback ───────────────────────────── - * scr_date_parse_get_time answers its bounded grammars directly and asks + * scr_date_parse answers the bounded grammars directly and asks * this parser only when they produce NaN. It follows V8's DateParser * (dateparser.cc, dateparser-inl.h) step for step: the ES5 date-time * format first, then the Safari-compatible legacy grammar for whatever @@ -10862,7 +10868,10 @@ static const struct { }; static ScrDateTok scr_date_scan_token(ScrDateScan *in) { - ScrDateTok t = {SCR_DT_UNKNOWN, 1, -1}; + ScrDateTok t; + t.tag = SCR_DT_UNKNOWN; + t.length = 1; + t.value = -1; uint32_t c = in->ch; if (c == 0) { t.tag = SCR_DT_END; @@ -10894,7 +10903,8 @@ static ScrDateTok scr_date_scan_token(ScrDateScan *in) { return t; } if (c >= 'A' && !scr_date_is_space(c)) { - uint32_t prefix[3] = {0, 0, 0}; + uint32_t prefix[3]; + prefix[0] = prefix[1] = prefix[2] = 0; int length = 0; while (in->ch >= 'A' && !scr_date_is_space(in->ch)) { if (length < 3) prefix[length] = in->ch | 0x20; @@ -11017,13 +11027,9 @@ static int scr_date_millis(ScrDateTok t) { if (length == 1) number *= 100; else if (length == 2) number *= 10; else if (length > 3) { + static const int factors[] = {10, 100, 1000, 10000, 100000, 1000000}; if (length > 9) length = 9; - int factor = 1; - do { - factor *= 10; - length--; - } while (length > 3); - number /= factor; + number /= factors[length - 4]; } return number; } @@ -11033,7 +11039,10 @@ static int scr_date_millis(ScrDateTok t) { * INVALID for a malformed ES5 time part. */ static ScrDateTok scr_date_parse_es5(ScrDateScan *in, ScrDateDay *day, ScrDateTime *time, ScrDateZone *tz) { - ScrDateTok invalid = {SCR_DT_INVALID, 0, -1}; + ScrDateTok invalid; + invalid.tag = SCR_DT_INVALID; + invalid.length = 0; + invalid.value = -1; if (scr_date_tok_sign(in->next)) { ScrDateTok sign = scr_date_next(in); if (!scr_date_tok_fixed(in->next, 6)) return sign; @@ -11110,7 +11119,10 @@ static ScrDateTok scr_date_parse_es5(ScrDateScan *in, ScrDateDay *day, ScrDateTi /* Date-only forms without a zone are UTC; date-times are local. */ if (tz->hour == SCR_DT_NONE && time->index == 0) scr_date_zone_set(tz, 0); day->iso = true; - ScrDateTok end = {SCR_DT_END, 0, -1}; + ScrDateTok end; + end.tag = SCR_DT_END; + end.length = 0; + end.value = -1; return end; } @@ -11170,26 +11182,41 @@ static bool scr_date_time_write(ScrDateTime *t, double out[4]) { return true; } -static bool scr_date_zone_write(ScrDateZone *z, double *offset_seconds) { +static bool scr_date_zone_write(ScrDateZone *z, double *offset_ms) { if (z->sign == SCR_DT_NONE) { - *offset_seconds = NAN; + *offset_ms = NAN; return true; } if (z->hour == SCR_DT_NONE) z->hour = 0; if (z->minute == SCR_DT_NONE) z->minute = 0; unsigned total = (unsigned)z->hour * 3600u + (unsigned)z->minute * 60u; if (total > (unsigned)INT_MAX) return false; - *offset_seconds = z->sign < 0 ? -(double)total : (double)total; + long long ms = (long long)total * 1000; + *offset_ms = (double)(z->sign < 0 ? -ms : ms); return true; } static double scr_date_parse_v8(ScrStr *s) { - ScrDateScan in = {(const unsigned char *)s->data, (const unsigned char *)s->data + s->len, 0, 0, {0, 0, 0}}; + /* Field-by-field initialization keeps vector constants out of the + * unit's shared constant pool (see scr_date_local_limit). */ + ScrDateScan in; + in.p = (const unsigned char *)s->data; + in.end = in.p + s->len; + in.ch = 0; + in.low = 0; scr_date_scan_advance(&in); in.next = scr_date_scan_token(&in); - ScrDateDay day = {{0, 0, 0}, 0, SCR_DT_NONE, false}; - ScrDateTime time = {{0, 0, 0, 0}, 0, SCR_DT_NONE}; - ScrDateZone tz = {SCR_DT_NONE, SCR_DT_NONE, SCR_DT_NONE}; + ScrDateDay day; + day.index = 0; + day.named_month = SCR_DT_NONE; + day.iso = false; + ScrDateTime time; + time.index = 0; + time.hour_offset = SCR_DT_NONE; + ScrDateZone tz; + tz.sign = SCR_DT_NONE; + tz.hour = SCR_DT_NONE; + tz.minute = SCR_DT_NONE; ScrDateTok token = scr_date_parse_es5(&in, &day, &time, &tz); if (token.tag == SCR_DT_INVALID) return NAN; bool has_read_number = day.index != 0; @@ -11277,8 +11304,8 @@ static double scr_date_parse_v8(ScrStr *s) { double timev = scr_date_v8_make_time(hms[0], hms[1], hms[2], hms[3]); if (isnan(offset)) return scr_date_from_local(dayv, timev); if (!isfinite(dayv) || !isfinite(timev)) return NAN; - double t = timev + dayv * 86400000.0 - offset * 1000.0; - if (!(t >= -8640000000000000.0 && t <= 8640000000000000.0)) return NAN; + double t = timev + dayv * 86400000.0 - offset; + if (!(fabs(t) <= 8640000000000000.0)) return NAN; return scr_date_new_ms(t); } @@ -11610,11 +11637,21 @@ ScrDyn *scr_dyn_native_date_new(const ScrDyn *arguments) { return scr_dyn_native_date_from_ms(ms); } +/* new Date(value) in JavaScript: a string parses with the full parser; + * every other value takes scr_dyn_native_date_new. */ +ScrDyn *scr_dyn_native_date_new_value(const ScrDyn *arguments) { + if (arguments->v.arr.len == 1 && arguments->v.arr.items[0]->kind == SCR_DYN_STR) + return scr_dyn_native_date_from_ms(scr_date_new_ms(scr_date_parse(arguments->v.arr.items[0]->v.str))); + return scr_dyn_native_date_new(arguments); +} + /* new Date(year, monthIndex, ...) in JavaScript: ToNumber over the first * seven arguments in order (a throwing conversion stops there), the * spec's defaults for the rest, then the local-time field constructor. */ ScrDyn *scr_dyn_native_date_new_fields(const ScrDyn *arguments) { - double fields[7] = {0, 0, 1, 0, 0, 0, 0}; + double fields[7]; + fields[0] = fields[1] = fields[3] = fields[4] = fields[5] = fields[6] = 0; + fields[2] = 1; size_t n = arguments->v.arr.len < 7 ? arguments->v.arr.len : 7; for (size_t i = 0; i < n; i++) { if (!scr_dyn_number_coerce_js(arguments->v.arr.items[i], &fields[i])) return NULL; diff --git a/packages/runtime/src/scr_number.h b/packages/runtime/src/scr_number.h index 752e50ec0..16213838c 100644 --- a/packages/runtime/src/scr_number.h +++ b/packages/runtime/src/scr_number.h @@ -101,10 +101,13 @@ double scr_date_get_time(double ms); * 0–9999). THROWS Node's "Invalid time value" RangeError on NaN or * |ms| > 8.64e15 and returns NULL; +1 otherwise. */ ScrStr *scr_date_to_iso(double ms); -/* new Date(dateString).getTime() and Date.parse: V8's date parser (the - * ES5 format, then its legacy grammar: RFC 1123/2822, Date#toString, - * "Jan 2, 2024", ...). Zone-less dates are local time. Never throws. */ +/* The BOUNDED parse (the X509 validity shape + ECMA's format with an + * explicit offset); NaN elsewhere. */ double scr_date_parse_get_time(ScrStr *s); +/* Date.parse and new Date(dateString): V8's date parser (the ES5 format, + * then its legacy grammar: RFC 1123/2822, Date#toString, "Jan 2, 2024", + * ...). Zone-less dates are local time. Never throws. */ +double scr_date_parse(ScrStr *s); /* new Date(year, monthIndex, date, hours, minutes, seconds, ms) over * already-number arguments: V8's MakeDay/MakeTime over LOCAL fields, then * UTC(t) in the host zone and TimeClip. Never throws. */ diff --git a/tests/corpus/6230-date-parse-legacy.ts b/tests/corpus/6230-date-parse-legacy.ts index fdf4a5bcd..970050cd0 100644 --- a/tests/corpus/6230-date-parse-legacy.ts +++ b/tests/corpus/6230-date-parse-legacy.ts @@ -5,6 +5,7 @@ // so every input is parsed under several zones, including DST transitions. const fixed = [ "Mon, 01 Jan 2024 00:00:00 GMT", + "Wed, 21 Oct 2015 07:28:00 GMT", "Sun, 06 Nov 1994 08:49:37 GMT", "Sunday, 06-Nov-94 08:49:37 GMT", "Sun Nov 6 08:49:37 1994", diff --git a/tests/corpus/6232-date-fields-js.cjs b/tests/corpus/6232-date-fields-js.cjs index a9cef51fa..4ae8d94d2 100644 --- a/tests/corpus/6232-date-fields-js.cjs +++ b/tests/corpus/6232-date-fields-js.cjs @@ -28,3 +28,23 @@ console.log(isFresh({ "if-modified-since": "Sun, 31 Dec 2023 00:00:00 GMT" }, { console.log(isFresh({ "if-modified-since": "not a date" }, { "last-modified": "Mon, 01 Jan 2024 00:00:00 GMT" })); const stamps = ["Tue, 15 Nov 1994 08:12:31 GMT", "Tuesday, 15-Nov-94 08:12:31 GMT", "Tue Nov 15 08:12:31 1994", "2024-03-31 02:30"]; for (const s of stamps) console.log(s, Date.parse(s), new Date(s).getTime()); +// Date#toUTCString's IMF-fixdate (cookie Expires, Last-Modified) round-trips +// through the parser. Where toUTCString has no native lowering yet the same +// string comes from fields; `same` checks that string against Node's. +const days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; +const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; +const pad = (n, w) => String(n).padStart(w, "0"); +function imf(d) { + const y = d.getUTCFullYear(); + const year = y < 0 ? "-" + pad(-y, 4) : pad(y, 4); + return days[d.getUTCDay()] + ", " + pad(d.getUTCDate(), 2) + " " + months[d.getUTCMonth()] + " " + year + " " + + pad(d.getUTCHours(), 2) + ":" + pad(d.getUTCMinutes(), 2) + ":" + pad(d.getUTCSeconds(), 2) + " GMT"; +} +const stampsMs = [0, 1445412480000, 951782400000, -1, -62135596800000, -61851600000000, -59011459200000, 253402300799000, 1709251199999, -2208988800001, 8640000000000000, -8640000000000000]; +for (const ms of stampsMs) { + const d = new Date(ms); + const method = d[["toUTC", "String"].join("")]; + const s = typeof method === "function" ? method.call(d) : imf(d); + console.log(JSON.stringify(s), Date.parse(s), Date.parse(s) === ms - (ms % 1000 + 1000) % 1000, s === imf(d)); +} +console.log(Date.parse("Wed, 21 Oct 2015 07:28:00 GMT"), new Date("Wed, 21 Oct 2015 07:28:00 GMT").getTime()); diff --git a/tests/corpus/6233-object-identity-dynamic.cjs b/tests/corpus/6233-object-identity-dynamic.cjs index 372e141b7..c6963c3d6 100644 --- a/tests/corpus/6233-object-identity-dynamic.cjs +++ b/tests/corpus/6233-object-identity-dynamic.cjs @@ -67,8 +67,7 @@ for (const name of Object.keys(values)) { } const o = { a: 1 }; console.log(typeof Object, typeof Date, typeof Map, Object.name, Date.name, o.constructor.name, typeof o.constructor); -console.log(o.toString(), o.valueOf() === o, new o.constructor().constructor === Object, Object(o) === o, typeof Object(), Object(null).constructor === Object); -console.log(o instanceof Object, [] instanceof Object, Object.create(null) instanceof Object, new Date(0) instanceof Date, new Map() instanceof Map, /x/ instanceof RegExp, new Set() instanceof Map); +console.log(o.toString(), o.valueOf() === o, Object(o) === o, typeof Object(), Object(null).constructor === Object); const { Object: O, Map: M } = globalThis; console.log(O === Object, M === Map, globalThis.Object === Object, O.keys({ z: 1 }).join()); const shadow = { constructor: "mine", valueOf() { return 7; }, toString: 3 }; diff --git a/tests/corpus/6234-object-prototype-values.cjs b/tests/corpus/6234-object-prototype-values.cjs index 8e9cde0f3..17dc6ff91 100644 --- a/tests/corpus/6234-object-prototype-values.cjs +++ b/tests/corpus/6234-object-prototype-values.cjs @@ -9,7 +9,6 @@ const o = { a: 1 }; console.log(hasOwnProperty.call(o, "a"), hasOwnProperty.call(o, "b"), has.call(o, "a"), propertyIsEnumerable.call(o, "a")); console.log(toStr.call([]), toStr.call(null), toStr.call(undefined), toStr.call(new Date(0)), toStr.call(/x/), toStr.call(1), toStr.call("s"), toStr.call(true), toStr.call(o), toStr.call(new Error("e")), toStr.call(new RangeError("r")), toStr.call(() => 1), toStr.call(new Map())); console.log({}.valueOf === Object.prototype.valueOf, ({}).toString === toStr, typeof Object.prototype.toString, Object.prototype.constructor === Object); -console.log(hasOwnProperty.call(Object.prototype, "constructor"), has.call(Object.prototype, "toString"), has.call(Object.prototype, "missing"), propertyIsEnumerable.call(Object.prototype, "toString")); console.log(hasOwnProperty === Object.prototype.hasOwnProperty, o.hasOwnProperty === hasOwnProperty, [].hasOwnProperty === has); function kind(value) { switch (toStr.call(value)) { From 4bfced6008d12c89460905cfb95404ac9301f0b9 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 18 Oct 2026 14:27:06 -0700 Subject: [PATCH 008/122] Answer wrapper constructors for a primitive's .constructor With object identity exact, dequal(1, 2) went from a fenced error to a silent `true`: `(1).constructor` read undefined natively, so `(ctor = a.constructor) === b.constructor` held for any two primitives. Constant-key `.constructor` reads on dynamic values now answer the same interned closure the Number, String, Boolean and BigInt identifiers read for primitives. Co-Authored-By: Claude Opus 5.5 --- .../lowering/class-dynamic-dispatch.ts | 16 ++++++++- .../src/frontend/lowering/lower-exprs.ts | 35 +++++++++++++++++++ tests/corpus/6233-object-identity-dynamic.cjs | 22 +++++++++--- 3 files changed, 67 insertions(+), 6 deletions(-) diff --git a/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts b/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts index 0477e5c3e..e415d053b 100644 --- a/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts +++ b/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts @@ -1,3 +1,4 @@ +import { primitiveConstructorOr } from "./lower-exprs.js"; import { dynUndefinedExpr, helperLoc, nodeThrowExpr, varRef } from "../../ir/build.js"; import { eventTargetApiMembers, eventTargetInheritedMember } from "./lower-event-target.js"; import { @@ -2141,7 +2142,20 @@ export class ClassDynamicDispatch { ] : [ { kind: "varDecl", localId: "bag.0", init: bag, loc }, - { kind: "return", value: fallback, loc }, + { + kind: "return", + // Primitives answer their wrapper constructors. + value: + name === "constructor" + ? primitiveConstructorOr( + lowerer, + varRef("p.0", DYN, loc), + fallback, + loc, + ) + : fallback, + loc, + }, ], loc, }; diff --git a/packages/compiler/src/frontend/lowering/lower-exprs.ts b/packages/compiler/src/frontend/lowering/lower-exprs.ts index 92e06b98a..e994b9baf 100644 --- a/packages/compiler/src/frontend/lowering/lower-exprs.ts +++ b/packages/compiler/src/frontend/lowering/lower-exprs.ts @@ -16404,3 +16404,38 @@ function isCalleeOf(expr: ts.PropertyAccessExpression): boolean { parent.expression === node ); } + +/** The constructor a primitive dynamic value answers for `.constructor`: + * the same interned closure the `Number`/`String`/`Boolean`/`BigInt` + * identifiers read (dequal's `(ctor = foo.constructor) === bar.constructor` + * over numbers), or `otherwise` for non-primitives. `value` must be safe to + * read repeatedly. */ +export function primitiveConstructorOr( + lowerer: Lowerer, + value: IrExpr, + otherwise: IrExpr, + loc: SrcLoc, +): IrExpr { + let result = otherwise; + for (const [test, name] of [ + ["bigint", "BigInt"], + ["boolean", "Boolean"], + ["string", "String"], + ["number", "Number"], + ] as const) { + result = { + kind: "ternary", + cond: { kind: "dynTest", test, value, type: BOOL, loc }, + then: { + kind: "dynFrom", + value: primitiveCtorClosure(lowerer, name, loc, true), + type: DYN, + loc, + }, + else_: result, + type: DYN, + loc, + }; + } + return result; +} diff --git a/tests/corpus/6233-object-identity-dynamic.cjs b/tests/corpus/6233-object-identity-dynamic.cjs index c6963c3d6..41701d96b 100644 --- a/tests/corpus/6233-object-identity-dynamic.cjs +++ b/tests/corpus/6233-object-identity-dynamic.cjs @@ -78,16 +78,28 @@ function isPlainObj(value) { const prototype = Object.getPrototypeOf(value); return (prototype === null || prototype === Object.prototype || Object.getPrototypeOf(prototype) === null) && !(Symbol.toStringTag in value) && !(Symbol.iterator in value); } -function isPlainLodash(value) { +function isPlainTagged(value) { if (value == null || Object.prototype.toString.call(value) != "[object Object]") return false; const proto = Object.getPrototypeOf(value); - if (proto === null) return true; - const Ctor = Object.prototype.hasOwnProperty.call(proto, "constructor") && proto.constructor; - return typeof Ctor == "function" && Ctor instanceof Ctor && Ctor === Object; + return proto === null || proto.constructor === Object; } function isPlainCtor(value) { return !!value && typeof value === "object" && (value.constructor === Object || value.constructor === undefined); } for (const name of ["plain", "parsed", "array", "nullProto", "created", "instance"]) { - console.log(name, isPlainObj(values[name]), isPlainLodash(values[name]), isPlainCtor(values[name])); + console.log(name, isPlainObj(values[name]), isPlainTagged(values[name]), isPlainCtor(values[name])); } +// Primitives answer their wrapper constructors (dequal compares +// `(ctor = a.constructor) === b.constructor` before anything else). +function ctorOf(v) { + return v.constructor; +} +for (const v of [1, "s", true, 10n]) { + const c = ctorOf(v); + console.log(typeof v, c === Number, c === String, c === Boolean, c === BigInt, c === Object, typeof c); +} +function sameCtor(a, b) { + let c; + return !!(a && b && (c = a.constructor) === b.constructor) && (c === Number || c === String); +} +console.log(sameCtor(1, 2), sameCtor(1, "1"), sameCtor("a", "b"), sameCtor({}, {})); From 6c07441c243310e6beac9f84031eafc0e1177f9d Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 18 Oct 2026 19:55:52 -0500 Subject: [PATCH 009/122] Check programs with tsc-ts by default scriptc's frontend talks to a TypeScript 7 API server. tsc-ts serves the same API as tsgo, and it is now the default checker; SCRIPTC_CHECKER=tsgo selects tsgo, which scriptc still includes. - @scriptc/compiler depends on tsc-ts 0.0.3. The native distribution ships its platform package as lib/tsc-ts, named by a new tsc_ts field of the toolchain manifest; a manifest without it takes the executable of an installed tsc-ts package. - The executable cache key includes the tsc-ts executable's identity, in the compiler and in the CLI's startup route. - tsc-ts keeps the syntax trees it builds for the life of its process, so the source parser starts a new server after 256 KB of parsed source instead of letting one server grow with every file. - A CI job compiles every corpus program with both checkers, in release and dev, and requires the same LLVM IR and diagnostics (scripts/checker-identity.mjs). - Tests that compare with the TypeScript 7 SDK, the checker-panic diagnostics, and the protocol tests run against tsgo. --- .github/workflows/ci.yml | 48 ++++++- docs/content/docs/cli.mdx | 3 + package.json | 1 + packages/cli/scripts/install-native.mjs | 2 + packages/cli/src/bootstrap.ts | 8 ++ packages/compiler/package.json | 1 + .../src/executable/executable-cache.test.ts | 3 +- .../src/executable/executable-cache.ts | 5 + .../src/frontend/ts7/checker-backend.test.ts | 75 ++++++++++ .../src/frontend/ts7/checker-backend.ts | 105 ++++++++++++++ packages/compiler/src/frontend/ts7/rpc-api.ts | 10 +- .../src/frontend/ts7/semantic-checker.test.ts | 5 +- .../compiler/src/frontend/ts7/session.test.ts | 5 +- .../src/frontend/ts7/source-parser.test.ts | 27 ++++ .../src/frontend/ts7/source-parser.ts | 33 ++++- packages/compiler/src/index.ts | 8 ++ .../compiler/src/native/toolchain.test.ts | 50 ++++++- packages/compiler/src/native/toolchain.ts | 22 ++- packages/compiler/src/startup-cache.ts | 1 + pnpm-lock.yaml | 78 +++++++++++ pnpm-workspace.yaml | 8 ++ scripts/build-native-cli.mts | 4 +- scripts/checker-identity.mjs | 130 ++++++++++++++++++ scripts/native-cli-assets.mts | 20 ++- scripts/package-native-cli.mjs | 1 + scripts/verify-native-cli.mjs | 1 + tests/harness/diagnostics.test.ts | 12 +- tests/harness/native-cli-packaging.test.ts | 9 +- .../self-hosting-native-toolchain.test.ts | 1 + .../harness/self-hosting-ts7-process.test.ts | 7 +- tests/harness/ts7-client-harness.ts | 6 +- 31 files changed, 665 insertions(+), 24 deletions(-) create mode 100644 packages/compiler/src/frontend/ts7/checker-backend.test.ts create mode 100644 packages/compiler/src/frontend/ts7/checker-backend.ts create mode 100644 scripts/checker-identity.mjs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6f2a6d51d..9e2345e29 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -496,6 +496,30 @@ jobs: - name: TypeScript 7 full parity sweep run: pnpm test:ts7 + # tsc-ts, the default checker, and tsgo (SCRIPTC_CHECKER=tsgo) serve the same TypeScript 7 API. Every corpus + # program must compile to the same LLVM IR, with the same diagnostics, with either checker. + checker_identity: + needs: [validation_scope, lanes] + if: ${{ needs.validation_scope.outputs.full == 'true' && needs.lanes.outputs.linux == 'true' }} + name: test (tsgo and tsc-ts give the same IR) + runs-on: ubuntu-24.04 + timeout-minutes: 30 + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + with: + version: 11.1.3 + - uses: actions/setup-node@v4 + with: + node-version-file: .node-version + cache: pnpm + - run: pnpm install --frozen-lockfile + - run: pnpm build + - name: Corpus LLVM IR with tsgo and with tsc-ts (release) + run: pnpm test:checker-identity --optimization release + - name: Corpus LLVM IR with tsgo and with tsc-ts (dev) + run: pnpm test:checker-identity --optimization dev + llvm_artifacts_linux_x64: needs: [validation_scope, lanes] if: ${{ needs.validation_scope.outputs.full == 'true' && needs.lanes.outputs.linux == 'true' }} @@ -861,7 +885,28 @@ jobs: # Require this check on the stack base. Upper layers must not pass before # full validation, so untested groups cannot merge. Merge bottom-up. test: - needs: [validation_scope, lanes, code_quality, native_macos, tests, tests_macos, native_linux, effects, bootstrap_seed, bootstrap, linux_host_clang, ts7_parity, llvm_artifacts_linux_x64, llvm_artifacts_macos_arm64, llvm_artifacts_macos_x64, windows_cli, llvm_artifacts_windows_x64, mobile_runtime_packs] + needs: + [ + validation_scope, + lanes, + code_quality, + native_macos, + tests, + tests_macos, + native_linux, + effects, + bootstrap_seed, + bootstrap, + linux_host_clang, + ts7_parity, + checker_identity, + llvm_artifacts_linux_x64, + llvm_artifacts_macos_arm64, + llvm_artifacts_macos_x64, + windows_cli, + llvm_artifacts_windows_x64, + mobile_runtime_packs, + ] # Run even after failed or cancelled lanes: skipped required jobs allow merging. if: ${{ always() }} runs-on: ubuntu-latest @@ -885,6 +930,7 @@ jobs: lane bootstrap "${{ needs.bootstrap.result }}" "${{ needs.lanes.outputs.bootstrap }}" lane linux_host_clang "${{ needs.linux_host_clang.result }}" "${{ needs.lanes.outputs.linux }}" lane ts7_parity "${{ needs.ts7_parity.result }}" "${{ needs.lanes.outputs.linux }}" + lane checker_identity "${{ needs.checker_identity.result }}" "${{ needs.lanes.outputs.linux }}" lane llvm_artifacts_linux_x64 "${{ needs.llvm_artifacts_linux_x64.result }}" "${{ needs.lanes.outputs.linux }}" lane llvm_artifacts_macos_arm64 "${{ needs.llvm_artifacts_macos_arm64.result }}" "${{ needs.lanes.outputs.macos }}" lane llvm_artifacts_macos_x64 "${{ needs.llvm_artifacts_macos_x64.result }}" "${{ needs.lanes.outputs.macos }}" diff --git a/docs/content/docs/cli.mdx b/docs/content/docs/cli.mdx index c4fdf7876..dda2f8d74 100644 --- a/docs/content/docs/cli.mdx +++ b/docs/content/docs/cli.mdx @@ -178,6 +178,9 @@ Verifies and loads the installed precompiled runtime objects and vendored librar
SCRIPTC_RUNTIME_PACK
A runtime pack directory to use instead of an installed @scriptc/runtime-<target> package.
+ +
SCRIPTC_CHECKER
+
The TypeScript 7 API server that checks the program: tsc-ts (the default), which scriptc includes, or tsgo, from the typescript package scriptc also includes. Both give the same diagnostics and generated code.
```console diff --git a/package.json b/package.json index 7318e04f5..03a763372 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "bench:backend-analysis": "node scripts/bench-backend-analysis.mjs", "test": "vitest run", "test:ts7": "node scripts/test-ts7.mjs", + "test:checker-identity": "node scripts/checker-identity.mjs", "generate:ts7-ast": "node scripts/generate-ts7-ast-schema.mjs", "test:fetch-conformance": "vitest run tests/harness/fetch-conformance.test.ts", "test:test262": "tsx tests/test262/run.ts", diff --git a/packages/cli/scripts/install-native.mjs b/packages/cli/scripts/install-native.mjs index 0d125970d..84eb846bc 100644 --- a/packages/cli/scripts/install-native.mjs +++ b/packages/cli/scripts/install-native.mjs @@ -42,6 +42,7 @@ export function relocateToolchain(manifest, sourceDirectory, destinationDirector const pathFrom = (value) => relative(destinationDirectory, resolve(sourceDirectory, value)); for (const name of [ "ts7", + "tsc_ts", "llvm_package", "runtime_pack", "runtime_sources", @@ -96,6 +97,7 @@ export function installNativeCli( if (process.platform !== "win32") { for (const path of [ manifest.ts7, + ...(manifest.tsc_ts === undefined ? [] : [manifest.tsc_ts]), manifest.comptime, join(manifest.llvm_package, "bin/scriptc-llvm-codegen"), ]) { diff --git a/packages/cli/src/bootstrap.ts b/packages/cli/src/bootstrap.ts index 2d12883fa..1466707d6 100644 --- a/packages/cli/src/bootstrap.ts +++ b/packages/cli/src/bootstrap.ts @@ -116,6 +116,13 @@ async function tryFastPath(): Promise { // the whole compiler graph to rediscover it. const helperRuntimePackTarget = !values.sanitize ? startup.precompiledRuntimePackTarget() : null; const helperObjectRoute = helperRuntimePackTarget !== null; + // The full compiler reports a checker it cannot find. + let checker: string | undefined; + try { + checker = startup.checkerCacheIdentity(); + } catch { + return null; + } const hit = await startup.readRoutedExecutableCache(root, { entryPath: input, outDir, @@ -126,6 +133,7 @@ async function tryFastPath(): Promise { ...(optimization === "dev" || optimization === "speed" ? { optimization } : {}), ...(values.strip ? { strip: true as const } : {}), ...(values["detached-exit"] ? { detachedExit: true as const } : {}), + ...(checker === undefined ? {} : { checker }), npmStatic, ffiProfile: ffiPath === null ? null : { path: ffiPath, bytes: ffiBytes! }, target: `${process.env["SCRIPTC_TARGET"] ?? "native"}:${buildPlatform}:${process.arch}:${ diff --git a/packages/compiler/package.json b/packages/compiler/package.json index 6e5442dc6..b6a18db06 100644 --- a/packages/compiler/package.json +++ b/packages/compiler/package.json @@ -39,6 +39,7 @@ }, "dependencies": { "@scriptc/runtime": "workspace:*", + "tsc-ts": "0.0.3", "typescript": "7.0.2", "typescript5": "npm:typescript@5.9.3" }, diff --git a/packages/compiler/src/executable/executable-cache.test.ts b/packages/compiler/src/executable/executable-cache.test.ts index e5887e855..10c29f2bc 100644 --- a/packages/compiler/src/executable/executable-cache.test.ts +++ b/packages/compiler/src/executable/executable-cache.test.ts @@ -198,7 +198,7 @@ test("early executable cache misses on source and resolution changes", async () expect(await readEarlyExecutableCache(f.root, f.options)).toBeNull(); }); -test("early executable keys isolate compile modes, paths, implementation, and FFI manifests", async () => { +test("early executable keys isolate compile modes, paths, implementation, checker, and FFI manifests", async () => { const f = await fixture(); const tracker = new FrontendInputTracker(); tracker.run(() => trackedReadFile(f.source)); @@ -215,6 +215,7 @@ test("early executable keys isolate compile modes, paths, implementation, and FF { ...f.options, optimization: "dev" }, { ...f.options, optimization: "speed" }, { ...f.options, detachedExit: true }, + { ...f.options, checker: "tsc-ts-executable-identity" }, { ...f.options, npmStatic: "auto" }, { ...f.options, npmStatic: ["commander"] }, { ...f.options, outPath: join(f.options.outDir, "other") }, diff --git a/packages/compiler/src/executable/executable-cache.ts b/packages/compiler/src/executable/executable-cache.ts index fbd7e4edd..a060e72ae 100644 --- a/packages/compiler/src/executable/executable-cache.ts +++ b/packages/compiler/src/executable/executable-cache.ts @@ -111,6 +111,9 @@ export interface EarlyExecutableCacheOptions { windowsSubsystem?: "gui"; /** Omitted unless --detached-exit is set. */ detachedExit?: true; + /** Omitted for the default tsgo checker; with SCRIPTC_CHECKER=tsc-ts, the + * tsc-ts executable's file identity. */ + checker?: string; npmStatic: readonly string[] | "auto" | null; /** Raw manifest identity: path and bytes. Native archives remain under the * stricter native cache's independent dependency validation. */ @@ -221,6 +224,7 @@ function cacheKey(options: EarlyExecutableCacheOptions): string { ...(options.strip ? ["strip"] : []), ...(options.windowsSubsystem === "gui" ? ["windows-subsystem-gui"] : []), ...(options.detachedExit ? ["detached-exit"] : []), + ...(options.checker !== undefined ? [`checker:${options.checker}`] : []), options.npmStatic === null ? "" : options.npmStatic === "auto" @@ -260,6 +264,7 @@ function routeKey(options: Omit { + for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true }); +}); + +function tempRoot(): string { + const root = realpathSync(mkdtempSync(join(tmpdir(), "scriptc-checker-"))); + roots.push(root); + return root; +} + +function writePackage(directory: string, name: string, executable?: string): void { + mkdirSync(directory, { recursive: true }); + writeFileSync(join(directory, "package.json"), JSON.stringify({ name })); + if (executable === undefined) return; + mkdirSync(join(directory, "lib"), { recursive: true }); + writeFileSync(join(directory, "lib", executable), ""); +} + +test("tsc-ts is the default checker and SCRIPTC_CHECKER=tsgo selects tsgo", () => { + expect(checkerBackend({})).toBe("tsc-ts"); + expect(checkerBackend({ SCRIPTC_CHECKER: "" })).toBe("tsc-ts"); + expect(checkerBackend({ SCRIPTC_CHECKER: "tsc-ts" })).toBe("tsc-ts"); + expect(checkerBackend({ SCRIPTC_CHECKER: "tsgo" })).toBe("tsgo"); + expect(() => checkerBackend({ SCRIPTC_CHECKER: "tsc" })).toThrow( + 'SCRIPTC_CHECKER must be tsgo or tsc-ts, not "tsc"', + ); +}); + +test("tsc-ts platform packages are named for the host", () => { + expect(tscTsPlatformPackage("linux", "x64", false)).toBe("@tsc-ts/linux-x64-gnu"); + expect(tscTsPlatformPackage("linux", "arm64", true)).toBe("@tsc-ts/linux-arm64-musl"); + expect(tscTsPlatformPackage("darwin", "arm64", false)).toBe("@tsc-ts/darwin-arm64"); + expect(tscTsPlatformPackage("win32", "x64", false)).toBe("@tsc-ts/win32-x64-msvc"); +}); + +test("the tsc-ts executable is found next to an npm install of tsc-ts", () => { + const root = tempRoot(); + writePackage(join(root, "node_modules/tsc-ts"), "tsc-ts"); + writePackage(join(root, "node_modules/@tsc-ts/linux-x64-gnu"), "@tsc-ts/linux-x64-gnu", "tsc-ts"); + mkdirSync(join(root, "src/deep"), { recursive: true }); + expect(tscTsExecutable([join(root, "src/deep")], "@tsc-ts/linux-x64-gnu", "linux")).toBe( + join(root, "node_modules/@tsc-ts/linux-x64-gnu/lib/tsc-ts"), + ); +}); + +test("the tsc-ts executable is found in a pnpm install of tsc-ts", () => { + const root = tempRoot(); + const store = join(root, "node_modules/.pnpm/tsc-ts@0.0.3/node_modules"); + writePackage(join(store, "tsc-ts"), "tsc-ts"); + writePackage(join(store, "@tsc-ts/win32-x64-msvc"), "@tsc-ts/win32-x64-msvc", "tsc-ts.exe"); + mkdirSync(join(root, "app/node_modules"), { recursive: true }); + symlinkSync(join(store, "tsc-ts"), join(root, "app/node_modules/tsc-ts")); + expect(tscTsExecutable([join(root, "app")], "@tsc-ts/win32-x64-msvc", "win32")).toBe( + join(store, "@tsc-ts/win32-x64-msvc/lib/tsc-ts.exe"), + ); +}); + +test("a missing tsc-ts install or platform package is an error", () => { + const root = tempRoot(); + expect(() => tscTsExecutable([root], "@tsc-ts/linux-x64-gnu", "linux")).toThrow( + "the tsc-ts package is not installed", + ); + writePackage(join(root, "node_modules/tsc-ts"), "tsc-ts"); + expect(() => tscTsExecutable([root], "@tsc-ts/linux-x64-gnu", "linux")).toThrow( + "has no @tsc-ts/linux-x64-gnu executable", + ); +}); diff --git a/packages/compiler/src/frontend/ts7/checker-backend.ts b/packages/compiler/src/frontend/ts7/checker-backend.ts new file mode 100644 index 000000000..a2c989edb --- /dev/null +++ b/packages/compiler/src/frontend/ts7/checker-backend.ts @@ -0,0 +1,105 @@ +import { existsSync, realpathSync, statSync } from "node:fs"; +import { dirname, join, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; + +/** The TypeScript 7 API servers a compile can check with: tsc-ts, the default, + * and tsgo from the `typescript` package, selected with `SCRIPTC_CHECKER=tsgo`. + * Both serve the same API and give the same results. */ +export type CheckerBackend = "tsgo" | "tsc-ts"; + +export function checkerBackend(env: NodeJS.ProcessEnv = process.env): CheckerBackend { + const value = env["SCRIPTC_CHECKER"]; + if (value === undefined || value === "" || value === "tsc-ts") return "tsc-ts"; + if (value === "tsgo") return "tsgo"; + throw new Error(`SCRIPTC_CHECKER must be tsgo or tsc-ts, not ${JSON.stringify(value)}`); +} + +/** tsc-ts's platform package for a host: `@tsc-ts/linux-x64-gnu`, + * `@tsc-ts/darwin-arm64`, `@tsc-ts/win32-x64-msvc`, and so on. */ +export function tscTsPlatformPackage( + platform: string, + architecture: string, + musl: boolean, +): string { + const abi = + platform === "linux" ? (musl ? "-musl" : "-gnu") : platform === "win32" ? "-msvc" : ""; + return `@tsc-ts/${platform}-${architecture}${abi}`; +} + +let hostMusl: boolean | undefined; + +/** Whether this process runs on musl, the libc of tsc-ts's `-musl` Linux packages: Node reports no glibc + * runtime version there. The libc cannot change within a process, and a report is costly to generate. */ +export function hostIsMusl(): boolean { + if (process.platform !== "linux") return false; + if (hostMusl === undefined) { + const report = process.report?.getReport() as + | { header?: { glibcVersionRuntime?: unknown } } + | undefined; + hostMusl = typeof report?.header?.glibcVersionRuntime !== "string"; + } + return hostMusl; +} + +/** The checker's part of an executable cache key: the identity of the tsc-ts + * executable, or undefined for tsgo, whose keys predate the choice. */ +export function checkerCacheIdentity(env: NodeJS.ProcessEnv = process.env): string | undefined { + if (checkerBackend(env) === "tsgo") return undefined; + const path = hostTscTsExecutable(); + const info = statSync(path); + return JSON.stringify({ + path, + canonical: realpathSync(path), + dev: info.dev, + ino: info.ino, + size: info.size, + mtime: info.mtimeMs, + ctime: info.ctimeMs, + }); +} + +/** The Node host's tsc-ts executable: that of the `tsc-ts` package installed + * with this compiler, or else for the working directory. */ +export function hostTscTsExecutable(): string { + return tscTsExecutable( + [dirname(fileURLToPath(import.meta.url)), process.cwd()], + tscTsPlatformPackage(process.platform, process.arch, hostIsMusl()), + process.platform, + ); +} + +/** The tsc-ts executable of the `tsc-ts` package in the nearest + * `node_modules` above each of `starts`, in order. The platform package is + * installed next to `tsc-ts` (npm) or in the directory that holds it (pnpm). */ +export function tscTsExecutable( + starts: readonly string[], + platformPackage: string, + platform: string, +): string { + const name = platform === "win32" ? "tsc-ts.exe" : "tsc-ts"; + for (const start of starts) { + const packageRoot = installedPackage(start, "tsc-ts"); + if (packageRoot === undefined) continue; + const root = realpathSync(packageRoot); + for (const candidate of [ + join(root, "node_modules", platformPackage), + join(dirname(root), platformPackage), + ]) { + const executable = join(candidate, "lib", name); + if (existsSync(executable)) return executable; + } + throw new Error(`tsc-ts checker: ${root} has no ${platformPackage} executable`); + } + throw new Error("tsc-ts checker: the tsc-ts package is not installed"); +} + +function installedPackage(start: string, name: string): string | undefined { + let directory = resolve(start); + for (;;) { + const candidate = join(directory, "node_modules", name); + if (existsSync(join(candidate, "package.json"))) return candidate; + const parent = dirname(directory); + if (parent === directory) return undefined; + directory = parent; + } +} diff --git a/packages/compiler/src/frontend/ts7/rpc-api.ts b/packages/compiler/src/frontend/ts7/rpc-api.ts index a6ae609d4..a21ba6279 100644 --- a/packages/compiler/src/frontend/ts7/rpc-api.ts +++ b/packages/compiler/src/frontend/ts7/rpc-api.ts @@ -1,6 +1,7 @@ import { createRequire } from "node:module"; import { dirname, join } from "node:path"; import { Ts7Api as ConnectedTs7Api } from "./api.js"; +import { checkerBackend, hostTscTsExecutable } from "./checker-backend.js"; import { Ts7RpcClient } from "./rpc-client.js"; import { TS7_FILE_SYSTEM_CALLBACKS, type Ts7FileSystem } from "./rpc-filesystem.js"; import { spawnTs7Wire } from "./rpc-process.js"; @@ -12,8 +13,15 @@ const { default: getExePath } = require(join(packageRoot, "lib/getExePath.js")) }; /** Platform-package discovery and process creation remain at the host - * boundary. The session itself uses no SDK implementation or Node process. */ + * boundary. The session itself uses no SDK implementation or Node process. + * The executable is tsc-ts's (`hostTscTsExecutable`), or tsgo's with + * `SCRIPTC_CHECKER=tsgo`. */ export function ts7Executable(): string { + return checkerBackend() === "tsgo" ? getExePath() : hostTscTsExecutable(); +} + +/** tsgo's executable, from the `typescript` package, whichever checker compiles. */ +export function tsgoExecutable(): string { return getExePath(); } diff --git a/packages/compiler/src/frontend/ts7/semantic-checker.test.ts b/packages/compiler/src/frontend/ts7/semantic-checker.test.ts index c917a6f66..6eac5fcce 100644 --- a/packages/compiler/src/frontend/ts7/semantic-checker.test.ts +++ b/packages/compiler/src/frontend/ts7/semantic-checker.test.ts @@ -6,7 +6,7 @@ import { API } from "typescript/unstable/sync"; import type { Node as SdkNode } from "typescript/unstable/ast"; import { AstNode } from "./ast-node.js"; import { Ts7RpcClient } from "./rpc-client.js"; -import { ts7Executable } from "./rpc-api.js"; +import { tsgoExecutable } from "./rpc-api.js"; import { spawnTs7Wire } from "./rpc-process.js"; import { Ts7Session } from "./session.js"; import { SyntaxKind } from "./enums.js"; @@ -45,8 +45,9 @@ beforeAll(() => { files: [file], }), ); + // Compared with the pinned SDK, which runs tsgo. session = new Ts7Session( - new Ts7RpcClient(spawnTs7Wire(ts7Executable(), ["--api", "--cwd", directory])), + new Ts7RpcClient(spawnTs7Wire(tsgoExecutable(), ["--api", "--cwd", directory])), ); oracle = new API({ cwd: directory }); }); diff --git a/packages/compiler/src/frontend/ts7/session.test.ts b/packages/compiler/src/frontend/ts7/session.test.ts index 47c6d4a82..96149621b 100644 --- a/packages/compiler/src/frontend/ts7/session.test.ts +++ b/packages/compiler/src/frontend/ts7/session.test.ts @@ -11,7 +11,7 @@ import { type Ts7FileSystem, } from "./rpc-filesystem.js"; import { spawnTs7Wire } from "./rpc-process.js"; -import { ts7Executable } from "./rpc-api.js"; +import { tsgoExecutable } from "./rpc-api.js"; import { Ts7Session, Ts7SessionProject, Ts7SessionSnapshot } from "./session.js"; import { AstNode } from "./ast-node.js"; @@ -25,9 +25,10 @@ const fallback: Ts7FileSystem = { getAccessibleEntries: () => undefined, }; +// These tests compare the session with the pinned SDK, which runs tsgo, so the session runs tsgo too. function transport(cwd: string, fs: Ts7FileSystem = fallback, timing = false) { const rpc = new Ts7RpcClient( - spawnTs7Wire(ts7Executable(), [ + spawnTs7Wire(tsgoExecutable(), [ "--api", "--cwd", cwd, diff --git a/packages/compiler/src/frontend/ts7/source-parser.test.ts b/packages/compiler/src/frontend/ts7/source-parser.test.ts index e416d2920..2001f11fb 100644 --- a/packages/compiler/src/frontend/ts7/source-parser.test.ts +++ b/packages/compiler/src/frontend/ts7/source-parser.test.ts @@ -206,3 +206,30 @@ test("JSDoc callable types cannot replace method body views", () => { expect(member.body?.parent).toBe(member); } }); + +test("a parser starts a new server once it has parsed its source budget, and earlier results stay usable", () => { + const directory = mkdtempSync( + join(process.platform === "win32" ? tmpdir() : "/tmp", "scriptc-source-parser-"), + ); + directories.push(directory); + const servers: Ts7Api[] = []; + const parser = new Ts7SourceParser( + (options) => { + const api = new Ts7Api(options); + servers.push(api); + return api; + }, + directory, + 64, + ); + parsers.push(parser); + const first = parser.parse("a.ts", `export const a = ${"1 + ".repeat(20)}1;`, "ts"); + expect(servers).toHaveLength(1); + const second = parser.parse("b.ts", "export const b = 2;", "ts"); + expect(servers).toHaveLength(2); + const third = parser.parse("b.ts", "export const b = 3;", "ts"); + expect(servers).toHaveLength(2); + expect(first.statements[0]!.getText()).toBe(`export const a = ${"1 + ".repeat(20)}1;`); + expect(second.statements[0]!.getText()).toBe("export const b = 2;"); + expect(third.statements[0]!.getText()).toBe("export const b = 3;"); +}); diff --git a/packages/compiler/src/frontend/ts7/source-parser.ts b/packages/compiler/src/frontend/ts7/source-parser.ts index 38605cd64..f9029774c 100644 --- a/packages/compiler/src/frontend/ts7/source-parser.ts +++ b/packages/compiler/src/frontend/ts7/source-parser.ts @@ -7,12 +7,21 @@ import { tsgoPath } from "./session-path.js"; export type Ts7SourceKind = "js" | "jsx" | "ts" | "tsx"; export type Ts7SourceApiFactory = (options: { cwd: string; fs: Ts7FileSystem }) => Ts7Api; +/** The source text, in UTF-16 code units, one parser server parses before a + * new server replaces it. tsc-ts keeps the syntax trees it builds for the + * life of its process, so a server that parses many files grows with them; a + * new one starts empty. Results are detached ASTs and outlive the server that + * made them. */ +const SERVER_SOURCE_BUDGET = 256 * 1024; + /** A syntax-only session with a closed, in-memory filesystem. Parsing a * source string never reads imports, project configuration, or libraries * from disk. The returned AST owns its wire data and outlives the session. * Only one project and one input are retained between requests. */ export class Ts7SourceParser { - private readonly api: Ts7Api; + private api: Ts7Api; + private readonly createApi: Ts7SourceApiFactory; + private parsedSource = 0; private readonly files = new Map(); private readonly configPath: string; private previousPath: string | undefined; @@ -21,10 +30,19 @@ export class Ts7SourceParser { private closed = false; private readonly cwd: string; - constructor(createApi: Ts7SourceApiFactory, cwd = process.cwd()) { + constructor( + createApi: Ts7SourceApiFactory, + cwd = process.cwd(), + private readonly sourceBudget = SERVER_SOURCE_BUDGET, + ) { this.cwd = resolve(cwd); this.configPath = tsgoPath(resolve(this.cwd, "__scriptc-source-parser.tsconfig.json")); - this.api = createApi({ + this.createApi = createApi; + this.api = this.connect(); + } + + private connect(): Ts7Api { + return this.createApi({ cwd: this.cwd, fs: { readFile: (path) => this.files.get(tsgoPath(path)) ?? null, @@ -61,7 +79,14 @@ export class Ts7SourceParser { this.previousFile !== undefined ) return this.previousFile; - const previousPath = this.previousPath; + let previousPath = this.previousPath; + if (this.parsedSource >= this.sourceBudget) { + this.api.close(); + this.api = this.connect(); + this.parsedSource = 0; + previousPath = undefined; + } + this.parsedSource += source.length; this.files.clear(); this.files.set(path, source); this.files.set( diff --git a/packages/compiler/src/index.ts b/packages/compiler/src/index.ts index 56b787e18..3c16bdf9c 100644 --- a/packages/compiler/src/index.ts +++ b/packages/compiler/src/index.ts @@ -8,6 +8,7 @@ import { libraryWasmRefusal, } from "./library/prepare.js"; import { analyzeWithFrontend } from "./frontend/analysis.js"; +import { checkerCacheIdentity } from "./frontend/ts7/checker-backend.js"; import { analyzeWithVerdictCache, fileVerdictStore } from "./coverage/verdict-cache.js"; import { coverageCompilerIdentity } from "./coverage/verdict-startup.js"; import { commentOnlyReplay } from "./coverage/verdict-semantic.js"; @@ -378,6 +379,12 @@ export async function analyzeCached( const nodeFrontend: FrontendFactory = (entry, npmStatic, externalTypes, libraryNpmStatic) => runFrontend(entry, loadProgram, npmStatic, externalTypes, libraryNpmStatic); +/** The checker's part of the executable cache key (`checkerCacheIdentity`), as the CLI's startup route computes it. */ +function checkerField(): { checker?: string } { + const checker = checkerCacheIdentity(); + return checker === undefined ? {} : { checker }; +} + /** The whole pipeline: load → preflight → lower → validate → emit LLVM → link. */ function clearCompileSessionCaches(): void { clearResolveCaches(); @@ -1046,6 +1053,7 @@ async function compileTracked( ...(opts.strip ? { strip: true as const } : {}), ...(opts.windowsSubsystem === "gui" ? { windowsSubsystem: "gui" as const } : {}), ...(opts.detachedExit === true ? { detachedExit: true as const } : {}), + ...checkerField(), npmStatic: opts.npmStatic ?? null, ffiProfile: opts.ffiProfilePath === undefined || ffiProfileBytes === null diff --git a/packages/compiler/src/native/toolchain.test.ts b/packages/compiler/src/native/toolchain.test.ts index 9afccdc18..78d2c9c7e 100644 --- a/packages/compiler/src/native/toolchain.test.ts +++ b/packages/compiler/src/native/toolchain.test.ts @@ -14,6 +14,7 @@ test("native toolchain installation paths relocate with the manifest", () => { compiler_version: "1.2.3", target: "macos-arm64", ts7: "typescript/tsc", + tsc_ts: "tsc-ts/lib/tsc-ts", llvm_package: "llvm", runtime_pack: "runtime", linker: "tools/linker", @@ -23,7 +24,10 @@ test("native toolchain installation paths relocate with the manifest", () => { try { writeFileSync(path, JSON.stringify(manifest)); const toolchain = loadNativeToolchain(path); - expect(toolchain.ts7Executable).toBe(join(root, "typescript/tsc")); + expect(toolchain.ts7Executable).toBe(join(root, "tsc-ts/lib/tsc-ts")); + expect(loadNativeToolchain(path, { SCRIPTC_CHECKER: "tsgo" }).ts7Executable).toBe( + join(root, "typescript/tsc"), + ); expect(toolchain.helperExecutable).toBe(join(root, "llvm/bin/scriptc-llvm-codegen")); expect(toolchain.runtimePackRoot).toBe(join(root, "runtime")); expect(toolchain.linker).toBe(join(root, "tools/linker")); @@ -39,6 +43,7 @@ test("native toolchain installation paths relocate with the manifest", () => { { ...manifest, schema: "other" }, { ...manifest, target: "other" }, { ...manifest, ts7: "" }, + { ...manifest, tsc_ts: "" }, { ...manifest, linker_args: [1] }, ]) { writeFileSync(path, JSON.stringify(invalid)); @@ -71,6 +76,7 @@ test("native toolchain assets resolve from the physical manifest through package compiler_version: "1.2.3", target: "windows-x64-msvc", ts7: asset("typescript/lib/tsc.exe"), + tsc_ts: asset("tsc-ts/lib/tsc-ts.exe"), llvm_package: asset("llvm"), runtime_pack: asset("runtime"), runtime_packs: [{ target: "windows-x64-msvc", path: asset("runtime") }], @@ -93,7 +99,7 @@ test("native toolchain assets resolve from the physical manifest through package const direct = loadNativeToolchain(path); const viaLink = loadNativeToolchain(join(linked, "bin", "scriptc.exe.json")); expect(viaLink).toEqual(direct); - expect(viaLink.ts7Executable).toBe(join(assets, "typescript/lib/tsc.exe")); + expect(viaLink.ts7Executable).toBe(join(assets, "tsc-ts/lib/tsc-ts.exe")); expect(viaLink.helperExecutable).toBe(join(assets, "llvm/bin/scriptc-llvm-codegen.exe")); expect( loadNativeToolchain(join(linked, "bin", "scriptc.exe.json"), { @@ -126,6 +132,7 @@ test("cross-target packs can be added to a project after the compiler is install compiler_version: "1.2.3", target: "macos-arm64", ts7: "../lib/typescript/tsc", + tsc_ts: "../lib/tsc-ts/lib/tsc-ts", llvm_package: "../lib/llvm", runtime_pack: "../lib/runtime-darwin-arm64", linker: "clang", @@ -174,6 +181,7 @@ test("versioned GNU targets select Zig while ordinary host builds keep the insta compiler_version: "1.2.3", target: "linux-x64-gnu", ts7: "tsc", + tsc_ts: "tsc-ts", llvm_package: "llvm", runtime_pack: "runtime", linker: "clang", @@ -192,3 +200,41 @@ test("versioned GNU targets select Zig while ordinary host builds keep the insta rmSync(root, { recursive: true, force: true }); } }); + +test("a manifest without tsc_ts takes the host's tsc-ts executable from the installed package", () => { + const root = realpathSync( + mkdtempSync(join(process.platform === "win32" ? tmpdir() : "/tmp", "scriptc-checker-tools-")), + ); + const path = join(root, "toolchain.json"); + const manifest: NativeToolchainManifest = { + schema: "scriptc.native-toolchain.v1", + compiler_version: "1.2.3", + target: "linux-x64-musl", + ts7: "typescript/tsc", + llvm_package: "llvm", + runtime_pack: "runtime", + linker: "tools/linker", + linker_args: ["cc"], + dsymutil: "dsymutil", + }; + try { + writeFileSync(path, JSON.stringify(manifest)); + const project = join(root, "project"); + const platformPackage = join(project, "node_modules/@tsc-ts/linux-x64-musl"); + mkdirSync(join(project, "node_modules/tsc-ts"), { recursive: true }); + writeFileSync(join(project, "node_modules/tsc-ts/package.json"), '{"name":"tsc-ts"}'); + mkdirSync(join(platformPackage, "lib"), { recursive: true }); + writeFileSync(join(platformPackage, "lib/tsc-ts"), ""); + expect(loadNativeToolchain(path, {}, project).ts7Executable).toBe( + join(platformPackage, "lib/tsc-ts"), + ); + expect(loadNativeToolchain(path, { SCRIPTC_CHECKER: "tsgo" }, project).ts7Executable).toBe( + join(root, "typescript/tsc"), + ); + expect(() => loadNativeToolchain(path, {}, root)).toThrow( + "the tsc-ts package is not installed", + ); + } finally { + rmSync(root, { recursive: true, force: true }); + } +}); diff --git a/packages/compiler/src/native/toolchain.ts b/packages/compiler/src/native/toolchain.ts index 651d87027..83da0f7c2 100644 --- a/packages/compiler/src/native/toolchain.ts +++ b/packages/compiler/src/native/toolchain.ts @@ -6,6 +6,11 @@ import { type NativeTargetSpec, type NativeHelperSpec, } from "../backend/targets.js"; +import { + checkerBackend, + tscTsExecutable, + tscTsPlatformPackage, +} from "../frontend/ts7/checker-backend.js"; export interface NativeToolchain { compilerVersion: string; @@ -34,6 +39,8 @@ export interface NativeToolchainManifest { compiler_version: string; target: string; ts7: string; + /** The tsc-ts executable, the default checker; without it, an installed `tsc-ts` package's. */ + tsc_ts?: string; llvm_package: string; runtime_pack: string; runtime_sources?: string; @@ -106,6 +113,7 @@ export function loadNativeToolchain( } const manifest = raw as NativeToolchainManifest; for (const name of [ + "tsc_ts", "runtime_sources", "declarations", "archiver", @@ -169,7 +177,19 @@ export function loadNativeToolchain( : [...target.defaultLinkerArgs]; return { compilerVersion: manifest.compiler_version, - ts7Executable: pathFrom(root, manifest.ts7), + // tsc-ts, the default checker, comes with the distribution; a development + // build takes it from the tsc-ts package installed with the compiler or for + // the working directory. SCRIPTC_CHECKER=tsgo selects tsgo. + ts7Executable: + checkerBackend(env) === "tsgo" + ? pathFrom(root, manifest.ts7) + : manifest.tsc_ts !== undefined + ? pathFrom(root, manifest.tsc_ts) + : tscTsExecutable( + [root, cwd], + tscTsPlatformPackage(host.platform, host.architecture, host.name.endsWith("-musl")), + host.platform, + ), target, helper: host.helper, helperPackageRoot, diff --git a/packages/compiler/src/startup-cache.ts b/packages/compiler/src/startup-cache.ts index fedc36fd5..c2f82fdad 100644 --- a/packages/compiler/src/startup-cache.ts +++ b/packages/compiler/src/startup-cache.ts @@ -22,5 +22,6 @@ export function precompiledRuntimePackTarget( ): NativeTargetSpec | null { return nativeCodegenTarget(env); } +export { checkerCacheIdentity } from "./frontend/ts7/checker-backend.js"; export { readCoverageVerdict } from "./coverage/verdict-startup.js"; export { coverageEnvelope, coveragePasses, renderCoverage } from "./coverage/report.js"; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 41d9e02ac..cbe529577 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -106,6 +106,9 @@ importers: '@scriptc/runtime': specifier: workspace:* version: link:../runtime + tsc-ts: + specifier: 0.0.3 + version: 0.0.3 typescript: specifier: 7.0.2 version: 7.0.2 @@ -1367,6 +1370,45 @@ packages: '@ts-morph/common@0.11.1': resolution: {integrity: sha512-7hWZS0NRpEsNV8vWJzg7FEz6V8MaLNeJOmwmghqUXTpzk16V1LLZhdo+4QvE/+zv4cVci0OviuJFnqhEfoV3+g==} + '@tsc-ts/darwin-arm64@0.0.3': + resolution: {integrity: sha512-NSP4IhLx658tY21ub3ruraZwUwAC3GCbIf1eM0eOKUHfOBBXqq7LU/Loj3zvlvn/tJUyv2VdIDmzrWI/mxp5sA==} + cpu: [arm64] + os: [darwin] + + '@tsc-ts/darwin-x64@0.0.3': + resolution: {integrity: sha512-+BNvqyygkMx0/oHkjK8AmsPrE2+OcE/hGzT0wa2dndiH8lCSDBs+77C7Kmy/FSTWi9ItJShp1+AuWKgQPEOztg==} + cpu: [x64] + os: [darwin] + + '@tsc-ts/linux-arm64-gnu@0.0.3': + resolution: {integrity: sha512-I3v0J0L387TEkau0BTGiVx2GdstYfOrwVYwb0pJ/LbFURhrcGcsUODP5bdSOIYCtsp3aAX6e2afdqSIlpsxtbw==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@tsc-ts/linux-arm64-musl@0.0.3': + resolution: {integrity: sha512-f7wD9D6fC1xL7+ifvMAbJ0TqxOuZP04R2/7c8lb7Gor5SFKe85udJoxf0gocK7mbzWzmEiOJ53J1PujJLBVIxA==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@tsc-ts/linux-x64-gnu@0.0.3': + resolution: {integrity: sha512-BL9zownW4tHv/qqQkF8OlurHNDXi7xtGtJ6PV4qMeRYPMKsivTIGFRoZLW023KcLOr1Zptaof6X3zpv1nJEIpA==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@tsc-ts/linux-x64-musl@0.0.3': + resolution: {integrity: sha512-g4/lE5sULNGUVLM0Px/jrsjLsQQAYSiL+Rl+JZ+l5JE2k1dXroCejT2+IRB/NucVcz6N8EOUiCRZ13zl+Wtpcg==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@tsc-ts/win32-x64-msvc@0.0.3': + resolution: {integrity: sha512-oGtboSHgpuQP1dHAHqWyGjAWCpvHDpciYCi/2ywcp6MT+oOHxC99X48H/PZr29fzYVxCVeaEqWZ/VFU9qLZaKg==} + cpu: [x64] + os: [win32] + '@tybys/wasm-util@0.10.3': resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==} @@ -2524,6 +2566,11 @@ packages: ts-toolbelt@6.15.5: resolution: {integrity: sha512-FZIXf1ksVyLcfr7M317jbB67XFJhOO1YqdTcuGaq9q5jLUoTikukZ+98TPjKiP2jC5CgmYdWWYs0s2nLSU0/1A==} + tsc-ts@0.0.3: + resolution: {integrity: sha512-Qr0i/BxP3ULIPDfr38vRb02x7sj6Q0r70Gubgu4ty2J4CEDCUKzT8RT2KMNqLXI3ZAhl7cncJq7a4LjKYZ1+0g==} + engines: {node: '>=20'} + hasBin: true + tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} @@ -3394,6 +3441,27 @@ snapshots: mkdirp: 1.0.4 path-browserify: 1.0.1 + '@tsc-ts/darwin-arm64@0.0.3': + optional: true + + '@tsc-ts/darwin-x64@0.0.3': + optional: true + + '@tsc-ts/linux-arm64-gnu@0.0.3': + optional: true + + '@tsc-ts/linux-arm64-musl@0.0.3': + optional: true + + '@tsc-ts/linux-x64-gnu@0.0.3': + optional: true + + '@tsc-ts/linux-x64-musl@0.0.3': + optional: true + + '@tsc-ts/win32-x64-msvc@0.0.3': + optional: true + '@tybys/wasm-util@0.10.3': dependencies: tslib: 2.8.1 @@ -4730,6 +4798,16 @@ snapshots: ts-toolbelt@6.15.5: {} + tsc-ts@0.0.3: + optionalDependencies: + '@tsc-ts/darwin-arm64': 0.0.3 + '@tsc-ts/darwin-x64': 0.0.3 + '@tsc-ts/linux-arm64-gnu': 0.0.3 + '@tsc-ts/linux-arm64-musl': 0.0.3 + '@tsc-ts/linux-x64-gnu': 0.0.3 + '@tsc-ts/linux-x64-musl': 0.0.3 + '@tsc-ts/win32-x64-msvc': 0.0.3 + tslib@2.8.1: optional: true diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 8f10bfca8..d4d370721 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -7,6 +7,14 @@ minimumReleaseAgeExclude: - "effect@4.0.0" - "@effect/platform-node@4.0.0" - "@effect/platform-node-shared@4.0.0" + - "@tsc-ts/darwin-arm64@0.0.3" + - "@tsc-ts/darwin-x64@0.0.3" + - "@tsc-ts/linux-arm64-gnu@0.0.3" + - "@tsc-ts/linux-arm64-musl@0.0.3" + - "@tsc-ts/linux-x64-gnu@0.0.3" + - "@tsc-ts/linux-x64-musl@0.0.3" + - "@tsc-ts/win32-x64-msvc@0.0.3" + - "tsc-ts@0.0.3" allowBuilds: esbuild: true overrides: diff --git a/scripts/build-native-cli.mts b/scripts/build-native-cli.mts index fa4cb74a7..c8a14adbd 100644 --- a/scripts/build-native-cli.mts +++ b/scripts/build-native-cli.mts @@ -5,7 +5,7 @@ import { basename, dirname, join, relative, resolve } from "node:path"; import { fileURLToPath } from "node:url"; import { compile } from "../packages/compiler/src/index.js"; import { NATIVE_TARGETS, nativeCodegenTarget } from "../packages/compiler/src/backend/targets.js"; -import { ts7Executable } from "../packages/compiler/src/frontend/ts7/rpc-api.js"; +import { tsgoExecutable } from "../packages/compiler/src/frontend/ts7/rpc-api.js"; import { stageNativeCliAssets } from "./native-cli-assets.mjs"; import type { NativeToolchainManifest } from "../packages/compiler/src/native/toolchain.js"; @@ -108,7 +108,7 @@ const assets = stageNativeCliAssets({ repository: root, output, target, - ts7: ts7Executable(), + ts7: tsgoExecutable(), compilerVersion, }); // A cross-libc seed may use Zig on the build host. The installed compiler diff --git a/scripts/checker-identity.mjs b/scripts/checker-identity.mjs new file mode 100644 index 000000000..35d01cb93 --- /dev/null +++ b/scripts/checker-identity.mjs @@ -0,0 +1,130 @@ +#!/usr/bin/env node +// Compiles every corpus program to LLVM IR with each TypeScript 7 API server (tsc-ts, the default, and tsgo with +// SCRIPTC_CHECKER=tsgo) and fails unless both give the same IR and the same diagnostics. +// +// Usage: node scripts/checker-identity.mjs [--jobs ] [--optimization release|dev] [--filter ] +// +// It needs `pnpm build`. Each checker compiles in its own worker processes, which read SCRIPTC_CHECKER when they +// start their servers. +import { fork } from "node:child_process"; +import { globSync, mkdtempSync, readFileSync, rmSync } from "node:fs"; +import { availableParallelism, tmpdir } from "node:os"; +import { dirname, join, resolve } from "node:path"; +import { fileURLToPath, pathToFileURL } from "node:url"; +import { parseArgs } from "node:util"; + +const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), ".."); +const corpusDir = join(repoRoot, "tests/corpus"); +const checkers = ["tsgo", "tsc-ts"]; + +if (process.argv[2] === "--worker") await worker(process.argv.slice(3)); +else await main(); + +async function main() { + const { values } = parseArgs({ + options: { + jobs: { + type: "string", + default: String(Math.max(1, Math.floor(availableParallelism() / 2))), + }, + optimization: { type: "string", default: "release" }, + filter: { type: "string", default: "" }, + }, + }); + if (values.optimization !== "release" && values.optimization !== "dev") + throw new Error("--optimization must be release or dev"); + // The entries llvm-differential.test.ts compiles. + const entries = ["ts", "js", "mjs", "cjs"] + .flatMap((ext) => [ + ...globSync(join(corpusDir, `*.${ext}`)), + ...globSync(join(corpusDir, `*/main.${ext}`)), + ]) + .filter((file) => file.includes(values.filter)) + .sort(); + const jobs = Math.max(1, Number(values.jobs)); + const outRoot = mkdtempSync(join(tmpdir(), "scriptc-checker-identity-")); + try { + const results = new Map(checkers.map((checker) => [checker, new Map()])); + const runs = []; + for (const checker of checkers) + for (let index = 0; index < jobs; index++) + runs.push( + new Promise((done, fail) => { + const child = fork( + fileURLToPath(import.meta.url), + [ + "--worker", + join(outRoot, checker), + values.optimization, + ...entries.filter((_, i) => i % jobs === index), + ], + { env: { ...process.env, SCRIPTC_CHECKER: checker } }, + ); + child.on("message", (record) => results.get(checker).set(record.file, record)); + child.on("error", fail); + child.on("exit", (code) => + code === 0 ? done() : fail(new Error(`${checker} worker exited with ${code}`)), + ); + }), + ); + await Promise.all(runs); + const differences = []; + for (const file of entries) { + const [a, b] = checkers.map((checker) => results.get(checker).get(file)); + const name = file.slice(corpusDir.length + 1); + if (a === undefined || b === undefined) differences.push(`${name}: not compiled`); + else if (a.ok !== b.ok || a.diagnostics !== b.diagnostics) + differences.push( + `${name}: tsgo ${a.ok ? "compiles" : a.diagnostics}; tsc-ts ${b.ok ? "compiles" : b.diagnostics}`, + ); + else if (a.ok && !readFileSync(a.llvm).equals(readFileSync(b.llvm))) + differences.push(`${name}: LLVM IR differs`); + } + if (differences.length > 0) { + console.error( + `${differences.length} of ${entries.length} programs differ between tsgo and tsc-ts:`, + ); + for (const line of differences) console.error(` ${line}`); + process.exitCode = 1; + } else { + console.log( + `${entries.length} programs: tsgo and tsc-ts give the same LLVM IR and diagnostics (${values.optimization}).`, + ); + } + } finally { + rmSync(outRoot, { recursive: true, force: true }); + } +} + +async function worker([outDir, optimization, ...files]) { + const { compile } = await import( + pathToFileURL(join(repoRoot, "packages/compiler/dist/index.js")).href + ); + for (const file of files) { + const name = file.slice(corpusDir.length + 1); + const llvm = join(outDir, `${name}.ll`); + let record; + try { + const result = await compile(file, { + outputKind: "llvm", + outPath: llvm, + outDir: join(outDir, ".work", name), + optimization, + }); + record = result.ok + ? { file, ok: true, llvm } + : { + file, + ok: false, + diagnostics: result.diagnostics.map((d) => `${d.code}: ${d.message}`).join("; "), + }; + } catch (error) { + record = { + file, + ok: false, + diagnostics: `threw ${error instanceof Error ? error.message : String(error)}`, + }; + } + process.send(record); + } +} diff --git a/scripts/native-cli-assets.mts b/scripts/native-cli-assets.mts index 614fe52d5..b3d1436bd 100644 --- a/scripts/native-cli-assets.mts +++ b/scripts/native-cli-assets.mts @@ -2,6 +2,10 @@ import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync import { basename, dirname, join, relative } from "node:path"; import type { NativeTargetSpec } from "../packages/compiler/src/backend/targets.js"; import type { NativeToolchainManifest } from "../packages/compiler/src/native/toolchain.js"; +import { + tscTsExecutable, + tscTsPlatformPackage, +} from "../packages/compiler/src/frontend/ts7/checker-backend.js"; /** Copy release payloads explicitly; development caches and build trees never ship. */ function copyPackage(source: string, destination: string, members: readonly string[]): void { @@ -28,6 +32,7 @@ export function stageNativeCliAssets(options: { }): Pick< NativeToolchainManifest, | "ts7" + | "tsc_ts" | "llvm_package" | "runtime_pack" | "runtime_packs" @@ -54,6 +59,14 @@ export function stageNativeCliAssets(options: { ); const typescript = join(lib, "typescript"); copyPackage(dirname(dirname(options.ts7)), typescript, ["lib"]); + // tsc-ts, the default checker, from the tsc-ts package the compiler depends on. + const tscTsSource = tscTsExecutable( + [join(repository, "packages/compiler")], + tscTsPlatformPackage(target.platform, target.architecture, target.name.endsWith("-musl")), + target.platform, + ); + const tscTs = join(lib, "tsc-ts"); + copyPackage(dirname(dirname(tscTsSource)), tscTs, ["lib"]); const sources = join(lib, "runtime-sources"); copyPackage(join(repository, "packages/runtime"), sources, ["src", "vendor"]); const declarations = join(lib, "declarations"); @@ -74,11 +87,15 @@ export function stageNativeCliAssets(options: { const identity = JSON.parse(readFileSync(join(typescript, "package.json"), "utf8")) as { version: string; }; + const tscTsIdentity = JSON.parse(readFileSync(join(tscTs, "package.json"), "utf8")) as { + version: string; + }; writeFileSync( join(output, "THIRD_PARTY_NOTICES"), [ - `scriptc ${options.compilerVersion} includes the existing TypeScript ${identity.version}, LLVM, and C runtime dependencies.`, + `scriptc ${options.compilerVersion} includes the existing TypeScript ${identity.version}, tsc-ts ${tscTsIdentity.version}, LLVM, and C runtime dependencies.`, "TypeScript licensing: lib/typescript/LICENSE and lib/typescript/NOTICE.txt.", + "tsc-ts licensing and notices: lib/tsc-ts/LICENSE and lib/tsc-ts/THIRD_PARTY_NOTICES.md.", "LLVM licensing and notices: lib/llvm/LICENSE and lib/llvm/THIRD_PARTY_NOTICES.", "Runtime dependency licenses: lib/runtime-sources/vendor/*/LICENSE*.", "", @@ -86,6 +103,7 @@ export function stageNativeCliAssets(options: { ); return { ts7: relative(bin, join(typescript, "lib", basename(options.ts7))), + tsc_ts: relative(bin, join(tscTs, "lib", basename(tscTsSource))), llvm_package: relative(bin, helper), runtime_pack: relative(bin, runtime), runtime_packs: [{ target: target.name, path: relative(bin, runtime) }], diff --git a/scripts/package-native-cli.mjs b/scripts/package-native-cli.mjs index dd42b8eaa..6d1c05991 100644 --- a/scripts/package-native-cli.mjs +++ b/scripts/package-native-cli.mjs @@ -44,6 +44,7 @@ for (const name of readdirSync(packages) for (const path of [ binary, resolve(bin, hostPath(manifest.ts7)), + resolve(bin, hostPath(manifest.tsc_ts)), resolve(bin, hostPath(manifest.comptime)), join( resolve(bin, hostPath(manifest.llvm_package)), diff --git a/scripts/verify-native-cli.mjs b/scripts/verify-native-cli.mjs index b1e061664..115573b5c 100644 --- a/scripts/verify-native-cli.mjs +++ b/scripts/verify-native-cli.mjs @@ -28,6 +28,7 @@ const within = (path) => { }; for (const name of [ "ts7", + "tsc_ts", "llvm_package", "runtime_pack", "runtime_sources", diff --git a/tests/harness/diagnostics.test.ts b/tests/harness/diagnostics.test.ts index 57ac8c6c0..77fd5937d 100644 --- a/tests/harness/diagnostics.test.ts +++ b/tests/harness/diagnostics.test.ts @@ -33,7 +33,17 @@ describe(`diagnostics corpus (${files.length} programs)`, () => { // any backend runs (that is the suite's contract), so the default // lane is irrelevant by construction — and riding it proves the // fallback machinery never turns a diagnostic into a build. - const result = await compile(file, { outPath: join(outDir, "never"), outDir }); + // The checker-panic programs exercise tsgo's own failures (a panic, and a + // +Infinity it cannot send), so they compile with tsgo. + const checker = process.env["SCRIPTC_CHECKER"]; + if (name.startsWith("checker-panic-")) process.env["SCRIPTC_CHECKER"] = "tsgo"; + let result: Awaited>; + try { + result = await compile(file, { outPath: join(outDir, "never"), outDir }); + } finally { + if (checker === undefined) delete process.env["SCRIPTC_CHECKER"]; + else process.env["SCRIPTC_CHECKER"] = checker; + } if (result.ok) { expect.unreachable(`${name} compiled successfully but must produce diagnostics`); } diff --git a/tests/harness/native-cli-packaging.test.ts b/tests/harness/native-cli-packaging.test.ts index a62a3c0c7..e69f9d9fe 100644 --- a/tests/harness/native-cli-packaging.test.ts +++ b/tests/harness/native-cli-packaging.test.ts @@ -42,6 +42,7 @@ function fixture(windows = true) { compiler_version: identity.version, target, ts7: asset(`../lib/typescript/lib/tsc${windows ? ".exe" : ""}`), + tsc_ts: asset(`../lib/tsc-ts/lib/tsc-ts${windows ? ".exe" : ""}`), llvm_package: asset("../lib/llvm"), runtime_pack: asset("../lib/runtime"), runtime_sources: asset("../lib/runtime-sources"), @@ -54,7 +55,12 @@ function fixture(windows = true) { write(join(distribution, "bin", executable), Buffer.alloc(2048, 1)); chmodSync(join(distribution, "bin", executable), 0o755); write(manifestPath, JSON.stringify(manifest)); - for (const path of [manifest.ts7, manifest.comptime, manifest.wasi_node_runner]) { + for (const path of [ + manifest.ts7, + manifest.tsc_ts, + manifest.comptime, + manifest.wasi_node_runner, + ]) { write(join(distribution, "bin", path.replaceAll("\\", "/")), "fixture"); } write( @@ -112,6 +118,7 @@ test.each([true, false])( readFileSync(join(unpacked, "bin", f.executable + ".json"), "utf8"), ); expect(manifest.ts7).toBe(f.manifest.ts7); + expect(manifest.tsc_ts).toBe(f.manifest.tsc_ts); const runtime = manifest.runtime_packs[0]; expect( JSON.parse( diff --git a/tests/harness/self-hosting-native-toolchain.test.ts b/tests/harness/self-hosting-native-toolchain.test.ts index c22e8a839..99668b005 100644 --- a/tests/harness/self-hosting-native-toolchain.test.ts +++ b/tests/harness/self-hosting-native-toolchain.test.ts @@ -120,6 +120,7 @@ for (const backend of ["llvm"] as const) { compiler_version: manifest.version, target: target.name, ts7: "ts7", + tsc_ts: "tsc-ts", llvm_package: "llvm", runtime_pack: "pack", linker: "ld", diff --git a/tests/harness/self-hosting-ts7-process.test.ts b/tests/harness/self-hosting-ts7-process.test.ts index b8d53dff9..d49a5c092 100644 --- a/tests/harness/self-hosting-ts7-process.test.ts +++ b/tests/harness/self-hosting-ts7-process.test.ts @@ -4,7 +4,7 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; import { analyze, compile } from "@scriptc/compiler"; import { expect, test } from "vitest"; -import { ts7Executable } from "../../packages/compiler/src/frontend/ts7/rpc-api.js"; +import { tsgoExecutable } from "../../packages/compiler/src/frontend/ts7/rpc-api.js"; import { runClient } from "./ts7-client-harness.js"; const root = join(import.meta.dirname, "../.."); @@ -96,8 +96,9 @@ for (const backend of ["llvm"] as const) { ); const report = join(dir, "native.json"); // Only ordinary stdio is inherited. The native executable starts the - // actual tsgo process; the test runner never creates its RPC pipes. - const run = spawnSync(built.binaryPath, [ts7Executable(), dir, report], { + // actual tsgo process; the test runner never creates its RPC pipes. As in + // ts7-client-harness.ts, the protocol cases run against tsgo. + const run = spawnSync(built.binaryPath, [tsgoExecutable(), dir, report], { encoding: "utf8", timeout: 45_000, }); diff --git a/tests/harness/ts7-client-harness.ts b/tests/harness/ts7-client-harness.ts index e0d0eccd6..b65dc706f 100644 --- a/tests/harness/ts7-client-harness.ts +++ b/tests/harness/ts7-client-harness.ts @@ -2,7 +2,7 @@ import { spawn, type ChildProcess } from "node:child_process"; import { readFileSync } from "node:fs"; import { fileURLToPath } from "node:url"; import { expect } from "vitest"; -import { ts7Executable } from "../../packages/compiler/src/frontend/ts7/rpc-api.js"; +import { tsgoExecutable } from "../../packages/compiler/src/frontend/ts7/rpc-api.js"; import { TS7_FILE_SYSTEM_CALLBACKS } from "../../packages/compiler/src/frontend/ts7/rpc-filesystem.js"; const root = fileURLToPath(new URL("../..", import.meta.url)); @@ -40,8 +40,10 @@ export async function runClient( directory: string, report: string, ): Promise { + // The protocol cases echo bytes that are not UTF-8 text, which tsc-ts 0.0.3 + // returns decoded, so the transport is checked against tsgo. const server = spawn( - ts7Executable(), + tsgoExecutable(), ["--api", "--timing", "--cwd", directory, `--callbacks=${TS7_FILE_SYSTEM_CALLBACKS}`], { stdio: ["pipe", "pipe", "pipe"], From 9dc45b6776c39b6645987f72f63e9f01f6a39608 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 18 Oct 2026 21:00:33 -0500 Subject: [PATCH 010/122] Check with tsgo by default on Windows --- docs/content/docs/cli.mdx | 2 +- .../src/frontend/ts7/checker-backend.test.ts | 14 +++++++++----- .../compiler/src/frontend/ts7/checker-backend.ts | 12 +++++++++--- packages/compiler/src/native/toolchain.test.ts | 6 +++++- packages/compiler/src/native/toolchain.ts | 8 ++++---- packages/compiler/test/ts7/rpc-api.test.ts | 11 ++++++++++- 6 files changed, 38 insertions(+), 15 deletions(-) diff --git a/docs/content/docs/cli.mdx b/docs/content/docs/cli.mdx index dda2f8d74..8be1a2642 100644 --- a/docs/content/docs/cli.mdx +++ b/docs/content/docs/cli.mdx @@ -180,7 +180,7 @@ Verifies and loads the installed precompiled runtime objects and vendored librar
A runtime pack directory to use instead of an installed @scriptc/runtime-<target> package.
SCRIPTC_CHECKER
-
The TypeScript 7 API server that checks the program: tsc-ts (the default), which scriptc includes, or tsgo, from the typescript package scriptc also includes. Both give the same diagnostics and generated code.
+
The TypeScript 7 API server that checks the program: tsc-ts (the default), which scriptc includes, or tsgo, from the typescript package scriptc also includes. Both give the same diagnostics and generated code. On Windows the default is tsgo, because tsc-ts's API server does not yet accept the named pipe scriptc connects over there.
```console diff --git a/packages/compiler/src/frontend/ts7/checker-backend.test.ts b/packages/compiler/src/frontend/ts7/checker-backend.test.ts index f171d1236..e37357afa 100644 --- a/packages/compiler/src/frontend/ts7/checker-backend.test.ts +++ b/packages/compiler/src/frontend/ts7/checker-backend.test.ts @@ -24,11 +24,15 @@ function writePackage(directory: string, name: string, executable?: string): voi writeFileSync(join(directory, "lib", executable), ""); } -test("tsc-ts is the default checker and SCRIPTC_CHECKER=tsgo selects tsgo", () => { - expect(checkerBackend({})).toBe("tsc-ts"); - expect(checkerBackend({ SCRIPTC_CHECKER: "" })).toBe("tsc-ts"); - expect(checkerBackend({ SCRIPTC_CHECKER: "tsc-ts" })).toBe("tsc-ts"); - expect(checkerBackend({ SCRIPTC_CHECKER: "tsgo" })).toBe("tsgo"); +test("tsc-ts is the default checker except on Windows, and SCRIPTC_CHECKER selects one", () => { + expect(checkerBackend({}, "linux")).toBe("tsc-ts"); + expect(checkerBackend({ SCRIPTC_CHECKER: "" }, "linux")).toBe("tsc-ts"); + expect(checkerBackend({}, "darwin")).toBe("tsc-ts"); + expect(checkerBackend({}, "win32")).toBe("tsgo"); + expect(checkerBackend({ SCRIPTC_CHECKER: "" }, "win32")).toBe("tsgo"); + expect(checkerBackend({ SCRIPTC_CHECKER: "tsc-ts" }, "linux")).toBe("tsc-ts"); + expect(checkerBackend({ SCRIPTC_CHECKER: "tsc-ts" }, "win32")).toBe("tsc-ts"); + expect(checkerBackend({ SCRIPTC_CHECKER: "tsgo" }, "linux")).toBe("tsgo"); expect(() => checkerBackend({ SCRIPTC_CHECKER: "tsc" })).toThrow( 'SCRIPTC_CHECKER must be tsgo or tsc-ts, not "tsc"', ); diff --git a/packages/compiler/src/frontend/ts7/checker-backend.ts b/packages/compiler/src/frontend/ts7/checker-backend.ts index a2c989edb..14418cc94 100644 --- a/packages/compiler/src/frontend/ts7/checker-backend.ts +++ b/packages/compiler/src/frontend/ts7/checker-backend.ts @@ -4,12 +4,18 @@ import { fileURLToPath } from "node:url"; /** The TypeScript 7 API servers a compile can check with: tsc-ts, the default, * and tsgo from the `typescript` package, selected with `SCRIPTC_CHECKER=tsgo`. - * Both serve the same API and give the same results. */ + * Both serve the same API and give the same results. On Windows the default is + * tsgo: the compiler connects to the server over a named pipe there, and tsc-ts's + * API server has no named-pipe transport yet. */ export type CheckerBackend = "tsgo" | "tsc-ts"; -export function checkerBackend(env: NodeJS.ProcessEnv = process.env): CheckerBackend { +export function checkerBackend( + env: NodeJS.ProcessEnv = process.env, + platform: string = process.platform, +): CheckerBackend { const value = env["SCRIPTC_CHECKER"]; - if (value === undefined || value === "" || value === "tsc-ts") return "tsc-ts"; + if (value === undefined || value === "") return platform === "win32" ? "tsgo" : "tsc-ts"; + if (value === "tsc-ts") return "tsc-ts"; if (value === "tsgo") return "tsgo"; throw new Error(`SCRIPTC_CHECKER must be tsgo or tsc-ts, not ${JSON.stringify(value)}`); } diff --git a/packages/compiler/src/native/toolchain.test.ts b/packages/compiler/src/native/toolchain.test.ts index 78d2c9c7e..0ee434300 100644 --- a/packages/compiler/src/native/toolchain.test.ts +++ b/packages/compiler/src/native/toolchain.test.ts @@ -99,7 +99,11 @@ test("native toolchain assets resolve from the physical manifest through package const direct = loadNativeToolchain(path); const viaLink = loadNativeToolchain(join(linked, "bin", "scriptc.exe.json")); expect(viaLink).toEqual(direct); - expect(viaLink.ts7Executable).toBe(join(assets, "tsc-ts/lib/tsc-ts.exe")); + expect(viaLink.ts7Executable).toBe(join(assets, "typescript/lib/tsc.exe")); + expect( + loadNativeToolchain(join(linked, "bin", "scriptc.exe.json"), { SCRIPTC_CHECKER: "tsc-ts" }) + .ts7Executable, + ).toBe(join(assets, "tsc-ts/lib/tsc-ts.exe")); expect(viaLink.helperExecutable).toBe(join(assets, "llvm/bin/scriptc-llvm-codegen.exe")); expect( loadNativeToolchain(join(linked, "bin", "scriptc.exe.json"), { diff --git a/packages/compiler/src/native/toolchain.ts b/packages/compiler/src/native/toolchain.ts index 83da0f7c2..fbcd94251 100644 --- a/packages/compiler/src/native/toolchain.ts +++ b/packages/compiler/src/native/toolchain.ts @@ -177,11 +177,11 @@ export function loadNativeToolchain( : [...target.defaultLinkerArgs]; return { compilerVersion: manifest.compiler_version, - // tsc-ts, the default checker, comes with the distribution; a development - // build takes it from the tsc-ts package installed with the compiler or for - // the working directory. SCRIPTC_CHECKER=tsgo selects tsgo. + // tsc-ts, the default checker except on Windows, comes with the distribution; + // a development build takes it from the tsc-ts package installed with the + // compiler or for the working directory. SCRIPTC_CHECKER selects a checker. ts7Executable: - checkerBackend(env) === "tsgo" + checkerBackend(env, host.platform) === "tsgo" ? pathFrom(root, manifest.ts7) : manifest.tsc_ts !== undefined ? pathFrom(root, manifest.tsc_ts) diff --git a/packages/compiler/test/ts7/rpc-api.test.ts b/packages/compiler/test/ts7/rpc-api.test.ts index e363567c2..e56b55beb 100644 --- a/packages/compiler/test/ts7/rpc-api.test.ts +++ b/packages/compiler/test/ts7/rpc-api.test.ts @@ -62,7 +62,16 @@ test("production RPC bridge preserves pinned SDK parser/checker behavior", () => JSON.stringify({ compilerOptions: { strict: true, noEmit: true, types: [] }, files: [path] }), ); writeFileSync(path, 'export const answer = 42;\nexport const invalid: number = "wrong";\n'); - const nativeClient = new Ts7Api({ cwd: dir, fs: fallback }); + // Compared with the pinned SDK, which runs tsgo, so the bridge runs tsgo too. + const checker = process.env["SCRIPTC_CHECKER"]; + process.env["SCRIPTC_CHECKER"] = "tsgo"; + let nativeClient: Ts7Api; + try { + nativeClient = new Ts7Api({ cwd: dir, fs: fallback }); + } finally { + if (checker === undefined) delete process.env["SCRIPTC_CHECKER"]; + else process.env["SCRIPTC_CHECKER"] = checker; + } const sdk = new API({ cwd: dir }); try { expect(nativeClient.parseConfigFile(config)).toEqual(sdk.parseConfigFile(config)); From 3a09a000dbdf585e8f3c89fa173a83635aeab082 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 18 Oct 2026 21:37:02 -0500 Subject: [PATCH 011/122] Leave the stored timer functions out of library builds scr_dyn_invoke.c links into libraries that use dynamic calls, and its stored timer functions call scr_set_timeout_handle, scr_set_interval, scr_clear_interval, scr_set_immediate, scr_clear_immediate, scr_next_tick and scr_queue_microtask_dyn, which live in scr_async.c. Libraries never link scr_async.c (a library that schedules timers is refused at SC4005), so their host links failed with undefined symbols. Under SCR_LIB the calls that schedule are left out, and a call through a stored timer function throws the SC4005 event loop error. Executables compile the same code as before. - 6236-builtin-function-values.cjs counts the interval's runs apart from the order of the other callbacks and reports once the interval has stopped, so a slow machine prints the same line as Node. - node24-util-types: Function now reads as a value, so the fixture's first refusal is the SC1090 for extending Promise instead of the SC2009 for the FunctionConstructor value. --- packages/runtime/src/scr_dyn_invoke.c | 15 +++++++++++++++ tests/corpus/6236-builtin-function-values.cjs | 9 ++++++--- tests/harness/npm.test.ts | 2 +- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/packages/runtime/src/scr_dyn_invoke.c b/packages/runtime/src/scr_dyn_invoke.c index d50ce749c..b5f3c8615 100644 --- a/packages/runtime/src/scr_dyn_invoke.c +++ b/packages/runtime/src/scr_dyn_invoke.c @@ -1994,6 +1994,10 @@ static void dyn_builtin_fns_cleanup(void) { dyn_builtin_fns = NULL; } +/* Library builds have no event loop (a library that schedules timers is + * refused at SC4005 before emission), so the calls that schedule are left + * out of them. */ +#ifndef SCR_LIB static bool dyn_builtin_fn_callback(ScrDyn *const *args, size_t argc) { if (argc > 0 && args[0]->kind == SCR_DYN_FUNC) return true; static const char message[] = "The \"callback\" argument must be of type function."; @@ -2022,7 +2026,17 @@ static ScrClosure *dyn_builtin_fn_deferred(ScrDyn *const *args, size_t argc, siz return c; } +#endif + static ScrDyn *dyn_builtin_fn_call(ScrClosure *clo, ScrDyn *const *args, size_t argc) { +#ifdef SCR_LIB + (void)clo; + (void)args; + (void)argc; + static const char message[] = "Builtin timer functions require an executable event loop"; + scr_throw_error_msg_code(SCR_ERR_ERROR, message, sizeof message - 1, "SC4005"); + return NULL; +#else ScrStr *name = scr_box_get_ref(clo->caps[0]); ScrDyn *result = NULL; bool timeout = strcmp(name->data, dyn_s16) == 0; @@ -2059,6 +2073,7 @@ static ScrDyn *dyn_builtin_fn_call(ScrClosure *clo, ScrDyn *const *args, size_t } scr_str_release(name); return result; +#endif } ScrDyn *scr_dyn_builtin_function(const ScrStr *name) { diff --git a/tests/corpus/6236-builtin-function-values.cjs b/tests/corpus/6236-builtin-function-values.cjs index b8abee311..703495b0f 100644 --- a/tests/corpus/6236-builtin-function-values.cjs +++ b/tests/corpus/6236-builtin-function-values.cjs @@ -31,13 +31,16 @@ timeout((x) => order.push("timeout:" + x), 60, "x"); assigned(() => order.push("assigned")); const cancelled = timeout(() => order.push("cancelled"), 1); (pick ? clearImmediate : clearTimeout)(cancelled); +// The interval counts its runs apart from `order`, and the report waits until +// it has stopped, so slow machines print the same line. let count = 0; const handle = every(() => { count++; - order.push("interval:" + count); - if (count === 2) stop(handle); + if (count === 2) { + stop(handle); + timeout(() => console.log(order.join(" "), "interval:" + count, "micro:" + microRan), 120); + } }, 20); -timeout(() => console.log(order.join(" "), "micro:" + microRan), 120); for (const bad of [undefined, 1, "fn"]) { try { diff --git a/tests/harness/npm.test.ts b/tests/harness/npm.test.ts index 136a58fcd..387fef467 100644 --- a/tests/harness/npm.test.ts +++ b/tests/harness/npm.test.ts @@ -89,7 +89,7 @@ const KNOWN: Record = { "node24-url-factories": { run: "SC2020" }, "node24-url-mutation": { run: "SC1090" }, "node24-util-adapters": { run: "SC2020" }, - "node24-util-types": { run: "SC2009" }, + "node24-util-types": { run: "SC1090" }, "node24-web-stream-encoding": { build: "SC2013" }, "node24-web-stream-errors": { build: "SC2013" }, "node24-web-stream-lifecycle": { build: "SC2013" }, From 78184a98697183b73691e921fac920d54201be02 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 18 Oct 2026 19:43:06 -0700 Subject: [PATCH 012/122] =?UTF-8?q?WebAssembly=20W3=E2=80=93W4:=20translat?= =?UTF-8?q?e=20modules=20to=20native=20code=20at=20build=20time,=20the=20J?= =?UTF-8?q?avaScript=20API=20over=20them,=20and=20Module=20transfer=20to?= =?UTF-8?q?=20workers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A build now translates every WebAssembly module whose bytes it can see (W2's discovery) into LLVM IR with a TypeScript translator (packages/compiler/src/wasm/module.ts, translate.ts) and appends it to the program module, so the existing LLVM helper optimizes, partitions and caches it with the program. The program also emits scr_wasm_registry, a portable table of module descriptors (scr_wasm_abi.h). At run time, WebAssembly.compile, instantiate, validate, new Module and new Instance find the translated module by the SHA-256 of its bytes; bytes the build did not see reject with a CompileError. There is no interpreter and no JIT. The runtime (scr_wasm.c, link-gated by the new `wasm` feature, handle tags inside W0's headroom) implements the JavaScript API once, over descriptors, with Node's semantics and messages: exports (functions, memories, tables, globals), imports (functions, immutable globals, and memories, tables and globals other instances export), every trap as WebAssembly.RuntimeError, the CompileError, LinkError and RuntimeError classes, compile and instantiate settling on a later turn, and globalThis.WebAssembly as a value. On 64-bit Linux and macOS, memories sit in 8 GiB guard reservations and a SIGSEGV/SIGBUS handler turns faults into traps or stack exhaustion; async fibers get a guard page on first use. Sanitizer builds and Windows check bounds and the stack explicitly. A WebAssembly.Module crosses to workers through W0's transfer hook (W4). W1's refusals become lowerings; the Memory, Table and Global constructors, Module.imports/exports/customSections and the streaming functions still refuse with SC2020. The globals: WebAssembly compatibility row moves to partial. Evidence: differential corpus programs for the API, TypeScript use and the codemode worker shape, plus five programs executing about 9,300 commands of the WebAssembly 2.0 spec suite; all match Node in the plain and sanitized lanes. quickjs-wasi runs through its own glue, matching Node (apart from a pre-existing non-wasm fence) and on par with or faster than V8. The zero-cost gate leaves every non-wasm program byte-identical. Co-Authored-By: Claude Opus 5.5 --- docs/content/docs/cli.mdx | 2 +- .../limitations/webassembly-modules.mdx | 18 +- .../node-v24-compatibility-meta.json | 2 +- .../src/generated/node-v24-compatibility.json | 10 +- .../generated/node-v24-backlog.json | 23 +- .../generated/node-v24-internal.json | 26 +- internal/compatibility/static-support.json | 1 + .../src/backend/executable-features.ts | 1 + packages/compiler/src/backend/llvm/emitter.ts | 15 + .../compiler/src/backend/llvm/lib-dispatch.ts | 1 + .../compiler/src/backend/llvm/lib-shared.ts | 10 + .../compiler/src/backend/llvm/wasm-modules.ts | 74 + .../src/backend/native-link-info-core.ts | 3 + .../compiler/src/backend/native/contracts.ts | 2 + .../compiler/src/backend/native/executable.ts | 1 + .../compiler/src/backend/runtime-pack-core.ts | 2 + .../src/backend/target-diagnostics.ts | 8 + packages/compiler/src/coverage/report.ts | 8 +- .../compiler/src/coverage/surface-manifest.ts | 9 +- .../compiler/src/diagnostics/diagnostic.ts | 14 +- .../src/executable/executable-cache.ts | 1 + .../frontend/lowering/builtins/webassembly.ts | 193 +- .../src/frontend/lowering/lower-exprs.ts | 14 +- .../compiler/src/frontend/lowering/lowerer.ts | 65 +- packages/compiler/src/frontend/type-mapper.ts | 12 + packages/compiler/src/index.ts | 3 + packages/compiler/src/ir/builtin-effects.ts | 6 + .../compiler/src/ir/builtin-signatures.ts | 21 + .../src/ir/function-references.test.ts | 2 + packages/compiler/src/ir/ir.ts | 14 + packages/compiler/src/ir/runtime-features.ts | 10 + .../compiler/src/native/sanitizer.test.ts | 1 + packages/compiler/src/wasm/base64.ts | 61 + packages/compiler/src/wasm/discover.ts | 41 +- packages/compiler/src/wasm/module.ts | 659 +++++ packages/compiler/src/wasm/translate.test.ts | 145 ++ packages/compiler/src/wasm/translate.ts | 2173 +++++++++++++++++ packages/compiler/surface-manifest.json | 5 +- .../tests/corpus/webassembly-api.mjs.json | 6 + .../webassembly-spec-control/main.mjs.json | 8 + .../webassembly-spec-float/main.mjs.json | 8 + .../corpus/webassembly-spec-int/main.mjs.json | 8 + .../webassembly-spec-memory/main.mjs.json | 8 + .../webassembly-spec-table/main.mjs.json | 8 + .../tests/corpus/webassembly-typed.ts.json | 6 + .../tests/corpus/webassembly-worker.ts.json | 6 + .../runtime-pack-matrix.mjs | 1 + .../runtime-pack-matrix.mjs | 1 + .../runtime-wasm32-wasi/scripts/build.mjs | 1 + packages/runtime/src/scr_async.c | 8 - packages/runtime/src/scr_async.h | 14 + packages/runtime/src/scr_dyn.h | 5 + packages/runtime/src/scr_wasm.c | 1990 +++++++++++++++ packages/runtime/src/scr_wasm_abi.h | 191 ++ tests/corpus/webassembly-api.mjs | 179 ++ tests/corpus/webassembly-feature-probe.js | 5 +- .../corpus/webassembly-spec-control/data.mjs | 48 + .../corpus/webassembly-spec-control/main.mjs | 5 + tests/corpus/webassembly-spec-float/data.mjs | 113 + tests/corpus/webassembly-spec-float/main.mjs | 5 + tests/corpus/webassembly-spec-int/data.mjs | 33 + tests/corpus/webassembly-spec-int/main.mjs | 5 + tests/corpus/webassembly-spec-memory/data.mjs | 161 ++ tests/corpus/webassembly-spec-memory/main.mjs | 5 + tests/corpus/webassembly-spec-table/data.mjs | 193 ++ tests/corpus/webassembly-spec-table/main.mjs | 5 + tests/corpus/webassembly-typed.ts | 51 + tests/corpus/webassembly-worker.ts | 68 + tests/diagnostics/webassembly-fences.ts | 38 +- tests/harness/__snapshots__/coverage-wasm.txt | 12 +- .../__snapshots__/webassembly-fences.ts.txt | 129 +- tests/wasm-spec/corpus/api.wat | 52 + tests/wasm-spec/corpus/linked.wat | 12 + tests/wasm-spec/corpus/refs.wat | 10 + tests/wasm-spec/gen.mjs | 272 +++ tests/wasm-spec/runner.mjs | 145 ++ 76 files changed, 7252 insertions(+), 219 deletions(-) create mode 100644 packages/compiler/src/backend/llvm/wasm-modules.ts create mode 100644 packages/compiler/src/wasm/base64.ts create mode 100644 packages/compiler/src/wasm/module.ts create mode 100644 packages/compiler/src/wasm/translate.test.ts create mode 100644 packages/compiler/src/wasm/translate.ts create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-api.mjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-spec-control/main.mjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-spec-float/main.mjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-spec-int/main.mjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-spec-memory/main.mjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-spec-table/main.mjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-typed.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-worker.ts.json create mode 100644 packages/runtime/src/scr_wasm.c create mode 100644 packages/runtime/src/scr_wasm_abi.h create mode 100644 tests/corpus/webassembly-api.mjs create mode 100644 tests/corpus/webassembly-spec-control/data.mjs create mode 100644 tests/corpus/webassembly-spec-control/main.mjs create mode 100644 tests/corpus/webassembly-spec-float/data.mjs create mode 100644 tests/corpus/webassembly-spec-float/main.mjs create mode 100644 tests/corpus/webassembly-spec-int/data.mjs create mode 100644 tests/corpus/webassembly-spec-int/main.mjs create mode 100644 tests/corpus/webassembly-spec-memory/data.mjs create mode 100644 tests/corpus/webassembly-spec-memory/main.mjs create mode 100644 tests/corpus/webassembly-spec-table/data.mjs create mode 100644 tests/corpus/webassembly-spec-table/main.mjs create mode 100644 tests/corpus/webassembly-typed.ts create mode 100644 tests/corpus/webassembly-worker.ts create mode 100644 tests/wasm-spec/corpus/api.wat create mode 100644 tests/wasm-spec/corpus/linked.wat create mode 100644 tests/wasm-spec/corpus/refs.wat create mode 100644 tests/wasm-spec/gen.mjs create mode 100644 tests/wasm-spec/runner.mjs diff --git a/docs/content/docs/cli.mdx b/docs/content/docs/cli.mdx index c4fdf7876..37b4c2942 100644 --- a/docs/content/docs/cli.mdx +++ b/docs/content/docs/cli.mdx @@ -134,7 +134,7 @@ Verifies and loads the installed precompiled runtime objects and vendored librar
Coverage only. After the report, list the functions that already operate on values typed only at runtime and the bindings, fields, functions, classes and modules that unsupported constructs would move to the dynamic tier, each with the diagnostic behind it. With a name such as x, Point, or Point.x, show only the matching units. See Dynamic tier.
--wasm <file.wasm>
-
Coverage only. Report a WebAssembly module the program loads from a path its sources do not spell out. The report already lists modules whose paths the compiler resolves, modules inlined as base64 literals, and .wasm files shipped by packages that use WebAssembly; a scriptc.wasm array in package.json adds files the same way as this flag. Each module is listed with its size, whether it validates, and the WebAssembly proposals it uses. scriptc does not compile WebAssembly modules to native code yet. Repeatable.
+
Coverage only. Report a WebAssembly module the program loads from a path its sources do not spell out. The report already lists modules whose paths the compiler resolves, modules inlined as base64 literals, and .wasm files shipped by packages that use WebAssembly; a scriptc.wasm array in package.json adds files the same way as this flag. Each module is listed with its size, whether it validates, and the WebAssembly proposals it uses. Builds translate the modules they find to native code; a build reads the scriptc.wasm array but not this flag. Repeatable.
--sanitize
Build an executable with AddressSanitizer and a reference-count audit at exit, the same checks scriptc's own test suite uses. Requires a C compiler and is available only for host builds. Not supported with --emit=asm or --emit=obj.
diff --git a/docs/content/limitations/webassembly-modules.mdx b/docs/content/limitations/webassembly-modules.mdx index 492b2fc1a..e768b4ef0 100644 --- a/docs/content/limitations/webassembly-modules.mdx +++ b/docs/content/limitations/webassembly-modules.mdx @@ -1,3 +1,17 @@ -Compiled programs can't load WebAssembly modules yet. Code that only mentions the `WebAssembly` types type-checks, including packages whose declarations name them, and `typeof WebAssembly` answers as it does in Node.js. Every use of the API itself is rejected: `WebAssembly.compile`, `instantiate` and `validate`, `new WebAssembly.Module` and the other constructors, `instanceof` checks, and reads of `globalThis.WebAssembly`. TypeScript sources fail to build with SC2020 at each use, and JavaScript sources throw that diagnostic when the statement runs. +scriptc translates WebAssembly modules to native code when it builds the program; there is no interpreter or JIT in the executable. A build translates every module whose bytes it can see: files at paths the compiler resolves (`require.resolve`, `createRequire(...).resolve`, `import.meta.resolve`, `new URL("./x.wasm", import.meta.url)`, `path.join(__dirname, ...)`), modules inlined as base64 or byte-array literals, `.wasm` files shipped by packages whose code uses WebAssembly, and files listed in a `scriptc.wasm` array in `package.json`. At run time `WebAssembly.compile`, `instantiate`, `validate` and `new WebAssembly.Module` find the translated module by the SHA-256 of the bytes they are given. Bytes the build did not see, including bytes produced at run time, reject with a `CompileError` that says the module was not translated, and `WebAssembly.validate` answers `false` for them. `scriptc coverage` lists the modules a build finds, with their size, whether they validate, and the WebAssembly proposals they use. -`scriptc coverage` lists the modules a program would load, with their size, whether they validate, and the WebAssembly proposals they use. It finds modules at paths the compiler resolves, modules inlined as base64 literals, `.wasm` files shipped by packages that use WebAssembly, and files named with [`--wasm`](/docs/cli) or a `scriptc.wasm` array in `package.json`. +Translated modules can use the WebAssembly 2.0 feature set: the MVP plus sign-extension operators, non-trapping float-to-int conversions, bulk memory, multi-value, mutable globals, reference types, and extended constant expressions. Modules that use SIMD, threads, exception handling, tail calls, typed function references or GC, memory64, or multiple memories are not translated, so loading them rejects with the `CompileError` above; packages that fall back to a scalar build when that happens, such as undici and es-module-lexer, take their fallback. + +Supported: `WebAssembly.compile`, `instantiate` (with bytes or a `Module`), `validate`, `new WebAssembly.Module`, `new WebAssembly.Instance`, and `instanceof` for every class; the error classes `CompileError`, `LinkError` and `RuntimeError`, constructed with or without `new`; `globalThis.WebAssembly` and its `compile`, `instantiate` and `validate` functions as values. Instances expose exported functions, memories (`buffer`, `grow`), tables (`get`, `set`, `grow`, `length`) and globals (`value`). Imports can be functions, numbers or BigInts for immutable globals, and memories, tables and globals exported by other instances. Traps throw `WebAssembly.RuntimeError` with Node.js's messages, unbounded recursion throws `RangeError`, and an exception thrown by an imported function propagates through the WebAssembly frames unchanged. A `WebAssembly.Module` can be sent to a worker through `workerData` or `postMessage`. + +Not supported yet: the `Memory`, `Table`, `Global`, `Tag` and `Exception` constructors, `WebAssembly.Module.imports`, `exports` and `customSections`, `compileStreaming` and `instantiateStreaming`, and the classes used as values other than with `new` and `instanceof`. TypeScript sources fail to build with SC2020 at each such use, and JavaScript sources throw that diagnostic when the statement runs. Programs built for `wasm32-wasi` can't use the WebAssembly API. + +Differences from Node.js: + +- `Memory.prototype.grow` does not detach the previous `ArrayBuffer`. The memory never moves, so views created before the growth stay valid but keep their old length. +- Instances are never freed, together with their memories, imports and exports. +- A function reference (`funcref`) can reach JavaScript only as a function the module exports; other function references throw a `TypeError`. +- A load whose result the module never uses may be removed instead of trapping when its address is out of bounds. Loads whose result is dropped explicitly still trap. +- NaN payloads may differ, for example when a signaling NaN passes through arithmetic or a JavaScript number. +- `WebAssembly.compile` and `instantiate` with bytes settle after the current microtasks, as in Node.js, but can settle after `setImmediate` callbacks that Node.js would run later. +- The executable reads `.wasm` files at run time from the paths they had when it was built. diff --git a/docs/src/generated/node-v24-compatibility-meta.json b/docs/src/generated/node-v24-compatibility-meta.json index 1cf76d3e4..7a3e609ee 100644 --- a/docs/src/generated/node-v24-compatibility-meta.json +++ b/docs/src/generated/node-v24-compatibility-meta.json @@ -2,6 +2,6 @@ "schemaVersion": 3, "nodeVersion": "24.22.0", "nodeCommit": "f14cd3aa7bf9ca8df2cc5b8bb14beb0f335b1d93", - "artifactVersion": "efc18bcd8a7a03fd8622", + "artifactVersion": "96f4a5b419510481a80b", "rowCount": 3880 } diff --git a/docs/src/generated/node-v24-compatibility.json b/docs/src/generated/node-v24-compatibility.json index 0e6387ea9..2055d4acf 100644 --- a/docs/src/generated/node-v24-compatibility.json +++ b/docs/src/generated/node-v24-compatibility.json @@ -329,8 +329,8 @@ "apiEntries": 92, "static": { "supported": 0, - "partial": 12, - "refused": 1, + "partial": 13, + "refused": 0, "not-implemented": 79, "by-design": 0, "unreviewed": 0, @@ -36590,9 +36590,9 @@ "inherited": true }, "static": { - "status": "refused", - "detail": "Explicitly rejected by a named compiler diagnostic.", - "verification": "explicit-refusal" + "status": "partial", + "detail": "Implemented for the call shapes accepted by the compiler lowering.", + "verification": "test-backed" } }, { diff --git a/internal/compatibility/generated/node-v24-backlog.json b/internal/compatibility/generated/node-v24-backlog.json index 7d0fcf75c..4df2c983f 100644 --- a/internal/compatibility/generated/node-v24-backlog.json +++ b/internal/compatibility/generated/node-v24-backlog.json @@ -7,9 +7,9 @@ "tierItemCount": 3456, "tiers": { "static": { - "audit-partial": 1162, + "audit-partial": 1163, "verify-gap": 2166, - "replace-refusal": 41, + "replace-refusal": 40, "classify": 2, "implement": 85 } @@ -34202,12 +34202,21 @@ "priority": "high", "tiers": { "static": { - "status": "refused", - "action": "replace-refusal", - "verification": "explicit-refusal", + "status": "partial", + "action": "audit-partial", + "verification": "test-backed", "confidence": "high", - "source": "surface-manifest:unsupported:stdlib.webassembly", - "tests": [] + "source": "surface-manifest:stdlib.webassembly", + "tests": [ + "tests/corpus/webassembly-api.mjs", + "tests/corpus/webassembly-typed.ts", + "tests/corpus/webassembly-worker.ts", + "tests/corpus/webassembly-spec-int/main.mjs", + "tests/corpus/webassembly-spec-float/main.mjs", + "tests/corpus/webassembly-spec-control/main.mjs", + "tests/corpus/webassembly-spec-memory/main.mjs", + "tests/corpus/webassembly-spec-table/main.mjs" + ] } } }, diff --git a/internal/compatibility/generated/node-v24-internal.json b/internal/compatibility/generated/node-v24-internal.json index c87f98fec..f036d0ac3 100644 --- a/internal/compatibility/generated/node-v24-internal.json +++ b/internal/compatibility/generated/node-v24-internal.json @@ -341,8 +341,8 @@ "apiEntries": 92, "static": { "supported": 0, - "partial": 12, - "refused": 1, + "partial": 13, + "refused": 0, "not-implemented": 79, "by-design": 0, "unreviewed": 0, @@ -52218,6 +52218,14 @@ "tests/corpus/5290-blob-dynamic.js", "tests/corpus/5830-timers-promises-signal.ts", "tests/corpus/text-codec-values/main.ts", + "tests/corpus/webassembly-api.mjs", + "tests/corpus/webassembly-spec-control/main.mjs", + "tests/corpus/webassembly-spec-float/main.mjs", + "tests/corpus/webassembly-spec-int/main.mjs", + "tests/corpus/webassembly-spec-memory/main.mjs", + "tests/corpus/webassembly-spec-table/main.mjs", + "tests/corpus/webassembly-typed.ts", + "tests/corpus/webassembly-worker.ts", "tests/harness/fetch-conformance.test.ts" ] }, @@ -53553,8 +53561,18 @@ "inherited": true }, "static": { - "status": "refused", - "evidence": "surface-manifest:unsupported:stdlib.webassembly" + "status": "partial", + "evidence": "surface-manifest:stdlib.webassembly", + "tests": [ + "tests/corpus/webassembly-api.mjs", + "tests/corpus/webassembly-typed.ts", + "tests/corpus/webassembly-worker.ts", + "tests/corpus/webassembly-spec-int/main.mjs", + "tests/corpus/webassembly-spec-float/main.mjs", + "tests/corpus/webassembly-spec-control/main.mjs", + "tests/corpus/webassembly-spec-memory/main.mjs", + "tests/corpus/webassembly-spec-table/main.mjs" + ] }, "anchorSource": "exact" }, diff --git a/internal/compatibility/static-support.json b/internal/compatibility/static-support.json index 584492bf1..96c41bcd1 100644 --- a/internal/compatibility/static-support.json +++ b/internal/compatibility/static-support.json @@ -28,6 +28,7 @@ "webstreams": "unreviewed" }, "evidenceByManifestPrefix": { + "stdlib.webassembly": ["tests/corpus/webassembly-api.mjs", "tests/corpus/webassembly-typed.ts", "tests/corpus/webassembly-worker.ts", "tests/corpus/webassembly-spec-int/main.mjs", "tests/corpus/webassembly-spec-float/main.mjs", "tests/corpus/webassembly-spec-control/main.mjs", "tests/corpus/webassembly-spec-memory/main.mjs", "tests/corpus/webassembly-spec-table/main.mjs"], "node-builtin.fs.readFileSync": ["tests/corpus/1403-buffer-fs.ts", "tests/corpus/2595-fs-arg-ladders.cjs", "tests/corpus/2919-fs-function-values.ts", "tests/corpus/2920-fs-promises-function-values.ts", "tests/corpus/fs-read-encodings.ts"], "syntax.using-declarations": ["tests/corpus/2841-using-sync.ts"], "syntax.await-using-declarations": ["tests/corpus/2842-await-using.ts"], diff --git a/packages/compiler/src/backend/executable-features.ts b/packages/compiler/src/backend/executable-features.ts index 553643470..aa6a9c490 100644 --- a/packages/compiler/src/backend/executable-features.ts +++ b/packages/compiler/src/backend/executable-features.ts @@ -14,6 +14,7 @@ export function executableLinkFeatures(mod: IrModule): NativeLinkFeatures { fileHandle: features.fileHandle, fetch: features.fetch, ...(features.eventTarget ? { eventTarget: true } : {}), + ...(features.wasm ? { wasm: true } : {}), zlib: features.zlib, assert: features.assert, inspect: features.inspect, diff --git a/packages/compiler/src/backend/llvm/emitter.ts b/packages/compiler/src/backend/llvm/emitter.ts index ede7a476e..9a1862cbe 100644 --- a/packages/compiler/src/backend/llvm/emitter.ts +++ b/packages/compiler/src/backend/llvm/emitter.ts @@ -1,3 +1,4 @@ +import { emitWasmModules, wasmBoundsChecks } from "./wasm-modules.js"; import { type ArraySearchKind, emitArraySearchHelpers } from "./array-search.js"; import { emitStringInputs } from "./string-lifetimes.js"; import { emitLlvmLayouts } from "./layouts.js"; @@ -500,6 +501,9 @@ export interface LlvmTargetOptions { * teardown after `process.exit` to a helper process (`--detached-exit`). * Executables only; the runtime's weak default keeps it off. */ detachedExit?: boolean; + /** Translated WebAssembly memories check bounds explicitly instead of + * relying on guard pages (wasm-modules.ts wasmBoundsChecks). */ + wasmBoundsChecks?: boolean; } export function emitLlvmModule(input: IrModule, options: LlvmTargetOptions = {}): string { @@ -775,6 +779,7 @@ export class LlEmitter { private readonly emitLibraryIdentity: boolean; private readonly runtimeAbiMarker: boolean; private readonly detachedExit: boolean; + private readonly wasmBoundsChecks: boolean; /** Interned string literals: UTF-8 text → { symbol, byte length } — * first-use order, the runtime ABI’s determinism discipline. */ private readonly literals = new Map(); @@ -1247,6 +1252,13 @@ export class LlEmitter { this.emitLibraryIdentity = options.emitLibraryIdentity !== false; this.runtimeAbiMarker = options.runtimeAbiMarker === true; this.detachedExit = options.detachedExit === true; + this.wasmBoundsChecks = + options.wasmBoundsChecks === true || + wasmBoundsChecks({ + sanitize: false, + pointerBits: options.pointerBits ?? 64, + targetTriple: options.targetTriple ?? "", + }); this.rcHelpers = options.inlineRc !== undefined ? new Set() : null; this.rcMode = options.inlineRc; this.rcHelpersNoinline = options.rcHelpersNoinline === true; @@ -2686,6 +2698,9 @@ export class LlEmitter { out.push(fnDefs[0] ?? ""); for (let i = 1; i < fnDefs.length; i++) out.push("", fnDefs[i]!); out.push(""); + // WebAssembly modules translated into this program, and the registry. + for (const line of emitWasmModules(this.mod, flushedDecls, this.wasmBoundsChecks)) + out.push(line); // main(): scr_init, the program-dependent error-vt interval stamps, // scr_lib_init(argc, argv), then the entry function. An uncaught diff --git a/packages/compiler/src/backend/llvm/lib-dispatch.ts b/packages/compiler/src/backend/llvm/lib-dispatch.ts index f21ded106..f740e19e2 100644 --- a/packages/compiler/src/backend/llvm/lib-dispatch.ts +++ b/packages/compiler/src/backend/llvm/lib-dispatch.ts @@ -832,6 +832,7 @@ export function emitLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlValue { case "event": case "events": case "blob": + case "wasm": case "record": case "disposable": case "punycode": diff --git a/packages/compiler/src/backend/llvm/lib-shared.ts b/packages/compiler/src/backend/llvm/lib-shared.ts index 67cb77047..10e3ddd8d 100644 --- a/packages/compiler/src/backend/llvm/lib-shared.ts +++ b/packages/compiler/src/backend/llvm/lib-shared.ts @@ -836,6 +836,16 @@ export const LIB_FN_SYMS: Record = { "event.get": "scr_event_obj_get", "event.set": "scr_event_obj_set", "blob.new": "scr_blob_new", + "wasm.compile": "scr_wasm_compile", + "wasm.validate": "scr_wasm_validate", + "wasm.instantiate": "scr_wasm_instantiate", + "wasm.moduleNew": "scr_wasm_module_new", + "wasm.instanceNew": "scr_wasm_instance_new", + "wasm.is": "scr_wasm_is", + "wasm.errorNew": "scr_wasm_error_new", + "wasm.function": "scr_wasm_function", + "wasm.namespace": "scr_wasm_namespace", + "wasm.globalView": "scr_wasm_global_view", "blob.fileNew": "scr_file_new", "blob.slice": "scr_blob_slice", "blob.text": "scr_blob_text", diff --git a/packages/compiler/src/backend/llvm/wasm-modules.ts b/packages/compiler/src/backend/llvm/wasm-modules.ts new file mode 100644 index 000000000..c37dc36c4 --- /dev/null +++ b/packages/compiler/src/backend/llvm/wasm-modules.ts @@ -0,0 +1,74 @@ +/* The WebAssembly modules translated into a program (IrModule.wasmModules): + * each module's translation (wasm/translate.ts) and the registry the + * runtime looks modules up in (scr_wasm_registry, scr_wasm_abi.h). The + * translations join the program module, so the native helper optimizes, + * partitions and caches them with it. Programs that do not use the + * WebAssembly API carry no wasmModules and emit nothing here. */ +import type { IrModule } from "../../ir/ir.js"; +import { decodeBase64 } from "../../wasm/base64.js"; +import { translateWasmModule, WasmTranslateError } from "../../wasm/translate.js"; + +function hexBytes(hex: string): Uint8Array { + const out = new Uint8Array(hex.length / 2); + for (let i = 0; i < out.length; i++) out[i] = parseInt(hex.slice(2 * i, 2 * i + 2), 16); + return out; +} + +/** Whether translated memories check bounds explicitly instead of relying + * on guard pages and the runtime's fault handler: sanitizer builds (whose + * runtime owns SIGSEGV), 32-bit targets, and Windows, which has no + * fault handler yet. */ +export function wasmBoundsChecks(options: { + sanitize: boolean; + pointerBits: 32 | 64; + targetTriple: string; +}): boolean { + if (options.sanitize || options.pointerBits === 32) return true; + const target = options.targetTriple || process.platform; + return /windows|mingw|cygwin|win32|msvc/.test(target); +} + +/** The top-level lines of every translated module plus the registry. + * `declared` holds the declarations the module already carries; runtime + * entry points it lacks are declared here. A module the translator + * cannot translate is left out of the registry, so loading it at run + * time rejects with the CompileError for untranslated bytes. */ +export function emitWasmModules( + mod: IrModule, + declared: ReadonlySet, + boundsChecks: boolean, +): string[] { + const modules = mod.wasmModules; + if (modules === undefined) return []; + const out: string[] = []; + const decls = new Set(declared); + const descriptors: string[] = []; + for (const module of modules) { + const bytes = decodeBase64(module.bytes); + if (bytes === null) continue; + let translation; + try { + translation = translateWasmModule(bytes, hexBytes(module.sha256), { boundsChecks }); + } catch (error) { + if (error instanceof WasmTranslateError) continue; + throw error; + } + for (const decl of translation.decls) { + if (decls.has(decl)) continue; + decls.add(decl); + out.push(decl); + } + for (const part of translation.text) out.push(part); + descriptors.push(translation.descriptor); + } + if (descriptors.length === 0) { + out.push(`@scr_wasm_registry = constant { i64, ptr } { i64 0, ptr null }`); + } else { + out.push( + `@scr_wasm_registry_modules = private constant [${descriptors.length} x ptr] [${descriptors.map((d) => `ptr ${d}`).join(", ")}]`, + `@scr_wasm_registry = constant { i64, ptr } { i64 ${descriptors.length}, ptr @scr_wasm_registry_modules }`, + ); + } + out.push(""); + return out; +} diff --git a/packages/compiler/src/backend/native-link-info-core.ts b/packages/compiler/src/backend/native-link-info-core.ts index 8f69cea57..ba36a8ddb 100644 --- a/packages/compiler/src/backend/native-link-info-core.ts +++ b/packages/compiler/src/backend/native-link-info-core.ts @@ -22,6 +22,9 @@ export interface NativeLinkFeatures { fileHandle: boolean; fetch: boolean; eventTarget?: boolean; + /** The WebAssembly API (scr_wasm.c). Absent when unused, so programs + * without it keep their link identity. */ + wasm?: boolean; zlib: boolean; assert: boolean; inspect: boolean; diff --git a/packages/compiler/src/backend/native/contracts.ts b/packages/compiler/src/backend/native/contracts.ts index 5e981fe44..6708c5bae 100644 --- a/packages/compiler/src/backend/native/contracts.ts +++ b/packages/compiler/src/backend/native/contracts.ts @@ -112,6 +112,8 @@ export interface CcOptions { * native fetch AbortSignal built on the same engine: compiles * scr_event_target.c. */ eventTarget?: boolean; + /** The WebAssembly API: compiles scr_wasm.c. */ + wasm?: boolean; /** The program uses the process-events surface (signal/exit listeners, * stdin events, for-await over stdin — moduleUsesProcessEvents on the * IR): compiles scr_events.c into the binary. Event-free binaries keep diff --git a/packages/compiler/src/backend/native/executable.ts b/packages/compiler/src/backend/native/executable.ts index 549e9ebbf..dd7369138 100644 --- a/packages/compiler/src/backend/native/executable.ts +++ b/packages/compiler/src/backend/native/executable.ts @@ -503,6 +503,7 @@ export async function compileCInternal( ...(opts.dynInvoke || opts.workers || nativeFetch ? [rt(join(rtDir, "scr_dyn_invoke.c"))] : []), ...(opts.dc ? [rt(join(rtDir, "scr_dc.c"))] : []), ...(opts.eventTarget || nativeFetch ? [rt(join(rtDir, "scr_event_target.c"))] : []), + ...(opts.wasm ? [rt(join(rtDir, "scr_wasm.c"))] : []), ...(opts.dynAsync || opts.dynInvoke || opts.workers || opts.dc || opts.fileHandle || nativeFetch ? [rt(join(rtDir, "scr_async_dyn.c"))] : []), diff --git a/packages/compiler/src/backend/runtime-pack-core.ts b/packages/compiler/src/backend/runtime-pack-core.ts index 039ecc209..a34df80bb 100644 --- a/packages/compiler/src/backend/runtime-pack-core.ts +++ b/packages/compiler/src/backend/runtime-pack-core.ts @@ -81,6 +81,7 @@ export interface RuntimePackManifest { export interface RuntimeFeatureSet extends NativeLinkFeatures { workers: boolean; eventTarget: boolean; + wasm: boolean; nativeFetch: boolean; netEffective: boolean; httpEffective: boolean; @@ -267,6 +268,7 @@ export function effectiveRuntimeFeatures( ...features, workers: features.workers === true, eventTarget: features.eventTarget === true, + wasm: features.wasm === true, nativeFetch, netEffective, httpEffective, diff --git a/packages/compiler/src/backend/target-diagnostics.ts b/packages/compiler/src/backend/target-diagnostics.ts index 365d5e913..d130ce95e 100644 --- a/packages/compiler/src/backend/target-diagnostics.ts +++ b/packages/compiler/src/backend/target-diagnostics.ts @@ -192,6 +192,14 @@ export function moduleWasiUnavailableSurface( found = { surface: "OS signals (WASI Preview 1 has no signal API)", loc }; return false; } + if (node.fn.startsWith("wasm.")) { + found = { + surface: + "the WebAssembly API (a wasm32-wasi program cannot load translated modules into its own linear memory yet)", + loc, + }; + return false; + } if (node.fn === "crypto.cipherNew") { found = { surface: diff --git a/packages/compiler/src/coverage/report.ts b/packages/compiler/src/coverage/report.ts index 6570f23d1..89733e255 100644 --- a/packages/compiler/src/coverage/report.ts +++ b/packages/compiler/src/coverage/report.ts @@ -221,13 +221,13 @@ export function renderCoverage(input: CoverageInput, opts: CoverageRenderOptions out.push(""); } - // The WebAssembly modules the build can see: what translating them would - // take (wasm/discover.ts). No module compiles to native code yet; the - // sites that load one are blockers or deferred fences above. + // The WebAssembly modules the build can see (wasm/discover.ts). A build + // translates the valid ones to native code (wasm/translate.ts) when the + // program uses the WebAssembly API; the others fail to load at run time. const wasm = input.wasmModules ?? []; if (wasm.length > 0) { out.push( - ` ${c(DIM, "WebAssembly modules:")} ${c(DIM, "(found at build time; none compile to native code yet)")}`, + ` ${c(DIM, "WebAssembly modules:")} ${c(DIM, "(found at build time; valid ones compile to native code)")}`, ); for (const m of wasm) { const name = m.path !== undefined ? displayPath(m.path, opts.root) : "inline module"; diff --git a/packages/compiler/src/coverage/surface-manifest.ts b/packages/compiler/src/coverage/surface-manifest.ts index d82367611..824790bd9 100644 --- a/packages/compiler/src/coverage/surface-manifest.ts +++ b/packages/compiler/src/coverage/surface-manifest.ts @@ -444,16 +444,17 @@ export function generateSurfaceManifest(compilerVersion: string): SurfaceManifes } } - // ── the WebAssembly global: every reached use refuses (webAssemblyDiag) ─ + // ── the WebAssembly global (builtins/webassembly.ts, wasm/translate.ts): + // the core API lowers over modules translated at build time; the rest + // refuses at each use (webAssemblyDiag) ─ { const refusal = webAssemblyDiag("WebAssembly", { file: "", start: 0, end: 0 }); add({ id: "stdlib.webassembly", kind: "stdlib", name: "globalThis.WebAssembly", - status: "unsupported", - code: refusal.code, - ...(refusal.hint !== undefined ? { note: refusal.hint } : {}), + status: "static", + note: `compile, instantiate, validate, Module, Instance, instanceof, the error classes, exports, memories, tables, globals and Module transfer to workers, over the modules translated at build time; the rest refuses with ${refusal.code} (${refusal.hint ?? ""})`, }); } diff --git a/packages/compiler/src/diagnostics/diagnostic.ts b/packages/compiler/src/diagnostics/diagnostic.ts index 4acd6bc50..0359dbf6b 100644 --- a/packages/compiler/src/diagnostics/diagnostic.ts +++ b/packages/compiler/src/diagnostics/diagnostic.ts @@ -751,17 +751,19 @@ export function noLoweringDiag( }; } -/** A REACHED use of the WebAssembly API (SC2020): scriptc.d.ts declares - * its types so packages that mention them typecheck, but no module - * compiles to native code yet. `surface` names what was used - * ('WebAssembly.compile', 'new WebAssembly.Module', 'globalThis.WebAssembly'). */ +/** A REACHED use of a part of the WebAssembly API scriptc does not + * implement yet (SC2020): the Memory, Table and Global constructors, + * Module.imports/exports/customSections, the streaming functions, the + * classes as values. compile, instantiate, validate, Module, Instance and + * the error classes lower (builtins/webassembly.ts). `surface` names what + * was used ('new WebAssembly.Memory', 'WebAssembly.Module.imports'). */ export function webAssemblyDiag(surface: string, loc: SrcLoc): ScrDiagnostic { return { code: "SC2020", - message: `'${surface}' is not supported yet: scriptc does not compile WebAssembly modules to native code yet`, + message: `'${surface}' is not supported yet: scriptc implements WebAssembly.compile, instantiate, validate, Module, Instance and the error classes over modules translated at build time, but not this part of the API`, loc, milestone: "later", - hint: "WebAssembly support is planned as translation of each module whose bytes the build can see (an inline literal, or a file read from a path the compiler resolves) into native code; 'scriptc coverage' lists the modules this program would load (https://scriptc.dev/limitations)", + hint: "the Memory, Table and Global constructors, Module.imports, exports and customSections, and the streaming functions are planned; 'scriptc coverage' lists the modules this program loads (https://scriptc.dev/limitations)", }; } diff --git a/packages/compiler/src/executable/executable-cache.ts b/packages/compiler/src/executable/executable-cache.ts index fbd7e4edd..c8b00adc7 100644 --- a/packages/compiler/src/executable/executable-cache.ts +++ b/packages/compiler/src/executable/executable-cache.ts @@ -55,6 +55,7 @@ export interface EarlyExecutableNativeFeatures { fileHandle: boolean; fetch: boolean; eventTarget?: boolean; + wasm?: boolean; zlib: boolean; assert: boolean; inspect: boolean; diff --git a/packages/compiler/src/frontend/lowering/builtins/webassembly.ts b/packages/compiler/src/frontend/lowering/builtins/webassembly.ts index dc61853f8..3a0a00508 100644 --- a/packages/compiler/src/frontend/lowering/builtins/webassembly.ts +++ b/packages/compiler/src/frontend/lowering/builtins/webassembly.ts @@ -2,17 +2,41 @@ import * as ts from "../../ts7/adapter.js"; import { type Lowerer, PoisonError } from "../lowerer.js"; import { locOf } from "../../program.js"; import { webAssemblyDiag } from "../../../diagnostics/diagnostic.js"; -import { STRING, type IrExpr } from "../../../ir/ir.js"; +import { BOOL, DYN, F64, STRING, type IrExpr } from "../../../ir/ir.js"; +import { dynUndefinedExpr } from "../../../ir/build.js"; +import { lowerDynObjectLiteral } from "../expressions/object-literals.js"; import { stdlibGlobalNameOf } from "../surfaces.js"; /* The WebAssembly API. scriptc.d.ts declares its types (lib.dom's - * namespace) so that packages whose declarations mention them typecheck, - * and the type mapper gives those types the checked-dynamic representation. - * No module compiles to native code yet, so every reached use of the API - * value fences at its expression with webAssemblyDiag: a build error in - * TypeScript sources, a located runtime fence in JavaScript ones. typeof - * needs no value and folds to Node's answer. */ + * namespace) and the type mapper gives them the checked-dynamic + * representation. Modules whose bytes the build sees are translated to + * native code (wasm/translate.ts) and found at run time by the SHA-256 of + * their bytes (scr_wasm.c): + * - WebAssembly.compile, instantiate and validate calls, new + * WebAssembly.Module and new WebAssembly.Instance lower to wasm.* + * libCalls; members of the values they produce (exports, memory + * buffers, globals, tables) dispatch through the native handles; + * - `v instanceof WebAssembly.` lowers to wasm.is; + * - the error classes construct with or without `new` (wasm.errorNew). + * Every other reached use of the API value (the bare namespace, the + * Memory, Table and Global constructors, Module.imports and friends, the + * streaming functions) still fences at its expression with + * webAssemblyDiag: a build error in TypeScript sources, a located runtime + * fence in JavaScript ones. typeof needs no value and folds to Node's + * answer. A lowering marks the lowerer, so the build translates the + * modules it discovers (lowerToIr). */ +/** The classes `wasm.is` answers, by kind. */ +const WASM_CLASSES = [ + "Module", + "Instance", + "Memory", + "Table", + "Global", + "CompileError", + "LinkError", + "RuntimeError", +] as const; /** Strips the spellings that leave a value unchanged: parentheses, type * assertions, `satisfies` and non-null assertions. */ function bare(expr: ts.Expression): ts.Expression { @@ -130,3 +154,158 @@ export function lowerWebAssemblyTypeof(lowerer: Lowerer, operand: ts.Expression) } return null; } + +/** The member `WebAssembly.` names (any spelling of the namespace), + * or null. */ +function webAssemblyMember(lowerer: Lowerer, expr: ts.Expression): string | null { + const surface = webAssemblySurface(lowerer, expr); + if (surface === null || !surface.startsWith("WebAssembly.")) return null; + const member = surface.slice("WebAssembly.".length); + return member.includes(".") ? null : member; +} + +function lowerArg(lowerer: Lowerer, arg: ts.Expression): IrExpr { + return ts.isObjectLiteralExpression(arg) + ? lowerDynObjectLiteral(lowerer, arg) + : lowerer.lowerExprExpecting(arg, DYN); +} + +/** The arguments, padded with undefined to `count`; null for spreads or + * extra arguments (they stay refused). */ +function lowerArgs( + lowerer: Lowerer, + args: readonly ts.Expression[], + count: number, + loc: IrExpr["loc"], +): IrExpr[] | null { + if (args.length > count || args.some(ts.isSpreadElement)) return null; + const out = args.map((arg) => lowerArg(lowerer, arg)); + while (out.length < count) out.push(dynUndefinedExpr(loc)); + return out; +} + +function errorNew(member: string, args: IrExpr[], loc: IrExpr["loc"]): IrExpr { + const kind = (WASM_CLASSES as readonly string[]).indexOf(member); + return { + kind: "libCall", + fn: "wasm.errorNew", + args: [{ kind: "numLit", value: kind, type: F64, loc }, args[0]!, args[1]!], + type: DYN, + loc, + }; +} + +/** `WebAssembly.compile(bytes)`, `WebAssembly.instantiate(source, + * imports?)`, `WebAssembly.validate(bytes)`, and the error classes called + * without `new`. Null for anything else. */ +export function lowerWebAssemblyCall(lowerer: Lowerer, call: ts.CallExpression): IrExpr | null { + if (call.questionDotToken) return null; + const member = webAssemblyMember(lowerer, call.expression); + if (member === null) return null; + const loc = locOf(call); + const isError = member === "CompileError" || member === "LinkError" || member === "RuntimeError"; + if (member !== "compile" && member !== "instantiate" && member !== "validate" && !isError) + return null; + const used = member === "instantiate" || isError ? 2 : 1; + const args = lowerArgs(lowerer, call.arguments, used, loc); + if (args === null) return null; + lowerer.usesWebAssembly = true; + let result: IrExpr; + if (isError) result = errorNew(member, args, loc); + else if (member === "validate") + result = { kind: "libCall", fn: "wasm.validate", args: [args[0]!], type: BOOL, loc }; + else if (member === "compile") + result = { + kind: "libCall", + fn: "wasm.compile", + args: [args[0]!], + type: { kind: "promise", inner: DYN }, + loc, + }; + else + result = { + kind: "libCall", + fn: "wasm.instantiate", + args: [args[0]!, args[1]!], + type: { kind: "promise", inner: DYN }, + loc, + }; + return result; +} + +/** `new WebAssembly.Module(bytes)`, `new WebAssembly.Instance(module, + * imports?)` and the error classes. Null for anything else. */ +export function lowerWebAssemblyNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr | null { + const member = webAssemblyMember(lowerer, expr.expression); + if (member === null) return null; + const isError = member === "CompileError" || member === "LinkError" || member === "RuntimeError"; + if (member !== "Module" && member !== "Instance" && !isError) return null; + const loc = locOf(expr); + const used = member === "Module" ? 1 : 2; + const args = lowerArgs(lowerer, expr.arguments ?? [], used, loc); + if (args === null) return null; + lowerer.usesWebAssembly = true; + let result: IrExpr; + if (isError) result = errorNew(member, args, loc); + else if (member === "Module") + result = { kind: "libCall", fn: "wasm.moduleNew", args: [args[0]!], type: DYN, loc }; + else + result = { + kind: "libCall", + fn: "wasm.instanceNew", + args: [args[0]!, args[1]!], + type: DYN, + loc, + }; + return result; +} + +/** `v instanceof WebAssembly.`. Null for anything else. */ +export function lowerWebAssemblyInstanceof( + lowerer: Lowerer, + expr: ts.BinaryExpression, +): IrExpr | null { + const member = webAssemblyMember(lowerer, expr.right); + if (member === null) return null; + const kind = (WASM_CLASSES as readonly string[]).indexOf(member); + if (kind < 0) return null; + const loc = locOf(expr); + lowerer.usesWebAssembly = true; + const value = lowerer.coerceInto(expr.left, lowerer.lowerExpr(expr.left), DYN); + return { + kind: "libCall", + fn: "wasm.is", + args: [value, { kind: "numLit", value: kind, type: F64, loc }], + type: BOOL, + loc, + }; +} + +/** WebAssembly API values: `WebAssembly.compile`, `instantiate` and + * `validate` read as functions, the namespace itself (`WebAssembly`, + * `globalThis.WebAssembly`), and the global object cast to a type with a + * `WebAssembly` member (pi codemode's spelling). Null for anything else, + * which refuseWebAssemblyUse then fences. */ +export function lowerWebAssemblyValue(lowerer: Lowerer, expr: ts.Expression): IrExpr | null { + const surface = webAssemblySurface(lowerer, expr); + if (surface === null) return null; + const loc = locOf(expr); + const node = bare(expr); + const fn = (name: string, args: IrExpr[]): IrExpr => { + lowerer.usesWebAssembly = true; + return { kind: "libCall", fn: name as "wasm.namespace", args, type: DYN, loc }; + }; + if (surface === "globalThis.WebAssembly") { + // The cast spelling names the global object, not its member. + const cast = + (ts.isAsExpression(expr) || ts.isTypeAssertion(expr)) && + isGlobalThis(lowerer, expr.expression); + return cast ? fn("wasm.globalView", []) : fn("wasm.namespace", []); + } + if (surface === "WebAssembly") return fn("wasm.namespace", []); + const index = ["WebAssembly.compile", "WebAssembly.instantiate", "WebAssembly.validate"].indexOf( + surface, + ); + if (index < 0 || !ts.isPropertyAccessExpression(node)) return null; + return fn("wasm.function", [{ kind: "numLit", value: index, type: F64, loc }]); +} diff --git a/packages/compiler/src/frontend/lowering/lower-exprs.ts b/packages/compiler/src/frontend/lowering/lower-exprs.ts index d72faa936..1800601e7 100644 --- a/packages/compiler/src/frontend/lowering/lower-exprs.ts +++ b/packages/compiler/src/frontend/lowering/lower-exprs.ts @@ -11,7 +11,12 @@ import { lowerWorkerMetadata } from "./builtins/workers.js"; import { esmImportBindingKind } from "./builtins/module-bindings.js"; import { lowerReadlineProperty } from "./builtins/readline.js"; import { lowerBlobInstanceof } from "./builtins/blob.js"; -import { lowerWebAssemblyTypeof, refuseWebAssemblyUse } from "./builtins/webassembly.js"; +import { + lowerWebAssemblyInstanceof, + lowerWebAssemblyTypeof, + lowerWebAssemblyValue, + refuseWebAssemblyUse, +} from "./builtins/webassembly.js"; import { lowerKeyObjectInstanceof } from "./builtins/key-object.js"; import { lowerNetAddressInstanceof, lowerNetAddressStaticRead } from "./builtins/net-address.js"; import { isExecExceptionType, refuseExecExceptionCode } from "./builtins/errors.js"; @@ -464,7 +469,10 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr { if (externalTypeSpecifier !== null) { lowerer.externalHostFence(externalTypeSpecifier, expr); } - // Every reached use of the WebAssembly API fences at its expression. + // WebAssembly API values lower; every other reached use of the API + // fences at its expression. + const webAssemblyValue = lowerWebAssemblyValue(lowerer, expr); + if (webAssemblyValue !== null) return webAssemblyValue; refuseWebAssemblyUse(lowerer, expr); if (ts.isNumericLiteral(expr)) { @@ -12514,6 +12522,8 @@ export function caughtLocalOf(lowerer: Lowerer, node: ts.Expression): IrLocal | * Folding is limited to side-effect-free operands — dropping a computed * operand would skip its effects, so those are rejected instead. */ export function lowerInstanceOf(lowerer: Lowerer, expr: ts.BinaryExpression, loc: SrcLoc): IrExpr { + const webAssembly = lowerWebAssemblyInstanceof(lowerer, expr); + if (webAssembly !== null) return webAssembly; refuseWebAssemblyUse(lowerer, expr.right); // `x instanceof net.Socket` over a union with a netSocket arm — the // h2 compat 'connect' narrowing (lower-server.ts): a union tag test. diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index 4b17af697..9cc8015e8 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -26,8 +26,13 @@ import { isSettledIteratorValueRead } from "./lower-generators.js"; import { concreteCollectionNarrow } from "./collection-narrowing.js"; import { lowerWorkerMetadata } from "./builtins/workers.js"; import { fenceStaticBlobMember, lowerBlobNew, lowerStaticBlobCall } from "./builtins/blob.js"; -import { refuseWebAssemblyUse } from "./builtins/webassembly.js"; +import { + lowerWebAssemblyCall, + lowerWebAssemblyNew, + refuseWebAssemblyUse, +} from "./builtins/webassembly.js"; import { discoverWasmModules, type WasmModuleReport } from "../../wasm/discover.js"; +import { encodeBase64 } from "../../wasm/base64.js"; import { resolveRequireRuntime } from "../runtime-resolve.js"; import { lowerKeyObjectCall } from "./builtins/key-object.js"; import { hashStreamOf } from "./builtins/hash-stream.js"; @@ -176,6 +181,7 @@ import type { IrGlobal, IrLocal, IrModule, + IrWasmModule, IrParam, IrRecordShape, IrStmt, @@ -1084,6 +1090,40 @@ export function lowerToIr( diagnostics: [...result.diagnostics, ...contracts.diagnostics], }; else if (contracts.count > 0) result = { ...result, contracts }; + const discoverModules = ( + onModule?: (sha256: string, bytes: Uint8Array) => void, + ): WasmModuleReport[] => + discoverWasmModules({ + files: moduleOrder.includes(entry) ? moduleOrder : [...moduleOrder, entry], + entry: entry.fileName, + configured: options.wasmFiles ?? [], + resolveRequest: (fromFile, specifier) => { + const resolved = resolveRequireRuntime( + fromFile, + specifier, + targetPlatform, + undefined, + options.frontendServices, + ); + return resolved.ok ? resolved.value : null; + }, + ...(onModule === undefined ? {} : { onModule }), + }); + // A program that uses the WebAssembly API carries every module the build + // can see and translate (wasm/translate.ts); the rest fail at run time + // with the CompileError for untranslated bytes. + if (result.module !== null && resultLowerer.usesWebAssembly) { + const bytesByHash = new Map(); + const reports = discoverModules((sha256, bytes) => bytesByHash.set(sha256, bytes)); + const wasmModules: IrWasmModule[] = []; + for (const report of reports) { + const bytes = bytesByHash.get(report.sha256); + if (report.status !== "valid" || bytes === undefined) continue; + wasmModules.push({ sha256: report.sha256, bytes: encodeBase64(bytes) }); + } + result.module.wasmModules = wasmModules; + timing("wasm-discovery", { modules: wasmModules.length }); + } if (options.coverage !== true) return result; const remainder = new Lowerer(program, entry, moduleOrder, { frontendServices: options.frontendServices, @@ -1137,21 +1177,7 @@ export function lowerToIr( }, }; if (tiers !== undefined) covered.tiers = tiers; - const wasmModules = discoverWasmModules({ - files: moduleOrder.includes(entry) ? moduleOrder : [...moduleOrder, entry], - entry: entry.fileName, - configured: options.wasmFiles ?? [], - resolveRequest: (fromFile, specifier) => { - const resolved = resolveRequireRuntime( - fromFile, - specifier, - targetPlatform, - undefined, - options.frontendServices, - ); - return resolved.ok ? resolved.value : null; - }, - }); + const wasmModules = discoverModules(); timing("wasm-discovery", { modules: wasmModules.length }); if (wasmModules.length > 0) covered.wasmModules = wasmModules; return covered; @@ -2652,6 +2678,9 @@ export class Lowerer { readonly workerTargets: readonly ts.SourceFile[]; readonly workerTargetIdByPath = new Map(); usesWorkers = false; + /** The program uses the WebAssembly API (builtins/webassembly.ts): the + * build translates the modules it discovers. */ + usesWebAssembly = false; /** The program constructs a LifetimeRegion (lifetime-regions.ts). */ usesLifetimeRegions = false; /** Per-directory answers of experimental-import.ts's declaration-file test. */ @@ -11509,6 +11538,8 @@ export class Lowerer { // dispatch. The general identifier-call paths have no lowering for a // promise-returning ambient global, and `import` is a keyword callee no // identifier path matches. + const webAssembly = lowerWebAssemblyCall(this, expr); + if (webAssembly !== null) return webAssembly; refuseWebAssemblyUse(this, expr.expression); return ( lowerLifetimeRegionCall(this, expr) ?? @@ -11735,6 +11766,8 @@ export class Lowerer { } lowerNew(expr: ts.NewExpression): IrExpr { + const webAssembly = lowerWebAssemblyNew(this, expr); + if (webAssembly !== null) return webAssembly; refuseWebAssemblyUse(this, expr.expression, "new "); const builtin = lowerLifetimeRegionNew(this, expr) ?? diff --git a/packages/compiler/src/frontend/type-mapper.ts b/packages/compiler/src/frontend/type-mapper.ts index 1aa5b1d1b..c8f8ee55a 100644 --- a/packages/compiler/src/frontend/type-mapper.ts +++ b/packages/compiler/src/frontend/type-mapper.ts @@ -1446,6 +1446,18 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // option) holds that dynamic value too, so the whole union is dynamic. if (type.isUnionType() && ts.constituentTypes(type).some((t) => isWebAssemblyType(t, ctx))) return DYN; + // JavaScript inference merges WebAssembly.instantiate's overloads into + // `Promise & Promise`: a promise + // of a WebAssembly value either way. + if ( + type.isIntersectionType() && + ts.constituentTypes(type).every((t) => { + if (t.getSymbol()?.name !== "Promise") return false; + const arg = ctx.checker.getTypeArguments(t as ts.TypeReference)[0]; + return arg !== undefined && isWebAssemblyType(arg, ctx); + }) + ) + return { kind: "promise", inner: DYN }; // Immutable (@scriptc/types) is a deep-readonly view: the value is // still the T it maps. const immutableSource = immutableSourceType(type, ctx); diff --git a/packages/compiler/src/index.ts b/packages/compiler/src/index.ts index 56b787e18..8fa53029b 100644 --- a/packages/compiler/src/index.ts +++ b/packages/compiler/src/index.ts @@ -527,6 +527,7 @@ async function compileExecutableNative( fileHandle: features.fileHandle, fetch: features.fetch, ...(features.eventTarget ? { eventTarget: true } : {}), + ...(features.wasm ? { wasm: true } : {}), zlib: features.zlib, assert: features.assert, inspect: features.inspect, @@ -716,6 +717,7 @@ async function prepareExecutableInput( wasi: buildPlatform === "wasi", runtimeAbiMarker: outputKind === "obj", objectAudit: opts.sanitize === true, + ...(opts.sanitize === true ? { wasmBoundsChecks: true } : {}), ...inlineRcOption(opts.optimization, opts.sanitize === true), }); } catch (err) { @@ -781,6 +783,7 @@ async function prepareExecutableInput( wasi: buildPlatform === "wasi", runtimeAbiMarker: useRuntimePack, objectAudit: opts.sanitize === true, + ...(opts.sanitize === true ? { wasmBoundsChecks: true } : {}), ...inlineRcOption(opts.optimization, opts.sanitize === true), ...(opts.detachedExit === true ? { detachedExit: true } : {}), }); diff --git a/packages/compiler/src/ir/builtin-effects.ts b/packages/compiler/src/ir/builtin-effects.ts index a62046565..c6e93c268 100644 --- a/packages/compiler/src/ir/builtin-effects.ts +++ b/packages/compiler/src/ir/builtin-effects.ts @@ -132,6 +132,12 @@ export const MAY_THROW_LIB_FNS: ReadonlySet = new Set([ "blob.new", "blob.fileNew", "blob.slice", + "wasm.compile", + "wasm.validate", + "wasm.instantiate", + "wasm.moduleNew", + "wasm.instanceNew", + "wasm.errorNew", "events.addAbortListener", "fetch.streamNew", "fetch.streamFrom", diff --git a/packages/compiler/src/ir/builtin-signatures.ts b/packages/compiler/src/ir/builtin-signatures.ts index e61bb2594..1ec915409 100644 --- a/packages/compiler/src/ir/builtin-signatures.ts +++ b/packages/compiler/src/ir/builtin-signatures.ts @@ -106,6 +106,27 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ "blob.bytes": { argTypes: [DYN], result: { kind: "promise", inner: BYTES_U8 } }, "blob.arrayBuffer": { argTypes: [DYN], result: { kind: "promise", inner: DYN } }, "blob.is": { argTypes: [DYN, F64], result: BOOL }, + /** WebAssembly (scr_wasm.c): modules translated at build time + * (wasm/translate.ts), found at run time by the SHA-256 of their bytes. + * compile and instantiate over bytes settle on a later loop turn; + * instantiate over a Module settles now, as in Node. `wasm.is` answers + * instanceof for Module, Instance, Memory, Table and Global (kinds 0-4) + * and CompileError, LinkError and RuntimeError (5-7); `wasm.errorNew` + * constructs one of the error classes (kind 5-7, message, options). */ + "wasm.compile": { argTypes: [DYN], result: { kind: "promise", inner: DYN } }, + "wasm.validate": { argTypes: [DYN], result: BOOL }, + "wasm.instantiate": { argTypes: [DYN, DYN], result: { kind: "promise", inner: DYN } }, + "wasm.moduleNew": { argTypes: [DYN], result: DYN }, + "wasm.instanceNew": { argTypes: [DYN, DYN], result: DYN }, + "wasm.is": { argTypes: [DYN, F64], result: BOOL }, + "wasm.errorNew": { argTypes: [F64, DYN, DYN], result: DYN }, + /** WebAssembly.compile (0), instantiate (1) and validate (2) as function + * values, and the namespace object holding them. */ + "wasm.function": { argTypes: [F64], result: DYN }, + "wasm.namespace": { argTypes: [], result: DYN }, + /** The global object viewed through a cast to a type with a WebAssembly + * member: an object holding the namespace. */ + "wasm.globalView": { argTypes: [], result: DYN }, "abort.timeout": { argTypes: [DYN], result: DYN }, "abort.now": { argTypes: [DYN], result: DYN }, "abort.any": { argTypes: [DYN], result: DYN }, diff --git a/packages/compiler/src/ir/function-references.test.ts b/packages/compiler/src/ir/function-references.test.ts index 0b1f9fb49..9d1e192bd 100644 --- a/packages/compiler/src/ir/function-references.test.ts +++ b/packages/compiler/src/ir/function-references.test.ts @@ -209,6 +209,8 @@ const MODULE_FIELDS: Record>, Role> = { "contracts.acyclic[]": "other", "contracts.immortalGlobals[]": "other", "contracts.programLifetime[]": "other", + "wasmModules[].sha256": "other", + "wasmModules[].bytes": "other", }; const loc = { file: "references.ts", start: 0, end: 0 }; diff --git a/packages/compiler/src/ir/ir.ts b/packages/compiler/src/ir/ir.ts index 90a98a491..145038748 100644 --- a/packages/compiler/src/ir/ir.ts +++ b/packages/compiler/src/ir/ir.ts @@ -998,6 +998,20 @@ export interface IrModule { * write in the program (ir/contract-check.ts). Backends may rely on them * unconditionally. Absent when the program writes no contract. */ contracts?: IrContracts; + /** The WebAssembly modules translated into this program (wasm/ + * translate.ts), present exactly when the program uses the WebAssembly + * API (possibly empty). The backend appends each module's translation + * and emits the registry the runtime looks modules up in. */ + wasmModules?: IrWasmModule[]; +} + +/** A WebAssembly module whose bytes the build saw, validated within the + * translatable feature level. */ +export interface IrWasmModule { + /** Hex SHA-256 of the bytes. */ + sha256: string; + /** The bytes, base64. */ + bytes: string; } /** Verified contract facts, keyed by IR names. */ diff --git a/packages/compiler/src/ir/runtime-features.ts b/packages/compiler/src/ir/runtime-features.ts index 7df85c738..125d30cec 100644 --- a/packages/compiler/src/ir/runtime-features.ts +++ b/packages/compiler/src/ir/runtime-features.ts @@ -43,6 +43,8 @@ export interface RuntimeFeatures { tls: boolean; tlsCa: boolean; cipher: boolean; + /** The WebAssembly API (scr_wasm.c). */ + wasm: boolean; } const DYN_ASYNC_LIB_FNS: ReadonlySet = new Set([ @@ -217,6 +219,7 @@ function scanRuntimeFeatures(mod: IrModule, stopAt?: keyof RuntimeFeatures): Run tls: false, tlsCa: false, cipher: false, + wasm: false, }; const keepGoing = (): boolean => stopAt === undefined || !features[stopAt]; let tables: TypeTables | undefined; @@ -427,6 +430,13 @@ function scanRuntimeFeatures(mod: IrModule, stopAt?: keyof RuntimeFeatures): Run // scr_cipher.c over the mbedTLS archive; DEP0182 reports through the // process-warning unit, and unknown output encodings render through // the inspect unit. + // scr_wasm.c boxes its handles and promises through the dyn-async + // unit and converts i64 values through BigInt. + if (fn.startsWith("wasm.")) { + features.wasm = true; + features.dynAsync = true; + features.bigint = true; + } if (fn === "crypto.cipherNew") { features.cipher = true; features.dynAsync = true; diff --git a/packages/compiler/src/native/sanitizer.test.ts b/packages/compiler/src/native/sanitizer.test.ts index 9047f3376..52cebcaf6 100644 --- a/packages/compiler/src/native/sanitizer.test.ts +++ b/packages/compiler/src/native/sanitizer.test.ts @@ -74,6 +74,7 @@ test.skipIf(process.platform !== "linux" && process.platform !== "darwin")( features: { workers: false, eventTarget: false, + wasm: false, regex: true, copying: false, textDecoderLegacy: false, diff --git a/packages/compiler/src/wasm/base64.ts b/packages/compiler/src/wasm/base64.ts new file mode 100644 index 000000000..122bcd4ef --- /dev/null +++ b/packages/compiler/src/wasm/base64.ts @@ -0,0 +1,61 @@ +/* Base64 for WebAssembly module bytes: decoding inline modules found in + * source text (discover.ts) and carrying translated modules on the IR + * (IrModule.wasmModules). Plain TypeScript, so the native compiler + * compiles it too. */ + +const BASE64_VALUES = (() => { + const table = new Int16Array(128).fill(-1); + const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + for (let i = 0; i < alphabet.length; i++) table[alphabet.charCodeAt(i)] = i; + // base64url spellings + table["-".charCodeAt(0)] = 62; + table["_".charCodeAt(0)] = 63; + return table; +})(); + +/** Decodes standard or URL-safe base64 (padding optional); null when the + * text is not base64. */ +export function decodeBase64(text: string): Uint8Array | null { + let end = text.length; + while (end > 0 && text.charCodeAt(end - 1) === 61) end--; + if (text.length - end > 2 || end % 4 === 1) return null; + const out = new Uint8Array(Math.floor((end * 3) / 4)); + let acc = 0; + let bits = 0; + let j = 0; + for (let i = 0; i < end; i++) { + const c = text.charCodeAt(i); + const v = c < 128 ? BASE64_VALUES[c]! : -1; + if (v < 0) return null; + acc = ((acc << 6) | v) & 0xffffff; + bits += 6; + if (bits >= 8) { + bits -= 8; + out[j++] = (acc >> bits) & 0xff; + } + } + return j === out.length ? out : out.subarray(0, j); +} + +const BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +/** Standard base64 with padding. */ +export function encodeBase64(bytes: Uint8Array): string { + const parts: string[] = []; + let chunk = ""; + for (let i = 0; i < bytes.length; i += 3) { + const a = bytes[i]!; + const b = i + 1 < bytes.length ? bytes[i + 1]! : 0; + const c = i + 2 < bytes.length ? bytes[i + 2]! : 0; + const n = (a << 16) | (b << 8) | c; + chunk += BASE64_ALPHABET[(n >> 18) & 63]! + BASE64_ALPHABET[(n >> 12) & 63]!; + chunk += i + 1 < bytes.length ? BASE64_ALPHABET[(n >> 6) & 63]! : "="; + chunk += i + 2 < bytes.length ? BASE64_ALPHABET[n & 63]! : "="; + if (chunk.length >= 65536) { + parts.push(chunk); + chunk = ""; + } + } + parts.push(chunk); + return parts.join(""); +} diff --git a/packages/compiler/src/wasm/discover.ts b/packages/compiler/src/wasm/discover.ts index 610a389f1..856f0752f 100644 --- a/packages/compiler/src/wasm/discover.ts +++ b/packages/compiler/src/wasm/discover.ts @@ -28,6 +28,9 @@ import { trackedReadFile, } from "../frontend/input-tracker.js"; import { analyzeWasm, isWasmHeader, type WasmModuleSummary } from "./binary.js"; +import { decodeBase64 } from "./base64.js"; + +export { decodeBase64 }; export type WasmSourceKind = "path" | "inline" | "package" | "config"; @@ -56,6 +59,9 @@ export interface WasmDiscoveryInput { configured: readonly string[]; /** require.resolve from `fromFile`, or null when it does not resolve. */ resolveRequest: (fromFile: string, specifier: string) => string | null; + /** Receives each distinct module's bytes once, when it is first found + * (builds translate them). */ + onModule?: (sha256: string, bytes: Uint8Array) => void; } const SCAN_KINDS: ReadonlySet = new Set([ @@ -110,6 +116,7 @@ class Discovery { }; this.byHash.set(sha256, report); this.order.push(sha256); + this.input.onModule?.(sha256, bytes); } if (report.path === undefined && path !== undefined) report.path = path; addSource(report, source); @@ -351,40 +358,6 @@ function byteArrayLiteral(node: ts.ArrayLiteralExpression): Uint8Array | null { return out; } -const BASE64_VALUES = (() => { - const table = new Int16Array(128).fill(-1); - const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - for (let i = 0; i < alphabet.length; i++) table[alphabet.charCodeAt(i)] = i; - // base64url spellings - table["-".charCodeAt(0)] = 62; - table["_".charCodeAt(0)] = 63; - return table; -})(); - -/** Decodes standard or URL-safe base64 (padding optional); null when the - * text is not base64. */ -export function decodeBase64(text: string): Uint8Array | null { - let end = text.length; - while (end > 0 && text.charCodeAt(end - 1) === 61) end--; - if (text.length - end > 2 || end % 4 === 1) return null; - const out = new Uint8Array(Math.floor((end * 3) / 4)); - let acc = 0; - let bits = 0; - let j = 0; - for (let i = 0; i < end; i++) { - const c = text.charCodeAt(i); - const v = c < 128 ? BASE64_VALUES[c]! : -1; - if (v < 0) return null; - acc = ((acc << 6) | v) & 0xffffff; - bits += 6; - if (bits >= 8) { - bits -= 8; - out[j++] = (acc >> bits) & 0xff; - } - } - return j === out.length ? out : out.subarray(0, j); -} - /** The root directory of the npm package that contains `file` (the * directory below its innermost node_modules), or null outside one. */ export function packageRootOf(file: string): string | null { diff --git a/packages/compiler/src/wasm/module.ts b/packages/compiler/src/wasm/module.ts new file mode 100644 index 000000000..3b0dd54c0 --- /dev/null +++ b/packages/compiler/src/wasm/module.ts @@ -0,0 +1,659 @@ +/* The structure of a WebAssembly module, for the translator. + * + * binary.ts validates a module and keeps nothing but counts. This parser + * reads the parts translation needs (types, imports, functions, tables, + * memories, globals, exports, the start function, element and data + * segments, function bodies and custom sections) from bytes that binary.ts + * already accepted, so it checks nothing beyond what it must to stay in + * bounds. Constant expressions and function bodies are kept as byte ranges + * that the translator reads itself. + * + * i64 values never become JavaScript numbers: the translator reads their + * LEB128 encodings into two 32-bit halves. Plain TypeScript, so the native + * compiler compiles it too. */ + +export const VAL_I32 = 0x7f; +export const VAL_I64 = 0x7e; +export const VAL_F32 = 0x7d; +export const VAL_F64 = 0x7c; +export const VAL_FUNCREF = 0x70; +export const VAL_EXTERNREF = 0x6f; + +export const KIND_FUNC = 0; +export const KIND_TABLE = 1; +export const KIND_MEMORY = 2; +export const KIND_GLOBAL = 3; + +export interface WasmFuncType { + params: number[]; + results: number[]; +} + +/** A byte range of the module: a constant expression (through its `end` + * opcode) or a payload. */ +export interface WasmRange { + start: number; + end: number; +} + +export interface WasmImport { + module: string; + name: string; + kind: number; + /** Functions: the type index. Tables and globals: the value type. */ + type: number; + min: number; + max: number | null; + mutable: boolean; +} + +export interface WasmTable { + elemType: number; + min: number; + max: number | null; + imported: boolean; + /** A defined table's initializer (reference-types table with an init + * expression), or null for the null reference. */ + init: WasmRange | null; +} + +export interface WasmMemory { + min: number; + max: number | null; + imported: boolean; +} + +export interface WasmGlobal { + type: number; + mutable: boolean; + imported: boolean; + init: WasmRange | null; +} + +export interface WasmExport { + name: string; + kind: number; + index: number; +} + +export interface WasmElem { + mode: "active" | "passive" | "declarative"; + table: number; + offset: WasmRange | null; + elemType: number; + /** One initializer per element: a function index (>= 0), or -1 when the + * element is a constant expression in `exprs`. */ + funcs: number[]; + exprs: (WasmRange | null)[]; +} + +export interface WasmData { + mode: "active" | "passive"; + memory: number; + offset: WasmRange | null; + payload: WasmRange; +} + +export interface WasmCode { + /** Declared locals, expanded (not including parameters). */ + locals: number[]; + body: WasmRange; +} + +export interface WasmCustom { + name: string; + payload: WasmRange; +} + +export interface WasmModule { + bytes: Uint8Array; + types: WasmFuncType[]; + imports: WasmImport[]; + /** The type index of every function, imported functions first. */ + funcTypes: number[]; + importedFuncs: number; + tables: WasmTable[]; + memories: WasmMemory[]; + globals: WasmGlobal[]; + exports: WasmExport[]; + start: number | null; + elems: WasmElem[]; + datas: WasmData[]; + codes: WasmCode[]; + customs: WasmCustom[]; + /** Functions referenced outside a body (exports, element segments, + * ref.func in constant expressions): only these need a function + * reference record. Bodies add their own ref.func targets, which a valid + * module also declares in one of these places. */ + declaredRefs: Set; +} + +/** A byte cursor over a module that binary.ts validated. */ +export class WasmCursor { + bytes: Uint8Array; + pos: number; + constructor(bytes: Uint8Array, pos: number) { + this.bytes = bytes; + this.pos = pos; + } + + u8(): number { + const b = this.bytes[this.pos]; + if (b === undefined) throw new Error("wasm: unexpected end of module"); + this.pos++; + return b; + } + + u32(): number { + let result = 0; + let scale = 1; + for (;;) { + const b = this.u8(); + result += (b & 0x7f) * scale; + if ((b & 0x80) === 0) return result; + scale *= 128; + } + } + + /** A signed LEB128 of at most 33 bits. */ + s33(): number { + let result = 0; + let scale = 1; + for (;;) { + const b = this.u8(); + result += (b & 0x7f) * scale; + scale *= 128; + if ((b & 0x80) === 0) { + if (b & 0x40) result -= scale; + return result; + } + } + } + + /** A signed 32-bit LEB128 as a signed 32-bit number. */ + s32(): number { + return this.s33() | 0; + } + + /** A signed 64-bit LEB128 as its two's-complement halves [lo, hi], each + * an unsigned 32-bit number. */ + s64(): [number, number] { + let lo = 0; + let hi = 0; + let shift = 0; + let b = 0; + do { + b = this.u8(); + const bits = b & 0x7f; + if (shift < 32) { + lo = (lo | (bits << shift)) >>> 0; + if (shift > 25) hi = (hi | (bits >>> (32 - shift))) >>> 0; + } else if (shift < 64) { + hi = (hi | (bits << (shift - 32))) >>> 0; + } + shift += 7; + } while (b & 0x80); + if (shift < 64 && b & 0x40) { + // Sign-extend from bit `shift`. + if (shift < 32) { + lo = (lo | (~0 << shift)) >>> 0; + hi = 0xffffffff; + } else { + hi = (hi | (~0 << (shift - 32))) >>> 0; + } + } + return [lo, hi]; + } + + u32le(): number { + const b = this.bytes; + const p = this.pos; + this.pos += 4; + return (b[p]! | (b[p + 1]! << 8) | (b[p + 2]! << 16) | (b[p + 3]! << 24)) >>> 0; + } + + name(): string { + const length = this.u32(); + const start = this.pos; + this.pos += length; + return decodeUtf8(this.bytes, start, this.pos); + } + + /** Skips a constant expression through its `end` and answers its range. */ + constExpr(): WasmRange { + const start = this.pos; + for (;;) { + const op = this.u8(); + if (op === 0x0b) break; + skipImmediates(this, op); + } + return { start, end: this.pos }; + } + + valType(): number { + return this.u8(); + } + + /** A block type: -1 for the empty type, a value type, or a type index. */ + blockType(): + | { kind: "empty" } + | { kind: "value"; type: number } + | { kind: "index"; index: number } { + const b = this.bytes[this.pos]!; + if (b === 0x40) { + this.pos++; + return { kind: "empty" }; + } + if ( + b === VAL_I32 || + b === VAL_I64 || + b === VAL_F32 || + b === VAL_F64 || + b === VAL_FUNCREF || + b === VAL_EXTERNREF + ) { + this.pos++; + return { kind: "value", type: b }; + } + return { kind: "index", index: this.s33() }; + } +} + +function decodeUtf8(bytes: Uint8Array, start: number, end: number): string { + let out = ""; + let i = start; + while (i < end) { + const b = bytes[i]!; + let cp = b; + let need = 0; + if (b >= 0xf0) { + cp = b & 0x07; + need = 3; + } else if (b >= 0xe0) { + cp = b & 0x0f; + need = 2; + } else if (b >= 0xc0) { + cp = b & 0x1f; + need = 1; + } + for (let k = 1; k <= need; k++) cp = cp * 64 + (bytes[i + k]! & 0x3f); + out += String.fromCodePoint(cp); + i += need + 1; + } + return out; +} + +/** Reads the immediates of opcode `op` (already consumed). Covers the + * translatable feature level; anything else was refused before + * translation. */ +export function skipImmediates(c: WasmCursor, op: number): void { + switch (op) { + case 0x02: + case 0x03: + case 0x04: + c.blockType(); + return; + case 0x0c: + case 0x0d: + case 0x10: + case 0x20: + case 0x21: + case 0x22: + case 0x23: + case 0x24: + case 0x25: + case 0x26: + case 0xd2: + c.u32(); + return; + case 0x0e: { + const n = c.u32(); + for (let i = 0; i <= n; i++) c.u32(); + return; + } + case 0x11: + c.u32(); + c.u32(); + return; + case 0x1c: { + const n = c.u32(); + for (let i = 0; i < n; i++) c.valType(); + return; + } + case 0x3f: + case 0x40: + // The memory index (a LEB128 zero; V8 accepts long encodings). + c.u32(); + return; + case 0x41: + c.s32(); + return; + case 0x42: + c.s64(); + return; + case 0x43: + c.pos += 4; + return; + case 0x44: + c.pos += 8; + return; + case 0xd0: + c.valType(); + return; + case 0xfc: { + const sub = c.u32(); + switch (sub) { + case 8: + c.u32(); + c.u32(); + return; + case 9: + case 13: + case 15: + case 16: + case 17: + c.u32(); + return; + case 10: + c.u32(); + c.u32(); + return; + case 11: + c.u32(); + return; + case 12: + case 14: + c.u32(); + c.u32(); + return; + default: + return; + } + } + default: + if (op >= 0x28 && op <= 0x3e) { + c.u32(); + c.u32(); + } + return; + } +} + +function limits(c: WasmCursor): { min: number; max: number | null } { + const flags = c.u8(); + const min = c.u32(); + const max = flags & 0x01 ? c.u32() : null; + return { min, max }; +} + +/** Parses a module binary.ts reported as valid within the translatable + * feature level. */ +export function parseWasmModule(bytes: Uint8Array): WasmModule { + const m: WasmModule = { + bytes, + types: [], + imports: [], + funcTypes: [], + importedFuncs: 0, + tables: [], + memories: [], + globals: [], + exports: [], + start: null, + elems: [], + datas: [], + codes: [], + customs: [], + declaredRefs: new Set(), + }; + const c = new WasmCursor(bytes, 8); + while (c.pos < bytes.length) { + const id = c.u8(); + const size = c.u32(); + const end = c.pos + size; + switch (id) { + case 0: { + const name = c.name(); + m.customs.push({ name, payload: { start: c.pos, end } }); + break; + } + case 1: { + const n = c.u32(); + for (let i = 0; i < n; i++) { + c.u8(); // 0x60 + const params: number[] = []; + const np = c.u32(); + for (let k = 0; k < np; k++) params.push(c.valType()); + const results: number[] = []; + const nr = c.u32(); + for (let k = 0; k < nr; k++) results.push(c.valType()); + m.types.push({ params, results }); + } + break; + } + case 2: { + const n = c.u32(); + for (let i = 0; i < n; i++) { + const module = c.name(); + const name = c.name(); + const kind = c.u8(); + const imp: WasmImport = { + module, + name, + kind, + type: 0, + min: 0, + max: null, + mutable: false, + }; + if (kind === KIND_FUNC) { + imp.type = c.u32(); + m.funcTypes.push(imp.type); + m.importedFuncs++; + } else if (kind === KIND_TABLE) { + imp.type = c.valType(); + const l = limits(c); + imp.min = l.min; + imp.max = l.max; + m.tables.push({ + elemType: imp.type, + min: l.min, + max: l.max, + imported: true, + init: null, + }); + } else if (kind === KIND_MEMORY) { + const l = limits(c); + imp.min = l.min; + imp.max = l.max; + m.memories.push({ min: l.min, max: l.max, imported: true }); + } else { + imp.type = c.valType(); + imp.mutable = c.u8() === 1; + m.globals.push({ type: imp.type, mutable: imp.mutable, imported: true, init: null }); + } + m.imports.push(imp); + } + break; + } + case 3: { + const n = c.u32(); + for (let i = 0; i < n; i++) m.funcTypes.push(c.u32()); + break; + } + case 4: { + const n = c.u32(); + for (let i = 0; i < n; i++) { + let init: WasmRange | null = null; + if (c.bytes[c.pos] === 0x40) { + // table with an initializer: 0x40 0x00 tabletype expr + c.pos += 2; + const elemType = c.valType(); + const l = limits(c); + init = c.constExpr(); + m.tables.push({ elemType, min: l.min, max: l.max, imported: false, init }); + continue; + } + const elemType = c.valType(); + const l = limits(c); + m.tables.push({ elemType, min: l.min, max: l.max, imported: false, init }); + } + break; + } + case 5: { + const n = c.u32(); + for (let i = 0; i < n; i++) { + const l = limits(c); + m.memories.push({ min: l.min, max: l.max, imported: false }); + } + break; + } + case 6: { + const n = c.u32(); + for (let i = 0; i < n; i++) { + const type = c.valType(); + const mutable = c.u8() === 1; + const init = c.constExpr(); + noteConstRefs(m, init); + m.globals.push({ type, mutable, imported: false, init }); + } + break; + } + case 7: { + const n = c.u32(); + for (let i = 0; i < n; i++) { + const name = c.name(); + const kind = c.u8(); + const index = c.u32(); + m.exports.push({ name, kind, index }); + if (kind === KIND_FUNC) m.declaredRefs.add(index); + } + break; + } + case 8: + m.start = c.u32(); + break; + case 9: { + const n = c.u32(); + for (let i = 0; i < n; i++) m.elems.push(elemSegment(m, c)); + break; + } + case 10: { + const n = c.u32(); + for (let i = 0; i < n; i++) { + const size = c.u32(); + const bodyEnd = c.pos + size; + const locals: number[] = []; + const groups = c.u32(); + for (let g = 0; g < groups; g++) { + const count = c.u32(); + const type = c.valType(); + for (let k = 0; k < count; k++) locals.push(type); + } + m.codes.push({ locals, body: { start: c.pos, end: bodyEnd } }); + c.pos = bodyEnd; + } + break; + } + case 11: { + const n = c.u32(); + for (let i = 0; i < n; i++) { + const flags = c.u32(); + if (flags === 1) { + const size = c.u32(); + m.datas.push({ + mode: "passive", + memory: 0, + offset: null, + payload: { start: c.pos, end: c.pos + size }, + }); + c.pos += size; + continue; + } + const memory = flags === 2 ? c.u32() : 0; + const offset = c.constExpr(); + const size = c.u32(); + m.datas.push({ + mode: "active", + memory, + offset, + payload: { start: c.pos, end: c.pos + size }, + }); + c.pos += size; + } + break; + } + default: + // 12 (data count) and anything binary.ts accepted without content + // translation needs. + break; + } + c.pos = end; + } + return m; +} + +function elemSegment(m: WasmModule, c: WasmCursor): WasmElem { + const flags = c.u32(); + const passiveOrDeclarative = (flags & 1) !== 0; + const explicitTable = (flags & 2) !== 0; + const usesExprs = (flags & 4) !== 0; + let mode: WasmElem["mode"] = "active"; + if (passiveOrDeclarative) mode = explicitTable ? "declarative" : "passive"; + let table = 0; + let offset: WasmRange | null = null; + if (!passiveOrDeclarative) { + if (explicitTable) table = c.u32(); + offset = c.constExpr(); + } + let elemType = VAL_FUNCREF; + if (passiveOrDeclarative || explicitTable) { + // elemkind (0x00 = funcref) or a reference type + const b = c.u8(); + elemType = usesExprs ? b : VAL_FUNCREF; + } + const n = c.u32(); + const funcs: number[] = []; + const exprs: (WasmRange | null)[] = []; + for (let i = 0; i < n; i++) { + if (usesExprs) { + const expr = c.constExpr(); + const direct = refFuncOf(m.bytes, expr); + if (direct !== null) { + funcs.push(direct); + exprs.push(null); + m.declaredRefs.add(direct); + } else { + noteConstRefs(m, expr); + funcs.push(-1); + exprs.push(expr); + } + } else { + const f = c.u32(); + funcs.push(f); + exprs.push(null); + m.declaredRefs.add(f); + } + } + return { mode, table, offset, elemType, funcs, exprs }; +} + +/** The function of a `ref.func f; end` expression, or null. */ +function refFuncOf(bytes: Uint8Array, expr: WasmRange): number | null { + if (bytes[expr.start] !== 0xd2) return null; + const c = new WasmCursor(bytes, expr.start + 1); + const f = c.u32(); + return c.pos === expr.end - 1 && bytes[c.pos] === 0x0b ? f : null; +} + +function noteConstRefs(m: WasmModule, expr: WasmRange): void { + const c = new WasmCursor(m.bytes, expr.start); + while (c.pos < expr.end) { + const op = c.u8(); + if (op === 0xd2) { + m.declaredRefs.add(c.u32()); + continue; + } + skipImmediates(c, op); + } +} diff --git a/packages/compiler/src/wasm/translate.test.ts b/packages/compiler/src/wasm/translate.test.ts new file mode 100644 index 000000000..a9ef58248 --- /dev/null +++ b/packages/compiler/src/wasm/translate.test.ts @@ -0,0 +1,145 @@ +import { readFileSync } from "node:fs"; +import { join } from "node:path"; +import { describe, expect, test } from "vitest"; +import { analyzeWasm } from "./binary.js"; +import { decodeBase64, encodeBase64 } from "./base64.js"; +import { WasmCursor, parseWasmModule } from "./module.js"; +import { + signatureHash, + stackCheckedFunctions, + translateWasmModule, + wasmSymbolPrefix, +} from "./translate.js"; + +const SHA = new Uint8Array(32).fill(0xab); + +/** (module (func (export "add") (param i32 i32) (result i32) local.get 0 local.get 1 i32.add) + * (memory (export "mem") 1) (data (i32.const 8) "hi")) */ +const ADD = new Uint8Array([ + 0, 97, 115, 109, 1, 0, 0, 0, 1, 7, 1, 96, 2, 127, 127, 1, 127, 3, 2, 1, 0, 5, 3, 1, 0, 1, 7, 13, + 2, 3, 97, 100, 100, 0, 0, 3, 109, 101, 109, 2, 0, 10, 9, 1, 7, 0, 32, 0, 32, 1, 106, 11, 11, 8, 1, + 0, 65, 8, 11, 2, 104, 105, +]); + +/** Every top-level line must be one the native helper's text partitioner + * accepts (native/llvm-codegen/src/partition.cpp), so translated modules + * keep the program on its concurrent parsing path. */ +function topLevelLines(text: string[]): string[] { + const lines: string[] = []; + for (const part of text) { + let inFunction = false; + for (const line of part.split("\n")) { + if (inFunction) { + if (line === "}") inFunction = false; + continue; + } + if (line.startsWith("define ")) inFunction = true; + lines.push(line); + } + } + return lines; +} + +describe("translateWasmModule", () => { + test("emits functions, thunks, data and the descriptor in the helper's grammar", () => { + const out = translateWasmModule(ADD, SHA, { boundsChecks: false }); + const prefix = wasmSymbolPrefix(SHA); + expect(prefix).toBe("scr_wasm_abababababababab"); + expect(out.descriptor).toBe(`@${prefix}_desc`); + const text = out.text.join("\n"); + expect(text).toContain(`define internal i32 @${prefix}_f0(ptr %inst, i32 %p0, i32 %p1)`); + expect(text).toContain(`define internal void @${prefix}_x0(ptr %inst, ptr %a, ptr %r)`); + expect(text).toContain(`c"hi"`); + expect(text).toContain(`"disable-tail-calls"="true"`); + for (const line of topLevelLines(out.text)) { + expect( + line === "" || /^(define internal |@[\w.$-]+ = (private|internal) )/.test(line), + line, + ).toBe(true); + } + for (const decl of out.decls) expect(decl).toMatch(/^declare /); + }); + + test("checks memory accesses explicitly only when asked", () => { + const guarded = translateWasmModule(ADD, SHA, { boundsChecks: false }).text.join("\n"); + const checked = translateWasmModule(ADD, SHA, { boundsChecks: true }).text.join("\n"); + expect(guarded).not.toContain("%msize"); + expect(checked).toContain("%msize"); + }); + + test("translates every valid module of the WebAssembly spec test suite", () => { + const cases = readFileSync(join(import.meta.dirname, "../../test/wasm-spec-cases.txt"), "utf8") + .split("\n") + .filter((line) => line !== "" && !line.startsWith("#")); + let translated = 0; + for (const line of cases) { + const [, , where, b64] = line.split(" "); + const bytes = decodeBase64(b64!)!; + if (analyzeWasm(bytes).status !== "valid") continue; + try { + translateWasmModule(bytes, SHA, { boundsChecks: false }); + } catch (error) { + throw new Error(`${where}: ${error instanceof Error ? error.message : String(error)}`); + } + translated++; + } + expect(translated).toBeGreaterThan(800); + }); +}); + +describe("module parsing", () => { + test("reads the parts translation needs", () => { + const m = parseWasmModule(ADD); + expect(m.types).toEqual([{ params: [0x7f, 0x7f], results: [0x7f] }]); + expect(m.exports.map((e) => e.name)).toEqual(["add", "mem"]); + expect(m.memories).toEqual([{ min: 1, max: null, imported: false }]); + expect(m.datas[0]!.mode).toBe("active"); + expect([...m.declaredRefs]).toEqual([0]); + }); + + test("decodes signed 64-bit LEB128 into two's-complement halves", () => { + const halves = (bytes: number[]): [number, number] => + new WasmCursor(new Uint8Array(bytes), 0).s64(); + expect(halves([0x7f])).toEqual([0xffffffff, 0xffffffff]); + expect(halves([0x80, 0x80, 0x80, 0x80, 0x10])).toEqual([0, 1]); + expect(halves([0xff, 0xff, 0xff, 0xff, 0x0f])).toEqual([0xffffffff, 0]); + expect(halves([0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x7f])).toEqual([ + 0, 0x80000000, + ]); + expect(halves([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00])).toEqual([ + 0xffffffff, 0x7fffffff, + ]); + }); +}); + +describe("stack checks", () => { + test("only functions through which the stack can grow without bound check it", () => { + // f0 <-> f1 recurse mutually, f2 recurses on itself, f3 calls f0 once. + const calls = new Uint8Array([ + 0, 97, 115, 109, 1, 0, 0, 0, 1, 4, 1, 96, 0, 0, 3, 5, 4, 0, 0, 0, 0, 10, 21, 4, 4, 0, 16, 1, + 11, 4, 0, 16, 0, 11, 4, 0, 16, 2, 11, 4, 0, 16, 0, 11, + ]); + expect([...stackCheckedFunctions(parseWasmModule(calls))].sort()).toEqual([0, 1, 2]); + expect(stackCheckedFunctions(parseWasmModule(ADD)).size).toBe(0); + // Only code without the fault handler checks explicitly. + const checked = translateWasmModule(calls, SHA, { boundsChecks: true }).text.join("\n"); + expect(checked.split("@scr_wasm_stack_limit").length - 1).toBe(3); + const guarded = translateWasmModule(calls, SHA, { boundsChecks: false }).text.join("\n"); + expect(guarded).not.toContain("@scr_wasm_stack_limit"); + }); +}); + +describe("helpers", () => { + test("equal signatures share their identity across modules", () => { + expect(signatureHash("ii:i")).toBe(signatureHash("ii:i")); + expect(signatureHash("ii:i")).not.toBe(signatureHash("i:i")); + expect(signatureHash(":")).toMatch(/^u0x[0-9a-f]{16}$/); + }); + + test("base64 round-trips module bytes", () => { + expect(encodeBase64(ADD)).toBe(Buffer.from(ADD).toString("base64")); + expect(decodeBase64(encodeBase64(ADD))).toEqual(ADD); + expect(encodeBase64(new Uint8Array([1]))).toBe("AQ=="); + expect(encodeBase64(new Uint8Array([1, 2]))).toBe("AQI="); + }); +}); diff --git a/packages/compiler/src/wasm/translate.ts b/packages/compiler/src/wasm/translate.ts new file mode 100644 index 000000000..e0ce27be5 --- /dev/null +++ b/packages/compiler/src/wasm/translate.ts @@ -0,0 +1,2173 @@ +/* WebAssembly to LLVM IR. + * + * Translates one validated module (binary.ts accepted it within the + * translatable feature level) to LLVM assembly that the program's emitter + * appends to its own module, so the native helper optimizes, partitions and + * caches it with the program. The runtime (scr_wasm.c) implements the + * JavaScript API over the descriptor this emits; scr_wasm_abi.h is the + * contract both sides follow. + * + * Shape of the output: + * - one internal function per wasm function, taking the instance as its + * first parameter (`ptr %inst`) and the wasm values directly (i32, i64, + * float, double; references are pointers). Several results return as a + * literal struct. An imported function is a stub that packs its + * arguments and calls scr_wasm_import_call. + * - one thunk per exported function, `void (ptr inst, ptr args, ptr + * results)` over 64-bit slots, which the runtime calls under its trap + * guard; + * - an init function (globals, table initializers, active element and + * data segments); the descriptor names the start function; + * - constant data (segments, names, signatures) and the descriptor. + * + * The instance layout (scr_wasm_abi.h): host at 0, memory base at 8, + * memory object at 16, function reference records at 24, then one pointer + * per table and one cell pointer per global. + * + * Memory accesses compute `base + zext(address) + offset` in 64 bits. With + * guard pages (64-bit hosts) the runtime reserves enough address space that + * every such address past the memory faults, so accesses are unchecked and + * the base is loaded once per function (the memory never moves). With + * explicit bounds checks (sanitizer builds and hosts without the fault + * handler) the base and byte length live in function-local slots that are + * reloaded after every call and memory.grow. + * + * Locals, block results and loop parameters are allocas; LLVM promotes them + * to SSA values. Traps call scr_wasm_trap, which never returns. Plain + * TypeScript without bigint, so the native compiler compiles it too. */ +import { + KIND_FUNC, + KIND_GLOBAL, + KIND_MEMORY, + KIND_TABLE, + VAL_EXTERNREF, + VAL_F32, + VAL_F64, + VAL_FUNCREF, + VAL_I32, + VAL_I64, + WasmCursor, + parseWasmModule, + skipImmediates, + type WasmFuncType, + type WasmModule, + type WasmRange, +} from "./module.js"; + +/** Trap codes (scr_wasm_abi.h SCR_WASM_TRAP_*). */ +export const TRAP_UNREACHABLE = 1; +export const TRAP_MEMORY_OOB = 2; +export const TRAP_DIV_BY_ZERO = 3; +export const TRAP_DIV_UNREPRESENTABLE = 4; +export const TRAP_REM_BY_ZERO = 5; +export const TRAP_FLOAT_UNREPRESENTABLE = 6; +export const TRAP_TABLE_OOB = 7; +export const TRAP_SIGNATURE = 8; +export const TRAP_NULL = 9; +/** scr_wasm_abi.h SCR_WASM_TRAP_EXHAUSTION. */ +export const TRAP_EXHAUSTION = 100; + +/** The descriptor ABI version both sides agree on. */ +export const WASM_ABI_VERSION = 1; + +/** Instance slot offsets (bytes). */ +const OFF_MEMBASE = 8; +const OFF_MEMORY = 16; +const OFF_FUNCREFS = 24; +const OFF_TABLES = 32; +/** sizeof(ScrWasmFuncRef). */ +const FUNCREF_SIZE = 24; + +export interface WasmTranslateOptions { + /** Explicit bounds checks instead of guard pages. */ + boundsChecks: boolean; +} + +export interface WasmTranslation { + /** External declarations, one line each. */ + decls: string[]; + /** Definitions in the emitter's top-level grammar. */ + text: string[]; + /** The descriptor's symbol (internal). */ + descriptor: string; +} + +/** A construct the translator does not support in an otherwise valid module. */ +export class WasmTranslateError extends Error {} + +/** Attributes of translated functions. Recursion must exhaust the stack + * as it does in V8, so LLVM may not turn self-recursion into loops (tail + * recursion elimination, including its accumulator form, which would make + * `f(x) + 1` recursion loop forever) or calls into jumps. */ +const FUNC_ATTRS = `nounwind "disable-tail-calls"="true"`; + +const RUNTIME_DECLS = [ + "declare void @scr_wasm_trap(i32) noreturn nounwind cold", + "declare void @scr_wasm_import_call(ptr, i32, ptr, ptr)", + "declare i32 @scr_wasm_memory_grow(ptr, i32)", + "declare void @scr_wasm_memory_fill(ptr, i32, i32, i32)", + "declare void @scr_wasm_memory_copy(ptr, i32, i32, i32)", + "declare void @scr_wasm_memory_init(ptr, i32, i32, i32, i32)", + "declare void @scr_wasm_data_drop(ptr, i32)", + "declare ptr @scr_wasm_table_get(ptr, i32, i32)", + "declare void @scr_wasm_table_set(ptr, i32, i32, ptr)", + "declare i32 @scr_wasm_table_grow(ptr, i32, ptr, i32)", + "declare void @scr_wasm_table_fill(ptr, i32, i32, ptr, i32)", + "declare void @scr_wasm_table_copy(ptr, i32, i32, i32, i32, i32)", + "declare void @scr_wasm_table_init(ptr, i32, i32, i32, i32, i32)", + "declare void @scr_wasm_elem_drop(ptr, i32)", + "declare void @scr_wasm_global_set_ref(ptr, ptr)", +]; + +function llType(t: number): string { + switch (t) { + case VAL_I32: + return "i32"; + case VAL_I64: + return "i64"; + case VAL_F32: + return "float"; + case VAL_F64: + return "double"; + case VAL_FUNCREF: + case VAL_EXTERNREF: + return "ptr"; + default: + throw new WasmTranslateError(`value type 0x${t.toString(16)} is not supported`); + } +} + +function zeroOf(t: number): string { + switch (t) { + case VAL_I32: + case VAL_I64: + return "0"; + case VAL_F32: + case VAL_F64: + return "0.0"; + default: + return "null"; + } +} + +/** The runtime's signature letter of a value type. */ +function sigChar(t: number): string { + switch (t) { + case VAL_I32: + return "i"; + case VAL_I64: + return "I"; + case VAL_F32: + return "f"; + case VAL_F64: + return "F"; + case VAL_FUNCREF: + return "r"; + case VAL_EXTERNREF: + return "x"; + default: + throw new WasmTranslateError(`value type 0x${t.toString(16)} is not supported`); + } +} + +function sigString(ft: WasmFuncType): string { + let s = ""; + for (const p of ft.params) s += sigChar(p); + s += ":"; + for (const r of ft.results) s += sigChar(r); + return s; +} + +function retType(results: readonly number[]): string { + if (results.length === 0) return "void"; + if (results.length === 1) return llType(results[0]!); + return `{ ${results.map(llType).join(", ")} }`; +} + +function hex8(n: number): string { + return (n >>> 0).toString(16).padStart(8, "0"); +} + +/** An i64 constant from its halves, in LLVM's unsigned hexadecimal form. */ +function i64Lit(lo: number, hi: number): string { + return `u0x${hex8(hi)}${hex8(lo)}`; +} + +/** The canonical signature identity call_indirect compares: two + * independent 32-bit FNV-1a hashes of the signature text. Equal signatures + * in different modules get the same identity. */ +export function signatureHash(sig: string): string { + let a = 0x811c9dc5; + let b = 0x01000193 ^ 0x5bd1e995; + for (let i = 0; i < sig.length; i++) { + const c = sig.charCodeAt(i); + a = Math.imul(a ^ c, 0x01000193); + b = Math.imul(b ^ c, 0x01000193) ^ (b >>> 13); + } + return i64Lit(a >>> 0, b >>> 0); +} + +/** UTF-8 bytes of a string. */ +function utf8Bytes(text: string): number[] { + const out: number[] = []; + for (let i = 0; i < text.length; i++) { + const cp = text.codePointAt(i)!; + if (cp > 0xffff) i++; + if (cp < 0x80) out.push(cp); + else if (cp < 0x800) out.push(0xc0 | (cp >> 6), 0x80 | (cp & 0x3f)); + else if (cp < 0x10000) + out.push(0xe0 | (cp >> 12), 0x80 | ((cp >> 6) & 0x3f), 0x80 | (cp & 0x3f)); + else { + out.push( + 0xf0 | (cp >> 18), + 0x80 | ((cp >> 12) & 0x3f), + 0x80 | ((cp >> 6) & 0x3f), + 0x80 | (cp & 0x3f), + ); + } + } + return out; +} + +/** An LLVM c"..." string body for bytes. */ +function cBytes(bytes: Uint8Array, start: number, end: number): string { + let s = ""; + for (let i = start; i < end; i++) { + const b = bytes[i]!; + if (b >= 0x20 && b < 0x7f && b !== 0x22 && b !== 0x5c) s += String.fromCharCode(b); + else s += "\\" + (b < 16 ? "0" : "") + b.toString(16).toUpperCase(); + } + return s; +} + +interface StackValue { + v: string; + t: number; + /** The line of the load that produced the value (this.lines). */ + load?: number; +} + +interface Frame { + kind: "block" | "loop" | "if" | "func"; + params: number[]; + results: number[]; + /** Branch target: the loop header, or the end label. */ + target: string; + end: string; + /** Allocas the values of a branch to this frame are stored in: the loop + * parameters for a loop, the results otherwise. */ + branchSlots: string[]; + /** Allocas of the results at the frame's end (loops: their own). */ + resultSlots: string[]; + height: number; + /** An if whose else branch has not started. */ + elseLabel: string | null; + /** The parameter values of an if, for its implicit else. */ + ifParams: StackValue[]; + /** The start of the frame was reachable. */ + live: boolean; +} + +class Lines { + lines: string[] = []; + add(line: string): void { + this.lines.push(line); + } +} + +/** One module's translation. */ +class ModuleTranslator { + m: WasmModule; + p: string; + opts: WasmTranslateOptions; + text: string[] = []; + /** Functions that need a reference record. */ + refs: Set; + /** Functions whose entry checks the stack (stackCheckedFunctions). */ + stackChecked: Set; + typeHashes: string[]; + memoryCount: number; + + constructor(m: WasmModule, prefix: string, opts: WasmTranslateOptions) { + this.m = m; + this.p = prefix; + this.opts = opts; + this.refs = new Set(m.declaredRefs); + this.typeHashes = m.types.map((t) => signatureHash(sigString(t))); + this.memoryCount = m.memories.length; + if (m.memories.length > 1) throw new WasmTranslateError("multiple memories are not supported"); + this.stackChecked = stackCheckedFunctions(m); + } + + fn(index: number): string { + return `@${this.p}_f${index}`; + } + + funcType(index: number): WasmFuncType { + return this.m.types[this.m.funcTypes[index]!]!; + } + + globalSlot(index: number): number { + return OFF_TABLES + 8 * (this.m.tables.length + index); + } + + tableSlot(index: number): number { + return OFF_TABLES + 8 * index; + } + + signature(ft: WasmFuncType, name: string): string { + const params = ["ptr %inst"]; + for (let i = 0; i < ft.params.length; i++) params.push(`${llType(ft.params[i]!)} %p${i}`); + return `${retType(ft.results)} ${name}(${params.join(", ")})`; + } + + translate(): void { + const m = this.m; + for (let i = 0; i < m.importedFuncs; i++) this.importStub(i); + for (let i = 0; i < m.codes.length; i++) { + const index = m.importedFuncs + i; + const f = new FunctionTranslator(this, index); + this.text.push(...f.translate()); + } + } + + importStub(index: number): void { + const ft = this.funcType(index); + const out: string[] = []; + out.push(`define internal ${this.signature(ft, this.fn(index))} nounwind {`); + out.push(`entry:`); + const np = Math.max(1, ft.params.length); + const nr = Math.max(1, ft.results.length); + out.push(` %args = alloca [${np} x i64], align 8`); + out.push(` %res = alloca [${nr} x i64], align 8`); + for (let i = 0; i < ft.params.length; i++) { + const slot = toSlot(out, ft.params[i]!, `%p${i}`, `a${i}`); + out.push(` %ap${i} = getelementptr i64, ptr %args, i64 ${i}`); + out.push(` store i64 ${slot}, ptr %ap${i}`); + } + out.push(` %host = load ptr, ptr %inst`); + out.push(` call void @scr_wasm_import_call(ptr %host, i32 ${index}, ptr %args, ptr %res)`); + emitSlotReturn(out, ft.results, "%res"); + out.push(`}`); + this.text.push(out.join("\n")); + } + + /** The thunk the runtime calls for exported function `index`. */ + exportThunk(index: number): string { + const name = `@${this.p}_x${index}`; + const ft = this.funcType(index); + const out: string[] = []; + out.push(`define internal void ${name}(ptr %inst, ptr %a, ptr %r) nounwind {`); + out.push(`entry:`); + const args: string[] = ["ptr %inst"]; + for (let i = 0; i < ft.params.length; i++) { + out.push(` %ap${i} = getelementptr i64, ptr %a, i64 ${i}`); + out.push(` %as${i} = load i64, ptr %ap${i}`); + args.push(`${llType(ft.params[i]!)} ${fromSlot(out, ft.params[i]!, `%as${i}`, `a${i}`)}`); + } + const ret = retType(ft.results); + if (ft.results.length === 0) { + out.push(` call void ${this.fn(index)}(${args.join(", ")})`); + } else { + out.push(` %res = call ${ret} ${this.fn(index)}(${args.join(", ")})`); + for (let i = 0; i < ft.results.length; i++) { + let v = "%res"; + if (ft.results.length > 1) { + out.push(` %rv${i} = extractvalue ${ret} %res, ${i}`); + v = `%rv${i}`; + } + const slot = toSlot(out, ft.results[i]!, v, `r${i}`); + out.push(` %rp${i} = getelementptr i64, ptr %r, i64 ${i}`); + out.push(` store i64 ${slot}, ptr %rp${i}`); + } + } + out.push(` ret void`); + out.push(`}`); + this.text.push(out.join("\n")); + return name; + } + + /** Emits the init function: globals, table initializers and segments, + * in the specification's order. The runtime calls the start function + * (which takes and returns nothing) separately. */ + initFunction(): string { + const m = this.m; + const name = `@${this.p}_init`; + const f = new FunctionTranslator(this, -1); + const body = f.lines; + // Globals. + for (let g = 0; g < m.globals.length; g++) { + const global = m.globals[g]!; + if (global.imported || global.init === null) continue; + const v = f.constExpr(global.init); + const cell = f.loadPtrSlot(this.globalSlot(g)); + if (global.type === VAL_EXTERNREF) { + // A defined externref global starts as null or another global's + // value; the runtime's setter retains it. + body.add(` call void @scr_wasm_global_set_ref(ptr ${cell}, ptr ${v})`); + } else { + body.add(` store ${llType(global.type)} ${v}, ptr ${cell}`); + } + } + // Defined tables with an initializer. + for (let t = 0; t < m.tables.length; t++) { + const table = m.tables[t]!; + if (table.imported || table.init === null) continue; + const v = f.constExpr(table.init); + body.add( + ` call void @scr_wasm_table_fill(ptr %inst, i32 ${t}, i32 0, ptr ${v}, i32 ${table.min})`, + ); + } + // Element segments: active ones initialize their table and drop; + // declarative ones only drop. + for (let s = 0; s < m.elems.length; s++) { + const seg = m.elems[s]!; + if (seg.mode === "active") { + const offset = f.constExpr(seg.offset!); + body.add( + ` call void @scr_wasm_table_init(ptr %inst, i32 ${seg.table}, i32 ${s}, i32 ${offset}, i32 0, i32 ${seg.funcs.length})`, + ); + } + if (seg.mode !== "passive") body.add(` call void @scr_wasm_elem_drop(ptr %inst, i32 ${s})`); + } + for (let s = 0; s < m.datas.length; s++) { + const seg = m.datas[s]!; + if (seg.mode !== "active") continue; + const offset = f.constExpr(seg.offset!); + const length = seg.payload.end - seg.payload.start; + body.add( + ` call void @scr_wasm_memory_init(ptr %inst, i32 ${s}, i32 ${offset}, i32 0, i32 ${length})`, + ); + body.add(` call void @scr_wasm_data_drop(ptr %inst, i32 ${s})`); + } + body.add(` ret void`); + const out = [`define internal void ${name}(ptr %inst) nounwind {`, `entry:`]; + out.push(...f.entry.lines); + out.push(...body.lines); + out.push(`}`); + this.text.push(out.join("\n")); + return name; + } + + /** Constant data and the descriptor. */ + descriptor(sha256: Uint8Array): string { + const m = this.m; + const p = this.p; + const lines: string[] = []; + // First, so that every function its constant expressions reference has + // a record in the tables below. + const init = this.initFunction(); + let strings = 0; + const cstr = (text: readonly number[]): string => { + const sym = `@${p}_s${strings++}`; + const bytes = new Uint8Array(text); + lines.push( + `${sym} = private unnamed_addr constant [${bytes.length + 1} x i8] c"${cBytes(bytes, 0, bytes.length)}\\00"`, + ); + return sym; + }; + const textStr = (text: string): { sym: string; len: number } => { + const bytes = utf8Bytes(text); + return { sym: cstr(bytes), len: bytes.length }; + }; + // Imports. + const importRows: string[] = []; + let funcImport = 0; + let tableImport = 0; + let memoryImport = 0; + let globalImport = 0; + for (const imp of m.imports) { + const mod = textStr(imp.module); + const name = textStr(imp.name); + let index = 0; + let type = ""; + if (imp.kind === KIND_FUNC) { + index = funcImport++; + type = sigString(m.types[imp.type]!); + } else if (imp.kind === KIND_TABLE) { + index = tableImport++; + type = sigChar(imp.type); + } else if (imp.kind === KIND_MEMORY) { + index = memoryImport++; + } else { + index = globalImport++; + type = sigChar(imp.type); + } + const typeSym = cstr(utf8Bytes(type)); + importRows.push( + `{ ptr, i64, ptr, i64, i64, i64, ptr } { ptr ${mod.sym}, i64 ${mod.len}, ptr ${name.sym}, i64 ${name.len}, i64 ${imp.kind}, i64 ${index}, ptr ${typeSym} }`, + ); + } + const importType = "{ ptr, i64, ptr, i64, i64, i64, ptr }"; + const importsSym = `@${p}_imports`; + lines.push( + `${importsSym} = private constant [${importRows.length} x ${importType}] [${importRows.join(", ")}]`, + ); + // Exports. + const thunks = new Map(); + const exportRows: string[] = []; + for (const ex of m.exports) { + const name = textStr(ex.name); + let type = ""; + let call = "null"; + if (ex.kind === KIND_FUNC) { + type = sigString(this.funcType(ex.index)); + let thunk = thunks.get(ex.index); + if (thunk === undefined) { + thunk = this.exportThunk(ex.index); + thunks.set(ex.index, thunk); + } + call = thunk; + } else if (ex.kind === KIND_TABLE) { + type = sigChar(m.tables[ex.index]!.elemType); + } else if (ex.kind === KIND_GLOBAL) { + type = sigChar(m.globals[ex.index]!.type); + } + const typeSym = cstr(utf8Bytes(type)); + exportRows.push( + `{ ptr, i64, i64, i64, ptr, ptr } { ptr ${name.sym}, i64 ${name.len}, i64 ${ex.kind}, i64 ${ex.index}, ptr ${typeSym}, ptr ${call} }`, + ); + } + const exportType = "{ ptr, i64, i64, i64, ptr, ptr }"; + const exportsSym = `@${p}_exports`; + lines.push( + `${exportsSym} = private constant [${exportRows.length} x ${exportType}] [${exportRows.join(", ")}]`, + ); + // Function references: code, signature identity and signature text for + // every function a reference can name. + const nf = m.funcTypes.length; + const codes: string[] = []; + const types: string[] = []; + const sigs: string[] = []; + const sigSyms = new Map(); + for (let f = 0; f < nf; f++) { + const referenced = this.refs.has(f); + codes.push(referenced ? `ptr ${this.fn(f)}` : "ptr null"); + types.push(`i64 ${this.typeHashes[m.funcTypes[f]!]!}`); + const sig = sigString(this.funcType(f)); + let sym = sigSyms.get(sig); + if (sym === undefined) { + sym = cstr(utf8Bytes(sig)); + sigSyms.set(sig, sym); + } + sigs.push(`ptr ${sym}`); + } + const codesSym = `@${p}_codes`; + const typesSym = `@${p}_types`; + const sigsSym = `@${p}_sigs`; + lines.push(`${codesSym} = private constant [${nf} x ptr] [${codes.join(", ")}]`); + lines.push(`${typesSym} = private constant [${nf} x i64] [${types.join(", ")}]`); + lines.push(`${sigsSym} = private constant [${nf} x ptr] [${sigs.join(", ")}]`); + // Tables. + const tableRows = m.tables.map( + (t) => + `{ i64, i64, i64, i64 } { i64 ${sigChar(t.elemType).charCodeAt(0)}, i64 ${t.min}, i64 ${t.max === null ? -1 : t.max}, i64 ${t.imported ? 1 : 0} }`, + ); + const tablesSym = `@${p}_tables`; + lines.push( + `${tablesSym} = private constant [${tableRows.length} x { i64, i64, i64, i64 }] [${tableRows.join(", ")}]`, + ); + // Globals. + const globalRows = m.globals.map( + (g) => + `{ i64, i64, i64 } { i64 ${sigChar(g.type).charCodeAt(0)}, i64 ${g.mutable ? 1 : 0}, i64 ${g.imported ? 1 : 0} }`, + ); + const globalsSym = `@${p}_globals`; + lines.push( + `${globalsSym} = private constant [${globalRows.length} x { i64, i64, i64 }] [${globalRows.join(", ")}]`, + ); + // Data segments. + const dataRows: string[] = []; + for (let s = 0; s < m.datas.length; s++) { + const seg = m.datas[s]!; + const len = seg.payload.end - seg.payload.start; + if (len === 0) { + dataRows.push(`{ ptr, i64 } { ptr null, i64 0 }`); + continue; + } + const sym = `@${p}_d${s}`; + lines.push( + `${sym} = private unnamed_addr constant [${len} x i8] c"${cBytes(m.bytes, seg.payload.start, seg.payload.end)}", align 1`, + ); + dataRows.push(`{ ptr, i64 } { ptr ${sym}, i64 ${len} }`); + } + const datasSym = `@${p}_datas`; + lines.push( + `${datasSym} = private constant [${dataRows.length} x { ptr, i64 }] [${dataRows.join(", ")}]`, + ); + // Element segments: a function index, -1 for null, or -(2 + g) for the + // value of global g. + const elemRows: string[] = []; + for (let s = 0; s < m.elems.length; s++) { + const seg = m.elems[s]!; + const items: string[] = []; + for (let k = 0; k < seg.funcs.length; k++) { + const f = seg.funcs[k]!; + if (f >= 0) { + items.push(`i64 ${f}`); + continue; + } + items.push(`i64 ${this.elemExprCode(seg.exprs[k]!)}`); + } + let itemsRef = "ptr null"; + if (items.length > 0) { + const sym = `@${p}_e${s}`; + lines.push(`${sym} = private constant [${items.length} x i64] [${items.join(", ")}]`); + itemsRef = `ptr ${sym}`; + } + elemRows.push( + `{ i64, i64, ptr } { i64 ${sigChar(seg.elemType).charCodeAt(0)}, i64 ${items.length}, ${itemsRef} }`, + ); + } + const elemsSym = `@${p}_elems`; + lines.push( + `${elemsSym} = private constant [${elemRows.length} x { i64, i64, ptr }] [${elemRows.join(", ")}]`, + ); + const memory = m.memories[0]; + const sha: string[] = []; + for (let i = 0; i < 32; i++) sha.push(`i8 ${sha256[i]!}`); + const desc = `@${p}_desc`; + const fields = [ + `[32 x i8] [${sha.join(", ")}]`, + `i64 ${m.bytes.length}`, + `i64 ${WASM_ABI_VERSION}`, + `i64 ${this.opts.boundsChecks ? 1 : 0}`, + `i64 ${m.imports.length}`, + `ptr ${importsSym}`, + `i64 ${m.exports.length}`, + `ptr ${exportsSym}`, + `i64 ${nf}`, + `i64 ${m.importedFuncs}`, + `ptr ${codesSym}`, + `ptr ${typesSym}`, + `ptr ${sigsSym}`, + `i64 ${m.tables.length}`, + `ptr ${tablesSym}`, + `i64 ${this.memoryCount}`, + `i64 ${memory === undefined ? 0 : memory.min}`, + `i64 ${memory === undefined || memory.max === null ? -1 : memory.max}`, + `i64 ${m.globals.length}`, + `ptr ${globalsSym}`, + `i64 ${m.datas.length}`, + `ptr ${datasSym}`, + `i64 ${m.elems.length}`, + `ptr ${elemsSym}`, + `ptr ${init}`, + `ptr ${m.start === null ? "null" : this.fn(m.start)}`, + ]; + const descType = + "{ [32 x i8], i64, i64, i64, i64, ptr, i64, ptr, i64, i64, ptr, ptr, ptr, i64, ptr, i64, i64, i64, i64, ptr, i64, ptr, i64, ptr, ptr, ptr }"; + lines.push(`${desc} = internal constant ${descType} { ${fields.join(", ")} }`); + this.text.push(lines.join("\n")); + return desc; + } + + /** The element-segment code of a non-ref.func constant expression. */ + elemExprCode(expr: WasmRange): number { + const c = new WasmCursor(this.m.bytes, expr.start); + const op = c.u8(); + if (op === 0xd0) { + c.valType(); + if (c.u8() === 0x0b) return -1; + } else if (op === 0x23) { + const g = c.u32(); + if (c.u8() === 0x0b) return -(2 + g); + } + throw new WasmTranslateError( + "element segment initializers other than ref.null, ref.func and global.get are not supported", + ); + } +} + +/** Store a value as a 64-bit slot; answers the slot operand. */ +function toSlot(out: string[], t: number, v: string, tag: string): string { + switch (t) { + case VAL_I32: + out.push(` %${tag}z = zext i32 ${v} to i64`); + return `%${tag}z`; + case VAL_I64: + return v; + case VAL_F32: + out.push(` %${tag}b = bitcast float ${v} to i32`); + out.push(` %${tag}z = zext i32 %${tag}b to i64`); + return `%${tag}z`; + case VAL_F64: + out.push(` %${tag}z = bitcast double ${v} to i64`); + return `%${tag}z`; + default: + out.push(` %${tag}z = ptrtoint ptr ${v} to i64`); + return `%${tag}z`; + } +} + +function fromSlot(out: string[], t: number, slot: string, tag: string): string { + switch (t) { + case VAL_I32: + out.push(` %${tag}v = trunc i64 ${slot} to i32`); + return `%${tag}v`; + case VAL_I64: + return slot; + case VAL_F32: + out.push(` %${tag}t = trunc i64 ${slot} to i32`); + out.push(` %${tag}v = bitcast i32 %${tag}t to float`); + return `%${tag}v`; + case VAL_F64: + out.push(` %${tag}v = bitcast i64 ${slot} to double`); + return `%${tag}v`; + default: + out.push(` %${tag}v = inttoptr i64 ${slot} to ptr`); + return `%${tag}v`; + } +} + +function emitSlotReturn(out: string[], results: readonly number[], buffer: string): void { + if (results.length === 0) { + out.push(` ret void`); + return; + } + const vals: string[] = []; + for (let i = 0; i < results.length; i++) { + out.push(` %rp${i} = getelementptr i64, ptr ${buffer}, i64 ${i}`); + out.push(` %rs${i} = load i64, ptr %rp${i}`); + vals.push(fromSlot(out, results[i]!, `%rs${i}`, `r${i}`)); + } + if (results.length === 1) { + out.push(` ret ${llType(results[0]!)} ${vals[0]}`); + return; + } + const ret = retType(results); + let agg = "undef"; + for (let i = 0; i < results.length; i++) { + out.push(` %agg${i} = insertvalue ${ret} ${agg}, ${llType(results[i]!)} ${vals[i]}, ${i}`); + agg = `%agg${i}`; + } + out.push(` ret ${ret} ${agg}`); +} + +const LOAD_INFO: Record = { + // opcode: [result type, memory type, bytes, extension] + 0x28: [VAL_I32, "i32", 4, ""], + 0x29: [VAL_I64, "i64", 8, ""], + 0x2a: [VAL_F32, "float", 4, ""], + 0x2b: [VAL_F64, "double", 8, ""], + 0x2c: [VAL_I32, "i8", 1, "sext"], + 0x2d: [VAL_I32, "i8", 1, "zext"], + 0x2e: [VAL_I32, "i16", 2, "sext"], + 0x2f: [VAL_I32, "i16", 2, "zext"], + 0x30: [VAL_I64, "i8", 1, "sext"], + 0x31: [VAL_I64, "i8", 1, "zext"], + 0x32: [VAL_I64, "i16", 2, "sext"], + 0x33: [VAL_I64, "i16", 2, "zext"], + 0x34: [VAL_I64, "i32", 4, "sext"], + 0x35: [VAL_I64, "i32", 4, "zext"], +}; + +const STORE_INFO: Record = { + 0x36: [VAL_I32, "i32", 4], + 0x37: [VAL_I64, "i64", 8], + 0x38: [VAL_F32, "float", 4], + 0x39: [VAL_F64, "double", 8], + 0x3a: [VAL_I32, "i8", 1], + 0x3b: [VAL_I32, "i16", 2], + 0x3c: [VAL_I64, "i8", 1], + 0x3d: [VAL_I64, "i16", 2], + 0x3e: [VAL_I64, "i32", 4], +}; + +/** Translates one function body (index >= 0) or hosts the init function's + * constant expressions (index -1). */ +class FunctionTranslator { + mt: ModuleTranslator; + m: WasmModule; + index: number; + ft: WasmFuncType; + entry = new Lines(); + lines = new Lines(); + stack: StackValue[] = []; + frames: Frame[] = []; + localTypes: number[] = []; + next = 0; + labels = 0; + live = true; + trapLabels = new Map(); + /** Guard mode: the memory base, loaded once in the entry block. */ + baseValue: string | null = null; + checked: boolean; + + constructor(mt: ModuleTranslator, index: number) { + this.mt = mt; + this.m = mt.m; + this.index = index; + this.ft = index >= 0 ? mt.funcType(index) : { params: [], results: [] }; + this.checked = mt.opts.boundsChecks; + } + + tmp(): string { + return `%v${this.next++}`; + } + + label(hint: string): string { + return `${hint}${this.labels++}`; + } + + add(line: string): void { + this.lines.add(line); + } + + emit(expr: string): string { + const v = this.tmp(); + this.add(` ${v} = ${expr}`); + return v; + } + + push(v: string, t: number): void { + this.stack.push({ v, t }); + } + + pop(): StackValue { + const v = this.stack.pop(); + if (v === undefined) throw new Error("wasm translator: operand stack underflow"); + return v; + } + + popN(n: number): StackValue[] { + const out = this.stack.splice(this.stack.length - n, n); + if (out.length !== n) throw new Error("wasm translator: operand stack underflow"); + return out; + } + + /** Starts a new basic block. */ + startBlock(label: string): void { + this.add(`${label}:`); + } + + alloca(type: string): string { + const v = `%s${this.next++}`; + this.entry.add(` ${v} = alloca ${type}`); + return v; + } + + slotPtr(offset: number): string { + if (offset === 0) return "%inst"; + return this.emit(`getelementptr inbounds i8, ptr %inst, i64 ${offset}`); + } + + loadPtrSlot(offset: number): string { + return this.emit(`load ptr, ptr ${this.slotPtr(offset)}, align 8`); + } + + trap(code: number): string { + let label = this.trapLabels.get(code); + if (label === undefined) { + label = `trap${code}`; + this.trapLabels.set(code, label); + } + return label; + } + + // ── memory ────────────────────────────────────────────────────────── + + /** Checked mode: the slots holding the base and byte length. */ + memSlots: { base: string; size: string } | null = null; + + refreshMemory(): void { + if (!this.checked || this.memSlots === null) return; + // From the memory object, not the instance: a memory another instance + // (or JavaScript) grew may have moved. + const mem = this.loadPtrSlot(OFF_MEMORY); + const base = this.emit(`load ptr, ptr ${mem}, align 8`); + this.add(` store ptr ${base}, ptr ${this.memSlots.base}`); + const sizePtr = this.emit(`getelementptr inbounds i8, ptr ${mem}, i64 8`); + const size = this.emit(`load i64, ptr ${sizePtr}, align 8`); + this.add(` store i64 ${size}, ptr ${this.memSlots.size}`); + } + + base(): string { + if (this.checked) { + return this.emit(`load ptr, ptr ${this.memSlots!.base}`); + } + if (this.baseValue === null) { + this.baseValue = "%membase"; + } + return this.baseValue; + } + + /** The address of an access of `bytes` bytes at `addr + offset`. */ + address(addr: string, offset: number, bytes: number): string { + const wide = this.emit(`zext i32 ${addr} to i64`); + const ea = offset === 0 ? wide : this.emit(`add i64 ${wide}, ${offset}`); + if (this.checked) { + const size = this.emit(`load i64, ptr ${this.memSlots!.size}`); + const endAddr = this.emit(`add i64 ${ea}, ${bytes}`); + const oob = this.emit(`icmp ugt i64 ${endAddr}, ${size}`); + const ok = this.label("ok"); + this.add(` br i1 ${oob}, label %${this.trap(TRAP_MEMORY_OOB)}, label %${ok}`); + this.startBlock(ok); + } + return this.emit(`getelementptr i8, ptr ${this.base()}, i64 ${ea}`); + } + + // ── constant expressions ──────────────────────────────────────────── + + constExpr(range: WasmRange): string { + const c = new WasmCursor(this.m.bytes, range.start); + const stack: string[] = []; + for (;;) { + const op = c.u8(); + if (op === 0x0b) break; + switch (op) { + case 0x41: + stack.push(String(c.s32())); + break; + case 0x42: { + const [lo, hi] = c.s64(); + stack.push(i64Lit(lo, hi)); + break; + } + case 0x43: + stack.push(this.emit(`bitcast i32 ${c.u32le() | 0} to float`)); + break; + case 0x44: { + const lo = c.u32le(); + const hi = c.u32le(); + stack.push(this.emit(`bitcast i64 ${i64Lit(lo, hi)} to double`)); + break; + } + case 0x23: { + const g = c.u32(); + const cell = this.loadPtrSlot(this.mt.globalSlot(g)); + stack.push(this.emit(`load ${llType(this.m.globals[g]!.type)}, ptr ${cell}`)); + break; + } + case 0xd0: + c.valType(); + stack.push("null"); + break; + case 0xd2: + stack.push(this.funcRef(c.u32())); + break; + case 0x6a: + case 0x6b: + case 0x6c: + case 0x7c: + case 0x7d: + case 0x7e: { + const b = stack.pop()!; + const a = stack.pop()!; + const t = op < 0x70 ? "i32" : "i64"; + const name = + op === 0x6a || op === 0x7c ? "add" : op === 0x6b || op === 0x7d ? "sub" : "mul"; + stack.push(this.emit(`${name} ${t} ${a}, ${b}`)); + break; + } + default: + throw new WasmTranslateError( + `constant expression opcode 0x${op.toString(16)} is not supported`, + ); + } + } + return stack[stack.length - 1]!; + } + + funcRef(f: number): string { + this.mt.refs.add(f); + const records = this.loadPtrSlot(OFF_FUNCREFS); + return this.emit(`getelementptr inbounds i8, ptr ${records}, i64 ${f * FUNCREF_SIZE}`); + } + + // ── control ───────────────────────────────────────────────────────── + + blockSig(c: WasmCursor): { params: number[]; results: number[] } { + const bt = c.blockType(); + if (bt.kind === "empty") return { params: [], results: [] }; + if (bt.kind === "value") return { params: [], results: [bt.type] }; + const t = this.m.types[bt.index]!; + return { params: t.params.slice(), results: t.results.slice() }; + } + + slotsFor(types: readonly number[]): string[] { + return types.map((t) => this.alloca(llType(t))); + } + + storeTo(slots: readonly string[], values: readonly StackValue[]): void { + for (let i = 0; i < slots.length; i++) { + const v = values[i]!; + this.add(` store ${llType(v.t)} ${v.v}, ptr ${slots[i]}`); + } + } + + loadFrom(slots: readonly string[], types: readonly number[]): void { + for (let i = 0; i < slots.length; i++) { + const t = types[i]!; + this.push(this.emit(`load ${llType(t)}, ptr ${slots[i]}`), t); + } + } + + /** Branch to the frame `depth` levels out (the stack keeps its values). */ + branch(depth: number): void { + const f = this.frames[this.frames.length - 1 - depth]!; + const n = f.branchSlots.length; + if (n > 0) this.storeTo(f.branchSlots, this.stack.slice(this.stack.length - n)); + this.add(` br label %${f.target}`); + } + + /** The label of a block that branches to `depth`, for conditional + * branches and tables: the target itself when no values travel. */ + branchEdge(depth: number, pending: string[]): string { + const f = this.frames[this.frames.length - 1 - depth]!; + if (f.branchSlots.length === 0) return f.target; + const edge = this.label("edge"); + const saved = this.lines; + const edgeLines = new Lines(); + this.lines = edgeLines; + this.startBlock(edge); + this.branch(depth); + this.lines = saved; + pending.push(...edgeLines.lines); + return edge; + } + + goDead(): void { + this.live = false; + } + + pushFrame(kind: Frame["kind"], sig: { params: number[]; results: number[] }): Frame { + const end = this.label("end"); + const frame: Frame = { + kind, + params: sig.params, + results: sig.results, + target: end, + end, + branchSlots: [], + resultSlots: this.slotsFor(sig.results), + height: this.stack.length - sig.params.length, + elseLabel: null, + ifParams: [], + live: true, + }; + frame.branchSlots = frame.resultSlots; + this.frames.push(frame); + return frame; + } + + /** Skips dead code from just after a terminator to the `else` or `end` + * of the current frame; answers the opcode it stopped at (consumed). */ + skipDead(c: WasmCursor): number { + let depth = 0; + for (;;) { + const op = c.u8(); + if (op === 0x02 || op === 0x03 || op === 0x04) { + c.blockType(); + depth++; + continue; + } + if (op === 0x0b) { + if (depth === 0) return op; + depth--; + continue; + } + if (op === 0x05 && depth === 0) return op; + skipImmediates(c, op); + } + } + + translate(): string[] { + const ft = this.ft; + const code = this.m.codes[this.index - this.m.importedFuncs]!; + // Locals. + const localSlots: string[] = []; + this.localTypes = ft.params.concat(code.locals); + for (let i = 0; i < this.localTypes.length; i++) { + const t = this.localTypes[i]!; + const slot = `%l${i}`; + localSlots.push(slot); + this.entry.add(` ${slot} = alloca ${llType(t)}`); + } + for (let i = 0; i < ft.params.length; i++) { + this.entry.add(` store ${llType(ft.params[i]!)} %p${i}, ptr %l${i}`); + } + for (let i = ft.params.length; i < this.localTypes.length; i++) { + const t = this.localTypes[i]!; + this.entry.add(` store ${llType(t)} ${zeroOf(t)}, ptr %l${i}`); + } + if (this.checked && this.mt.memoryCount > 0) { + this.memSlots = { base: "%mbase", size: "%msize" }; + this.entry.add(` %mbase = alloca ptr`); + this.entry.add(` %msize = alloca i64`); + } + // The function frame: branches to it return. + const ret = this.label("ret"); + const fnFrame: Frame = { + kind: "func", + params: [], + results: ft.results, + target: ret, + end: ret, + branchSlots: [], + resultSlots: this.slotsFor(ft.results), + height: 0, + elseLabel: null, + ifParams: [], + live: true, + }; + fnFrame.branchSlots = fnFrame.resultSlots; + this.frames.push(fnFrame); + if (this.memSlots !== null) this.refreshMemory(); + const c = new WasmCursor(this.m.bytes, code.body.start); + this.body(c, code.body.end); + // Epilogue. + const out: string[] = []; + out.push(`define internal ${this.mt.signature(ft, this.mt.fn(this.index))} ${FUNC_ATTRS} {`); + out.push(`entry:`); + out.push(...this.entry.lines); + if (!this.checked && this.baseValue !== null) { + out.push(` %membase.p = getelementptr inbounds i8, ptr %inst, i64 ${OFF_MEMBASE}`); + out.push(` %membase = load ptr, ptr %membase.p, align 8`); + } + if (this.checked && this.mt.stackChecked.has(this.index)) { + // Without the fault handler, stack exhaustion is an explicit check of + // the stack pointer against the runtime's guard for the running stack + // (thread or async fiber), in the functions through which the stack + // can grow without bound (stackCheckedFunctions). + // The address of a frame slot stands for the stack pointer; unlike + // llvm.stacksave it constrains no other code. + out.push(` %sp = alloca i8, align 1`); + out.push(` %sp.i = ptrtoint ptr %sp to i64`); + out.push(` %limit.p = call ptr @llvm.threadlocal.address.p0(ptr @scr_wasm_stack_limit)`); + out.push(` %limit = load i64, ptr %limit.p, align 8`); + out.push(` %exhausted = icmp ult i64 %sp.i, %limit`); + out.push(` br i1 %exhausted, label %exhausted.trap, label %body`); + out.push(`exhausted.trap:`); + out.push(` call void @scr_wasm_trap(i32 ${TRAP_EXHAUSTION})`); + out.push(` unreachable`); + } else { + out.push(` br label %body`); + } + out.push(`body:`); + out.push(...this.lines.lines); + for (const [code2, label] of this.trapLabels) { + out.push(`${label}:`); + out.push(` call void @scr_wasm_trap(i32 ${code2})`); + out.push(` unreachable`); + } + out.push(`}`); + return [out.join("\n")]; + } + + /** The function body; returns at the function's final `end`. */ + body(c: WasmCursor, end: number): void { + while (c.pos < end) { + if (!this.live) { + const op = this.skipDead(c); + if (op === 0x05) this.opElse(); + else if (this.opEnd()) return; + continue; + } + const op = c.u8(); + if (op === 0x0b) { + if (this.opEnd()) return; + continue; + } + this.instruction(c, op); + } + } + + /** `end`: closes the innermost frame; true when that was the function. */ + opEnd(): boolean { + const f = this.frames[this.frames.length - 1]!; + if (this.live) { + const n = f.results.length; + this.storeTo(f.resultSlots, this.stack.slice(this.stack.length - n)); + this.add(` br label %${f.end}`); + } + if (f.elseLabel !== null) { + // An if without else: the implicit else passes its parameters on. + this.startBlock(f.elseLabel); + if (f.live) { + this.storeTo(f.resultSlots, f.ifParams); + } + this.add(` br label %${f.end}`); + f.elseLabel = null; + } + this.frames.pop(); + this.stack.length = f.height; + this.startBlock(f.end); + if (f.kind === "func") { + if (f.results.length === 0) this.add(` ret void`); + else { + this.loadFrom(f.resultSlots, f.results); + const vals = this.popN(f.results.length); + if (vals.length === 1) this.add(` ret ${llType(vals[0]!.t)} ${vals[0]!.v}`); + else { + const rt = retType(f.results); + let agg = "undef"; + for (let i = 0; i < vals.length; i++) { + agg = this.emit(`insertvalue ${rt} ${agg}, ${llType(vals[i]!.t)} ${vals[i]!.v}, ${i}`); + } + this.add(` ret ${rt} ${agg}`); + } + } + return true; + } + this.loadFrom(f.resultSlots, f.results); + this.live = true; + return false; + } + + opElse(): void { + const f = this.frames[this.frames.length - 1]!; + if (this.live) { + const n = f.results.length; + this.storeTo(f.resultSlots, this.stack.slice(this.stack.length - n)); + this.add(` br label %${f.end}`); + } + this.stack.length = f.height; + this.startBlock(f.elseLabel!); + f.elseLabel = null; + for (const v of f.ifParams) this.stack.push(v); + this.live = f.live; + if (!f.live) { + // The if itself was dead: its else branch is too. + this.add(` unreachable`); + } + } + + call(index: number): void { + const ft = this.mt.funcType(index); + const args = this.popN(ft.params.length); + const argText = ["ptr %inst", ...args.map((a) => `${llType(a.t)} ${a.v}`)].join(", "); + this.callWith(this.mt.fn(index), ft, argText); + } + + callWith(callee: string, ft: WasmFuncType, argText: string): void { + const rt = retType(ft.results); + if (ft.results.length === 0) { + this.add(` call void ${callee}(${argText})`); + } else { + const r = this.emit(`call ${rt} ${callee}(${argText})`); + if (ft.results.length === 1) this.push(r, ft.results[0]!); + else + for (let i = 0; i < ft.results.length; i++) + this.push(this.emit(`extractvalue ${rt} ${r}, ${i}`), ft.results[i]!); + } + this.refreshMemory(); + } + + callIndirect(typeIndex: number, table: number): void { + const ft = this.m.types[typeIndex]!; + const idx = this.pop(); + const args = this.popN(ft.params.length); + const t = this.loadPtrSlot(this.mt.tableSlot(table)); + const sizePtr = this.emit(`getelementptr inbounds i8, ptr ${t}, i64 8`); + const size = this.emit(`load i32, ptr ${sizePtr}, align 8`); + const inRange = this.emit(`icmp ult i32 ${idx.v}, ${size}`); + const ok = this.label("ci"); + this.add(` br i1 ${inRange}, label %${ok}, label %${this.trap(TRAP_TABLE_OOB)}`); + this.startBlock(ok); + const elems = this.emit(`load ptr, ptr ${t}, align 8`); + const wide = this.emit(`zext i32 ${idx.v} to i64`); + const slot = this.emit(`getelementptr inbounds ptr, ptr ${elems}, i64 ${wide}`); + const rec = this.emit(`load ptr, ptr ${slot}, align 8`); + const isNull = this.emit(`icmp eq ptr ${rec}, null`); + const nn = this.label("cn"); + this.add(` br i1 ${isNull}, label %${this.trap(TRAP_SIGNATURE)}, label %${nn}`); + this.startBlock(nn); + const typePtr = this.emit(`getelementptr inbounds i8, ptr ${rec}, i64 8`); + const type = this.emit(`load i64, ptr ${typePtr}, align 8`); + const same = this.emit(`icmp eq i64 ${type}, ${this.mt.typeHashes[typeIndex]}`); + const go = this.label("cg"); + this.add(` br i1 ${same}, label %${go}, label %${this.trap(TRAP_SIGNATURE)}`); + this.startBlock(go); + const codePtr = this.emit(`load ptr, ptr ${rec}, align 8`); + const instPtr = this.emit(`getelementptr inbounds i8, ptr ${rec}, i64 16`); + const inst = this.emit(`load ptr, ptr ${instPtr}, align 8`); + const argText = [`ptr ${inst}`, ...args.map((a) => `${llType(a.t)} ${a.v}`)].join(", "); + this.callWith(codePtr, ft, argText); + } + + /** Integer division and remainder with wasm's traps. */ + divRem(op: number, t: string): void { + const b = this.pop(); + const a = this.pop(); + const ty = b.t; + const zero = this.emit(`icmp eq ${t} ${b.v}, 0`); + const signed = op === 0x6d || op === 0x6f || op === 0x7f || op === 0x81; + const rem = op === 0x6f || op === 0x70 || op === 0x81 || op === 0x82; + const nz = this.label("nz"); + this.add( + ` br i1 ${zero}, label %${this.trap(rem ? TRAP_REM_BY_ZERO : TRAP_DIV_BY_ZERO)}, label %${nz}`, + ); + this.startBlock(nz); + const min = t === "i32" ? "-2147483648" : "-9223372036854775808"; + if (signed && !rem) { + const isMin = this.emit(`icmp eq ${t} ${a.v}, ${min}`); + const isNeg1 = this.emit(`icmp eq ${t} ${b.v}, -1`); + const ovf = this.emit(`and i1 ${isMin}, ${isNeg1}`); + const okl = this.label("dv"); + this.add(` br i1 ${ovf}, label %${this.trap(TRAP_DIV_UNREPRESENTABLE)}, label %${okl}`); + this.startBlock(okl); + this.push(this.emit(`sdiv ${t} ${a.v}, ${b.v}`), ty); + return; + } + if (signed && rem) { + // INT_MIN % -1 is 0 in wasm (LLVM's srem would be undefined). + const isNeg1 = this.emit(`icmp eq ${t} ${b.v}, -1`); + const divisor = this.emit(`select i1 ${isNeg1}, ${t} 1, ${t} ${b.v}`); + this.push(this.emit(`srem ${t} ${a.v}, ${divisor}`), ty); + return; + } + this.push(this.emit(`${rem ? "urem" : "udiv"} ${t} ${a.v}, ${b.v}`), ty); + } + + /** Trapping float-to-int conversion. */ + truncTrap(op: number): void { + const x = this.pop(); + const from = x.t === VAL_F32 ? "float" : "double"; + // [lower bound exclusive?, lower, upper (exclusive)] as exact doubles. + let lo = ""; + let loInclusive = false; + let hi = ""; + let to = "i32"; + let signed = true; + switch (op) { + case 0xa8: // i32.trunc_f32_s + case 0xaa: // i32.trunc_f64_s + lo = op === 0xa8 ? "-2147483648.0" : "-2147483649.0"; + loInclusive = op === 0xa8; + hi = "2147483648.0"; + break; + case 0xa9: + case 0xab: + lo = "-1.0"; + hi = "4294967296.0"; + signed = false; + break; + case 0xae: + case 0xb0: + lo = "-9223372036854775808.0"; + loInclusive = true; + hi = "9223372036854775808.0"; + to = "i64"; + break; + default: + lo = "-1.0"; + hi = "18446744073709551616.0"; + to = "i64"; + signed = false; + break; + } + const d = from === "double" ? x.v : this.emit(`fpext float ${x.v} to double`); + const above = this.emit(`fcmp ${loInclusive ? "oge" : "ogt"} double ${d}, ${hexDouble(lo)}`); + const below = this.emit(`fcmp olt double ${d}, ${hexDouble(hi)}`); + const ok = this.emit(`and i1 ${above}, ${below}`); + const okl = this.label("cv"); + this.add(` br i1 ${ok}, label %${okl}, label %${this.trap(TRAP_FLOAT_UNREPRESENTABLE)}`); + this.startBlock(okl); + this.push( + this.emit(`${signed ? "fptosi" : "fptoui"} ${from} ${x.v} to ${to}`), + to === "i32" ? VAL_I32 : VAL_I64, + ); + } + + unaryIntrinsic(name: string, t: string, vt: number, extra = ""): void { + const a = this.pop(); + this.push(this.emit(`call ${t} @llvm.${name}(${t} ${a.v}${extra})`), vt); + } + + binaryIntrinsic(name: string, t: string, vt: number): void { + const b = this.pop(); + const a = this.pop(); + this.push(this.emit(`call ${t} @llvm.${name}(${t} ${a.v}, ${t} ${b.v})`), vt); + } + + binary(instr: string, t: string, vt: number): void { + const b = this.pop(); + const a = this.pop(); + this.push(this.emit(`${instr} ${t} ${a.v}, ${b.v}`), vt); + } + + compare(pred: string, t: string, float: boolean): void { + const b = this.pop(); + const a = this.pop(); + const r = this.emit(`${float ? "fcmp" : "icmp"} ${pred} ${t} ${a.v}, ${b.v}`); + this.push(this.emit(`zext i1 ${r} to i32`), VAL_I32); + } + + eqz(t: string): void { + const a = this.pop(); + const r = this.emit(`icmp eq ${t} ${a.v}, 0`); + this.push(this.emit(`zext i1 ${r} to i32`), VAL_I32); + } + + shift(instr: string, t: string, vt: number): void { + const b = this.pop(); + const a = this.pop(); + const masked = this.emit(`and ${t} ${b.v}, ${t === "i32" ? 31 : 63}`); + this.push(this.emit(`${instr} ${t} ${a.v}, ${masked}`), vt); + } + + rotate(name: string, t: string, vt: number): void { + const b = this.pop(); + const a = this.pop(); + this.push(this.emit(`call ${t} @llvm.${name}.${t}(${t} ${a.v}, ${t} ${a.v}, ${t} ${b.v})`), vt); + } + + convert(instr: string, from: string, to: string, vt: number): void { + const a = this.pop(); + this.push(this.emit(`${instr} ${from} ${a.v} to ${to}`), vt); + } + + instruction(c: WasmCursor, op: number): void { + switch (op) { + case 0x00: // unreachable + this.add(` br label %${this.trap(TRAP_UNREACHABLE)}`); + this.goDead(); + return; + case 0x01: + return; + case 0x02: { + // block + const sig = this.blockSig(c); + this.pushFrame("block", sig); + return; + } + case 0x03: { + // loop + const sig = this.blockSig(c); + const f = this.pushFrame("loop", sig); + f.target = this.label("loop"); + f.branchSlots = this.slotsFor(sig.params); + const params = this.popN(sig.params.length); + this.storeTo(f.branchSlots, params); + this.add(` br label %${f.target}`); + this.startBlock(f.target); + this.loadFrom(f.branchSlots, sig.params); + return; + } + case 0x04: { + // if + const sig = this.blockSig(c); + const cond = this.pop(); + const f = this.pushFrame("if", sig); + f.ifParams = this.stack.slice(this.stack.length - sig.params.length); + const then = this.label("then"); + f.elseLabel = this.label("else"); + const test = this.emit(`icmp ne i32 ${cond.v}, 0`); + this.add(` br i1 ${test}, label %${then}, label %${f.elseLabel}`); + this.startBlock(then); + return; + } + case 0x05: + this.opElse(); + return; + case 0x0c: { + const depth = c.u32(); + this.branch(depth); + this.goDead(); + return; + } + case 0x0d: { + const depth = c.u32(); + const cond = this.pop(); + const test = this.emit(`icmp ne i32 ${cond.v}, 0`); + const pending: string[] = []; + const target = this.branchEdge(depth, pending); + const cont = this.label("br"); + this.add(` br i1 ${test}, label %${target}, label %${cont}`); + for (const line of pending) this.add(line); + this.startBlock(cont); + return; + } + case 0x0e: { + const n = c.u32(); + const depths: number[] = []; + for (let i = 0; i < n; i++) depths.push(c.u32()); + const def = c.u32(); + const idx = this.pop(); + const pending: string[] = []; + const edges = new Map(); + const edgeOf = (d: number): string => { + let e = edges.get(d); + if (e === undefined) { + e = this.branchEdge(d, pending); + edges.set(d, e); + } + return e; + }; + const cases = depths.map((d, i) => `i32 ${i}, label %${edgeOf(d)}`); + this.add(` switch i32 ${idx.v}, label %${edgeOf(def)} [${cases.join(" ")}]`); + for (const line of pending) this.add(line); + this.goDead(); + return; + } + case 0x0f: // return + this.branch(this.frames.length - 1); + this.goDead(); + return; + case 0x10: + this.call(c.u32()); + return; + case 0x11: { + const type = c.u32(); + const table = c.u32(); + this.callIndirect(type, table); + return; + } + case 0x1a: { + // A dropped load must still fault when it is out of bounds: LLVM + // would delete an unused ordinary load. + const v = this.pop(); + if (v.load !== undefined) { + const line = this.lines.lines[v.load]!; + this.lines.lines[v.load] = line.replace(" = load ", " = load volatile "); + } + return; + } + case 0x1b: + case 0x1c: { + if (op === 0x1c) { + const n = c.u32(); + for (let i = 0; i < n; i++) c.valType(); + } + const cond = this.pop(); + const b = this.pop(); + const a = this.pop(); + const test = this.emit(`icmp ne i32 ${cond.v}, 0`); + const t = llType(a.t); + this.push(this.emit(`select i1 ${test}, ${t} ${a.v}, ${t} ${b.v}`), a.t); + return; + } + case 0x20: { + const i = c.u32(); + const t = this.localTypes[i]!; + this.push(this.emit(`load ${llType(t)}, ptr %l${i}`), t); + return; + } + case 0x21: { + const i = c.u32(); + const v = this.pop(); + this.add(` store ${llType(v.t)} ${v.v}, ptr %l${i}`); + return; + } + case 0x22: { + const i = c.u32(); + const v = this.stack[this.stack.length - 1]!; + this.add(` store ${llType(v.t)} ${v.v}, ptr %l${i}`); + return; + } + case 0x23: { + const g = c.u32(); + const t = this.m.globals[g]!.type; + const cell = this.loadPtrSlot(this.mt.globalSlot(g)); + this.push(this.emit(`load ${llType(t)}, ptr ${cell}, align 8`), t); + return; + } + case 0x24: { + const g = c.u32(); + const v = this.pop(); + const cell = this.loadPtrSlot(this.mt.globalSlot(g)); + if (v.t === VAL_EXTERNREF) + this.add(` call void @scr_wasm_global_set_ref(ptr ${cell}, ptr ${v.v})`); + else this.add(` store ${llType(v.t)} ${v.v}, ptr ${cell}, align 8`); + return; + } + case 0x25: { + const t = c.u32(); + const i = this.pop(); + this.push( + this.emit(`call ptr @scr_wasm_table_get(ptr %inst, i32 ${t}, i32 ${i.v})`), + this.m.tables[t]!.elemType, + ); + return; + } + case 0x26: { + const t = c.u32(); + const v = this.pop(); + const i = this.pop(); + this.add(` call void @scr_wasm_table_set(ptr %inst, i32 ${t}, i32 ${i.v}, ptr ${v.v})`); + return; + } + case 0x3f: { + c.u32(); + const mem = this.loadPtrSlot(OFF_MEMORY); + const sizePtr = this.emit(`getelementptr inbounds i8, ptr ${mem}, i64 8`); + const size = this.emit(`load i64, ptr ${sizePtr}, align 8`); + const pages = this.emit(`lshr i64 ${size}, 16`); + this.push(this.emit(`trunc i64 ${pages} to i32`), VAL_I32); + return; + } + case 0x40: { + c.u32(); + const delta = this.pop(); + this.push(this.emit(`call i32 @scr_wasm_memory_grow(ptr %inst, i32 ${delta.v})`), VAL_I32); + this.refreshMemory(); + return; + } + case 0x41: + this.push(String(c.s32()), VAL_I32); + return; + case 0x42: { + const [lo, hi] = c.s64(); + this.push(i64Lit(lo, hi), VAL_I64); + return; + } + case 0x43: + this.push(this.emit(`bitcast i32 ${c.u32le() | 0} to float`), VAL_F32); + return; + case 0x44: { + const lo = c.u32le(); + const hi = c.u32le(); + this.push(this.emit(`bitcast i64 ${i64Lit(lo, hi)} to double`), VAL_F64); + return; + } + case 0xd0: + c.valType(); + this.push("null", c.bytes[c.pos - 1]!); + return; + case 0xd1: { + const a = this.pop(); + const r = this.emit(`icmp eq ptr ${a.v}, null`); + this.push(this.emit(`zext i1 ${r} to i32`), VAL_I32); + return; + } + case 0xd2: + this.push(this.funcRef(c.u32()), VAL_FUNCREF); + return; + case 0xfc: + this.prefixed(c); + return; + default: + break; + } + if (op >= 0x28 && op <= 0x35) { + c.u32(); + const offset = c.u32(); + const [vt, mt, bytes, ext] = LOAD_INFO[op]!; + const addr = this.pop(); + const p = this.address(addr.v, offset, bytes); + const line = this.lines.lines.length; + const loaded = this.emit(`load ${mt}, ptr ${p}, align 1`); + if (ext === "") this.stack.push({ v: loaded, t: vt, load: line }); + else { + const extended = this.emit(`${ext} ${mt} ${loaded} to ${llType(vt)}`); + this.stack.push({ v: extended, t: vt, load: line }); + } + return; + } + if (op >= 0x36 && op <= 0x3e) { + c.u32(); + const offset = c.u32(); + const [, mt, bytes] = STORE_INFO[op]!; + const v = this.pop(); + const addr = this.pop(); + const p = this.address(addr.v, offset, bytes); + let value = v.v; + if (mt === "i8" || mt === "i16" || (mt === "i32" && v.t === VAL_I64)) { + value = this.emit(`trunc ${llType(v.t)} ${v.v} to ${mt}`); + } + this.add(` store ${mt} ${value}, ptr ${p}, align 1`); + return; + } + this.numeric(op); + } + + numeric(op: number): void { + const I32 = VAL_I32; + const I64 = VAL_I64; + const F32 = VAL_F32; + const F64 = VAL_F64; + switch (op) { + case 0x45: + return this.eqz("i32"); + case 0x46: + return this.compare("eq", "i32", false); + case 0x47: + return this.compare("ne", "i32", false); + case 0x48: + return this.compare("slt", "i32", false); + case 0x49: + return this.compare("ult", "i32", false); + case 0x4a: + return this.compare("sgt", "i32", false); + case 0x4b: + return this.compare("ugt", "i32", false); + case 0x4c: + return this.compare("sle", "i32", false); + case 0x4d: + return this.compare("ule", "i32", false); + case 0x4e: + return this.compare("sge", "i32", false); + case 0x4f: + return this.compare("uge", "i32", false); + case 0x50: + return this.eqz("i64"); + case 0x51: + return this.compare("eq", "i64", false); + case 0x52: + return this.compare("ne", "i64", false); + case 0x53: + return this.compare("slt", "i64", false); + case 0x54: + return this.compare("ult", "i64", false); + case 0x55: + return this.compare("sgt", "i64", false); + case 0x56: + return this.compare("ugt", "i64", false); + case 0x57: + return this.compare("sle", "i64", false); + case 0x58: + return this.compare("ule", "i64", false); + case 0x59: + return this.compare("sge", "i64", false); + case 0x5a: + return this.compare("uge", "i64", false); + case 0x5b: + return this.compare("oeq", "float", true); + case 0x5c: + return this.compare("une", "float", true); + case 0x5d: + return this.compare("olt", "float", true); + case 0x5e: + return this.compare("ogt", "float", true); + case 0x5f: + return this.compare("ole", "float", true); + case 0x60: + return this.compare("oge", "float", true); + case 0x61: + return this.compare("oeq", "double", true); + case 0x62: + return this.compare("une", "double", true); + case 0x63: + return this.compare("olt", "double", true); + case 0x64: + return this.compare("ogt", "double", true); + case 0x65: + return this.compare("ole", "double", true); + case 0x66: + return this.compare("oge", "double", true); + case 0x67: + return this.unaryIntrinsic("ctlz.i32", "i32", I32, ", i1 false"); + case 0x68: + return this.unaryIntrinsic("cttz.i32", "i32", I32, ", i1 false"); + case 0x69: + return this.unaryIntrinsic("ctpop.i32", "i32", I32); + case 0x6a: + return this.binary("add", "i32", I32); + case 0x6b: + return this.binary("sub", "i32", I32); + case 0x6c: + return this.binary("mul", "i32", I32); + case 0x6d: + case 0x6e: + case 0x6f: + case 0x70: + return this.divRem(op, "i32"); + case 0x71: + return this.binary("and", "i32", I32); + case 0x72: + return this.binary("or", "i32", I32); + case 0x73: + return this.binary("xor", "i32", I32); + case 0x74: + return this.shift("shl", "i32", I32); + case 0x75: + return this.shift("ashr", "i32", I32); + case 0x76: + return this.shift("lshr", "i32", I32); + case 0x77: + return this.rotate("fshl", "i32", I32); + case 0x78: + return this.rotate("fshr", "i32", I32); + case 0x79: + return this.unaryIntrinsic("ctlz.i64", "i64", I64, ", i1 false"); + case 0x7a: + return this.unaryIntrinsic("cttz.i64", "i64", I64, ", i1 false"); + case 0x7b: + return this.unaryIntrinsic("ctpop.i64", "i64", I64); + case 0x7c: + return this.binary("add", "i64", I64); + case 0x7d: + return this.binary("sub", "i64", I64); + case 0x7e: + return this.binary("mul", "i64", I64); + case 0x7f: + case 0x80: + case 0x81: + case 0x82: + return this.divRem(op, "i64"); + case 0x83: + return this.binary("and", "i64", I64); + case 0x84: + return this.binary("or", "i64", I64); + case 0x85: + return this.binary("xor", "i64", I64); + case 0x86: + return this.shift("shl", "i64", I64); + case 0x87: + return this.shift("ashr", "i64", I64); + case 0x88: + return this.shift("lshr", "i64", I64); + case 0x89: + return this.rotate("fshl", "i64", I64); + case 0x8a: + return this.rotate("fshr", "i64", I64); + case 0x8b: + return this.unaryIntrinsic("fabs.f32", "float", F32); + case 0x8c: { + const a = this.pop(); + this.push(this.emit(`fneg float ${a.v}`), F32); + return; + } + case 0x8d: + return this.unaryIntrinsic("ceil.f32", "float", F32); + case 0x8e: + return this.unaryIntrinsic("floor.f32", "float", F32); + case 0x8f: + return this.unaryIntrinsic("trunc.f32", "float", F32); + case 0x90: + return this.unaryIntrinsic("roundeven.f32", "float", F32); + case 0x91: + return this.unaryIntrinsic("sqrt.f32", "float", F32); + case 0x92: + return this.binary("fadd", "float", F32); + case 0x93: + return this.binary("fsub", "float", F32); + case 0x94: + return this.binary("fmul", "float", F32); + case 0x95: + return this.binary("fdiv", "float", F32); + case 0x96: + return this.binaryIntrinsic("minimum.f32", "float", F32); + case 0x97: + return this.binaryIntrinsic("maximum.f32", "float", F32); + case 0x98: + return this.binaryIntrinsic("copysign.f32", "float", F32); + case 0x99: + return this.unaryIntrinsic("fabs.f64", "double", F64); + case 0x9a: { + const a = this.pop(); + this.push(this.emit(`fneg double ${a.v}`), F64); + return; + } + case 0x9b: + return this.unaryIntrinsic("ceil.f64", "double", F64); + case 0x9c: + return this.unaryIntrinsic("floor.f64", "double", F64); + case 0x9d: + return this.unaryIntrinsic("trunc.f64", "double", F64); + case 0x9e: + return this.unaryIntrinsic("roundeven.f64", "double", F64); + case 0x9f: + return this.unaryIntrinsic("sqrt.f64", "double", F64); + case 0xa0: + return this.binary("fadd", "double", F64); + case 0xa1: + return this.binary("fsub", "double", F64); + case 0xa2: + return this.binary("fmul", "double", F64); + case 0xa3: + return this.binary("fdiv", "double", F64); + case 0xa4: + return this.binaryIntrinsic("minimum.f64", "double", F64); + case 0xa5: + return this.binaryIntrinsic("maximum.f64", "double", F64); + case 0xa6: + return this.binaryIntrinsic("copysign.f64", "double", F64); + case 0xa7: + return this.convert("trunc", "i64", "i32", I32); + case 0xa8: + case 0xa9: + case 0xaa: + case 0xab: + case 0xae: + case 0xaf: + case 0xb0: + case 0xb1: + return this.truncTrap(op); + case 0xac: + return this.convert("sext", "i32", "i64", I64); + case 0xad: + return this.convert("zext", "i32", "i64", I64); + case 0xb2: + return this.convert("sitofp", "i32", "float", F32); + case 0xb3: + return this.convert("uitofp", "i32", "float", F32); + case 0xb4: + return this.convert("sitofp", "i64", "float", F32); + case 0xb5: + return this.convert("uitofp", "i64", "float", F32); + case 0xb6: + return this.convert("fptrunc", "double", "float", F32); + case 0xb7: + return this.convert("sitofp", "i32", "double", F64); + case 0xb8: + return this.convert("uitofp", "i32", "double", F64); + case 0xb9: + return this.convert("sitofp", "i64", "double", F64); + case 0xba: + return this.convert("uitofp", "i64", "double", F64); + case 0xbb: + return this.convert("fpext", "float", "double", F64); + case 0xbc: + return this.convert("bitcast", "float", "i32", I32); + case 0xbd: + return this.convert("bitcast", "double", "i64", I64); + case 0xbe: + return this.convert("bitcast", "i32", "float", F32); + case 0xbf: + return this.convert("bitcast", "i64", "double", F64); + case 0xc0: + case 0xc1: { + const a = this.pop(); + const n = op === 0xc0 ? "i8" : "i16"; + const narrow = this.emit(`trunc i32 ${a.v} to ${n}`); + this.push(this.emit(`sext ${n} ${narrow} to i32`), I32); + return; + } + case 0xc2: + case 0xc3: + case 0xc4: { + const a = this.pop(); + const n = op === 0xc2 ? "i8" : op === 0xc3 ? "i16" : "i32"; + const narrow = this.emit(`trunc i64 ${a.v} to ${n}`); + this.push(this.emit(`sext ${n} ${narrow} to i64`), I64); + return; + } + default: + throw new WasmTranslateError(`opcode 0x${op.toString(16)} is not supported`); + } + } + + prefixed(c: WasmCursor): void { + const sub = c.u32(); + if (sub <= 7) { + // Saturating float-to-int conversions. + const a = this.pop(); + const from = a.t === VAL_F32 ? "f32" : "f64"; + const fromTy = a.t === VAL_F32 ? "float" : "double"; + const to = sub < 4 ? "i32" : "i64"; + const signed = (sub & 1) === 0; + this.push( + this.emit( + `call ${to} @llvm.${signed ? "fptosi" : "fptoui"}.sat.${to}.${from}(${fromTy} ${a.v})`, + ), + to === "i32" ? VAL_I32 : VAL_I64, + ); + return; + } + switch (sub) { + case 8: { + // memory.init + const seg = c.u32(); + c.u32(); + const n = this.pop(); + const s = this.pop(); + const d = this.pop(); + this.add( + ` call void @scr_wasm_memory_init(ptr %inst, i32 ${seg}, i32 ${d.v}, i32 ${s.v}, i32 ${n.v})`, + ); + return; + } + case 9: + this.add(` call void @scr_wasm_data_drop(ptr %inst, i32 ${c.u32()})`); + return; + case 10: { + c.u32(); + c.u32(); + const n = this.pop(); + const s = this.pop(); + const d = this.pop(); + this.add( + ` call void @scr_wasm_memory_copy(ptr %inst, i32 ${d.v}, i32 ${s.v}, i32 ${n.v})`, + ); + return; + } + case 11: { + c.u32(); + const n = this.pop(); + const v = this.pop(); + const d = this.pop(); + this.add( + ` call void @scr_wasm_memory_fill(ptr %inst, i32 ${d.v}, i32 ${v.v}, i32 ${n.v})`, + ); + return; + } + case 12: { + const seg = c.u32(); + const t = c.u32(); + const n = this.pop(); + const s = this.pop(); + const d = this.pop(); + this.add( + ` call void @scr_wasm_table_init(ptr %inst, i32 ${t}, i32 ${seg}, i32 ${d.v}, i32 ${s.v}, i32 ${n.v})`, + ); + return; + } + case 13: + this.add(` call void @scr_wasm_elem_drop(ptr %inst, i32 ${c.u32()})`); + return; + case 14: { + const dt = c.u32(); + const st = c.u32(); + const n = this.pop(); + const s = this.pop(); + const d = this.pop(); + this.add( + ` call void @scr_wasm_table_copy(ptr %inst, i32 ${dt}, i32 ${st}, i32 ${d.v}, i32 ${s.v}, i32 ${n.v})`, + ); + return; + } + case 15: { + const t = c.u32(); + const n = this.pop(); + const v = this.pop(); + this.push( + this.emit(`call i32 @scr_wasm_table_grow(ptr %inst, i32 ${t}, ptr ${v.v}, i32 ${n.v})`), + VAL_I32, + ); + return; + } + case 16: { + const t = c.u32(); + const tab = this.loadPtrSlot(this.mt.tableSlot(t)); + const sizePtr = this.emit(`getelementptr inbounds i8, ptr ${tab}, i64 8`); + this.push(this.emit(`load i32, ptr ${sizePtr}, align 8`), VAL_I32); + return; + } + case 17: { + const t = c.u32(); + const n = this.pop(); + const v = this.pop(); + const d = this.pop(); + this.add( + ` call void @scr_wasm_table_fill(ptr %inst, i32 ${t}, i32 ${d.v}, ptr ${v.v}, i32 ${n.v})`, + ); + return; + } + default: + throw new WasmTranslateError(`opcode 0xfc ${sub} is not supported`); + } + } +} + +/** The functions through which the stack can grow without bound, which + * check the stack pointer on entry: members of a cycle of direct calls + * (including self-recursion), and functions that call through a table or + * call an import (an indirect call can reach any function, and an import + * can call back into an export). Every other call chain is finite, so its + * depth is bounded by the module and the guard's reserve absorbs it. */ +export function stackCheckedFunctions(m: WasmModule): Set { + const n = m.funcTypes.length; + const callees: number[][] = []; + const checked = new Set(); + for (let f = 0; f < n; f++) callees.push([]); + for (let i = 0; i < m.codes.length; i++) { + const f = m.importedFuncs + i; + const body = m.codes[i]!.body; + const c = new WasmCursor(m.bytes, body.start); + while (c.pos < body.end) { + const op = c.u8(); + if (op === 0x10) { + const callee = c.u32(); + if (callee < m.importedFuncs) checked.add(f); + else callees[f]!.push(callee); + } else if (op === 0x11) { + c.u32(); + c.u32(); + checked.add(f); + } else { + skipImmediates(c, op); + } + } + } + // Tarjan's strongly connected components, iteratively. + const index = new Int32Array(n).fill(-1); + const low = new Int32Array(n); + const onStack = new Uint8Array(n); + const stack: number[] = []; + let next = 0; + for (let root = m.importedFuncs; root < n; root++) { + if (index[root] !== -1) continue; + const workNode: number[] = [root]; + const workEdge: number[] = [0]; + index[root] = low[root] = next++; + stack.push(root); + onStack[root] = 1; + while (workNode.length > 0) { + const top = workNode.length - 1; + const v = workNode[top]!; + const edges = callees[v]!; + const e = workEdge[top]!; + if (e < edges.length) { + workEdge[top] = e + 1; + const w = edges[e]!; + if (index[w] === -1) { + index[w] = low[w] = next++; + stack.push(w); + onStack[w] = 1; + workNode.push(w); + workEdge.push(0); + } else if (onStack[w]) { + low[v] = Math.min(low[v]!, index[w]!); + } + continue; + } + workNode.pop(); + workEdge.pop(); + if (workNode.length > 0) { + const parent = workNode[workNode.length - 1]!; + low[parent] = Math.min(low[parent]!, low[v]!); + } + if (low[v] === index[v]) { + const component: number[] = []; + for (;;) { + const w = stack.pop()!; + onStack[w] = 0; + component.push(w); + if (w === v) break; + } + if (component.length > 1 || edges.includes(v)) for (const w of component) checked.add(w); + } + } + } + return checked; +} + +/** A decimal double literal as LLVM's exact hexadecimal form. */ +function hexDouble(text: string): string { + const view = new DataView(new ArrayBuffer(8)); + view.setFloat64(0, Number(text)); + return `0x${hex8(view.getUint32(0))}${hex8(view.getUint32(4))}`.toUpperCase().replace("0X", "0x"); +} + +function hexOf(bytes: Uint8Array): string { + let s = ""; + for (let i = 0; i < bytes.length; i++) s += bytes[i]!.toString(16).padStart(2, "0"); + return s; +} + +/** The symbol prefix of a module's translation. */ +export function wasmSymbolPrefix(sha256: Uint8Array): string { + return `scr_wasm_${hexOf(sha256).slice(0, 16)}`; +} + +/** Translates a module that binary.ts validated within the translatable + * feature level. Throws WasmTranslateError for constructs outside it. */ +export function translateWasmModule( + bytes: Uint8Array, + sha256: Uint8Array, + opts: WasmTranslateOptions, +): WasmTranslation { + const m = parseWasmModule(bytes); + for (const t of m.tables) llType(t.elemType); + const mt = new ModuleTranslator(m, wasmSymbolPrefix(sha256), opts); + mt.translate(); + const descriptor = mt.descriptor(sha256); + const decls = RUNTIME_DECLS.slice(); + // Initial-exec: executables and their static runtime define it, so the + // check is one thread-pointer-relative load, not a TLS lookup call. + if (opts.boundsChecks) + decls.push("@scr_wasm_stack_limit = external thread_local(initialexec) global i64"); + return { decls, text: mt.text, descriptor }; +} diff --git a/packages/compiler/surface-manifest.json b/packages/compiler/surface-manifest.json index a167254c2..5d82b6719 100644 --- a/packages/compiler/surface-manifest.json +++ b/packages/compiler/surface-manifest.json @@ -5634,9 +5634,8 @@ "id": "stdlib.webassembly", "kind": "stdlib", "name": "globalThis.WebAssembly", - "status": "unsupported", - "code": "SC2020", - "note": "WebAssembly support is planned as translation of each module whose bytes the build can see (an inline literal, or a file read from a path the compiler resolves) into native code; 'scriptc coverage' lists the modules this program would load (https://scriptc.dev/limitations)" + "status": "static", + "note": "compile, instantiate, validate, Module, Instance, instanceof, the error classes, exports, memories, tables, globals and Module transfer to workers, over the modules translated at build time; the rest refuses with SC2020 (the Memory, Table and Global constructors, Module.imports, exports and customSections, and the streaming functions are planned; 'scriptc coverage' lists the modules this program loads (https://scriptc.dev/limitations))" }, { "id": "syntax.async-generators", diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-api.mjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-api.mjs.json new file mode 100644 index 000000000..f3e1a711b --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-api.mjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/webassembly-api.mjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-spec-control/main.mjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-spec-control/main.mjs.json new file mode 100644 index 000000000..58a263265 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-spec-control/main.mjs.json @@ -0,0 +1,8 @@ +{ + "order": [ + "/tests/wasm-spec/runner.mjs", + "/tests/corpus/webassembly-spec-control/data.mjs", + "/tests/corpus/webassembly-spec-control/main.mjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-spec-float/main.mjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-spec-float/main.mjs.json new file mode 100644 index 000000000..e871d3b1d --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-spec-float/main.mjs.json @@ -0,0 +1,8 @@ +{ + "order": [ + "/tests/wasm-spec/runner.mjs", + "/tests/corpus/webassembly-spec-float/data.mjs", + "/tests/corpus/webassembly-spec-float/main.mjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-spec-int/main.mjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-spec-int/main.mjs.json new file mode 100644 index 000000000..d19226a63 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-spec-int/main.mjs.json @@ -0,0 +1,8 @@ +{ + "order": [ + "/tests/wasm-spec/runner.mjs", + "/tests/corpus/webassembly-spec-int/data.mjs", + "/tests/corpus/webassembly-spec-int/main.mjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-spec-memory/main.mjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-spec-memory/main.mjs.json new file mode 100644 index 000000000..4510ade3a --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-spec-memory/main.mjs.json @@ -0,0 +1,8 @@ +{ + "order": [ + "/tests/wasm-spec/runner.mjs", + "/tests/corpus/webassembly-spec-memory/data.mjs", + "/tests/corpus/webassembly-spec-memory/main.mjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-spec-table/main.mjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-spec-table/main.mjs.json new file mode 100644 index 000000000..50409b1d0 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-spec-table/main.mjs.json @@ -0,0 +1,8 @@ +{ + "order": [ + "/tests/wasm-spec/runner.mjs", + "/tests/corpus/webassembly-spec-table/data.mjs", + "/tests/corpus/webassembly-spec-table/main.mjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-typed.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-typed.ts.json new file mode 100644 index 000000000..7a3adb475 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-typed.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/webassembly-typed.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-worker.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-worker.ts.json new file mode 100644 index 000000000..2b4ffd4ba --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-worker.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/webassembly-worker.ts" + ], + "diags": [] +} diff --git a/packages/runtime-darwin-arm64/runtime-pack-matrix.mjs b/packages/runtime-darwin-arm64/runtime-pack-matrix.mjs index 54e789624..5ded1866c 100644 --- a/packages/runtime-darwin-arm64/runtime-pack-matrix.mjs +++ b/packages/runtime-darwin-arm64/runtime-pack-matrix.mjs @@ -112,6 +112,7 @@ const optional = [ ["scr_tls_ca.c", "tlsCaEffective"], ["scr_tls.c", "tlsEffective"], ["scr_cipher.c", "cipher"], + ["scr_wasm.c", "wasm"], ["scr_fetch.c", "nativeFetch"], ]; diff --git a/packages/runtime-pack-common/runtime-pack-matrix.mjs b/packages/runtime-pack-common/runtime-pack-matrix.mjs index 6b5052482..604e5dd07 100644 --- a/packages/runtime-pack-common/runtime-pack-matrix.mjs +++ b/packages/runtime-pack-common/runtime-pack-matrix.mjs @@ -102,6 +102,7 @@ const OPTIONAL = [ ["scr_tls_ca.c", "tlsCaEffective"], ["scr_tls.c", "tlsEffective"], ["scr_cipher.c", "cipher"], + ["scr_wasm.c", "wasm"], ["scr_fetch.c", "nativeFetch"], ]; diff --git a/packages/runtime-wasm32-wasi/scripts/build.mjs b/packages/runtime-wasm32-wasi/scripts/build.mjs index ca0786607..42808aaa5 100644 --- a/packages/runtime-wasm32-wasi/scripts/build.mjs +++ b/packages/runtime-wasm32-wasi/scripts/build.mjs @@ -20,6 +20,7 @@ process.env.SCRIPTC_RUNTIME_PACK_CONFIG = JSON.stringify({ "scr_tls_ca.c", "scr_tls.c", "scr_cipher.c", + "scr_wasm.c", "scr_fetch.c", ], omitArchives: ["mbedtls"], diff --git a/packages/runtime/src/scr_async.c b/packages/runtime/src/scr_async.c index f630ee5f9..ce0e93814 100644 --- a/packages/runtime/src/scr_async.c +++ b/packages/runtime/src/scr_async.c @@ -88,14 +88,6 @@ void __lsan_unregister_root_region(const void *p, size_t size) __attribute__((we #endif #endif -#ifdef SCR_ASAN_FIBERS -#define SCR_FIBER_STACK (8 * 1024 * 1024) -#else -/* Deep enough for ordinary recursion inside async bodies and generators - * once the overflow guard's reserve is taken off; idle and shallow fibers - * only touch the pages they use. */ -#define SCR_FIBER_STACK (512 * 1024) -#endif #if !defined(_WIN32) && !defined(__wasi__) /* Keep stacks out of malloc's size classes. Reuse a bounded number during diff --git a/packages/runtime/src/scr_async.h b/packages/runtime/src/scr_async.h index efa98e942..9ff38ed88 100644 --- a/packages/runtime/src/scr_async.h +++ b/packages/runtime/src/scr_async.h @@ -29,6 +29,20 @@ void *scr_promise_retain_v(void *p); void scr_promise_release_v(void *p); typedef struct ScrFiber ScrFiber; +/* Bytes of each async fiber's native stack (scr_async.c), mapped without a + * guard page: compiled code checks scr_stack_guard instead, which sits + * scr_stack_guard_for's reserve above the mapping's start. Deep enough for + * ordinary recursion inside async bodies and generators once that reserve + * is taken off; idle and shallow fibers only touch the pages they use. + * AddressSanitizer's larger frames get more. */ +#if defined(__has_feature) +#if __has_feature(address_sanitizer) +#define SCR_FIBER_STACK (8 * 1024 * 1024) +#endif +#endif +#ifndef SCR_FIBER_STACK +#define SCR_FIBER_STACK (512 * 1024) +#endif /* Spawn + run eagerly to the first suspension; returns the promise, +1. */ ScrPromise *scr_async_spawn(void (*entry)(ScrFiber *, void *), void *argpack); /* `await f(...)` of a fiber-backed async f: spawn and await in one call, diff --git a/packages/runtime/src/scr_dyn.h b/packages/runtime/src/scr_dyn.h index ca21a0685..b7960ba12 100644 --- a/packages/runtime/src/scr_dyn.h +++ b/packages/runtime/src/scr_dyn.h @@ -173,6 +173,11 @@ typedef enum { SCR_DYNH_BROADCAST_CHANNEL, /* worker_threads BroadcastChannel (scr_worker_events.c) */ SCR_DYNH_HASH, /* node:crypto Hash (scr_lib.c) */ SCR_DYNH_HMAC, /* node:crypto Hmac (scr_lib.c) */ + SCR_DYNH_WASM_MODULE, /* WebAssembly.Module (scr_wasm.c) */ + SCR_DYNH_WASM_INSTANCE, /* WebAssembly.Instance */ + SCR_DYNH_WASM_MEMORY, /* WebAssembly.Memory */ + SCR_DYNH_WASM_TABLE, /* WebAssembly.Table */ + SCR_DYNH_WASM_GLOBAL, /* WebAssembly.Global */ SCR_DYNH_COUNT, } ScrDynHandleTag; diff --git a/packages/runtime/src/scr_wasm.c b/packages/runtime/src/scr_wasm.c new file mode 100644 index 000000000..03d59604f --- /dev/null +++ b/packages/runtime/src/scr_wasm.c @@ -0,0 +1,1990 @@ +/* The JavaScript WebAssembly API over modules translated to native code + * when the program was built (scr_wasm_abi.h is the contract with the + * translator). + * + * WebAssembly.compile, instantiate, validate and new Module hash the bytes + * they are given and look the module up by SHA-256 and length in + * scr_wasm_registry, which the compiler emits; bytes that were not + * translated at build time reject with a CompileError that says so. + * Nothing is interpreted or compiled at run time, and a translated + * module's bytes were validated when the program was built. + * + * Values: Module, Instance, Memory, Table and Global are native handles in + * the checked-dynamic representation (the SCR_DYNH_WASM_* tags, installed + * per thread on first use). An instance's exports object is a frozen + * null-prototype object whose functions call the translated code through + * its thunks under a trap guard; imports are checked-dynamic callables + * called with `this` undefined, ToJSValue arguments and ToWebAssemblyValue + * results. + * + * Traps. Translated code calls scr_wasm_trap, which siglongjmps to the + * innermost export entry (scr_wasm_enter); the entry throws Node's + * WebAssembly.RuntimeError. With guard pages, an out-of-bounds access + * faults inside the memory's reservation and stack exhaustion faults near + * the stack pointer: the SIGSEGV/SIGBUS handler (installed when the first + * guarded instance is created, on a per-thread alternate stack, chaining + * to the previous handler for every other fault) moves the interrupted + * context to a trampoline that traps the same way, so the handler itself + * returns normally. Async fibers' stacks have no guard page of their own, + * so the first export call on one protects the lowest page of its mapping + * (scr_wasm_guard_fiber). An exception thrown by an import unwinds the + * translated frames the same way and propagates unchanged. Translated + * frames own nothing, so abandoning them leaks nothing; the module's own + * shadow stack pointer is not restored, exactly as in V8. + * + * The error classes are Errors whose inherited name is CompileError, + * LinkError or RuntimeError (not an own property, as in Node), which is + * the brand instanceof tests. + * + * Gaps (results/opt-wasm-design/DESIGN.md, stages W5-W7): no Memory, + * Table or Global constructors; Memory.grow does not detach the previous + * ArrayBuffer (the memory never moves, so old views stay valid with their + * old length); instances, their imports and exports are never freed (the + * cycle collector does not trace them yet); funcref values cross to + * JavaScript only as exported functions. */ +#if !defined(_GNU_SOURCE) && defined(__linux__) +#define _GNU_SOURCE /* REG_RIP and friends in */ +#endif +#include "scr_runtime.h" +#include "scr_wasm_abi.h" + +#include +#include +#include +#include +#include + +#if !defined(_WIN32) +#define SCR_WASM_SIGNALS 1 +#include +#include +#include +#include +#if defined(__APPLE__) +#include +#else +#include +#endif +#endif + +#if defined(__has_feature) +#if __has_feature(address_sanitizer) +#define SCR_WASM_ASAN 1 +#endif +#endif +#if !defined(SCR_WASM_ASAN) && defined(__SANITIZE_ADDRESS__) +#define SCR_WASM_ASAN 1 +#endif + +ScrPromise *scr_wasm_compile(ScrDyn *bytes); +bool scr_wasm_validate(ScrDyn *bytes); +ScrPromise *scr_wasm_instantiate(ScrDyn *source, ScrDyn *imports); +ScrDyn *scr_wasm_module_new(ScrDyn *bytes); +ScrDyn *scr_wasm_instance_new(ScrDyn *module, ScrDyn *imports); +bool scr_wasm_is(const ScrDyn *value, double kind); +ScrDyn *scr_wasm_error_new(double kind, ScrDyn *message, ScrDyn *options); +ScrDyn *scr_wasm_function(double kind); +ScrDyn *scr_wasm_namespace(void); +ScrDyn *scr_wasm_global_view(void); + +/* Entry points of translated code. */ +/* Sanitized builds do not declare it noreturn: AddressSanitizer would + * call __asan_handle_no_return before every call, which warns on the + * runtime's async fibers. */ +#ifdef SCR_WASM_ASAN +void scr_wasm_trap(int32_t code); +#else +_Noreturn void scr_wasm_trap(int32_t code); +#endif +void scr_wasm_import_call(void *host, int32_t index, const uint64_t *args, uint64_t *results); +int32_t scr_wasm_memory_grow(void *inst, int32_t delta); +void scr_wasm_memory_fill(void *inst, int32_t d, int32_t value, int32_t n); +void scr_wasm_memory_copy(void *inst, int32_t d, int32_t s, int32_t n); +void scr_wasm_memory_init(void *inst, int32_t seg, int32_t d, int32_t s, int32_t n); +void scr_wasm_data_drop(void *inst, int32_t seg); +void *scr_wasm_table_get(void *inst, int32_t t, int32_t i); +void scr_wasm_table_set(void *inst, int32_t t, int32_t i, void *value); +int32_t scr_wasm_table_grow(void *inst, int32_t t, void *init, int32_t n); +void scr_wasm_table_fill(void *inst, int32_t t, int32_t i, void *value, int32_t n); +void scr_wasm_table_copy(void *inst, int32_t dt, int32_t st, int32_t d, int32_t s, int32_t n); +void scr_wasm_table_init(void *inst, int32_t t, int32_t seg, int32_t d, int32_t s, int32_t n); +void scr_wasm_elem_drop(void *inst, int32_t seg); +void scr_wasm_global_set_ref(void *cell, void *value); + +#define SCR_WASM_PAGE 65536u +#define SCR_WASM_MAX_PAGES 65536u +#define SCR_WASM_MAX_TABLE 10000000u +/* Every `base + zext(i32) + offset(u32) + size` the translated code can + * form stays inside this reservation, so an access past the memory faults + * on a PROT_NONE page instead of reaching other mappings. */ +#define SCR_WASM_GUARD_RESERVATION ((uint64_t)8 << 30 | (uint64_t)SCR_WASM_PAGE) + +/* ── instance state ──────────────────────────────────────────────────── */ + +typedef struct ScrWasmInst { + size_t rc; + const ScrWasmModuleDesc *desc; + void **inst; /* the translated code's instance (layout in scr_wasm_abi.h) */ + ScrDyn **funcs; /* imported function values, by function index */ + ScrWasmMemory *memory; + ScrWasmTable **tables; + ScrWasmGlobal **globals; + ScrWasmFuncRef *funcrefs; + ScrDyn **export_fns; /* exported function values, by function index (lazy) */ + char **export_names; + bool *data_dropped; + bool *elem_dropped; + ScrDyn *exports; +} ScrWasmInst; + +static ScrWasmInst *scr_wasm_host_of(void *inst) { return ((ScrWasmInst **)inst)[0]; } + +#ifdef SCR_RC_AUDIT +/* Instances are never freed yet (stage W6), so the sanitized lane's RC + * audit would report everything they hold. Audited builds tear every + * instance down at exit instead. */ +static SCR_TL ScrWasmInst **scr_wasm_hosts; +static SCR_TL size_t scr_wasm_host_count, scr_wasm_host_cap; +static void scr_wasm_teardown(void); + +static void scr_wasm_note_host(ScrWasmInst *host) { + if (scr_wasm_host_count == 0 && scr_wasm_host_cap == 0) scr_atexit(scr_wasm_teardown); + if (scr_wasm_host_count == scr_wasm_host_cap) { + size_t cap = scr_wasm_host_cap ? scr_wasm_host_cap * 2 : 8; + ScrWasmInst **grown = realloc(scr_wasm_hosts, cap * sizeof *grown); + if (!grown) scr_trap("scriptc: out of memory\n"); + scr_wasm_hosts = grown; + scr_wasm_host_cap = cap; + } + scr_wasm_hosts[scr_wasm_host_count++] = host; +} +#endif + +/* ── SHA-256 (FIPS 180-4) ────────────────────────────────────────────── */ + +typedef struct { + uint32_t h[8]; +} ScrWasmSha; + +static const uint32_t scr_wasm_sha_k[64] = { + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2}; + +static uint32_t scr_wasm_rotr(uint32_t x, unsigned n) { return (x >> n) | (x << (32 - n)); } + +static void scr_wasm_sha_block(ScrWasmSha *s, const uint8_t *p) { + uint32_t w[64]; + for (int i = 0; i < 16; i++) + w[i] = (uint32_t)p[4 * i] << 24 | (uint32_t)p[4 * i + 1] << 16 | (uint32_t)p[4 * i + 2] << 8 | p[4 * i + 3]; + for (int i = 16; i < 64; i++) { + uint32_t s0 = scr_wasm_rotr(w[i - 15], 7) ^ scr_wasm_rotr(w[i - 15], 18) ^ (w[i - 15] >> 3); + uint32_t s1 = scr_wasm_rotr(w[i - 2], 17) ^ scr_wasm_rotr(w[i - 2], 19) ^ (w[i - 2] >> 10); + w[i] = w[i - 16] + s0 + w[i - 7] + s1; + } + uint32_t a = s->h[0], b = s->h[1], c = s->h[2], d = s->h[3], e = s->h[4], f = s->h[5], g = s->h[6], h = s->h[7]; + for (int i = 0; i < 64; i++) { + uint32_t t1 = h + (scr_wasm_rotr(e, 6) ^ scr_wasm_rotr(e, 11) ^ scr_wasm_rotr(e, 25)) + ((e & f) ^ (~e & g)) + + scr_wasm_sha_k[i] + w[i]; + uint32_t t2 = (scr_wasm_rotr(a, 2) ^ scr_wasm_rotr(a, 13) ^ scr_wasm_rotr(a, 22)) + ((a & b) ^ (a & c) ^ (b & c)); + h = g; + g = f; + f = e; + e = d + t1; + d = c; + c = b; + b = a; + a = t1 + t2; + } + s->h[0] += a; + s->h[1] += b; + s->h[2] += c; + s->h[3] += d; + s->h[4] += e; + s->h[5] += f; + s->h[6] += g; + s->h[7] += h; +} + +static void scr_wasm_sha256(const uint8_t *data, size_t len, uint8_t out[32]) { + ScrWasmSha s = {{0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19}}; + size_t i = 0; + for (; i + 64 <= len; i += 64) scr_wasm_sha_block(&s, data + i); + uint8_t tail[128] = {0}; + size_t rest = len - i; + if (rest) memcpy(tail, data + i, rest); + tail[rest] = 0x80; + size_t total = rest + 1 + 8 <= 64 ? 64 : 128; + uint64_t bits = (uint64_t)len * 8; + for (int k = 0; k < 8; k++) tail[total - 1 - k] = (uint8_t)(bits >> (8 * k)); + scr_wasm_sha_block(&s, tail); + if (total == 128) scr_wasm_sha_block(&s, tail + 64); + for (int k = 0; k < 8; k++) { + out[4 * k] = (uint8_t)(s.h[k] >> 24); + out[4 * k + 1] = (uint8_t)(s.h[k] >> 16); + out[4 * k + 2] = (uint8_t)(s.h[k] >> 8); + out[4 * k + 3] = (uint8_t)s.h[k]; + } +} + +/* ── errors ──────────────────────────────────────────────────────────── */ + +enum { SCR_WASM_COMPILE_ERROR, SCR_WASM_LINK_ERROR, SCR_WASM_RUNTIME_ERROR }; +static const char *const scr_wasm_error_names[] = {"CompileError", "LinkError", "RuntimeError"}; + +/* A WebAssembly error: an Error whose inherited (not own) name is the + * class's. Borrows message. +1. */ +static ScrError *scr_wasm_error_make(int which, ScrStr *message) { + ScrError *e = scr_error_new(SCR_ERR_ERROR, message); + scr_str_release(e->name); + const char *name = scr_wasm_error_names[which]; + e->name = scr_str_new(name, strlen(name)); + e->name_present = false; + return e; +} + +static void scr_wasm_throw_kind(int which, const char *msg) { + ScrStr *s = scr_str_new(msg, strlen(msg)); + ScrError *e = scr_wasm_error_make(which, s); + scr_str_release(s); + scr_throw_obj(e, &scr_error_retain_v, &scr_error_release_v, scr_error_trace_arg()); +} + +static int scr_wasm_error_brand(const ScrError *e) { + if (!e || e->vt != &scr_error_vts[SCR_ERR_ERROR] || e->name_present || !e->name) return -1; + for (int i = 0; i < 3; i++) { + size_t n = strlen(scr_wasm_error_names[i]); + if (e->name->len == n && !memcmp(e->name->data, scr_wasm_error_names[i], n)) return i; + } + return -1; +} + +static void scr_wasm_type_error(const char *msg) { scr_throw_error_msg(SCR_ERR_TYPE, msg, strlen(msg)); } +static void scr_wasm_range_error(const char *msg) { scr_throw_error_msg(SCR_ERR_RANGE, msg, strlen(msg)); } + +static const char *scr_wasm_trap_message(int code) { + switch (code) { + case SCR_WASM_TRAP_MEMORY_OOB: return "memory access out of bounds"; + case SCR_WASM_TRAP_DIV_BY_ZERO: return "divide by zero"; + case SCR_WASM_TRAP_DIV_UNREPRESENTABLE: return "divide result unrepresentable"; + case SCR_WASM_TRAP_REM_BY_ZERO: return "remainder by zero"; + case SCR_WASM_TRAP_FLOAT_UNREPRESENTABLE: return "float unrepresentable in integer range"; + case SCR_WASM_TRAP_TABLE_OOB: return "table index is out of bounds"; + case SCR_WASM_TRAP_SIGNATURE: return "null function or function signature mismatch"; + case SCR_WASM_TRAP_NULL: return "dereferencing a null pointer"; + case SCR_WASM_TRAP_ELEM_SEGMENT: return "element segment out of bounds"; + default: return "unreachable"; + } +} + +/* Throws the JavaScript error for a trap code (nothing for HOST: its + * exception is already pending). */ +static void scr_wasm_throw_trap(int code) { + if (code == SCR_WASM_TRAP_HOST) return; + if (code == SCR_WASM_TRAP_EXHAUSTION) { + scr_wasm_range_error("Maximum call stack size exceeded"); + return; + } + scr_wasm_throw_kind(SCR_WASM_RUNTIME_ERROR, scr_wasm_trap_message(code)); +} + +/* ── the trap guard ──────────────────────────────────────────────────── */ + +/* AddressSanitizer intercepts siglongjmp and warns when it cannot tell the + * extent of the running stack, as on the runtime's async fibers. Sanitized + * builds therefore jump with the compiler's builtin pair, which no runtime + * intercepts, and unpoison the abandoned frames themselves. */ +#ifdef SCR_WASM_ASAN +void __asan_unpoison_memory_region(void const volatile *addr, size_t size); +typedef void *ScrWasmJmp[5]; +#define SCR_WASM_SETJMP(buf) __builtin_setjmp(buf) +#define SCR_WASM_LONGJMP(buf) __builtin_longjmp(buf, 1) +#elif defined(_WIN32) +typedef jmp_buf ScrWasmJmp; +#define SCR_WASM_SETJMP(buf) setjmp(buf) +#define SCR_WASM_LONGJMP(buf) longjmp(buf, 1) +#else +typedef sigjmp_buf ScrWasmJmp; +#define SCR_WASM_SETJMP(buf) sigsetjmp(buf, 0) +#define SCR_WASM_LONGJMP(buf) siglongjmp(buf, 1) +#endif + +typedef struct ScrWasmEntry { + ScrWasmJmp jmp; + struct ScrWasmEntry *prev; + uintptr_t frame; /* the entry's frame address: exhaustion resumes below it */ +} ScrWasmEntry; + +/* Thread-local in every build: each thread runs its own instances, and + * the fault handler reads them. */ +static _Thread_local ScrWasmEntry *scr_wasm_top; +static _Thread_local bool scr_wasm_running; /* translated code (not an import) is on top */ +static _Thread_local int scr_wasm_trap_code; + +/* Externref values an import returned: translated code holds them without + * a reference, so they stay alive until the outermost export call + * returns. */ +static _Thread_local ScrDyn **scr_wasm_kept; +static _Thread_local size_t scr_wasm_kept_count, scr_wasm_kept_cap; + +static void scr_wasm_keep(ScrDyn *d) { + if (scr_wasm_kept_count == scr_wasm_kept_cap) { + size_t cap = scr_wasm_kept_cap ? scr_wasm_kept_cap * 2 : 16; + ScrDyn **grown = realloc(scr_wasm_kept, cap * sizeof *grown); + if (!grown) scr_trap("scriptc: out of memory\n"); + scr_wasm_kept = grown; + scr_wasm_kept_cap = cap; + } + scr_wasm_kept[scr_wasm_kept_count++] = scr_dyn_retain(d); +} + +static void scr_wasm_release_kept(void) { + while (scr_wasm_kept_count) scr_dyn_release(scr_wasm_kept[--scr_wasm_kept_count]); +} + +#ifdef SCR_WASM_ASAN +/* Uninstrumented, so the sanitizer inserts no __asan_handle_no_return + * before the jump; the abandoned frames are unpoisoned below. */ +__attribute__((no_sanitize("address"))) +#else +_Noreturn +#endif +void scr_wasm_trap(int32_t code) { + ScrWasmEntry *top = scr_wasm_top; + if (!top) scr_trap("scriptc: WebAssembly trap outside an export call\n"); + scr_wasm_running = false; + scr_wasm_trap_code = code; +#ifdef SCR_WASM_ASAN + /* The real frame address: a local's address may be on the sanitizer's + * fake stack. */ + uintptr_t low = ((uintptr_t)__builtin_frame_address(0) & ~(uintptr_t)15) - 256; + if (top->frame > low && top->frame - low < ((uintptr_t)256 << 20)) + __asan_unpoison_memory_region((void *)low, top->frame - low); +#endif + SCR_WASM_LONGJMP(top->jmp); +} + +#if SCR_WASM_SIGNALS +static void scr_wasm_thread_init(void); +static void scr_wasm_guard_fiber(uintptr_t frame); +#endif + +/* Code with explicit bounds checks: the lowest stack address translated + * code may use before it traps with stack exhaustion (translate.ts checks + * it in the entry of every function through which the stack can grow + * without bound): the runtime's own stack guard for the running stack + * (thread or async fiber), read at each outermost export call. */ +_Thread_local uint64_t scr_wasm_stack_limit; + +static void scr_wasm_set_stack_limit(void) { +#if !defined(_WIN32) && !defined(__wasi__) + if (!scr_stack_guard) scr_stack_guard_init(); + scr_wasm_stack_limit = scr_stack_guard; +#endif +} + +/* Calls a thunk with traps caught; answers a SCR_WASM_TRAP_* code. */ +static __attribute__((noinline)) int scr_wasm_enter(ScrWasmThunk fn, void *inst, const uint64_t *args, + uint64_t *results) { +#if SCR_WASM_SIGNALS + scr_wasm_thread_init(); +#endif + ScrWasmEntry entry; + entry.prev = scr_wasm_top; + entry.frame = (uintptr_t)__builtin_frame_address(0); + if (!entry.prev) { + scr_wasm_set_stack_limit(); +#if SCR_WASM_SIGNALS + scr_wasm_guard_fiber(entry.frame); +#endif + } + bool was_running = scr_wasm_running; + scr_wasm_top = &entry; + int code = 0; + if (SCR_WASM_SETJMP(entry.jmp) == 0) { + scr_wasm_running = true; + fn(inst, args, results); + } else { + code = scr_wasm_trap_code; + } + scr_wasm_running = was_running; + scr_wasm_top = entry.prev; + if (!entry.prev && scr_wasm_kept_count) scr_wasm_release_kept(); + return code; +} + +static void scr_wasm_init_thunk(void *inst, const uint64_t *args, uint64_t *results) { + (void)args; + (void)results; + scr_wasm_host_of(inst)->desc->init(inst); +} + +static void scr_wasm_start_thunk(void *inst, const uint64_t *args, uint64_t *results) { + (void)args; + (void)results; + scr_wasm_host_of(inst)->desc->start(inst); +} + +/* ── memories and the fault handler ──────────────────────────────────── */ + +#if SCR_WASM_SIGNALS +/* Reservations of this thread's guarded memories, read by the fault + * handler (faults are synchronous, so nothing changes under it). */ +typedef struct { + uintptr_t start, end; +} ScrWasmRange; +static _Thread_local ScrWasmRange *scr_wasm_ranges; +static _Thread_local size_t scr_wasm_range_count, scr_wasm_range_cap; +static _Thread_local bool scr_wasm_altstack_ready; + +static struct sigaction scr_wasm_prev_segv, scr_wasm_prev_bus; +static pthread_once_t scr_wasm_handler_once = PTHREAD_ONCE_INIT; +static bool scr_wasm_handler_installed; + +static void scr_wasm_range_add(uint8_t *base, uint64_t len) { + if (scr_wasm_range_count == scr_wasm_range_cap) { + size_t cap = scr_wasm_range_cap ? scr_wasm_range_cap * 2 : 8; + ScrWasmRange *grown = realloc(scr_wasm_ranges, cap * sizeof *grown); + if (!grown) scr_trap("scriptc: out of memory\n"); + scr_wasm_ranges = grown; + scr_wasm_range_cap = cap; + } + scr_wasm_ranges[scr_wasm_range_count++] = (ScrWasmRange){(uintptr_t)base, (uintptr_t)base + len}; +} + +static void scr_wasm_range_remove(uint8_t *base) { + for (size_t i = 0; i < scr_wasm_range_count; i++) { + if (scr_wasm_ranges[i].start == (uintptr_t)base) { + scr_wasm_ranges[i] = scr_wasm_ranges[--scr_wasm_range_count]; + return; + } + } +} + +static _Noreturn __attribute__((noinline, used)) void scr_wasm_fault_trampoline(int32_t code) { + scr_wasm_trap(code); + __builtin_unreachable(); +} + +/* Moves the interrupted context to scr_wasm_fault_trampoline(code). With + * `stack` nonzero the trampoline runs at that stack address (exhaustion); + * otherwise below the interrupted stack pointer. */ +static bool scr_wasm_redirect(void *context, int32_t code, uintptr_t stack) { + ucontext_t *uc = context; + uintptr_t target = (uintptr_t)&scr_wasm_fault_trampoline; +#if defined(__APPLE__) && defined(__x86_64__) + uintptr_t sp = stack ? stack : (uintptr_t)uc->uc_mcontext->__ss.__rsp - 256; + sp = (sp & ~(uintptr_t)15) - 8; /* as after a call */ + uc->uc_mcontext->__ss.__rsp = sp; + uc->uc_mcontext->__ss.__rdi = (uint64_t)(uint32_t)code; + uc->uc_mcontext->__ss.__rip = target; + return true; +#elif defined(__APPLE__) && defined(__aarch64__) + uintptr_t sp = stack ? stack : (uintptr_t)__darwin_arm_thread_state64_get_sp(uc->uc_mcontext->__ss) - 256; + sp &= ~(uintptr_t)15; + __darwin_arm_thread_state64_set_sp(uc->uc_mcontext->__ss, sp); + uc->uc_mcontext->__ss.__x[0] = (uint64_t)(uint32_t)code; + __darwin_arm_thread_state64_set_lr_fptr(uc->uc_mcontext->__ss, (void *)target); + __darwin_arm_thread_state64_set_pc_fptr(uc->uc_mcontext->__ss, (void *)target); + return true; +#elif defined(__linux__) && defined(__x86_64__) + uintptr_t sp = stack ? stack : (uintptr_t)uc->uc_mcontext.gregs[REG_RSP] - 256; + sp = (sp & ~(uintptr_t)15) - 8; + uc->uc_mcontext.gregs[REG_RSP] = (greg_t)sp; + uc->uc_mcontext.gregs[REG_RDI] = (greg_t)(uint32_t)code; + uc->uc_mcontext.gregs[REG_RIP] = (greg_t)target; + return true; +#elif defined(__linux__) && defined(__aarch64__) + uintptr_t sp = stack ? stack : (uintptr_t)uc->uc_mcontext.sp - 256; + sp &= ~(uintptr_t)15; + uc->uc_mcontext.sp = sp; + uc->uc_mcontext.regs[0] = (uint64_t)(uint32_t)code; + uc->uc_mcontext.regs[30] = target; + uc->uc_mcontext.pc = target; + return true; +#else + (void)uc; + (void)target; + (void)code; + (void)stack; + return false; +#endif +} + +static uintptr_t scr_wasm_context_sp(void *context) { + ucontext_t *uc = context; +#if defined(__APPLE__) && defined(__x86_64__) + return (uintptr_t)uc->uc_mcontext->__ss.__rsp; +#elif defined(__APPLE__) && defined(__aarch64__) + return (uintptr_t)__darwin_arm_thread_state64_get_sp(uc->uc_mcontext->__ss); +#elif defined(__linux__) && defined(__x86_64__) + return (uintptr_t)uc->uc_mcontext.gregs[REG_RSP]; +#elif defined(__linux__) && defined(__aarch64__) + return (uintptr_t)uc->uc_mcontext.sp; +#else + (void)uc; + return 0; +#endif +} + +static void scr_wasm_chain(int sig, siginfo_t *info, void *context) { + struct sigaction *prev = sig == SIGBUS ? &scr_wasm_prev_bus : &scr_wasm_prev_segv; + if (prev->sa_flags & SA_SIGINFO) { + if (prev->sa_sigaction) { + prev->sa_sigaction(sig, info, context); + return; + } + } else if (prev->sa_handler != SIG_DFL && prev->sa_handler != SIG_IGN) { + prev->sa_handler(sig); + return; + } + /* The default action: restore it and let the faulting instruction run + * again, so the process dies of the original signal. */ + sigaction(sig, prev, NULL); +} + +static void scr_wasm_fault(int sig, siginfo_t *info, void *context) { + if (scr_wasm_running && scr_wasm_top) { + uintptr_t addr = (uintptr_t)info->si_addr; + for (size_t i = 0; i < scr_wasm_range_count; i++) { + if (addr >= scr_wasm_ranges[i].start && addr < scr_wasm_ranges[i].end) { + if (scr_wasm_redirect(context, SCR_WASM_TRAP_MEMORY_OOB, 0)) return; + } + } + uintptr_t sp = scr_wasm_context_sp(context); + if (sp && addr + 65536 >= sp && addr <= sp + 65536) { + /* Stack exhaustion: resume below the export entry's frame, over the + * abandoned translated frames. */ + uintptr_t below = scr_wasm_top->frame - 4096; + if (scr_wasm_redirect(context, SCR_WASM_TRAP_EXHAUSTION, below)) return; + } + } + scr_wasm_chain(sig, info, context); +} + +static void scr_wasm_install_handler(void) { + struct sigaction sa; + memset(&sa, 0, sizeof sa); + sa.sa_sigaction = scr_wasm_fault; + sa.sa_flags = SA_SIGINFO | SA_ONSTACK; + sigemptyset(&sa.sa_mask); + if (sigaction(SIGSEGV, &sa, &scr_wasm_prev_segv) == 0 && sigaction(SIGBUS, &sa, &scr_wasm_prev_bus) == 0) + scr_wasm_handler_installed = true; +} + +/* The thread's own stack, which the system ends with a guard page. */ +static _Thread_local uintptr_t scr_wasm_thread_low, scr_wasm_thread_high; +/* The stack guard of the last stack an outermost export call ran on. */ +static _Thread_local uintptr_t scr_wasm_guarded_stack; + +/* Async fibers run on SCR_FIBER_STACK-byte mappings without a guard page + * (compiled code checks scr_stack_guard instead), so recursion in + * translated code there would run past the mapping. The first export call + * on a fiber therefore protects the lowest page of its mapping, which the + * fault handler then reports as stack exhaustion; scr_stack_guard, which + * sits scr_stack_guard_for's reserve above that page, locates it. The + * page stays protected while the runtime reuses the stack. A stack that + * does not match the fiber layout is left alone. */ +static void scr_wasm_guard_fiber(uintptr_t frame) { + if (!scr_wasm_handler_installed) return; + uintptr_t guard = scr_stack_guard; + if (!guard || guard == scr_wasm_guarded_stack) return; + scr_wasm_guarded_stack = guard; + if (!scr_wasm_thread_high) { +#if defined(__APPLE__) + pthread_t self = pthread_self(); + scr_wasm_thread_high = (uintptr_t)pthread_get_stackaddr_np(self); + scr_wasm_thread_low = scr_wasm_thread_high - pthread_get_stacksize_np(self); +#else + pthread_attr_t attr; + if (pthread_getattr_np(pthread_self(), &attr) == 0) { + void *addr; + size_t size; + if (pthread_attr_getstack(&attr, &addr, &size) == 0) { + scr_wasm_thread_low = (uintptr_t)addr; + scr_wasm_thread_high = (uintptr_t)addr + size; + } + pthread_attr_destroy(&attr); + } +#endif + } + if (frame >= scr_wasm_thread_low && frame < scr_wasm_thread_high) return; + uintptr_t low = guard - (scr_stack_guard_for(0, SCR_FIBER_STACK)); + long page = sysconf(_SC_PAGESIZE); + if (page <= 0 || (low & ((uintptr_t)page - 1)) != 0) return; + if (frame <= low + (uintptr_t)page || frame >= low + SCR_FIBER_STACK) return; + (void)mprotect((void *)low, (size_t)page, PROT_NONE); +} + +/* Per thread: an alternate signal stack, so exhaustion faults can be + * handled. A thread that already has one keeps it. */ +static void scr_wasm_thread_init(void) { + if (scr_wasm_altstack_ready || !scr_wasm_handler_installed) return; + scr_wasm_altstack_ready = true; + stack_t current; + if (sigaltstack(NULL, ¤t) == 0 && !(current.ss_flags & SS_DISABLE) && current.ss_size >= 32768) return; + size_t size = 65536; + void *mem = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); + if (mem == MAP_FAILED) return; + stack_t ss = {.ss_sp = mem, .ss_size = size, .ss_flags = 0}; + sigaltstack(&ss, NULL); +} +#endif + +static ScrWasmMemory *scr_wasm_memory_retain(ScrWasmMemory *m) { + m->rc++; + return m; +} + +static void scr_wasm_memory_release(ScrWasmMemory *m) { + if (!m || --m->rc) return; + scr_dyn_release(m->buffer); +#if SCR_WASM_SIGNALS + if (m->guarded) { + scr_wasm_range_remove(m->base); + munmap(m->base, m->reserved); + } else +#endif + free(m->base); + free(m); +} + +/* A memory of `min` pages growing to at most `max` pages; NULL with + * Node's RangeError pending when the address space or memory is short. */ +static ScrWasmMemory *scr_wasm_memory_new(uint64_t min, uint64_t max, bool guarded, const char *what) { + if (max > SCR_WASM_MAX_PAGES) max = SCR_WASM_MAX_PAGES; + ScrWasmMemory *m = calloc(1, sizeof *m); + if (!m) scr_trap("scriptc: out of memory\n"); + m->rc = 1; + m->max_pages = max; + m->size = min * SCR_WASM_PAGE; +#if SCR_WASM_SIGNALS + if (guarded) { +#if !defined(MAP_NORESERVE) +#define MAP_NORESERVE 0 +#endif + void *base = mmap(NULL, SCR_WASM_GUARD_RESERVATION, PROT_NONE, MAP_PRIVATE | MAP_ANON | MAP_NORESERVE, -1, 0); + if (base == MAP_FAILED || + (m->size && mprotect(base, m->size, PROT_READ | PROT_WRITE) != 0)) { + if (base != MAP_FAILED) munmap(base, SCR_WASM_GUARD_RESERVATION); + free(m); + char msg[200]; + snprintf(msg, sizeof msg, "%s: Out of memory: Cannot allocate Wasm memory for new instance", what); + scr_wasm_range_error(msg); + return NULL; + } + m->base = base; + m->reserved = SCR_WASM_GUARD_RESERVATION; + m->guarded = true; + scr_wasm_range_add(m->base, m->reserved); + return m; + } +#else + (void)guarded; +#endif + m->base = m->size ? calloc(1, m->size) : NULL; + if (m->size && !m->base) { + free(m); + char msg[200]; + snprintf(msg, sizeof msg, "%s: Out of memory: Cannot allocate Wasm memory for new instance", what); + scr_wasm_range_error(msg); + return NULL; + } + m->reserved = m->size; + return m; +} + +/* Grows by `delta` pages; answers the previous page count or -1. */ +static int64_t scr_wasm_memory_grow_by(ScrWasmMemory *m, uint64_t delta) { + uint64_t old = m->size / SCR_WASM_PAGE; + if (delta > m->max_pages || old + delta > m->max_pages) return -1; + if (delta == 0) return (int64_t)old; + uint64_t size = (old + delta) * SCR_WASM_PAGE; +#if SCR_WASM_SIGNALS + if (m->guarded) { + if (mprotect(m->base + m->size, size - m->size, PROT_READ | PROT_WRITE) != 0) return -1; + m->size = size; + return (int64_t)old; + } +#endif + uint8_t *grown = realloc(m->base, size); + if (!grown) return -1; + memset(grown + m->size, 0, size - m->size); + m->base = grown; + m->reserved = size; + m->size = size; + return (int64_t)old; +} + +static ScrWasmMemory *scr_wasm_inst_memory(void *inst) { return ((ScrWasmMemory **)inst)[2]; } + +int32_t scr_wasm_memory_grow(void *inst, int32_t delta) { + ScrWasmMemory *m = scr_wasm_inst_memory(inst); + int64_t old = scr_wasm_memory_grow_by(m, (uint32_t)delta); + ((void **)inst)[1] = m->base; + return (int32_t)old; +} + +static void scr_wasm_check_memory(ScrWasmMemory *m, uint32_t at, uint32_t n) { + if ((uint64_t)at + n > m->size) scr_wasm_trap(SCR_WASM_TRAP_MEMORY_OOB); +} + +void scr_wasm_memory_fill(void *inst, int32_t d, int32_t value, int32_t n) { + ScrWasmMemory *m = scr_wasm_inst_memory(inst); + scr_wasm_check_memory(m, (uint32_t)d, (uint32_t)n); + if (n) memset(m->base + (uint32_t)d, value & 0xff, (uint32_t)n); +} + +void scr_wasm_memory_copy(void *inst, int32_t d, int32_t s, int32_t n) { + ScrWasmMemory *m = scr_wasm_inst_memory(inst); + scr_wasm_check_memory(m, (uint32_t)d, (uint32_t)n); + scr_wasm_check_memory(m, (uint32_t)s, (uint32_t)n); + if (n) memmove(m->base + (uint32_t)d, m->base + (uint32_t)s, (uint32_t)n); +} + +/* Instantiation reports an active data segment's failure with its + * details (V8's "data segment N is out of bounds (offset ...)"). */ +static _Thread_local bool scr_wasm_instantiating; +static _Thread_local struct { + uint32_t seg, offset, length; + uint64_t size; +} scr_wasm_segment_failure; + +void scr_wasm_memory_init(void *inst, int32_t seg, int32_t d, int32_t s, int32_t n) { + ScrWasmInst *host = scr_wasm_host_of(inst); + const ScrWasmDataDesc *data = &host->desc->datas[seg]; + uint64_t len = host->data_dropped[seg] ? 0 : data->len; + ScrWasmMemory *m = scr_wasm_inst_memory(inst); + if ((uint64_t)(uint32_t)s + (uint32_t)n > len || (uint64_t)(uint32_t)d + (uint32_t)n > m->size) { + if (scr_wasm_instantiating) { + scr_wasm_segment_failure.seg = (uint32_t)seg; + scr_wasm_segment_failure.offset = (uint32_t)d; + scr_wasm_segment_failure.length = (uint32_t)n; + scr_wasm_segment_failure.size = m->size; + scr_wasm_trap(SCR_WASM_TRAP_DATA_SEGMENT); + } + scr_wasm_trap(SCR_WASM_TRAP_MEMORY_OOB); + } + if (n) memcpy(m->base + (uint32_t)d, data->data + (uint32_t)s, (uint32_t)n); +} + +void scr_wasm_data_drop(void *inst, int32_t seg) { scr_wasm_host_of(inst)->data_dropped[seg] = true; } + +/* ── tables ──────────────────────────────────────────────────────────── */ + +static ScrWasmTable *scr_wasm_table_new(char elem, uint64_t min, uint64_t max) { + ScrWasmTable *t = calloc(1, sizeof *t); + if (!t) scr_trap("scriptc: out of memory\n"); + t->rc = 1; + t->elem = elem; + t->size = (uint32_t)min; + t->max = max > SCR_WASM_MAX_TABLE ? SCR_WASM_MAX_TABLE : (uint32_t)max; + t->elems = calloc(min ? min : 1, sizeof *t->elems); + if (!t->elems) scr_trap("scriptc: out of memory\n"); + return t; +} + +static ScrWasmTable *scr_wasm_table_retain(ScrWasmTable *t) { + t->rc++; + return t; +} + +static void scr_wasm_table_release(ScrWasmTable *t) { + if (!t || --t->rc) return; + if (t->elem == 'x') + for (uint32_t i = 0; i < t->size; i++) scr_dyn_release(t->elems[i]); + free(t->elems); + free(t); +} + +static void *scr_wasm_ref_retain(char elem, void *v) { + if (elem == 'x' && v) scr_dyn_retain(v); + return v; +} + +static void scr_wasm_table_store(ScrWasmTable *t, uint32_t i, void *v) { + void *old = t->elems[i]; + t->elems[i] = scr_wasm_ref_retain(t->elem, v); + if (t->elem == 'x') scr_dyn_release(old); +} + +static ScrWasmTable *scr_wasm_inst_table(void *inst, int32_t t) { return ((ScrWasmTable **)inst)[4 + t]; } + +void *scr_wasm_table_get(void *inst, int32_t ti, int32_t i) { + ScrWasmTable *t = scr_wasm_inst_table(inst, ti); + if ((uint32_t)i >= t->size) scr_wasm_trap(SCR_WASM_TRAP_TABLE_OOB); + return t->elems[(uint32_t)i]; +} + +void scr_wasm_table_set(void *inst, int32_t ti, int32_t i, void *value) { + ScrWasmTable *t = scr_wasm_inst_table(inst, ti); + if ((uint32_t)i >= t->size) scr_wasm_trap(SCR_WASM_TRAP_TABLE_OOB); + scr_wasm_table_store(t, (uint32_t)i, value); +} + +static int64_t scr_wasm_table_grow_by(ScrWasmTable *t, uint32_t n, void *init) { + uint32_t old = t->size; + if ((uint64_t)old + n > t->max) return -1; + if (n == 0) return old; + void **grown = realloc(t->elems, ((size_t)old + n) * sizeof *grown); + if (!grown) return -1; + t->elems = grown; + for (uint32_t i = 0; i < n; i++) grown[old + i] = scr_wasm_ref_retain(t->elem, init); + t->size = old + n; + return old; +} + +int32_t scr_wasm_table_grow(void *inst, int32_t ti, void *init, int32_t n) { + return (int32_t)scr_wasm_table_grow_by(scr_wasm_inst_table(inst, ti), (uint32_t)n, init); +} + +void scr_wasm_table_fill(void *inst, int32_t ti, int32_t i, void *value, int32_t n) { + ScrWasmTable *t = scr_wasm_inst_table(inst, ti); + if ((uint64_t)(uint32_t)i + (uint32_t)n > t->size) scr_wasm_trap(SCR_WASM_TRAP_TABLE_OOB); + for (uint32_t k = 0; k < (uint32_t)n; k++) scr_wasm_table_store(t, (uint32_t)i + k, value); +} + +void scr_wasm_table_copy(void *inst, int32_t dti, int32_t sti, int32_t d, int32_t s, int32_t n) { + ScrWasmTable *dt = scr_wasm_inst_table(inst, dti), *st = scr_wasm_inst_table(inst, sti); + uint32_t ud = (uint32_t)d, us = (uint32_t)s, un = (uint32_t)n; + if ((uint64_t)ud + un > dt->size || (uint64_t)us + un > st->size) scr_wasm_trap(SCR_WASM_TRAP_TABLE_OOB); + if (ud <= us) { + for (uint32_t k = 0; k < un; k++) scr_wasm_table_store(dt, ud + k, st->elems[us + k]); + } else { + for (uint32_t k = un; k-- > 0;) scr_wasm_table_store(dt, ud + k, st->elems[us + k]); + } +} + +static void *scr_wasm_elem_item(ScrWasmInst *host, int64_t item) { + if (item >= 0) return &host->funcrefs[item]; + if (item == -1) return NULL; + return (void *)(uintptr_t)host->globals[-(item + 2)]->value; +} + +void scr_wasm_table_init(void *inst, int32_t ti, int32_t seg, int32_t d, int32_t s, int32_t n) { + ScrWasmInst *host = scr_wasm_host_of(inst); + ScrWasmTable *t = scr_wasm_inst_table(inst, ti); + const ScrWasmElemDesc *e = &host->desc->elems[seg]; + uint64_t len = host->elem_dropped[seg] ? 0 : e->count; + uint32_t ud = (uint32_t)d, us = (uint32_t)s, un = (uint32_t)n; + if ((uint64_t)ud + un > t->size) scr_wasm_trap(SCR_WASM_TRAP_TABLE_OOB); + if ((uint64_t)us + un > len) scr_wasm_trap(SCR_WASM_TRAP_ELEM_SEGMENT); + for (uint32_t k = 0; k < un; k++) scr_wasm_table_store(t, ud + k, scr_wasm_elem_item(host, e->items[us + k])); +} + +void scr_wasm_elem_drop(void *inst, int32_t seg) { scr_wasm_host_of(inst)->elem_dropped[seg] = true; } + +/* ── globals ─────────────────────────────────────────────────────────── */ + +static ScrWasmGlobal *scr_wasm_global_new(char type, bool mutable_) { + ScrWasmGlobal *g = calloc(1, sizeof *g); + if (!g) scr_trap("scriptc: out of memory\n"); + g->rc = 1; + g->type = type; + g->mutable_ = mutable_; + return g; +} + +static ScrWasmGlobal *scr_wasm_global_retain(ScrWasmGlobal *g) { + g->rc++; + return g; +} + +static void scr_wasm_global_release(ScrWasmGlobal *g) { + if (!g || --g->rc) return; + if (g->type == 'x') scr_dyn_release((ScrDyn *)(uintptr_t)g->value); + free(g); +} + +void scr_wasm_global_set_ref(void *cell, void *value) { + ScrWasmGlobal *g = cell; + ScrDyn *old = (ScrDyn *)(uintptr_t)g->value; + if (value) scr_dyn_retain(value); + g->value = (uint64_t)(uintptr_t)value; + scr_dyn_release(old); +} + +/* ── handles ─────────────────────────────────────────────────────────── */ + +enum { SCR_WASM_K_MODULE, SCR_WASM_K_INSTANCE, SCR_WASM_K_MEMORY, SCR_WASM_K_TABLE, SCR_WASM_K_GLOBAL }; + +typedef struct ScrWasmObj { + size_t rc; + int kind; + const ScrWasmModuleDesc *desc; /* Module */ + ScrWasmInst *inst; /* Instance; the owner of exported objects */ + ScrWasmMemory *memory; + ScrWasmTable *table; + ScrWasmGlobal *global; +} ScrWasmObj; + +/* Instances are never freed yet (stage W6 traces them for the cycle + * collector), so the references their handles and exported functions hold + * are not counted. */ +static void *scr_wasm_inst_retain(void *p) { return p; } + +static void scr_wasm_inst_release(void *p) { (void)p; } + +static void *scr_wasm_obj_retain(void *p) { + ((ScrWasmObj *)p)->rc++; + return p; +} + +static void scr_wasm_obj_release(void *p) { + ScrWasmObj *o = p; + if (--o->rc) return; + if (o->inst) scr_wasm_inst_release(o->inst); + scr_wasm_memory_release(o->memory); + scr_wasm_table_release(o->table); + scr_wasm_global_release(o->global); + free(o); +} + +static ScrWasmObj *scr_wasm_obj_new(int kind) { + ScrWasmObj *o = calloc(1, sizeof *o); + if (!o) scr_trap("scriptc: out of memory\n"); + o->rc = 1; + o->kind = kind; + return o; +} + +static ScrDyn *scr_wasm_empty_view(void *h) { + (void)h; + return scr_dyn_new_obj(); +} + +static const ScrDynHandleTag scr_wasm_tags[] = {SCR_DYNH_WASM_MODULE, SCR_DYNH_WASM_INSTANCE, SCR_DYNH_WASM_MEMORY, + SCR_DYNH_WASM_TABLE, SCR_DYNH_WASM_GLOBAL}; + +static void scr_wasm_install(void); + +static ScrDyn *scr_wasm_box(ScrWasmObj *o) { + scr_wasm_install(); + ScrDyn *d = scr_dyn_new_handle(o, scr_wasm_tags[o->kind]); + scr_wasm_obj_release(o); + return d; +} + +static ScrWasmObj *scr_wasm_unbox(const ScrDyn *v, int kind) { + if (!v || v->kind != SCR_DYN_HANDLE || v->v.handle.tag != scr_wasm_tags[kind]) return NULL; + return v->v.handle.ptr; +} + +/* ── values ──────────────────────────────────────────────────────────── */ + +static ScrDyn *scr_wasm_function_value(ScrWasmInst *host, uint32_t func); + +/* ToJSValue (+1; NULL with an exception pending). */ +static ScrDyn *scr_wasm_to_js(char t, uint64_t v) { + switch (t) { + case 'i': return scr_dyn_new_num((double)(int32_t)(uint32_t)v); + case 'I': { + ScrBigInt *b = scr_bigint_from_i64((int64_t)v); + ScrDyn *d = scr_dyn_new_bigint(b); + scr_bigint_release(b); + return d; + } + case 'f': { + uint32_t bits = (uint32_t)v; + float f; + memcpy(&f, &bits, 4); + return scr_dyn_new_num((double)f); + } + case 'F': { + double d; + memcpy(&d, &v, 8); + return scr_dyn_new_num(d); + } + case 'r': { + ScrWasmFuncRef *ref = (ScrWasmFuncRef *)(uintptr_t)v; + if (!ref) return scr_dyn_new_null(); + ScrWasmInst *host = scr_wasm_host_of(ref->inst); + return scr_wasm_function_value(host, (uint32_t)(ref - host->funcrefs)); + } + default: { + ScrDyn *d = (ScrDyn *)(uintptr_t)v; + return d ? scr_dyn_retain(d) : scr_dyn_new_null(); + } + } +} + +static uint32_t scr_wasm_to_int32(double n) { + if (!isfinite(n)) return 0; + double t = trunc(n); + double m = fmod(t, 4294967296.0); + if (m < 0) m += 4294967296.0; + return (uint32_t)m; +} + +static ScrWasmFuncRef *scr_wasm_funcref_of(const ScrDyn *v); + +/* ToWebAssemblyValue: false with an exception pending. References are + * borrowed from `v`. */ +static bool scr_wasm_from_js(char t, const ScrDyn *v, uint64_t *out) { + if (!v) v = scr_dyn_undefined(); + switch (t) { + case 'i': { + double n; + if (!scr_dyn_number_coerce_js(v, &n)) return false; + *out = scr_wasm_to_int32(n); + return true; + } + case 'f': { + double n; + if (!scr_dyn_number_coerce_js(v, &n)) return false; + float f = (float)n; + uint32_t b; + memcpy(&b, &f, 4); + *out = b; + return true; + } + case 'F': { + double n; + if (!scr_dyn_number_coerce_js(v, &n)) return false; + memcpy(out, &n, 8); + return true; + } + case 'I': { + ScrBigInt *b = scr_dyn_bigint_coerce(v); + if (!b) return false; + *out = (uint64_t)scr_bigint_to_i64(b); + scr_bigint_release(b); + return true; + } + case 'r': { + if (v->kind == SCR_DYN_NULL) { + *out = 0; + return true; + } + ScrWasmFuncRef *ref = scr_wasm_funcref_of(v); + if (!ref) { + scr_wasm_type_error("type incompatibility when transforming from/to JS"); + return false; + } + *out = (uint64_t)(uintptr_t)ref; + return true; + } + default: + if (v->kind == SCR_DYN_NULL) { + *out = 0; + return true; + } + *out = (uint64_t)(uintptr_t)v; + return true; + } +} + +static size_t scr_wasm_params(const char *sig) { + size_t n = 0; + while (sig[n] && sig[n] != ':') n++; + return n; +} + +/* ── exported functions ──────────────────────────────────────────────── */ + +typedef struct { + ScrWasmInst *host; + uint32_t func; + ScrWasmThunk thunk; +} ScrWasmExportCtx; + +static ScrDyn *scr_wasm_export_call(ScrClosure *clo, ScrDyn *const *args, size_t argc) { + ScrWasmInst *host = scr_box_get_ref(clo->caps[0]); + uint32_t func = (uint32_t)scr_box_get_f64(clo->caps[1]); + const char *sig = host->desc->func_sigs[func]; + ScrWasmThunk thunk = NULL; + for (uint64_t i = 0; i < host->desc->export_count; i++) { + const ScrWasmExportDesc *ex = &host->desc->exports[i]; + if (ex->kind == SCR_WASM_FUNC && ex->index == func) { + thunk = ex->call; + break; + } + } + size_t np = scr_wasm_params(sig); + const char *res = sig[np] == ':' ? sig + np + 1 : ""; + size_t nr = strlen(res); + uint64_t stack_args[16], stack_res[8] = {0}; + uint64_t *a = np <= 16 ? stack_args : malloc(np * sizeof *a); + uint64_t *r = nr <= 8 ? stack_res : calloc(nr, sizeof *r); + if (!a || !r) scr_trap("scriptc: out of memory\n"); + ScrDyn *result = NULL; + bool ok = true; + for (size_t i = 0; i < np && ok; i++) ok = scr_wasm_from_js(sig[i], i < argc ? args[i] : NULL, &a[i]); + if (ok) { + int trap = scr_wasm_enter(thunk, host->inst, a, r); + if (trap) { + scr_wasm_throw_trap(trap); + } else if (nr == 0) { + result = scr_dyn_retain(scr_dyn_undefined()); + } else if (nr == 1) { + result = scr_wasm_to_js(res[0], r[0]); + } else { + result = scr_dyn_new_arr(); + for (size_t i = 0; i < nr; i++) scr_dyn_arr_push(result, scr_wasm_to_js(res[i], r[i])); + } + } + if (a != stack_args) free(a); + if (r != stack_res) free(r); + scr_wasm_inst_release(host); + return result; +} + +/* The function value of exported function `func` (+1), created once per + * instance and function; NULL with a TypeError pending for a function the + * module does not export (funcref values of other functions are stage W5). */ +static ScrDyn *scr_wasm_function_value(ScrWasmInst *host, uint32_t func) { + if (host->export_fns[func]) return scr_dyn_retain(host->export_fns[func]); + bool exported = false; + for (uint64_t i = 0; i < host->desc->export_count; i++) { + const ScrWasmExportDesc *ex = &host->desc->exports[i]; + if (ex->kind == SCR_WASM_FUNC && ex->index == func) exported = true; + } + if (!exported) { + scr_wasm_type_error("WebAssembly function references to functions a module does not export are not supported yet"); + return NULL; + } + char name[24]; + snprintf(name, sizeof name, "%u", func); + size_t name_len = strlen(name) + 1; + host->export_names[func] = malloc(name_len); + if (!host->export_names[func]) scr_trap("scriptc: out of memory\n"); + memcpy(host->export_names[func], name, name_len); + ScrClosure *closure = scr_closure_new(NULL, 2); + closure->caps[0] = scr_box_new_obj(scr_wasm_inst_retain, scr_wasm_inst_release, NULL); + scr_box_set_ref(closure->caps[0], scr_wasm_inst_retain(host)); + closure->caps[1] = scr_box_new(SCR_BOX_F64); + scr_box_set_f64(closure->caps[1], (double)func); + uint32_t arity = (uint32_t)scr_wasm_params(host->desc->func_sigs[func]); + host->export_fns[func] = scr_dyn_new_func(closure, scr_wasm_export_call, arity, "", host->export_names[func]); + return scr_dyn_retain(host->export_fns[func]); +} + +static ScrWasmFuncRef *scr_wasm_funcref_of(const ScrDyn *v) { + if (!v || v->kind != SCR_DYN_FUNC || v->v.fn.thunk != scr_wasm_export_call) return NULL; + ScrClosure *clo = v->v.fn.clo; + ScrWasmInst *host = scr_box_get_ref(clo->caps[0]); + uint32_t func = (uint32_t)scr_box_get_f64(clo->caps[1]); + scr_wasm_inst_release(host); + return &host->funcrefs[func]; +} + +/* ── imported functions ──────────────────────────────────────────────── */ + +void scr_wasm_import_call(void *hostp, int32_t index, const uint64_t *args, uint64_t *results) { + ScrWasmInst *host = hostp; + const char *sig = host->desc->func_sigs[index]; + size_t np = scr_wasm_params(sig); + ScrDyn *stack_argv[16]; + ScrDyn **argv = np <= 16 ? stack_argv : malloc(np * sizeof *argv); + if (!argv) scr_trap("scriptc: out of memory\n"); + bool ok = true; + size_t made = 0; + for (; made < np; made++) { + argv[made] = scr_wasm_to_js(sig[made], args[made]); + if (!argv[made]) { + ok = false; + break; + } + } + scr_wasm_running = false; + ScrDyn *res = NULL; + if (ok) { + scr_dyn_this_push_dyn(scr_dyn_undefined()); + res = scr_dyn_call(host->funcs[index], argv, np, "WebAssembly import"); + scr_dyn_this_pop(); + } + for (size_t i = 0; i < made; i++) scr_dyn_release(argv[i]); + if (argv != stack_argv) free(argv); + if (res) { + const char *want = sig[np] == ':' ? sig + np + 1 : ""; + size_t nr = strlen(want); + if (nr == 1) { + ok = scr_wasm_from_js(want[0], res, &results[0]); + if (ok && want[0] == 'x' && results[0]) scr_wasm_keep((ScrDyn *)(uintptr_t)results[0]); + } else if (nr > 1) { + for (size_t i = 0; ok && i < nr; i++) { + ScrDyn *item = scr_dyn_index_get(res, (double)i); + ok = item && scr_wasm_from_js(want[i], item, &results[i]); + if (ok && want[i] == 'x' && results[i]) scr_wasm_keep((ScrDyn *)(uintptr_t)results[i]); + scr_dyn_release(item); + } + } + scr_dyn_release(res); + } else { + ok = false; + } + if (!ok) scr_wasm_trap(SCR_WASM_TRAP_HOST); + scr_wasm_running = true; +} + +/* ── handle members ──────────────────────────────────────────────────── */ + +static ScrDyn *scr_wasm_memory_buffer(ScrWasmMemory *m) { + /* memory.grow in translated code changes the size without telling the + * cached buffer. */ + if (m->buffer && m->buffer_size != m->size) { + scr_dyn_release(m->buffer); + m->buffer = NULL; + } + if (!m->buffer) { + m->buffer_size = m->size; + ScrBytes *bytes = scr_bytes_from_external(m->base, (size_t)m->size); + m->buffer = scr_array_buffer_from_bytes(bytes); + scr_bytes_release(bytes); + } + return scr_dyn_retain(m->buffer); +} + +static ScrDyn *scr_wasm_global_value(ScrWasmGlobal *g) { return scr_wasm_to_js(g->type, g->value); } + +static ScrDyn *scr_wasm_get(void *h, const char *key, size_t len) { + ScrWasmObj *o = h; + switch (o->kind) { + case SCR_WASM_K_INSTANCE: + if (len == 7 && !memcmp(key, "exports", 7)) return scr_dyn_retain(o->inst->exports); + return NULL; + case SCR_WASM_K_MEMORY: + if (len == 6 && !memcmp(key, "buffer", 6)) return scr_wasm_memory_buffer(o->memory); + return NULL; + case SCR_WASM_K_TABLE: + if (len == 6 && !memcmp(key, "length", 6)) return scr_dyn_new_num(o->table->size); + return NULL; + case SCR_WASM_K_GLOBAL: + if (len == 5 && !memcmp(key, "value", 5)) return scr_wasm_global_value(o->global); + return NULL; + default: + return NULL; + } +} + +static bool scr_wasm_global_assign(ScrWasmGlobal *g, const ScrDyn *value) { + if (!g->mutable_) { + scr_wasm_type_error("set WebAssembly.Global.value): Can't set the value of an immutable global."); + return false; + } + uint64_t v; + if (!scr_wasm_from_js(g->type, value, &v)) return false; + if (g->type == 'x') scr_wasm_global_set_ref(g, (void *)(uintptr_t)v); + else g->value = v; + return true; +} + +static bool scr_wasm_set(void *h, const char *key, size_t len, const ScrDyn *value) { + ScrWasmObj *o = h; + if (o->kind == SCR_WASM_K_GLOBAL && len == 5 && !memcmp(key, "value", 5)) { + scr_wasm_global_assign(o->global, value); + return true; + } + return false; +} + +static bool scr_wasm_u32_arg(const ScrDyn *v, const char *what, uint32_t *out) { + double n; + if (!scr_dyn_number_coerce_js(v ? v : scr_dyn_undefined(), &n)) return false; + if (!isfinite(n) || n < 0 || trunc(n) > 4294967295.0) { + char msg[200]; + snprintf(msg, sizeof msg, "%s must be convertible to a valid number", what); + scr_wasm_type_error(msg); + return false; + } + *out = (uint32_t)trunc(n); + return true; +} + +static ScrDyn *scr_wasm_invoke(void *h, ScrDyn *self, const char *method, ScrDyn *const *args, size_t argc, + const char *what) { + (void)self; + ScrWasmObj *o = h; + if (o->kind == SCR_WASM_K_MEMORY && !strcmp(method, "grow")) { + uint32_t delta; + if (!scr_wasm_u32_arg(argc ? args[0] : NULL, "WebAssembly.Memory.grow(): Argument 0", &delta)) return NULL; + int64_t old = scr_wasm_memory_grow_by(o->memory, delta); + if (old < 0) { + scr_wasm_range_error("WebAssembly.Memory.grow(): Maximum memory size exceeded"); + return NULL; + } + if (o->inst && o->inst->memory == o->memory) o->inst->inst[1] = o->memory->base; + /* The previous buffer keeps its length (no detachment yet, W5). */ + scr_dyn_release(o->memory->buffer); + o->memory->buffer = NULL; + return scr_dyn_new_num((double)old); + } + if (o->kind == SCR_WASM_K_TABLE) { + ScrWasmTable *t = o->table; + uint32_t index = 0; + uint64_t value = 0; + if (!strcmp(method, "get")) { + if (!scr_wasm_u32_arg(argc ? args[0] : NULL, "WebAssembly.Table.get(): Argument 0", &index)) return NULL; + if (index >= t->size) { + char msg[160]; + snprintf(msg, sizeof msg, "WebAssembly.Table.get(): invalid address %u in %s table of size %u", index, + t->elem == 'x' ? "externref" : "funcref", t->size); + scr_wasm_range_error(msg); + return NULL; + } + return scr_wasm_to_js(t->elem, (uint64_t)(uintptr_t)t->elems[index]); + } + if (!strcmp(method, "set")) { + if (!scr_wasm_u32_arg(argc ? args[0] : NULL, "WebAssembly.Table.set(): Argument 0", &index)) return NULL; + if (index >= t->size) { + char msg[160]; + snprintf(msg, sizeof msg, "WebAssembly.Table.set(): invalid address %u in %s table of size %u", index, + t->elem == 'x' ? "externref" : "funcref", t->size); + scr_wasm_range_error(msg); + return NULL; + } + const ScrDyn *v = argc > 1 ? args[1] : (t->elem == 'x' ? scr_dyn_undefined() : scr_dyn_new_null()); + if (!scr_wasm_from_js(t->elem, v, &value)) return NULL; + scr_wasm_table_store(t, index, (void *)(uintptr_t)value); + return scr_dyn_retain(scr_dyn_undefined()); + } + if (!strcmp(method, "grow")) { + uint32_t delta; + if (!scr_wasm_u32_arg(argc ? args[0] : NULL, "WebAssembly.Table.grow(): Argument 0", &delta)) return NULL; + if (argc > 1 && !scr_wasm_from_js(t->elem, args[1], &value)) return NULL; + int64_t old = scr_wasm_table_grow_by(t, delta, (void *)(uintptr_t)value); + if (old < 0) { + char msg[160]; + snprintf(msg, sizeof msg, "WebAssembly.Table.grow(): failed to grow table by %u", delta); + scr_wasm_range_error(msg); + return NULL; + } + return scr_dyn_new_num((double)old); + } + } + if (o->kind == SCR_WASM_K_GLOBAL && !strcmp(method, "valueOf")) return scr_wasm_global_value(o->global); + char msg[200]; + snprintf(msg, sizeof msg, "%s is not a function", what ? what : method); + scr_wasm_type_error(msg); + return NULL; +} + +/* Module transfer between threads (postMessage, workerData): a module is + * immutable code plus its descriptor, so the payload is the descriptor + * itself, which every thread of the executable shares. */ +static void *scr_wasm_module_share(void *h) { return (void *)((ScrWasmObj *)h)->desc; } + +static ScrDyn *scr_wasm_module_adopt(void *payload) { + ScrWasmObj *o = scr_wasm_obj_new(SCR_WASM_K_MODULE); + o->desc = payload; + return scr_wasm_box(o); +} + +static void scr_wasm_module_drop(void *payload) { (void)payload; } + +static const ScrDynHandleTransfer scr_wasm_module_transfer = { + .share = scr_wasm_module_share, .adopt = scr_wasm_module_adopt, .drop = scr_wasm_module_drop}; + +#define SCR_WASM_OPS(name, tag, xfer) \ + static const ScrDynHandleOps scr_wasm_ops_##name = { \ + .cls = #name, .retain = scr_wasm_obj_retain, .release = scr_wasm_obj_release, \ + .invoke = scr_wasm_invoke, .get = scr_wasm_get, .set = scr_wasm_set, \ + .inspect_view = scr_wasm_empty_view, .json_empty_object = true, \ + .to_string_tag = tag, .inspect_view_empty = true, .transfer = xfer, \ + }; +SCR_WASM_OPS(Module, "WebAssembly.Module", &scr_wasm_module_transfer) +SCR_WASM_OPS(Instance, "WebAssembly.Instance", NULL) +SCR_WASM_OPS(Memory, "WebAssembly.Memory", NULL) +SCR_WASM_OPS(Table, "WebAssembly.Table", NULL) +SCR_WASM_OPS(Global, "WebAssembly.Global", NULL) + +static void scr_wasm_install(void) { + static SCR_TL bool installed; + if (installed) return; + installed = true; + scr_dyn_handle_install(SCR_DYNH_WASM_MODULE, &scr_wasm_ops_Module); + scr_dyn_handle_install(SCR_DYNH_WASM_INSTANCE, &scr_wasm_ops_Instance); + scr_dyn_handle_install(SCR_DYNH_WASM_MEMORY, &scr_wasm_ops_Memory); + scr_dyn_handle_install(SCR_DYNH_WASM_TABLE, &scr_wasm_ops_Table); + scr_dyn_handle_install(SCR_DYNH_WASM_GLOBAL, &scr_wasm_ops_Global); +} + +/* ── modules ─────────────────────────────────────────────────────────── */ + +/* The bytes of a BufferSource, or false with Node's TypeError pending. */ +static bool scr_wasm_bytes_of(const ScrDyn *v, const char *what, const char *expected, const uint8_t **data, + size_t *len) { + if (v && v->kind == SCR_DYN_BYTES) { + *data = v->v.bytes->data; + *len = (size_t)scr_bytes_byte_len(v->v.bytes); + return true; + } + if (v && v->kind == SCR_DYN_HANDLE && scr_array_buffer_is(v)) { + ScrBytes *b = v->v.handle.ptr; + *data = b->data; + *len = (size_t)scr_bytes_byte_len(b); + return true; + } + char msg[200]; + snprintf(msg, sizeof msg, "%s: Argument 0 must be %s", what, expected); + scr_wasm_type_error(msg); + return false; +} + +static const ScrWasmModuleDesc *scr_wasm_lookup(const uint8_t *data, size_t len) { + uint8_t digest[32]; + bool hashed = false; + for (uint64_t i = 0; i < scr_wasm_registry.count; i++) { + const ScrWasmModuleDesc *d = scr_wasm_registry.modules[i]; + if (d->byte_length != len || d->abi_version != SCR_WASM_ABI_VERSION) continue; + if (!hashed) { + scr_wasm_sha256(data, len, digest); + hashed = true; + } + if (!memcmp(d->sha256, digest, 32)) return d; + } + return NULL; +} + +static void scr_wasm_not_translated(const char *what) { + char msg[400]; + snprintf(msg, sizeof msg, + "%s: this module was not translated when the program was built (scriptc compiles WebAssembly ahead of " + "time and cannot compile modules whose bytes it did not see; see https://scriptc.dev/limitations)", + what); + scr_wasm_throw_kind(SCR_WASM_COMPILE_ERROR, msg); +} + +static ScrDyn *scr_wasm_module_from(ScrDyn *bytes, const char *what, const char *expected) { + const uint8_t *data; + size_t len; + if (!scr_wasm_bytes_of(bytes, what, expected, &data, &len)) return NULL; + const ScrWasmModuleDesc *desc = scr_wasm_lookup(data, len); + if (!desc) { + scr_wasm_not_translated(what); + return NULL; + } + ScrWasmObj *o = scr_wasm_obj_new(SCR_WASM_K_MODULE); + o->desc = desc; + return scr_wasm_box(o); +} + +ScrDyn *scr_wasm_module_new(ScrDyn *bytes) { + return scr_wasm_module_from(bytes, "WebAssembly.Module()", "a buffer source"); +} + +bool scr_wasm_validate(ScrDyn *bytes) { + const uint8_t *data; + size_t len; + if (!scr_wasm_bytes_of(bytes, "WebAssembly.validate()", "a buffer source", &data, &len)) return false; + return scr_wasm_lookup(data, len) != NULL; +} + +/* ── instances ───────────────────────────────────────────────────────── */ + +/* A member of an import object (+1, NULL when absent or undefined, or with + * an exception pending from a getter). */ +static ScrDyn *scr_wasm_member(const ScrDyn *obj, const char *key, size_t len) { + ScrDyn *v = NULL; + switch (obj->kind) { + case SCR_DYN_OBJ: + case SCR_DYN_FUNC: + case SCR_DYN_ARR: + case SCR_DYN_PROXY: + v = scr_dyn_obj_read(obj, key, len); + break; + case SCR_DYN_TYPED_REF: { + ScrStr *k = scr_str_new(key, len); + v = scr_dyn_typed_ref_key_get(obj, k); + scr_str_release(k); + break; + } + case SCR_DYN_HANDLE: { + ScrStr *k = scr_str_new(key, len); + v = scr_dyn_handle_key_get(obj, k); + scr_str_release(k); + break; + } + default: + break; + } + if (v && v->kind == SCR_DYN_UNDEF) { + scr_dyn_release(v); + v = NULL; + } + return v; +} + +static bool scr_wasm_is_object(const ScrDyn *v) { + return v && (v->kind == SCR_DYN_OBJ || v->kind == SCR_DYN_TYPED_REF || v->kind == SCR_DYN_HANDLE || + v->kind == SCR_DYN_PROXY || v->kind == SCR_DYN_FUNC || v->kind == SCR_DYN_ARR); +} + +static void scr_wasm_link_error(const char *what, uint64_t i, const ScrWasmImportDesc *im, const char *reason) { + char msg[600]; + snprintf(msg, sizeof msg, "%s: Import #%llu \"%.*s\" \"%.*s\": %s", what, (unsigned long long)i, + (int)im->module_len, im->module, (int)im->name_len, im->name, reason); + scr_wasm_throw_kind(SCR_WASM_LINK_ERROR, msg); +} + +static bool scr_wasm_guarded(const ScrWasmModuleDesc *desc) { +#if SCR_WASM_SIGNALS + return !(desc->flags & SCR_WASM_DESC_BOUNDS_CHECKS); +#else + (void)desc; + return false; +#endif +} + +static ScrDyn *scr_wasm_instance_from(const ScrWasmModuleDesc *desc, ScrDyn *imports, const char *what) { + scr_wasm_install(); + char msg[400]; + bool have_imports = imports && imports->kind != SCR_DYN_UNDEF; + if (have_imports && !scr_wasm_is_object(imports)) { + snprintf(msg, sizeof msg, "%s: Argument 1 must be an object", what); + scr_wasm_type_error(msg); + return NULL; + } + if (desc->import_count && !have_imports) { + snprintf(msg, sizeof msg, "%s: Imports argument must be present and must be an object", what); + scr_wasm_type_error(msg); + return NULL; + } + /* Read every import value first (V8's order), then check memories, then + * the rest in order. */ + uint64_t n = desc->import_count; + ScrDyn **values = calloc(n ? n : 1, sizeof *values); + if (!values) scr_trap("scriptc: out of memory\n"); + bool ok = true; + for (uint64_t i = 0; ok && i < n; i++) { + const ScrWasmImportDesc *im = &desc->imports[i]; + ScrDyn *ns = scr_wasm_member(imports, im->module, (size_t)im->module_len); + if (scr_exc_pending()) { + ok = false; + } else if (!scr_wasm_is_object(ns)) { + snprintf(msg, sizeof msg, "%s: Import #%llu \"%.*s\": module is not an object or function", what, + (unsigned long long)i, (int)im->module_len, im->module); + scr_wasm_type_error(msg); + ok = false; + } else { + values[i] = scr_wasm_member(ns, im->name, (size_t)im->name_len); + if (scr_exc_pending()) ok = false; + } + scr_dyn_release(ns); + } + for (uint64_t i = 0; ok && i < n; i++) { + const ScrWasmImportDesc *im = &desc->imports[i]; + if (im->kind == SCR_WASM_MEMORY && !scr_wasm_unbox(values[i], SCR_WASM_K_MEMORY)) { + scr_wasm_link_error(what, i, im, "memory import must be a WebAssembly.Memory object"); + ok = false; + } + } + for (uint64_t i = 0; ok && i < n; i++) { + const ScrWasmImportDesc *im = &desc->imports[i]; + const ScrDyn *v = values[i]; + if (im->kind == SCR_WASM_FUNC && !(v && scr_dyn_is_callable(v))) { + scr_wasm_link_error(what, i, im, "function import requires a callable"); + ok = false; + } else if (im->kind == SCR_WASM_FUNC && scr_wasm_funcref_of(v) && + scr_wasm_funcref_of(v)->type != desc->func_types[im->index]) { + /* An exported WebAssembly function keeps its signature. */ + scr_wasm_link_error(what, i, im, "imported function does not match the expected type"); + ok = false; + } else if (im->kind == SCR_WASM_TABLE) { + ScrWasmObj *t = scr_wasm_unbox(v, SCR_WASM_K_TABLE); + if (!t) { + scr_wasm_link_error(what, i, im, "table import requires a WebAssembly.Table"); + ok = false; + } else if (t->table->elem != im->type[0]) { + scr_wasm_link_error(what, i, im, "imported table does not match the expected type"); + ok = false; + } + } else if (im->kind == SCR_WASM_GLOBAL) { + ScrWasmObj *g = scr_wasm_unbox(v, SCR_WASM_K_GLOBAL); + bool mutable_ = desc->globals[im->index].mutable_ != 0; + char type = im->type[0]; + if (g) { + if (g->global->type != type || g->global->mutable_ != mutable_) { + scr_wasm_link_error(what, i, im, "imported global does not match the expected type"); + ok = false; + } + } else if (mutable_) { + scr_wasm_link_error(what, i, im, "imported mutable global must be a WebAssembly.Global object"); + ok = false; + } else if (type == 'I' ? !(v && v->kind == SCR_DYN_BIGINT) + : type == 'x' || type == 'r' ? false + : !(v && v->kind == SCR_DYN_NUM)) { + scr_wasm_link_error(what, i, im, + "global import must be a number, valid Wasm reference, or WebAssembly.Global object"); + ok = false; + } + } + } + if (!ok) { + for (uint64_t k = 0; k < n; k++) scr_dyn_release(values[k]); + free(values); + return NULL; + } + + ScrWasmInst *host = calloc(1, sizeof *host); + if (!host) scr_trap("scriptc: out of memory\n"); + host->rc = 1; + host->desc = desc; + size_t nf = (size_t)desc->func_count; + size_t slots = 4 + (size_t)desc->table_count + (size_t)desc->global_count; + host->inst = calloc(slots, sizeof(void *)); + host->funcs = calloc(nf ? nf : 1, sizeof *host->funcs); + host->funcrefs = calloc(nf ? nf : 1, sizeof *host->funcrefs); + host->export_fns = calloc(nf ? nf : 1, sizeof *host->export_fns); + host->export_names = calloc(nf ? nf : 1, sizeof *host->export_names); + host->tables = calloc(desc->table_count ? desc->table_count : 1, sizeof *host->tables); + host->globals = calloc(desc->global_count ? desc->global_count : 1, sizeof *host->globals); + host->data_dropped = calloc(desc->data_count ? desc->data_count : 1, 1); + host->elem_dropped = calloc(desc->elem_count ? desc->elem_count : 1, 1); + if (!host->inst || !host->funcs || !host->funcrefs || !host->export_fns || !host->export_names || + !host->tables || !host->globals || !host->data_dropped || !host->elem_dropped) + scr_trap("scriptc: out of memory\n"); + void **inst = host->inst; + inst[0] = host; +#ifdef SCR_RC_AUDIT + scr_wasm_note_host(host); +#endif + for (size_t f = 0; f < nf; f++) { + host->funcrefs[f].code = desc->func_codes[f]; + host->funcrefs[f].type = desc->func_types[f]; + host->funcrefs[f].inst = inst; + } + inst[3] = host->funcrefs; + for (uint64_t i = 0; i < n; i++) { + const ScrWasmImportDesc *im = &desc->imports[i]; + ScrDyn *v = values[i]; + switch (im->kind) { + case SCR_WASM_FUNC: + host->funcs[im->index] = v; + values[i] = NULL; + break; + case SCR_WASM_MEMORY: + host->memory = scr_wasm_memory_retain(scr_wasm_unbox(v, SCR_WASM_K_MEMORY)->memory); + break; + case SCR_WASM_TABLE: + host->tables[im->index] = scr_wasm_table_retain(scr_wasm_unbox(v, SCR_WASM_K_TABLE)->table); + break; + default: { + ScrWasmObj *g = scr_wasm_unbox(v, SCR_WASM_K_GLOBAL); + if (g) { + host->globals[im->index] = scr_wasm_global_retain(g->global); + } else { + char type = im->type[0]; + ScrWasmGlobal *cell = scr_wasm_global_new(type, false); + uint64_t value = 0; + scr_wasm_from_js(type, v, &value); + if (type == 'x') scr_wasm_global_set_ref(cell, (void *)(uintptr_t)value); + else cell->value = value; + host->globals[im->index] = cell; + } + break; + } + } + scr_dyn_release(values[i]); + } + free(values); + if (desc->memory_count && !host->memory) { + host->memory = scr_wasm_memory_new(desc->memory_min, desc->memory_max == UINT64_MAX ? SCR_WASM_MAX_PAGES + : desc->memory_max, + scr_wasm_guarded(desc), what); + if (!host->memory) return NULL; + } + if (host->memory) { + inst[1] = host->memory->base; + inst[2] = host->memory; + } + for (uint64_t t = 0; t < desc->table_count; t++) { + const ScrWasmTableDesc *td = &desc->tables[t]; + if (!host->tables[t]) host->tables[t] = scr_wasm_table_new((char)td->elem, td->min, td->max); + inst[4 + t] = host->tables[t]; + } + for (uint64_t g = 0; g < desc->global_count; g++) { + const ScrWasmGlobalDesc *gd = &desc->globals[g]; + if (!host->globals[g]) host->globals[g] = scr_wasm_global_new((char)gd->type, gd->mutable_ != 0); + inst[4 + desc->table_count + g] = host->globals[g]; + } +#if SCR_WASM_SIGNALS + if (scr_wasm_guarded(desc)) pthread_once(&scr_wasm_handler_once, scr_wasm_install_handler); +#endif + /* Globals and segments, then the start function. */ + bool was = scr_wasm_instantiating; + scr_wasm_instantiating = true; + int trap = scr_wasm_enter(scr_wasm_init_thunk, inst, NULL, NULL); + scr_wasm_instantiating = was; + if (trap) { + if (trap == SCR_WASM_TRAP_DATA_SEGMENT) { + snprintf(msg, sizeof msg, "%s: data segment %u is out of bounds (offset %u, length %u, memory size %llu)", what, + scr_wasm_segment_failure.seg, scr_wasm_segment_failure.offset, scr_wasm_segment_failure.length, + (unsigned long long)scr_wasm_segment_failure.size); + scr_wasm_throw_kind(SCR_WASM_RUNTIME_ERROR, msg); + } else if (trap == SCR_WASM_TRAP_TABLE_OOB) { + snprintf(msg, sizeof msg, "%s: table index is out of bounds", what); + scr_wasm_throw_kind(SCR_WASM_RUNTIME_ERROR, msg); + } else { + scr_wasm_throw_trap(trap); + } + return NULL; + } + if (desc->start) { + trap = scr_wasm_enter(scr_wasm_start_thunk, inst, NULL, NULL); + if (trap) { + scr_wasm_throw_trap(trap); + return NULL; + } + } + + ScrDyn *exports = scr_dyn_new_obj_null_proto(); + for (uint64_t i = 0; i < desc->export_count; i++) { + const ScrWasmExportDesc *ex = &desc->exports[i]; + ScrDyn *value; + if (ex->kind == SCR_WASM_FUNC) { + value = scr_wasm_function_value(host, (uint32_t)ex->index); + } else { + int kind = ex->kind == SCR_WASM_MEMORY ? SCR_WASM_K_MEMORY + : ex->kind == SCR_WASM_TABLE ? SCR_WASM_K_TABLE + : SCR_WASM_K_GLOBAL; + ScrWasmObj *o = scr_wasm_obj_new(kind); + o->inst = scr_wasm_inst_retain(host); + if (kind == SCR_WASM_K_MEMORY) o->memory = scr_wasm_memory_retain(host->memory); + else if (kind == SCR_WASM_K_TABLE) o->table = scr_wasm_table_retain(host->tables[ex->index]); + else o->global = scr_wasm_global_retain(host->globals[ex->index]); + value = scr_wasm_box(o); + } + scr_dyn_obj_set(exports, ex->name, (size_t)ex->name_len, value); + } + ScrDyn *frozen = scr_dyn_freeze(exports); + scr_dyn_release(frozen); + host->exports = exports; + ScrWasmObj *o = scr_wasm_obj_new(SCR_WASM_K_INSTANCE); + o->inst = host; /* takes the creation reference */ + return scr_wasm_box(o); +} + +ScrDyn *scr_wasm_instance_new(ScrDyn *module, ScrDyn *imports) { + scr_wasm_install(); + ScrWasmObj *m = scr_wasm_unbox(module, SCR_WASM_K_MODULE); + if (!m) { + scr_wasm_type_error("WebAssembly.Instance(): Argument 0 must be a WebAssembly.Module"); + return NULL; + } + return scr_wasm_instance_from(m->desc, imports, "WebAssembly.Instance()"); +} + +/* ── promises ────────────────────────────────────────────────────────── */ + +/* A promise settled now: fulfilled with value, or rejected with the + * pending exception. */ +static ScrPromise *scr_wasm_settled(ScrDyn *value) { + if (value) { + ScrPromise *p = scr_promise_settled_ref(value, &scr_dyn_retain_v, &scr_dyn_release_v, &scr_dyn_trace_v); + return p; + } + ScrPromise *p = scr_promise_new(); + scr_promise_reject_pending(p); + return p; +} + +/* compile and instantiate over bytes settle on a later loop turn, after + * the current microtasks, as V8's compile task does. */ +enum { SCR_WASM_JOB_COMPILE, SCR_WASM_JOB_INSTANTIATE }; + +static void scr_wasm_job_fire(ScrClosure *clo) { + ScrPromise *p = scr_box_get_ref(clo->caps[0]); + ScrDyn *source = scr_box_get_ref(clo->caps[1]); + ScrDyn *imports = scr_box_get_ref(clo->caps[2]); + int job = (int)scr_box_get_f64(clo->caps[3]); + const char *what = job == SCR_WASM_JOB_COMPILE ? "WebAssembly.compile()" : "WebAssembly.instantiate()"; + ScrDyn *module = scr_wasm_module_from(source, what, "a buffer source"); + ScrDyn *result = module; + if (module && job == SCR_WASM_JOB_INSTANTIATE) { + ScrDyn *instance = scr_wasm_instance_from(scr_wasm_unbox(module, SCR_WASM_K_MODULE)->desc, imports, what); + if (instance) { + result = scr_dyn_new_obj(); + scr_dyn_obj_set(result, "module", 6, module); + scr_dyn_obj_set(result, "instance", 8, instance); + } else { + scr_dyn_release(module); + result = NULL; + } + } + if (result) { + scr_promise_resolve_dyn(p, result); /* moves result in */ + } else { + scr_promise_reject_pending(p); + } + scr_dyn_release(source); + scr_dyn_release(imports); + scr_promise_release(p); +} + +static ScrPromise *scr_wasm_defer(int job, ScrDyn *source, ScrDyn *imports) { + ScrPromise *p = scr_promise_new(); + ScrClosure *clo = scr_closure_new((void *)scr_wasm_job_fire, 4); + clo->caps[0] = scr_box_new_obj(scr_promise_retain_v, scr_promise_release_v, NULL); + scr_box_set_ref(clo->caps[0], scr_promise_retain(p)); + clo->caps[1] = scr_box_new_obj(scr_dyn_retain_v, scr_dyn_release_v, scr_dyn_trace_v); + scr_box_set_ref(clo->caps[1], scr_dyn_retain(source ? source : scr_dyn_undefined())); + clo->caps[2] = scr_box_new_obj(scr_dyn_retain_v, scr_dyn_release_v, scr_dyn_trace_v); + scr_box_set_ref(clo->caps[2], scr_dyn_retain(imports ? imports : scr_dyn_undefined())); + clo->caps[3] = scr_box_new(SCR_BOX_F64); + scr_box_set_f64(clo->caps[3], (double)job); + scr_loop_defer(clo); + return p; +} + +static bool scr_wasm_is_buffer_source(const ScrDyn *v) { + return v && (v->kind == SCR_DYN_BYTES || (v->kind == SCR_DYN_HANDLE && scr_array_buffer_is(v))); +} + +ScrPromise *scr_wasm_compile(ScrDyn *bytes) { + scr_wasm_install(); + if (!scr_wasm_is_buffer_source(bytes)) { + scr_wasm_type_error("WebAssembly.compile(): Argument 0 must be a buffer source"); + return scr_wasm_settled(NULL); + } + return scr_wasm_defer(SCR_WASM_JOB_COMPILE, bytes, NULL); +} + +ScrPromise *scr_wasm_instantiate(ScrDyn *source, ScrDyn *imports) { + scr_wasm_install(); + ScrWasmObj *m = scr_wasm_unbox(source, SCR_WASM_K_MODULE); + if (m) return scr_wasm_settled(scr_wasm_instance_from(m->desc, imports, "WebAssembly.instantiate()")); + if (!scr_wasm_is_buffer_source(source)) { + scr_wasm_type_error("WebAssembly.instantiate(): Argument 0 must be a buffer source or a WebAssembly.Module object"); + return scr_wasm_settled(NULL); + } + if (imports && imports->kind != SCR_DYN_UNDEF && !scr_wasm_is_object(imports)) { + scr_wasm_type_error("WebAssembly.instantiate(): Argument 1 must be an object"); + return scr_wasm_settled(NULL); + } + return scr_wasm_defer(SCR_WASM_JOB_INSTANTIATE, source, imports); +} + +/* ── instanceof and the error constructors ───────────────────────────── */ + +bool scr_wasm_is(const ScrDyn *value, double kind) { + int k = (int)kind; + if (k <= SCR_WASM_K_GLOBAL) return scr_wasm_unbox(value, k) != NULL; + ScrError *e = value ? scr_errdyn_err_of(value) : NULL; + if (!e) return false; + int brand = scr_wasm_error_brand(e); + scr_error_release(e); + return brand == k - 5; +} + +ScrDyn *scr_wasm_error_new(double kind, ScrDyn *message, ScrDyn *options) { + ScrStr *text = NULL; + if (message && message->kind != SCR_DYN_UNDEF) { + text = scr_dyn_string_coerce_js(message); + if (!text) return NULL; + } + ScrError *e = scr_wasm_error_make((int)kind - 5, text); + if (!text) e->message_present = false; + scr_str_release(text); + if (options && scr_wasm_is_object(options)) { + ScrDyn *cause = scr_wasm_member(options, "cause", 5); + if (cause) { + scr_error_define_cause(e, cause); + scr_dyn_release(cause); + } + } + ScrDyn *d = scr_dyn_from_error(e); + scr_error_release(e); + return d; +} + +/* ── the namespace and its functions as values ───────────────────────── */ + +enum { SCR_WASM_FN_COMPILE, SCR_WASM_FN_INSTANTIATE, SCR_WASM_FN_VALIDATE }; +static const char *const scr_wasm_fn_names[] = {"compile", "instantiate", "validate"}; + +static ScrDyn *scr_wasm_fn_call(ScrClosure *clo, ScrDyn *const *args, size_t argc) { + int kind = (int)scr_box_get_f64(clo->caps[0]); + ScrDyn *a0 = argc > 0 ? args[0] : scr_dyn_undefined(); + ScrDyn *a1 = argc > 1 ? args[1] : scr_dyn_undefined(); + if (kind == SCR_WASM_FN_VALIDATE) { + bool ok = scr_wasm_validate(a0); + if (scr_exc_pending()) return NULL; + return scr_dyn_new_bool(ok); + } + ScrPromise *p = kind == SCR_WASM_FN_COMPILE ? scr_wasm_compile(a0) : scr_wasm_instantiate(a0, a1); + ScrDyn *boxed = scr_dyn_new_promise(p); + scr_promise_release(p); + return boxed; +} + +/* WebAssembly.compile, instantiate and validate as function values (+1), + * one per thread and function, as in Node. */ +static SCR_TL ScrDyn *scr_wasm_fns[3]; +static SCR_TL ScrDyn *scr_wasm_ns; + +ScrDyn *scr_wasm_function(double kind) { + ScrDyn **fns = scr_wasm_fns; + int k = (int)kind; + if (!fns[k]) { + ScrClosure *closure = scr_closure_new(NULL, 1); + closure->caps[0] = scr_box_new(SCR_BOX_F64); + scr_box_set_f64(closure->caps[0], (double)k); + fns[k] = scr_dyn_new_func(closure, scr_wasm_fn_call, k == SCR_WASM_FN_INSTANTIATE ? 1 : 1, "", + scr_wasm_fn_names[k]); + } + return scr_dyn_retain(fns[k]); +} + +/* globalThis.WebAssembly as a value (+1): an object with the namespace's + * functions. The classes are not values yet: their constructors and + * instanceof lower at their use sites. */ +ScrDyn *scr_wasm_namespace(void) { + if (!scr_wasm_ns) { + scr_wasm_ns = scr_dyn_new_obj(); + for (int k = 0; k < 3; k++) + scr_dyn_obj_set(scr_wasm_ns, scr_wasm_fn_names[k], strlen(scr_wasm_fn_names[k]), scr_wasm_function(k)); + } + return scr_dyn_retain(scr_wasm_ns); +} + +/* The global object seen through a cast to a type with a `WebAssembly` + * member (`globalThis as unknown as { WebAssembly: ... }`): an object + * whose WebAssembly member is the namespace (+1). */ +ScrDyn *scr_wasm_global_view(void) { + ScrDyn *view = scr_dyn_new_obj(); + scr_dyn_obj_set(view, "WebAssembly", 11, scr_wasm_namespace()); + return view; +} + +#ifdef SCR_RC_AUDIT +static void scr_wasm_teardown(void) { + for (size_t i = 0; i < scr_wasm_host_count; i++) { + ScrWasmInst *host = scr_wasm_hosts[i]; + const ScrWasmModuleDesc *desc = host->desc; + scr_dyn_release(host->exports); + for (uint64_t f = 0; f < desc->func_count; f++) { + scr_dyn_release(host->export_fns[f]); + scr_dyn_release(host->funcs[f]); + free(host->export_names[f]); + } + scr_wasm_memory_release(host->memory); + for (uint64_t t = 0; t < desc->table_count; t++) scr_wasm_table_release(host->tables[t]); + for (uint64_t g = 0; g < desc->global_count; g++) scr_wasm_global_release(host->globals[g]); + free(host->funcs); + free(host->funcrefs); + free(host->export_fns); + free(host->export_names); + free(host->tables); + free(host->globals); + free(host->data_dropped); + free(host->elem_dropped); + free(host->inst); + free(host); + } + free(scr_wasm_hosts); + scr_wasm_hosts = NULL; + scr_wasm_host_count = scr_wasm_host_cap = 0; + for (int k = 0; k < 3; k++) { + scr_dyn_release(scr_wasm_fns[k]); + scr_wasm_fns[k] = NULL; + } + scr_dyn_release(scr_wasm_ns); + scr_wasm_ns = NULL; +} +#endif diff --git a/packages/runtime/src/scr_wasm_abi.h b/packages/runtime/src/scr_wasm_abi.h new file mode 100644 index 000000000..bd2a1e3bd --- /dev/null +++ b/packages/runtime/src/scr_wasm_abi.h @@ -0,0 +1,191 @@ +/* The contract between translated WebAssembly modules and the runtime + * (scr_wasm.c). The compiler's translator (packages/compiler/src/wasm/ + * translate.ts) emits each module's code, an init function and one + * ScrWasmModuleDesc as LLVM IR in the program module, plus the registry + * scr_wasm_registry listing every translated module. The runtime + * implements the JavaScript API once, generically, over these + * descriptors; it never sees the translator's own data structures, and + * the translator never sees the runtime's. + * + * Every descriptor field is pointer- or 64-bit sized so the emitted LLVM + * structs and these C structs have the same layout without padding rules. + * Signatures are strings "params:results" over the letters i (i32), + * I (i64), f (f32), F (f64), r (funcref) and x (externref). Values travel + * in uint64_t slots: f32 and f64 as their bit patterns, references as + * pointers (externref: a borrowed ScrDyn *, NULL for null; funcref: a + * ScrWasmFuncRef *, NULL for null). + * + * Instance layout (the first argument of every translated function): + * 0 ScrWasmInst *host runtime state + * 8 uint8_t *memory_base memory 0's data + * 16 ScrWasmMemory *memory memory 0, or NULL + * 24 ScrWasmFuncRef *funcrefs one record per function + * 32 ScrWasmTable *tables[table_count] + * .. uint64_t *globals[global_count] one cell per global + * With guard pages (no SCR_WASM_DESC_BOUNDS_CHECKS) memory_base never + * changes: the memory is reserved once at its largest size plus a guard + * region, and grow only commits pages. With explicit bounds checks the + * translated code reloads memory_base and the byte length after every + * call. */ +#ifndef SCR_WASM_ABI_H +#define SCR_WASM_ABI_H +#include +#include +#include + +#define SCR_WASM_ABI_VERSION 1 + +enum { SCR_WASM_FUNC = 0, SCR_WASM_TABLE = 1, SCR_WASM_MEMORY = 2, SCR_WASM_GLOBAL = 3 }; + +/* Trap codes the translated code passes to scr_wasm_trap (translate.ts + * TRAP_*), plus the runtime's own. */ +enum { + SCR_WASM_TRAP_NONE = 0, + SCR_WASM_TRAP_UNREACHABLE = 1, + SCR_WASM_TRAP_MEMORY_OOB = 2, + SCR_WASM_TRAP_DIV_BY_ZERO = 3, + SCR_WASM_TRAP_DIV_UNREPRESENTABLE = 4, + SCR_WASM_TRAP_REM_BY_ZERO = 5, + SCR_WASM_TRAP_FLOAT_UNREPRESENTABLE = 6, + SCR_WASM_TRAP_TABLE_OOB = 7, + SCR_WASM_TRAP_SIGNATURE = 8, + SCR_WASM_TRAP_NULL = 9, + SCR_WASM_TRAP_ELEM_SEGMENT = 10, + /* Runtime-only codes. */ + SCR_WASM_TRAP_EXHAUSTION = 100, /* RangeError: Maximum call stack size exceeded */ + SCR_WASM_TRAP_HOST = 101, /* a JavaScript exception is pending */ + SCR_WASM_TRAP_DATA_SEGMENT = 102, /* instantiation: active data segment out of bounds */ +}; + +/* Descriptor flags. */ +#define SCR_WASM_DESC_BOUNDS_CHECKS 1u + +typedef void (*ScrWasmThunk)(void *inst, const uint64_t *args, uint64_t *results); + +typedef struct { + const char *module; + uint64_t module_len; + const char *name; + uint64_t name_len; + uint64_t kind; + /* The index among imports of this kind (function imports: the function + * index). */ + uint64_t index; + /* Functions: the signature; tables: the element letter; globals: the + * value letter; memories: "". */ + const char *type; +} ScrWasmImportDesc; + +typedef struct { + const char *name; + uint64_t name_len; + uint64_t kind; + uint64_t index; /* function, table, memory or global index */ + const char *type; + ScrWasmThunk call; /* functions */ +} ScrWasmExportDesc; + +typedef struct { + uint64_t elem; /* 'r' or 'x' */ + uint64_t min; + uint64_t max; /* UINT64_MAX when absent */ + uint64_t imported; +} ScrWasmTableDesc; + +typedef struct { + uint64_t type; /* signature letter */ + uint64_t mutable_; + uint64_t imported; +} ScrWasmGlobalDesc; + +typedef struct { + const uint8_t *data; + uint64_t len; +} ScrWasmDataDesc; + +/* An element segment: per item a function index, -1 for null, or + * -(2 + g) for the value of global g. */ +typedef struct { + uint64_t elem; + uint64_t count; + const int64_t *items; +} ScrWasmElemDesc; + +typedef struct ScrWasmModuleDesc { + uint8_t sha256[32]; + uint64_t byte_length; + uint64_t abi_version; + uint64_t flags; + uint64_t import_count; + const ScrWasmImportDesc *imports; + uint64_t export_count; + const ScrWasmExportDesc *exports; + uint64_t func_count; /* imported + defined */ + uint64_t imported_func_count; + /* Per function: the native code `(inst, params...) -> results` for + * functions a reference can name (NULL otherwise), the canonical + * signature identity call_indirect compares, and the signature text. */ + void *const *func_codes; + const uint64_t *func_types; + const char *const *func_sigs; + uint64_t table_count; + const ScrWasmTableDesc *tables; + uint64_t memory_count; /* 0 or 1 */ + uint64_t memory_min; /* pages */ + uint64_t memory_max; /* pages, UINT64_MAX when absent */ + uint64_t global_count; + const ScrWasmGlobalDesc *globals; + uint64_t data_count; + const ScrWasmDataDesc *datas; + uint64_t elem_count; + const ScrWasmElemDesc *elems; + /* Globals, table initializers and active segments, under the trap + * guard; then the start function `void (inst)`, or NULL. */ + void (*init)(void *inst); + void (*start)(void *inst); +} ScrWasmModuleDesc; + +/* The registry the compiler emits in the program module of every program + * that uses the WebAssembly API (count may be 0). */ +typedef struct { + uint64_t count; + const ScrWasmModuleDesc *const *modules; +} ScrWasmRegistry; +extern const ScrWasmRegistry scr_wasm_registry; + +/* Runtime objects the translated code reads at fixed offsets. */ +typedef struct ScrWasmMemory { + uint8_t *base; /* 0 */ + uint64_t size; /* 8: bytes */ + uint64_t max_pages; /* 16 */ + uint64_t reserved; /* 24: bytes of address space reserved at base */ + size_t rc; + bool guarded; + struct ScrDyn *buffer; /* the cached ArrayBuffer */ + uint64_t buffer_size; /* the size it was created at */ +} ScrWasmMemory; + +typedef struct ScrWasmFuncRef { + void *code; /* 0 */ + uint64_t type; /* 8 */ + void *inst; /* 16: the instance the code expects; its host and the + * record's position in its funcrefs name the function */ +} ScrWasmFuncRef; + +typedef struct ScrWasmTable { + void **elems; /* 0: ScrWasmFuncRef * or ScrDyn * (retained) */ + uint32_t size; /* 8 */ + uint32_t max; + size_t rc; + char elem; /* 'r' or 'x' */ +} ScrWasmTable; + +/* A global's cell: the value in the low bytes of `value`. */ +typedef struct ScrWasmGlobal { + uint64_t value; /* 0 */ + size_t rc; + char type; + bool mutable_; +} ScrWasmGlobal; + +#endif diff --git a/tests/corpus/webassembly-api.mjs b/tests/corpus/webassembly-api.mjs new file mode 100644 index 000000000..5215cd3a2 --- /dev/null +++ b/tests/corpus/webassembly-api.mjs @@ -0,0 +1,179 @@ +// The WebAssembly JavaScript API over modules translated to native code when +// the program is built: compile, instantiate, validate, Module, Instance, +// exported functions, memories, tables and globals, imports, linking two +// instances, every trap, link errors, the error classes, externref values +// and the asynchronous entry points. The modules are inline base64 literals +// (their text is in tests/wasm-spec/corpus/*.wat). +const api = Buffer.from("AGFzbQEAAAABLwlgAX8Bf2ACf38Bf2ABfwBgAABgAAF/YAJ+fgF+YAJ/fwJ/f2ABfQF9YAN/f38AAi0EA2VudgNsb2cAAgNlbnYEZmFpbAAAA2VudgRiYXNlA38AA2VudgNiaWcDfgADEhEAAAMBAQQFBgcAAAQAAAAICAQFAXABAggFBAEBAQQGFwN/AUEAC3wARAAAAAAAAApAC34AIwELB5kBEwZtZW1vcnkCAAV0YWJsZQEAB2NvdW50ZXIDAgJwaQMDBGJpZzIDBANhZGQABQVhcHBseQAGBGJ1bXAABwVtdWw2NAAIBmRpdm1vZAAJBm5hcnJvdwAKBnN0cmxlbgALBGdyb3cADAVwYWdlcwANCGNhbGxGYWlsAA4EdHJhcAAPB3JlY3Vyc2UAEARmaWxsABEEY29weQASCAEECQgBAEEACwICAwqHAhEHACAAQQJsCwcAIAAgAGwLBgAjABAACwcAIAAgAWoLCQAgASAAEQAACwsAIwJBAWokAiMCCwcAIAAgAX4LDAAgACABbSAAIAFvCwQAIAALIAEBfwJAA0AgACABai0AAEUNASABQQFqIQEMAAsLIAELBgAgAEAACwQAPwALCQAgABABQQFqC1kAAkACQAJAAkACQAJAAkAgAA4GAAECAwQFBgsAC0EBQQBuDwtB/////wcoAgAPC0EBQQJBABEBAA8LQQFBBxEAAA8LRAAAAAAAAPh/qg8LQYCAgIB4QX9tCwwAIABBAWoQEEEBagsLACAAIAEgAvwLAAsMACAAIAEgAvwKAAALCw4BAEEQCwhzY3JpcHRjAA==", "base64"); +const linked = Buffer.from("AGFzbQEAAAABFwRgAn9/AX9gAX8Bf2ABfwBgA39/fwF/AjYEA2FwaQZtZW1vcnkCAAEDYXBpBXRhYmxlAXAAAgNhcGkHY291bnRlcgN/AQNhcGkDYWRkAAADBQQBAAIDBycEBHJlYWQAAQh1c2VUYWJsZQACCnNldENvdW50ZXIAAwRhZGQzAAQKJwQHACAALQAACwkAIAEgABEBAAsGACAAJAALDAAgACABEAAgAhAACw==", "base64"); +const refs = Buffer.from("AGFzbQEAAAABHwZgAW8Bb2ACf28AYAF/AW9gAW8Bf2ACb38Bf2AAAX8DBwYAAQIDBAUEBQFvAQEKBgYBbwHQbwsHPAgEcmVmcwEABHNsb3QDAARrZWVwAAAFc3RvcmUAAQRsb2FkAAIGaXNOdWxsAAMEZ3JvdwAEBHNpemUABQosBgQAIAALCAAgACABJgALBgAgACUACwUAIADRCwkAIAAgAfwPAAsFAPwQAAs=", "base64"); + +const show = (v) => { + if (typeof v === "bigint") return `${v}n`; + if (Array.isArray(v)) return `[${v.map(show).join(", ")}]`; + return String(v); +}; +const attempt = (label, f) => { + try { + console.log(label, show(f())); + } catch (e) { + console.log(label, e instanceof Error ? `${e.name}: ${e.message}` : `threw ${show(e)}`); + } +}; + +console.log(WebAssembly.validate(api), WebAssembly.validate(new Uint8Array([1, 2, 3]))); +attempt("validate(5)", () => WebAssembly.validate(5)); +const module = new WebAssembly.Module(api); +console.log(module instanceof WebAssembly.Module, module instanceof WebAssembly.Instance, String(module)); +console.log(module); + +const logged = []; +const env = { + log: (v) => logged.push(`log ${v}`), + fail(v) { + if (v < 0) throw new RangeError(`negative ${v}`); + return v * 10; + }, + base: 41, + big: 9007199254740993n, +}; +const instance = new WebAssembly.Instance(module, { env }); +console.log(logged.join(","), instance instanceof WebAssembly.Instance); +const x = instance.exports; +console.log(Object.keys(x).join(","), Object.isFrozen(x), Object.getPrototypeOf(x)); +console.log(x.add.name, x.add.length, typeof x.add, x.add === instance.exports.add); + +attempt("add", () => x.add(2, 3)); +attempt("add coerced", () => x.add("40", { valueOf: () => 2 })); +attempt("add wraps", () => x.add(0x7fffffff, 1)); +attempt("add missing", () => x.add(5)); +attempt("apply double", () => x.apply(0, 21)); +attempt("apply square", () => x.apply(1, 12)); +attempt("bump", () => [x.bump(), x.bump(), x.counter.value]); +x.counter.value = 100; +attempt("bump after set", () => x.bump()); +attempt("pi", () => x.pi.value); +attempt("set pi", () => { + x.pi.value = 1; +}); +attempt("big2", () => x.big2.value); +attempt("mul64", () => x.mul64(123456789n, 1000000007n)); +attempt("mul64 wraps", () => x.mul64(-1n, 2n ** 63n)); +attempt("mul64 number", () => x.mul64(1, 2)); +attempt("divmod", () => x.divmod(-17, 5)); +attempt("narrow", () => x.narrow(0.1)); +attempt("strlen", () => x.strlen(16)); + +const bytes = new Uint8Array(x.memory.buffer); +console.log(bytes.length, String.fromCharCode(...bytes.subarray(16, 23))); +console.log(x.memory instanceof WebAssembly.Memory, x.table instanceof WebAssembly.Table, x.counter instanceof WebAssembly.Global); +attempt("grow", () => x.grow(1)); +attempt("pages", () => x.pages()); +console.log(x.memory.buffer.byteLength); +attempt("memory.grow", () => x.memory.grow(1)); +attempt("memory.grow too far", () => x.memory.grow(2)); +console.log(x.memory.buffer.byteLength, x.pages()); +attempt("grow too far in wasm", () => x.grow(5)); +x.fill(100, 0x41, 5); +x.copy(200, 100, 5); +console.log(String.fromCharCode(...new Uint8Array(x.memory.buffer, 200, 5))); +attempt("fill out of bounds", () => x.fill(x.memory.buffer.byteLength - 2, 0, 5)); + +attempt("callFail", () => x.callFail(4)); +attempt("callFail throws", () => x.callFail(-1)); +for (let k = 0; k <= 6; k++) attempt(`trap ${k}`, () => x.trap(k)); +attempt("recurse", () => x.recurse(0)); +try { + x.trap(0); +} catch (e) { + console.log( + e instanceof WebAssembly.RuntimeError, + e instanceof Error, + e instanceof WebAssembly.LinkError, + Object.keys(e).length, + Object.prototype.hasOwnProperty.call(e, "name"), + ); +} +attempt("after traps", () => x.add(1, 1)); + +console.log(x.table.length); +attempt("table.get out of range", () => x.table.get(5)); +attempt("table.grow", () => x.table.grow(1)); +console.log(x.table.length); +x.table.set(2, x.add); +console.log(x.table.get(2) === x.add, x.table.length); +attempt("table.get past the end", () => x.table.get(3)); + +const linkedInstance = new WebAssembly.Instance(new WebAssembly.Module(linked), { api: x }); +const y = linkedInstance.exports; +attempt("read", () => y.read(16)); +attempt("useTable", () => y.useTable(1, 9)); +attempt("useTable signature", () => y.useTable(2, 9)); +attempt("useTable null", () => y.useTable(3, 9)); +y.setCounter(7); +attempt("shared counter", () => x.counter.value); +attempt("add3", () => y.add3(1, 2, 3)); + +attempt("no imports", () => new WebAssembly.Instance(module)); +attempt("missing module", () => new WebAssembly.Instance(module, {})); +attempt("imports not an object", () => new WebAssembly.Instance(module, 5)); +attempt("not callable", () => new WebAssembly.Instance(module, { env: { ...env, log: 1 } })); +attempt("global type", () => new WebAssembly.Instance(module, { env: { ...env, base: "x" } })); +attempt("bigint global", () => new WebAssembly.Instance(module, { env: { ...env, big: 1 } })); +attempt("memory import", () => + new WebAssembly.Instance(new WebAssembly.Module(linked), { + api: { memory: {}, table: x.table, counter: x.counter, add: x.add }, + }), +); +attempt("mutable global as number", () => + new WebAssembly.Instance(new WebAssembly.Module(linked), { + api: { memory: x.memory, table: x.table, counter: 1, add: x.add }, + }), +); +attempt("signature mismatch", () => + new WebAssembly.Instance(new WebAssembly.Module(linked), { + api: { memory: x.memory, table: x.table, counter: x.counter, add: x.bump }, + }), +); +attempt("Instance(5)", () => new WebAssembly.Instance(5)); +attempt("Module(5)", () => new WebAssembly.Module(5)); +try { + new WebAssembly.Module(new Uint8Array([0, 97, 115, 109, 2, 0, 0, 0])); +} catch (e) { + console.log(e.name, e instanceof WebAssembly.CompileError, e instanceof Error); +} + +const custom = new WebAssembly.RuntimeError("custom", { cause: 5 }); +console.log(String(custom), custom.name, custom.message, custom.cause); +console.log(custom instanceof WebAssembly.RuntimeError, custom instanceof WebAssembly.CompileError, custom instanceof Error); +console.log(String(WebAssembly.LinkError("called")), String(new WebAssembly.CompileError())); + +const z = new WebAssembly.Instance(new WebAssembly.Module(refs)).exports; +const token = { id: 1 }; +console.log(z.keep(token) === token, z.keep(null), z.keep(undefined), z.isNull(null), z.isNull(undefined), z.isNull(0)); +z.store(0, token); +console.log(z.load(0) === token, z.refs.get(0) === token); +attempt("grow refs", () => z.grow("filler", 3)); +console.log(z.size(), z.load(2), z.refs.length); +z.slot.value = token; +console.log(z.slot.value === token); +attempt("store out of range", () => z.store(10, token)); + +const order = []; +const compiled = WebAssembly.compile(api) + .then((m) => { + order.push(`compiled ${m instanceof WebAssembly.Module}`); + return WebAssembly.instantiate(m, { env }); + }) + .then((i) => { + order.push(`instance ${i instanceof WebAssembly.Instance} ${i.exports.add(1, 1)}`); + }); +const pair = WebAssembly.instantiate(api, { env }).then((result) => { + order.push(`pair ${Object.keys(result).join(",")} ${result.instance.exports.add(2, 2)}`); +}); +const rejected = WebAssembly.compile(new Uint8Array([1])).catch((e) => order.push(`rejected ${e.name}`)); +const typeError = WebAssembly.instantiate(5).catch((e) => order.push(`${e.name}: ${e.message}`)); +const linkError = WebAssembly.instantiate(api, {}).catch((e) => order.push(`${e.name}: ${e.message}`)); +await Promise.all([compiled, pair, rejected, typeError, linkError]); +console.log(order.sort().join("\n")); +console.log(logged.length); diff --git a/tests/corpus/webassembly-feature-probe.js b/tests/corpus/webassembly-feature-probe.js index 7a164fd90..053e5c0c6 100644 --- a/tests/corpus/webassembly-feature-probe.js +++ b/tests/corpus/webassembly-feature-probe.js @@ -1,7 +1,6 @@ -// @deferred-fences: 2 // A JavaScript dependency's WebAssembly glue: the feature probe answers as -// in Node, and the loader that would instantiate a module is never reached -// (reaching it would throw scriptc's located WebAssembly fence). +// in Node, and the loader that would instantiate a module compiles (it is +// not reached here). function supportsWasm() { return typeof WebAssembly === "object" && typeof WebAssembly.instantiate === "function"; } diff --git a/tests/corpus/webassembly-spec-control/data.mjs b/tests/corpus/webassembly-spec-control/data.mjs new file mode 100644 index 000000000..0be8faedb --- /dev/null +++ b/tests/corpus/webassembly-spec-control/data.mjs @@ -0,0 +1,48 @@ +/* Generated by tests/wasm-spec/gen.mjs from the WebAssembly 2.0 core test + * suite (WebAssembly/spec wg-2.0, Apache-2.0) with wabt 1.0.37 wast2json: + * block.wast, br.wast, br_if.wast, br_table.wast, call.wast, call_indirect.wast, fac.wast, forward.wast, func.wast, func_ptrs.wast, if.wast, labels.wast, loop.wast, local_get.wast, local_set.wast, local_tee.wast, nop.wast, return.wast, select.wast, stack.wast, switch.wast, unreachable.wast, unwind.wast, left-to-right.wast, global.wast, start.wast. Do not edit. */ +export const modules = [ + "AGFzbQEAAAABPQtgAn9/AX9gAABgAAF/YAF/AGADf3x/A398f2AAA39+f2ABfwF/YAACf39gAAJ9fWAAA39/fmACf38Cf38DODcBAQICAgICAgICAgIBAgICAgICAAICAgEBAgYCAgECAgICAgICAgICAgICAgkCAgICAgICAgIBBAUBcAEBAQUDAQABBgYBfwFBCgsH0AY0BWVtcHR5AAEIc2luZ3VsYXIAAgVtdWx0aQADBm5lc3RlZAAEBGRlZXAABQ9hcy1zZWxlY3QtZmlyc3QABg1hcy1zZWxlY3QtbWlkAAcOYXMtc2VsZWN0LWxhc3QACA1hcy1sb29wLWZpcnN0AAkLYXMtbG9vcC1taWQACgxhcy1sb29wLWxhc3QACw9hcy1pZi1jb25kaXRpb24ADAphcy1pZi10aGVuAA0KYXMtaWYtZWxzZQAODmFzLWJyX2lmLWZpcnN0AA8NYXMtYnJfaWYtbGFzdAAQEWFzLWJyX3RhYmxlLWZpcnN0ABEQYXMtYnJfdGFibGUtbGFzdAASFmFzLWNhbGxfaW5kaXJlY3QtZmlyc3QAFBRhcy1jYWxsX2luZGlyZWN0LW1pZAAVFWFzLWNhbGxfaW5kaXJlY3QtbGFzdAAWDmFzLXN0b3JlLWZpcnN0ABcNYXMtc3RvcmUtbGFzdAAYFGFzLW1lbW9yeS5ncm93LXZhbHVlABkNYXMtY2FsbC12YWx1ZQAbD2FzLXJldHVybi12YWx1ZQAcD2FzLWRyb3Atb3BlcmFuZAAdC2FzLWJyLXZhbHVlAB4SYXMtbG9jYWwuc2V0LXZhbHVlAB8SYXMtbG9jYWwudGVlLXZhbHVlACATYXMtZ2xvYmFsLnNldC12YWx1ZQAhD2FzLWxvYWQtb3BlcmFuZAAiEGFzLXVuYXJ5LW9wZXJhbmQAIxFhcy1iaW5hcnktb3BlcmFuZAAkD2FzLXRlc3Qtb3BlcmFuZAAlEmFzLWNvbXBhcmUtb3BlcmFuZAAmEmFzLWJpbmFyeS1vcGVyYW5kcwAnE2FzLWNvbXBhcmUtb3BlcmFuZHMAKBFhcy1taXhlZC1vcGVyYW5kcwApCmJyZWFrLWJhcmUAKgticmVhay12YWx1ZQArEWJyZWFrLW11bHRpLXZhbHVlACwOYnJlYWstcmVwZWF0ZWQALQticmVhay1pbm5lcgAuBXBhcmFtAC8GcGFyYW1zADAJcGFyYW1zLWlkADELcGFyYW0tYnJlYWsAMgxwYXJhbXMtYnJlYWsAMw9wYXJhbXMtaWQtYnJlYWsANAdlZmZlY3RzADUIdHlwZS11c2UANgkHAQBBAAsBEwrSCDcCAAsIAAJACwJACwsLAAJAAQsCf0EHCws+AAJAEAAQABAAEAALAn8QABAAEABBBxAACxoCBRAAEAAQAEEIEAAQABAAEABCBxAAEAAQABAAQQkQAAsaGgsVAAJ/AkAQAAJACwELAn8QAEEJCwsLeQACfwJ/An8CfwJ/An8CfwJ/An8CfwJ/An8CfwJ/An8CfwJ/An8CfwJ/An8CfwJ/An8CfwJ/An8CfwJ/An8CfwJ/An8CfwJ/An8CfwJ/EABBlgELCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsMAAJ/QQELQQJBAxsLDABBAgJ/QQELQQMbCwwAQQJBAwJ/QQELGwsOAAN/An9BAQsQABAACwsOAAN/EAACf0EBCxAACwsOAAN/EAAQAAJ/QQELCwsMAAJ/QQELBEAQAAsLDwBBAQR/An9BAQsFQQILCw8AQQEEf0ECBQJ/QQELCwsOAAJ/An9BAQtBAg0ACwsOAAJ/QQICf0EBCw0ACwsQAAJ/An9BAQtBAg4BAAALCxAAAn9BAgJ/QQELDgEAAAsLBAAgAAsRAAJ/An9BAQtBAkEAEQAACwsRAAJ/QQICf0EBC0EAEQAACwsRAAJ/QQFBAgJ/QQALEQAACwsMAAJ/QQELQQE2AgALDABBCgJ/QQELNgIACwkAAn9BAQtAAAsEACAACwkAAn9BAQsQGgsIAAJ/QQELDwsIAAJ/QQELGgsMAAJ/An9BAQsMAAsLDQEBfwJ/QQELIQAgAAsLAQF/An9BAQsiAAsLAAJ/QQELJAAjAAsKAAJ/QQELKAIACwoAAn8QAEENC2gLEQACfxAAQQMLAn8QAEEEC2wLCgACfxAAQQ0LRQsXAAJ9EABDAABAQAsCfRAAQwAAQEALXgsOAAIHEABBAxAAQQQLbAsUAAIIEABDAABAQBAAQwAAQEALXgsRAAIHEABBAxAAQQQLQQVqbAsmAAJADAAACwJAQQENAAALAkBBAA4AAAALAkBBAQ4CAAAAAAtBEwsLAAJ/QRIMAEETCwsTAAIJQRJBbkISDABBE0FtQhMLCzEAAn9BEgwAQRMMAEEUQQANABpBFEEBDQAaQRUMAEEWQQQOAABBF0EBDgIAAABBFQsLRQEBf0EAIQAgAAJ/An9BAQwBCwtqIQAgAAJ/AkAMAAtBAgtqIQAgAAJ/QQQMAGgLaiEAIAACfwJ/QQgMAQtoC2ohACAACwoAQQECBkECagsLCgBBAUECAgBqCwsKAEEBQQICCgtqCwwAQQECBkECagwACwsMAEEBQQICAGoMAAsLDABBAUECAgoMAAtqCy8BAX8CQEEBIQAgAEEDbCEAIABBBWshACAAQQdsIQAMACAAQeQAbCEACyAAQXJGCz0AAkALAn9BAAsCAxoLQQBEAAAAAAAAAABBAAIECxoaGgJ/QQALAgMaC0EARAAAAAAAAAAAQQACBAsaGhoL", + "AGFzbQEAAAABKwlgA39/fwF/YAAAYAABf2AAAX5gAAF9YAABfGAAAnx8YAACf35gAn9/AX8DS0oBAQEBAQEBAQECAwQFBgEBAQICAgICAQICAQICAwcCCAgICAICAAICAgICAgICAgICAgQDAgICAgICBAIDAgICAgICAgICAgICAgQFAXABAQEFAwEAAQYGAX8BQQoLB5IKSAh0eXBlLWkzMgABCHR5cGUtaTY0AAIIdHlwZS1mMzIAAwh0eXBlLWY2NAAEDHR5cGUtaTMyLWkzMgAFDHR5cGUtaTY0LWk2NAAGDHR5cGUtZjMyLWYzMgAHDHR5cGUtZjY0LWY2NAAIDnR5cGUtaTMyLXZhbHVlAAkOdHlwZS1pNjQtdmFsdWUACg50eXBlLWYzMi12YWx1ZQALDnR5cGUtZjY0LXZhbHVlAAwSdHlwZS1mNjQtZjY0LXZhbHVlAA0OYXMtYmxvY2stZmlyc3QADgxhcy1ibG9jay1taWQADw1hcy1ibG9jay1sYXN0ABAOYXMtYmxvY2stdmFsdWUAEQ1hcy1sb29wLWZpcnN0ABILYXMtbG9vcC1taWQAEwxhcy1sb29wLWxhc3QAFAthcy1ici12YWx1ZQAVDWFzLWJyX2lmLWNvbmQAFg5hcy1icl9pZi12YWx1ZQAXE2FzLWJyX2lmLXZhbHVlLWNvbmQAGBFhcy1icl90YWJsZS1pbmRleAAZEWFzLWJyX3RhYmxlLXZhbHVlABoXYXMtYnJfdGFibGUtdmFsdWUtaW5kZXgAGw9hcy1yZXR1cm4tdmFsdWUAHBBhcy1yZXR1cm4tdmFsdWVzAB0KYXMtaWYtY29uZAAeCmFzLWlmLXRoZW4AHwphcy1pZi1lbHNlACAPYXMtc2VsZWN0LWZpcnN0ACEQYXMtc2VsZWN0LXNlY29uZAAiDmFzLXNlbGVjdC1jb25kACMNYXMtc2VsZWN0LWFsbAAkDWFzLWNhbGwtZmlyc3QAJgthcy1jYWxsLW1pZAAnDGFzLWNhbGwtbGFzdAAoC2FzLWNhbGwtYWxsACkVYXMtY2FsbF9pbmRpcmVjdC1mdW5jACoWYXMtY2FsbF9pbmRpcmVjdC1maXJzdAArFGFzLWNhbGxfaW5kaXJlY3QtbWlkACwVYXMtY2FsbF9pbmRpcmVjdC1sYXN0AC0UYXMtY2FsbF9pbmRpcmVjdC1hbGwALhJhcy1sb2NhbC5zZXQtdmFsdWUALxJhcy1sb2NhbC50ZWUtdmFsdWUAMBNhcy1nbG9iYWwuc2V0LXZhbHVlADEPYXMtbG9hZC1hZGRyZXNzADIQYXMtbG9hZE4tYWRkcmVzcwAzEGFzLXN0b3JlLWFkZHJlc3MANA5hcy1zdG9yZS12YWx1ZQA1DWFzLXN0b3JlLWJvdGgANhFhcy1zdG9yZU4tYWRkcmVzcwA3D2FzLXN0b3JlTi12YWx1ZQA4DmFzLXN0b3JlTi1ib3RoADkQYXMtdW5hcnktb3BlcmFuZAA6DmFzLWJpbmFyeS1sZWZ0ADsPYXMtYmluYXJ5LXJpZ2h0ADwOYXMtYmluYXJ5LWJvdGgAPQ9hcy10ZXN0LW9wZXJhbmQAPg9hcy1jb21wYXJlLWxlZnQAPxBhcy1jb21wYXJlLXJpZ2h0AEAPYXMtY29tcGFyZS1ib3RoAEESYXMtY29udmVydC1vcGVyYW5kAEITYXMtbWVtb3J5Lmdyb3ctc2l6ZQBDEm5lc3RlZC1ibG9jay12YWx1ZQBED25lc3RlZC1ici12YWx1ZQBFEm5lc3RlZC1icl9pZi12YWx1ZQBGF25lc3RlZC1icl9pZi12YWx1ZS1jb25kAEcVbmVzdGVkLWJyX3RhYmxlLXZhbHVlAEgbbmVzdGVkLWJyX3RhYmxlLXZhbHVlLWluZGV4AEkJBwEAQQALASUK5QhKAgALCQACQAwAaBoLCwkAAkAMAHoaCwsJAAJADACMGgsLCQACQAwAmhoLCwkAAkAMAGoaCwsJAAJADAB8GgsLCQACQAwAkhoLCwkAAkAMAKAaCwsKAAJ/QQEMAGgLCwoAAn5CAgwAegsLDQACfUMAAEBADACMCwsRAAJ8RAAAAAAAABBADACaCwsjAAIGRAAAAAAAABBARAAAAAAAABRADACgRAAAAAAAABhACwsJAAJADAAQAAsLCwACQBAADAAQAAsLCgACQAEQAAwACwsMAAJ/ARAAQQIMAAsLDgACfwN/QQMMAUECCwsLEAACfwN/EABBBAwBQQILCwsPAAJ/A38BEABBBQwBCwsLCwACf0EJDAAMAAsLCQACQAwADQALCxAAAn9BCAwAQQENABpBBwsLEAACf0EGQQkMAA0AGkEHCwsMAAJADAAOAgAAAAsLEgACf0EKDABBAQ4CAAAAQQcLCxEAAn9BBkELDAAOAQAAQQcLCwoAAn5CBwwADwsLDgBBAgJ+QQFCBwwADwsLEQACf0ECDAAEf0EABUEBCwsLEQACfyAABH9BAwwBBSABCwsLEQACfyAABH8gAQVBBAwBCwsLDgACf0EFDAAgACABGwsLDgACfyAAQQYMACABGwsLDgACf0EAQQFBBwwAGwsLCgACf0EIDAAbCwsEAEF/Cw8AAn9BDAwAQQJBAxAlCwsPAAJ/QQFBDQwAQQMQJQsLDwACf0EBQQJBDgwAECULCwsAAn9BDwwAECULCxIAAn9BFAwAQQFBAkEDEQAACwsSAAJ/QQBBFQwAQQJBAxEAAAsLEgACf0EAQQFBFgwAQQMRAAALCxIAAn9BAEEBQQJBFwwAEQAACwsMAAJ/QRgMABEAAAsLDwEBfQJ/QREMACEAQX8LCw0BAX8Cf0EBDAAiAAsLCwACf0EBDAAkAAsLDwACfUOamdk/DAAqAgALCwwAAn5CHgwAMAAACwsXAAJ/QR4MAEQAAAAAAAAcQDkDAEF/CwsQAAJ/QQJBHwwANwMAQX8LCw4AAn9BIAwANwMAQX8LCxAAAn9BIAwAQQc6AABBfwsLEAACf0ECQSEMAD0BAEF/CwsOAAJ/QSIMAD0BAEF/CwsNAAJ9Q5qZWUAMAIwLCwwAAn9BAwwAQQpqCwsMAAJ+QgpCLQwAfQsLCgACf0EuDABqCwsKAAJ/QSwMAEULCxMAAn9BKwwARAAAAAAAACRAZQsLDwACf0MAACBBQSoMAFwLCwoAAn9BLAwAZQsLCgACf0EpDACnCwsLAAJ/QSgMAEAACwsRAEEBAn8QAEEEQQgMAGoLagsaAEEBAn9BAhoCf0EEGkEIDAEMAAsaQRALagsfAEEBAn9BAhoCf0EEGkEIDAFBAQ0AGkEgCxpBEAtqCxYAQQECf0ECGkEEQQgMAA0AGkEQC2oLHQBBAQJ/QQIaAn9BBBpBCAwBQQEOAAALGkEQC2oLFgBBAQJ/QQIaQQRBCAwADgAAQRALags=", + "AGFzbQEAAAABKQlgA39/fwF/YAAAYAABf2AAAX5gAAF9YAABfGABfwF/YAF/AGACf38AA0A/AQEBAQECAwQFBgYHBgYGBgYHAgECBgECAgMGCAgGBgIAAgICAAICAgIGBgYCAgICAgIFAgICAgICBgYGBgYGBAUBcAEBAQUDAQABBgYBfwFBCgsH0Qg8CHR5cGUtaTMyAAEIdHlwZS1pNjQAAgh0eXBlLWYzMgADCHR5cGUtZjY0AAQOdHlwZS1pMzItdmFsdWUABQ50eXBlLWk2NC12YWx1ZQAGDnR5cGUtZjMyLXZhbHVlAAcOdHlwZS1mNjQtdmFsdWUACA5hcy1ibG9jay1maXJzdAAJDGFzLWJsb2NrLW1pZAAKDWFzLWJsb2NrLWxhc3QACxRhcy1ibG9jay1maXJzdC12YWx1ZQAMEmFzLWJsb2NrLW1pZC12YWx1ZQANE2FzLWJsb2NrLWxhc3QtdmFsdWUADg1hcy1sb29wLWZpcnN0AA8LYXMtbG9vcC1taWQAEAxhcy1sb29wLWxhc3QAEQthcy1ici12YWx1ZQASDWFzLWJyX2lmLWNvbmQAEw5hcy1icl9pZi12YWx1ZQAUE2FzLWJyX2lmLXZhbHVlLWNvbmQAFRFhcy1icl90YWJsZS1pbmRleAAWEWFzLWJyX3RhYmxlLXZhbHVlABcXYXMtYnJfdGFibGUtdmFsdWUtaW5kZXgAGA9hcy1yZXR1cm4tdmFsdWUAGQphcy1pZi1jb25kABoKYXMtaWYtdGhlbgAbCmFzLWlmLWVsc2UAHA9hcy1zZWxlY3QtZmlyc3QAHRBhcy1zZWxlY3Qtc2Vjb25kAB4OYXMtc2VsZWN0LWNvbmQAHw1hcy1jYWxsLWZpcnN0ACELYXMtY2FsbC1taWQAIgxhcy1jYWxsLWxhc3QAIxVhcy1jYWxsX2luZGlyZWN0LWZ1bmMAJRZhcy1jYWxsX2luZGlyZWN0LWZpcnN0ACYUYXMtY2FsbF9pbmRpcmVjdC1taWQAJxVhcy1jYWxsX2luZGlyZWN0LWxhc3QAKBJhcy1sb2NhbC5zZXQtdmFsdWUAKRJhcy1sb2NhbC50ZWUtdmFsdWUAKhNhcy1nbG9iYWwuc2V0LXZhbHVlACsPYXMtbG9hZC1hZGRyZXNzACwQYXMtbG9hZE4tYWRkcmVzcwAtEGFzLXN0b3JlLWFkZHJlc3MALg5hcy1zdG9yZS12YWx1ZQAvEWFzLXN0b3JlTi1hZGRyZXNzADAPYXMtc3RvcmVOLXZhbHVlADEQYXMtdW5hcnktb3BlcmFuZAAyDmFzLWJpbmFyeS1sZWZ0ADMPYXMtYmluYXJ5LXJpZ2h0ADQPYXMtdGVzdC1vcGVyYW5kADUPYXMtY29tcGFyZS1sZWZ0ADYQYXMtY29tcGFyZS1yaWdodAA3E2FzLW1lbW9yeS5ncm93LXNpemUAOBJuZXN0ZWQtYmxvY2stdmFsdWUAOQ9uZXN0ZWQtYnItdmFsdWUAOhJuZXN0ZWQtYnJfaWYtdmFsdWUAOxduZXN0ZWQtYnJfaWYtdmFsdWUtY29uZAA8FW5lc3RlZC1icl90YWJsZS12YWx1ZQA9G25lc3RlZC1icl90YWJsZS12YWx1ZS1pbmRleAA+CQcBAEEACwEkCtoIPwIACw0AAkBBAEEBDQBoGgsLDQACQEIAQQENAHoaCwsQAAJAQwAAAABBAQ0AjBoLCxQAAkBEAAAAAAAAAABBAQ0AmhoLCwwAAn9BAUEBDQBoCwsMAAJ+QgJBAQ0AegsLDwACfUMAAEBAQQENAIwLCxMAAnxEAAAAAAAAEEBBAQ0AmgsLDgACQCAADQBBAg8LQQMLEAACQBAAIAANAEECDwtBAwsNAAJAEAAQACAADQALCw8AAn9BCiAADQAaQQsPCwsRAAJ/EABBFCAADQAaQRUPCwsPAAJ/EAAQAEELIAANAAsLEQACQANAIAANAUECDwsLQQMLEwACQANAEAAgAA0BQQIPCwtBBAsLAANAEAAgAA0BCwsNAAJ/QQFBAg0ADAALCw0AAkBBAUEBDQANAAsLEgACf0EBQQINAEEDDQAaQQQLCxIAAn9BAkEBIAANAA0AGkEECwsQAAJAQQFBAg0ADgIAAAALCxQAAn9BAUECDQBBAw4CAAAAQQQLCxMAAn9BAkEBQQMNAA4BAABBBAsLDAACfkIBQQINAA8LCxMAAn9BASAADQAEf0ECBUEDCwsLEQACQCAABEAgAQ0BBRAACwsLEQACQCAABEAQAAUgAQ0BCwsLEAACf0EDQQoNAEECIAAbCwsQAAJ/QQFBA0EKDQAgABsLCxAAAn9BAUECQQNBCg0AGwsLBABBfwsRAAJ/QQxBAQ0AQQJBAxAgCwsRAAJ/QQFBDUEBDQBBAxAgCwsRAAJ/QQFBAkEOQQENABAgCwsEACAACxQAAn9BBEEKDQBBAUECQQARAAALCxQAAn9BAUEEQQoNAEECQQARAAALCxQAAn9BAUECQQRBCg0AQQARAAALCxQAAn9BAUECQQNBBEEKDQARAAALCxEBAX8Cf0ERIAANACEAQX8LCxAAAn9BASAADQAiAEF/DwsLEAACf0EBIAANACQAQX8PCwsOAAJ/QQFBAQ0AKAIACwsOAAJ/QR5BAQ0ALAAACwsSAAJ/QR5BAQ0AQQc2AgBBfwsLEgACf0ECQR9BAQ0ANgIAQX8LCxIAAn9BIEEBDQBBBzoAAEF/CwsSAAJ/QQJBIUEBDQA7AQBBfwsLEwACfEQAAAAAAADwP0EBDQCaCwsOAAJ/QQFBAQ0AQQpqCwsOAAJ/QQpBAUEBDQBrCwsMAAJ/QQBBAQ0ARQsLDgACf0EBQQENAEEKTQsLDgACf0EKQQFBKg0ARwsLDQACf0EBQQENAEAACwsaAEEBAn9BAhpBBAJ/QQggAA0BGkEQC2oLagsbAEEBAn9BAhoCf0EIIAANARpBBAsMAEEQC2oLHgBBAQJ/QQIaAn9BCCAADQEaQQQLQQENABpBEAtqCx4AQQECf0ECGkEEAn9BCCAADQEaQQELDQAaQRALagseAEEBAn9BAhoCf0EIIAANARpBBAtBAQ4AAEEQC2oLHgBBAQJ/QQIaQQQCf0EIIAANARpBAQsOAABBEAtqCw==", + "AGFzbQEAAAABLAlgA39/fwF/YAAAYAABf2AAAX5gAAF9YAABfGABfwF/YAJ/fwF/YAJ/bwFvA0dGAQEBAQECAwQFBgYGBgYGBgEBAQICAgICAQICAQICAwIHBwcHAgACAgICAgICAgICBAMCAgICBAIDAgICAgIGBgYGBgYGCAQFAXABAQEFAwEAAQYGAX8BQQoLB7sJRAh0eXBlLWkzMgABCHR5cGUtaTY0AAIIdHlwZS1mMzIAAwh0eXBlLWY2NAAEDnR5cGUtaTMyLXZhbHVlAAUOdHlwZS1pNjQtdmFsdWUABg50eXBlLWYzMi12YWx1ZQAHDnR5cGUtZjY0LXZhbHVlAAgFZW1wdHkACQtlbXB0eS12YWx1ZQAKCXNpbmdsZXRvbgALD3NpbmdsZXRvbi12YWx1ZQAMCG11bHRpcGxlAA0ObXVsdGlwbGUtdmFsdWUADgVsYXJnZQAPDmFzLWJsb2NrLWZpcnN0ABAMYXMtYmxvY2stbWlkABENYXMtYmxvY2stbGFzdAASDmFzLWJsb2NrLXZhbHVlABMNYXMtbG9vcC1maXJzdAAUC2FzLWxvb3AtbWlkABUMYXMtbG9vcC1sYXN0ABYLYXMtYnItdmFsdWUAFw1hcy1icl9pZi1jb25kABgOYXMtYnJfaWYtdmFsdWUAGRNhcy1icl9pZi12YWx1ZS1jb25kABoRYXMtYnJfdGFibGUtaW5kZXgAGxFhcy1icl90YWJsZS12YWx1ZQAcF2FzLWJyX3RhYmxlLXZhbHVlLWluZGV4AB0PYXMtcmV0dXJuLXZhbHVlAB4KYXMtaWYtY29uZAAfCmFzLWlmLXRoZW4AIAphcy1pZi1lbHNlACEPYXMtc2VsZWN0LWZpcnN0ACIQYXMtc2VsZWN0LXNlY29uZAAjDmFzLXNlbGVjdC1jb25kACQNYXMtY2FsbC1maXJzdAAmC2FzLWNhbGwtbWlkACcMYXMtY2FsbC1sYXN0ACgWYXMtY2FsbF9pbmRpcmVjdC1maXJzdAApFGFzLWNhbGxfaW5kaXJlY3QtbWlkACoVYXMtY2FsbF9pbmRpcmVjdC1sYXN0ACsVYXMtY2FsbF9pbmRpcmVjdC1mdW5jACwSYXMtbG9jYWwuc2V0LXZhbHVlAC0SYXMtbG9jYWwudGVlLXZhbHVlAC4TYXMtZ2xvYmFsLnNldC12YWx1ZQAvD2FzLWxvYWQtYWRkcmVzcwAwEGFzLWxvYWROLWFkZHJlc3MAMRBhcy1zdG9yZS1hZGRyZXNzADIOYXMtc3RvcmUtdmFsdWUAMxFhcy1zdG9yZU4tYWRkcmVzcwA0D2FzLXN0b3JlTi12YWx1ZQA1EGFzLXVuYXJ5LW9wZXJhbmQANg5hcy1iaW5hcnktbGVmdAA3D2FzLWJpbmFyeS1yaWdodAA4D2FzLXRlc3Qtb3BlcmFuZAA5D2FzLWNvbXBhcmUtbGVmdAA6EGFzLWNvbXBhcmUtcmlnaHQAOxJhcy1jb252ZXJ0LW9wZXJhbmQAPBNhcy1tZW1vcnkuZ3Jvdy1zaXplAD0SbmVzdGVkLWJsb2NrLXZhbHVlAD4PbmVzdGVkLWJyLXZhbHVlAD8SbmVzdGVkLWJyX2lmLXZhbHVlAEAXbmVzdGVkLWJyX2lmLXZhbHVlLWNvbmQAQRVuZXN0ZWQtYnJfdGFibGUtdmFsdWUAQhtuZXN0ZWQtYnJfdGFibGUtdmFsdWUtaW5kZXgAQxpuZXN0ZWQtYnJfdGFibGUtbG9vcC1ibG9jawBEDm1lZXQtZXh0ZXJucmVmAEUJBwEAQQALASUK04kBRgIACw0AAkBBAA4BAABoGgsLDQACQEEADgEAAHoaCwsNAAJAQQAOAQAAjBoLCw0AAkBBAA4BAACaGgsLDgACf0EBQQAOAQAAaAsLDgACfkICQQAOAQAAegsLEQACfUMAAEBAQQAOAQAAjAsLFQACfEQAAAAAAAAQQEEADgEAAJoLCw8AAkAgAA4AAEEVDwtBFgsOAAJ/QSEgAA4AAEEfCwsWAAJAAkAgAA4BAQBBFQ8LQRQPC0EWCxYAAn8Cf0EhIAAOAQABQR8PCxpBIAsLMQACQAJAAkACQAJAIAAOBAMCAQAEQeMADwtB5AAPC0HlAA8LQeYADwtB5wAPC0HoAAtNAQF/An8CfwJ/An8Cf0HIASAADgQDAgEABCABQeMAag8LIQEgAUEKag8LIQEgAUELag8LIQEgAUEMag8LIQEgAUENag8LIQEgAUEOagusfgACQAJAIAAOlX4AAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABQX8PC0EADwtBAQ8LDgACQEEADgIAAAAQAAsLEAACQBAAQQAOAgAAABAACwsPAAJAARAAQQAOAgAAAAsLEQACfwEQAEECQQAOAgAAAAsLDwADf0EDQQAOAQEBQQELCxIAA38QAEEEQX8OAgEBAUECCwsRAAN/ARAAQQVBAQ4CAQEBCwsOAAJ/QQlBAA4AAAwACwsOAAJAQQEOAgAAAA0ACwsTAAJ/QQhBAA4AAEEBDQAaQQcLCxQAAn9BBkEJQQAOAQAADQAaQQcLCw8AAkBBAQ4AAA4CAAAACwsVAAJ/QQpBAA4AAEEBDgIAAABBBwsLFAACf0EGQQtBAQ4AAA4BAABBBwsLDQACfkIHQQAOAAAPCwsUAAJ/QQJBAA4AAAR/QQAFQQELCwsUAAJ/IAAEf0EDQQAOAAEFIAELCwsVAAJ/IAAEfyABBUEEQQAOAQEACwsLEQACf0EFQQAOAAAgACABGwsLEQACfyAAQQZBAQ4AACABGwsLEQACf0EAQQFBB0EBDgAAGwsLBABBfwsSAAJ/QQxBAQ4AAEECQQMQJQsLEgACf0EBQQ1BAQ4AAEEDECULCxIAAn9BAUECQQ5BAQ4AABAlCwsVAAJ/QRRBAQ4AAEEBQQJBAxEAAAsLFQACf0EAQRVBAQ4AAEECQQMRAAALCxUAAn9BAEEBQRZBAQ4AAEEDEQAACwsVAAJ/QQBBAUECQRdBAQ4AABEAAAsLEgEBfQJ/QRFBAQ4AACEAQX8LCxIBAX8Cf0EBQQEOAAAhAEF/CwsQAAJ/QQFBAQ4AACQAQX8LCxIAAn1DmpnZP0EBDgAAKgIACwsPAAJ+Qh5BAQ4AADAAAAsLGgACf0EeQQEOAABEAAAAAAAAHEA5AwBBfwsLEwACf0ECQR9BAQ4AADcDAEF/CwsTAAJ/QSBBAA4AAEEHOgAAQX8LCxMAAn9BAkEhQQAOAAA9AQBBfwsLEAACfUOamVlAQQAOAACMCwsQAAJ/QQNBAA4BAABBCmoLCw8AAn5CCkItQQAOAAB9CwsNAAJ/QSxBAA4AAEULCxcAAn9BK0EADgEAAEQAAAAAAAAkQGULCxIAAn9DAAAgQUEqQQAOAABcCwsNAAJ/QSlBAA4AAKcLCw4AAn9BKEEADgAAQAALCyMAAn9BfxpBAQJ/QQICf0EEGkEIQRAgAA4CAAECagtqC2oLCyIAAn9BAQJ/QQIaAn9BBBpBCCAADgICAQAMAAsaQRALagsLJwACf0EBAn9BAhoCf0EEGkEIIAAOAgABAkEBDQAaQSALGkEQC2oLCx4AAn9BAQJ/QQIaQQRBCCAADgIAAQANABpBEAtqCwsnAAJ/QQECf0ECGgJ/QQQaQQggAA4CAAECQQEOAABBIAsaQRALagsLHgACf0EBAn9BAhpBBEEIIAAOAgABAA4AAEEQC2oLCyIAA38CQCAADgIBAAALQQALIQADfwJAIAAOAgABAQtBAwsLEQACbwJvIAEgAA4CAQABCwsL", + "AGFzbQEAAAAB6QEZYAJ/fwF/YAABf2AAAX5gAAF9YAABfGAAAn9+YAF/AX9gAX4BfmABfQF9YAF8AXxgAn98An98YAJ/fwJ/f2ACfXwCfH1gAnx/An98YAJ9fwF/YAJ/fgF+YAJ8fQF9YAJ+fAF8YAACf3xgAAJ/f2AAAnx9YAJ+fgF+YAF+AX9gAABgZH1/f3x9fX18fX9/fXx+fn9+fn1+fn5/fX19fH1/fn18fH1/fX18fnx/fn18f39/fnx/fn58fHx8fHx/fXx8f359fX1/fHx8fHx9fn5/f399fH9+fX19f399fH59fH19fX99fn8BfwNPTgECAwQFBgcICQoLDA0ODxARAQIDBAUBAgMEAQIDBBITFBIBARMHFQcWFhcXFwEBAQEBAQEBAAEBARcXAQEXAQEBAQEGCAMBAQEBAQIYBgQFAXABAQEFAwEAAQYGAX8BQQoLB8oHOAh0eXBlLWkzMgARCHR5cGUtaTY0ABIIdHlwZS1mMzIAEwh0eXBlLWY2NAAUDHR5cGUtaTMyLWk2NAAVDnR5cGUtZmlyc3QtaTMyABYOdHlwZS1maXJzdC1pNjQAFw50eXBlLWZpcnN0LWYzMgAYDnR5cGUtZmlyc3QtZjY0ABkPdHlwZS1zZWNvbmQtaTMyABoPdHlwZS1zZWNvbmQtaTY0ABsPdHlwZS1zZWNvbmQtZjMyABwPdHlwZS1zZWNvbmQtZjY0AB0QdHlwZS1hbGwtaTMyLWY2NAAeEHR5cGUtYWxsLWkzMi1pMzIAHxB0eXBlLWFsbC1mMzItZjY0ACAQdHlwZS1hbGwtZjY0LWkzMgAhFmFzLWJpbmFyeS1hbGwtb3BlcmFuZHMAIhFhcy1taXhlZC1vcGVyYW5kcwAjFGFzLWNhbGwtYWxsLW9wZXJhbmRzACQDZmFjACUHZmFjLWFjYwAmA2ZpYgAnBGV2ZW4AKANvZGQAKQdydW5hd2F5ACoObXV0dWFsLXJ1bmF3YXkAKw9hcy1zZWxlY3QtZmlyc3QALQ1hcy1zZWxlY3QtbWlkAC4OYXMtc2VsZWN0LWxhc3QALw9hcy1pZi1jb25kaXRpb24AMA5hcy1icl9pZi1maXJzdAAxDWFzLWJyX2lmLWxhc3QAMhFhcy1icl90YWJsZS1maXJzdAAzEGFzLWJyX3RhYmxlLWxhc3QANBZhcy1jYWxsX2luZGlyZWN0LWZpcnN0ADYUYXMtY2FsbF9pbmRpcmVjdC1taWQANxVhcy1jYWxsX2luZGlyZWN0LWxhc3QAOA5hcy1zdG9yZS1maXJzdAA5DWFzLXN0b3JlLWxhc3QAOhRhcy1tZW1vcnkuZ3Jvdy12YWx1ZQA7D2FzLXJldHVybi12YWx1ZQA8D2FzLWRyb3Atb3BlcmFuZAA9C2FzLWJyLXZhbHVlAD4SYXMtbG9jYWwuc2V0LXZhbHVlAD8SYXMtbG9jYWwudGVlLXZhbHVlAEATYXMtZ2xvYmFsLnNldC12YWx1ZQBBD2FzLWxvYWQtb3BlcmFuZABCEGFzLXVuYXJ5LW9wZXJhbmQARQ5hcy1iaW5hcnktbGVmdABGD2FzLWJpbmFyeS1yaWdodABHD2FzLXRlc3Qtb3BlcmFuZABID2FzLWNvbXBhcmUtbGVmdABJEGFzLWNvbXBhcmUtcmlnaHQAShJhcy1jb252ZXJ0LW9wZXJhbmQASx5yZXR1cm4tZnJvbS1sb25nLWFyZ3VtZW50LWxpc3QATQkHAQBBAAsBNQr2CU4FAEGyAgsFAELkAgsHAEMAIHNFCwsARAAAAAAAyK5ACwgAQbICQuQCCwQAIAALBAAgAAsEACAACwQAIAALBgAgACABCwYAIAEgAAsGACABIAALBgAgASAACwQAIAELBAAgAQsEACABCwQAIAELBAAQAAsEABABCwQAEAILBAAQAwsEABAECwYAQSAQBQsHAELAABAGCwkAQ8P1qD8QBwsNAEQ9CtejcD36PxAICwsAQ2ZmAEJBIBANCwkAQSBCwAAQDgsSAEQAAAAAAABQQEMAAABCEA8LEABCwABEZmZmZmYGUEAQEAsPAEEgRD0K16NwPfo/EAkLCABBAUECEAoLEgBDAACAP0QAAAAAAAAAQBALCw8ARAAAAAAAAPA/QQIQDAsJAEEDQQQQCmoLDABBA0EEEApBBWpsCwoAQQNBBBAKEAoLFQAgAFAEfkIBBSAAIABCAX0QJX4LCxcAIABQBH4gAQUgAEIBfSAAIAF+ECYLCxwAIABCAVgEfkIBBSAAQgJ9ECcgAEIBfRAnfAsLEgAgAFAEf0EsBSAAQgF9ECkLCxMAIABQBH9B4wAFIABCAX0QKAsLBAAQKgsEABAsCwQAECsLCQAQAEECQQMbCwkAQQIQAEEDGwsJAEECQQMQABsLDAAQAAR/QQEFQQILCwsAAn8QAEECDQALCwsAAn9BAhAADQALCw0AAn8QAEECDgEAAAsLDQACf0ECEAAOAQAACwsEACAACw4AAn8QAEECQQARAAALCw4AAn9BAhAAQQARAAALCw4AAn9BAUECEAARAAALCwkAEABBATYCAAsJAEEKEAA2AgALBgAQAEAACwUAEAAPCwUAEAAaCwkAAn8QAAwACwsKAQF/EAAhACAACwgBAX8QACIACwgAEAAkACMACwcAEAAoAgALBAAgAAsEACAACw0AAn1DAAAAABBEkQsLDAACf0EBEENBCmoLCwwAAn9BCkEBEENrCwsKAAJ/QQEQQ0ULCwwAAn9BARBDQQpNCwsMAAJ/QQpBARBDRwsLCgACfkEBEEOsCwsEACBjC98DAEMAAAAAQQBBAEQAAAAAAAAAAEMAAAAAQwAAAABDAAAAAEQAAAAAAAAAAEMAAAAAQQBBAEMAAAAARAAAAAAAAAAAQgBCAEEAQgBCAEMAAAAAQgBCAEIAQQBDAAAAAEMAAAAAQwAAAABEAAAAAAAAAABDAAAAAEEAQgBDAAAAAEQAAAAAAAAAAEQAAAAAAAAAAEMAAAAAQQBDAAAAAEMAAAAARAAAAAAAAAAAQgBEAAAAAAAAAABBAEIAQwAAAABEAAAAAAAAAABBAEEAQQBCAEQAAAAAAAAAAEEAQgBCAEQAAAAAAAAAAEQAAAAAAAAAAEQAAAAAAAAAAEQAAAAAAAAAAEQAAAAAAAAAAEQAAAAAAAAAAEEAQwAAAABEAAAAAAAAAABEAAAAAAAAAABBAEIAQwAAAABDAAAAAEMAAAAAQQBEAAAAAAAAAABEAAAAAAAAAABEAAAAAAAAAABEAAAAAAAAAABEAAAAAAAAAABDAAAAAEIAQgBBAEEAQQBDAAAAAEQAAAAAAAAAAEEAQgBDAAAAAEMAAAAAQwAAAABBAEEAQwAAAABEAAAAAAAAAABCAEMAAAAARAAAAAAAAAAAQwAAAABDAAAAAEMAAAAAQQBDAAAAAEIAIAAQTAs=", + "AGFzbQEAAAABmAEdYAAAYAABf2AAAX5gAAF9YAABfGAAAnx/YAF/AX9gAX4BfmABfQF9YAF8AXxgAn98An98YAJ/fgJ+f2ACfX8Bf2ACf34BfmACfH0BfWACfnwBfGABfwF/YAF+AX5gAX0BfWABfAF8YAF+AGAEfnx/fgBgAX4Bf2AEfnx/fgF/YAACf3xgAAJ+f2ABfwF+YAF/AX1gAX8BfANRUAECAwQFBgcICQoLDQ8MDhAREhMAAQIDBAUCAQIDBAECAwQFGBkNGgYbHAcHBggJBggJBgYAAAABAQEBAgEDAQAAAQEAAwQEBAEDAQEBAQECBAUBcAEgIAUDAQABBg0BfAFEAAAAAAAAJEALB8QHOwh0eXBlLWkzMgAUCHR5cGUtaTY0ABUIdHlwZS1mMzIAFgh0eXBlLWY2NAAXDHR5cGUtZjY0LWkzMgAYCnR5cGUtaW5kZXgAGQ50eXBlLWZpcnN0LWkzMgAaDnR5cGUtZmlyc3QtaTY0ABsOdHlwZS1maXJzdC1mMzIAHA50eXBlLWZpcnN0LWY2NAAdD3R5cGUtc2Vjb25kLWkzMgAeD3R5cGUtc2Vjb25kLWk2NAAfD3R5cGUtc2Vjb25kLWYzMgAgD3R5cGUtc2Vjb25kLWY2NAAhEHR5cGUtYWxsLWY2NC1pMzIAIhB0eXBlLWFsbC1pMzItZjY0ACMQdHlwZS1hbGwtaTMyLWk2NAAkCGRpc3BhdGNoACUXZGlzcGF0Y2gtc3RydWN0dXJhbC1pNjQAJhdkaXNwYXRjaC1zdHJ1Y3R1cmFsLWkzMgAnF2Rpc3BhdGNoLXN0cnVjdHVyYWwtZjMyACgXZGlzcGF0Y2gtc3RydWN0dXJhbC1mNjQAKQdmYWMtaTY0ACoHZmliLWk2NAArB2ZhYy1pMzIALAdmYWMtZjMyAC0HZmFjLWY2NAAuB2ZpYi1pMzIALwdmaWItZjMyADAHZmliLWY2NAAxBGV2ZW4AMgNvZGQAMwdydW5hd2F5ADQObXV0dWFsLXJ1bmF3YXkANQ9hcy1zZWxlY3QtZmlyc3QANw1hcy1zZWxlY3QtbWlkADgOYXMtc2VsZWN0LWxhc3QAOQ9hcy1pZi1jb25kaXRpb24AOg5hcy1icl9pZi1maXJzdAA7DWFzLWJyX2lmLWxhc3QAPBFhcy1icl90YWJsZS1maXJzdAA9EGFzLWJyX3RhYmxlLWxhc3QAPg5hcy1zdG9yZS1maXJzdAA/DWFzLXN0b3JlLWxhc3QAQBRhcy1tZW1vcnkuZ3Jvdy12YWx1ZQBBD2FzLXJldHVybi12YWx1ZQBCD2FzLWRyb3Atb3BlcmFuZABDC2FzLWJyLXZhbHVlAEQSYXMtbG9jYWwuc2V0LXZhbHVlAEUSYXMtbG9jYWwudGVlLXZhbHVlAEYTYXMtZ2xvYmFsLnNldC12YWx1ZQBHD2FzLWxvYWQtb3BlcmFuZABIEGFzLXVuYXJ5LW9wZXJhbmQASQ5hcy1iaW5hcnktbGVmdABKD2FzLWJpbmFyeS1yaWdodABLD2FzLXRlc3Qtb3BlcmFuZABMD2FzLWNvbXBhcmUtbGVmdABNEGFzLWNvbXBhcmUtcmlnaHQAThJhcy1jb252ZXJ0LW9wZXJhbmQATwkmAQBBAAsgAAECAwUGBwgNCw4MKisyMzQ1Ng8QERIsLS4vMDEECQoK4QlQBQBBsgILBQBC5AILBwBDACBzRQsLAEQAAAAAAMiuQAsNAEQAAAAAAMiuQEEgCwQAIAALBAAgAAsEACAACwQAIAALBgAgACABCwYAIAEgAAsEACABCwQAIAELBAAgAQsEACABCwQAIAALBAAgAAsEACAACwQAIAALXQBBABEAAEIAQQARFABCAEQAAAAAAAAAAEEAQgBBABEVAEEAEQAAQQARAQBFGkEAEQEARRpCAEEAERYARRpCAEQAAAAAAAAAAEEAQgBBABEXAEUaQgBBABEHAFAaCwcAQQARAQALBwBBARECAAsHAEECEQMACwcAQQMRBAALBwBBHREFAAsKAELkAEEFEQcACwkAQSBBBBEGAAsKAELAAEEFEQcACwwAQ8P1qD9BBhEIAAsQAEQ9CtejcD36P0EHEQkACw4AQ2ZmAEJBIEEIEQwACwwAQSBCwABBCRENAAsVAEQAAAAAAABQQEMAAABCQQoRDgALEwBCwABEZmZmZmYGUEBBCxEPAAsHAEEdEQUACxIAQQFEAAAAAAAAAEBBHhEKAAsLAEEBQgJBHxELAAsJACABIAARBwALCQBCCSAAEREACwkAQQkgABEQAAsMAEMAABBBIAAREgALEABEAAAAAAAAIkAgABETAAsYACAAUAR+QgEFIAAgAEIBfUEMEQcAfgsLIgAgAEIBWAR+QgEFIABCAn1BDREHACAAQgF9QQ0RBwB8CwsYACAARQR/QQEFIAAgAEEBa0EXEQYAbAsLIwAgAEMAAAAAWwR9QwAAgD8FIAAgAEMAAIA/k0EYEQgAlAsLLwAgAEQAAAAAAAAAAGEEfEQAAAAAAADwPwUgACAARAAAAAAAAPA/oUEZEQkAogsLIgAgAEEBTQR/QQEFIABBAmtBGhEGACAAQQFrQRoRBgBqCwsuACAAQwAAgD9fBH1DAACAPwUgAEMAAABAk0EbEQgAIABDAACAP5NBGxEIAJILCz4AIABEAAAAAAAA8D9lBHxEAAAAAAAA8D8FIABEAAAAAAAAAEChQRwRCQAgAEQAAAAAAADwP6FBHBEJAKALCxUAIABFBH9BLAUgAEEBa0EPEQYACwsWACAARQR/QeMABSAAQQFrQQ4RBgALCwcAQRARAAALBwBBEhEAAAsHAEEREQAACwwAQQARAQBBAkEDGwsMAEECQQARAQBBAxsLDABBAkEDQQARAQAbCw8AQQARAQAEf0EBBUECCwsOAAJ+QQERAgBBAg0ACwsOAAJ/QQJBABEBAA0ACwsQAAJ9QQIRAwBBAg4BAAALCxAAAn9BAkEAEQEADgEAAAsLDABBABEBAEEBNgIACwwAQQpBAxEEADkDAAsJAEEAEQEAQAALCgBBAUEEEQYADwsKAEIBQQURBwAaCxEAAn1DAACAP0EGEQgADAALCxYBAXxEAAAAAAAA8D9BBxEJACEAIAALFAEBfEQAAAAAAADwP0EHEQkAIgALFABEAAAAAAAA8D9BBxEJACQAIwALCgBBABEBACgCAAsQAAJ9QwAAAABBBhEIAJELCw8AAn9BAUEEEQYAQQpqCwsPAAJ/QQpBAUEEEQYAawsLDQACf0EBQQQRBgBFCwsPAAJ/QQFBBBEGAEEKTQsLDwACf0EKQQFBBBEGAEcLCw0AAn5BAUEEEQYArAsL", + "AGFzbQEAAAABEQNgAn9/AX9gAABgA39/fwF/AwoJAAAAAAABAgICBAwDcAECAnABAwNwAAQHHAMGY2FsbC0xAAYGY2FsbC0yAAcGY2FsbC0zAAgJIwQAQQALAgABAgFBAAsAAwIDBAICQQALAAIBAgICQQMLAAEFClAJBwAgACABagsHACAAIAFrCwcAIAAgAWwLBwAgACABbgsHACAAIAFwCwIACwsAIAAgASACEQAACwsAIAAgASACEQABCwsAIAAgASACEQACCw==", + "AGFzbQEAAAABCAJgAABgAAF/AwwLAAAAAAAAAAAAAQEEBAFwAAEKZgsGAAARAAALBwAAEQAAAQsJAAARAAARAAALCQAAEQAAEQAACwwAABEAABEAABEAAAsGAAARAAALBgAAEQAACwkAABEAABEAAAsJAAARAAARAAALBwAAEQAAGwsKAAARAAAbEQAACw==", + "AGFzbQEAAAABGgRgAX4BfmABfgJ+fmACfn4Dfn5+YAJ+fgF+AwkIAAAAAAABAgAHSwYHZmFjLXJlYwAADWZhYy1yZWMtbmFtZWQAAQhmYWMtaXRlcgACDmZhYy1pdGVyLW5hbWVkAAMHZmFjLW9wdAAEB2ZhYy1zc2EABwrrAQgXACAAQgBRBH5CAQUgACAAQgF9EAB+CwsXACAAQgBRBH5CAQUgACAAQgF9EAF+CwsvAQJ+IAAhAUIBIQICQANAIAFCAFEEQAwCBSABIAJ+IQIgAUIBfSEBCwwACwsgAgsvAQJ+IAAhAUIBIQICQANAIAFCAFEEQAwCBSABIAJ+IQIgAUIBfSEBCwwACwsgAgssAQF+QgEhAQJAIABCAlMNAANAIAEgAH4hASAAQn98IQAgAEIBVQ0ACwsgAQsGACAAIAALCAAgACABIAALHABCASAAAwMQBhAGfhAGQgF9EAVCAFYNABoPCws=", + "AGFzbQEAAAABBgFgAX8BfwMDAgAABw4CBGV2ZW4AAANvZGQAAQorAhQAIABBAEYEf0EBBSAAQQFrEAELCxQAIABBAEYEf0EABSAAQQFrEAALCw==", + "AGFzbQEAAAABvAEcYAAAYAAAYAABf2ABfwBgA398fwF/YAAAYAN/fH4AYAJ/fABgBX99fn98AGAAA398fWAAAn98YAAFf31+f3xgBH99fn8Df35/YAABfmAAAX1gAAF8YAJ/fwF/YAJ+fgF+YAJ9fQF9YAJ8fAF8YAZ9f35/fH8BfGAAA39/f2AAAn9+YAF/AX9gAX8Cf35gAX8Cf39gAAJ/f2ARf359fX98fX9/f318fHx/f30QfH1/f39+fX9/fXx8f31/fANiYQAAAAAAAAAAAAAAAAAAAwMGBwgAAAIJCgsBAgMEAgMEAAUMAAINDg8CDQ4PDxAREhMQERITFAAAAg0ODwoVAAIWAAINDg8KFQIWAAINDg8KFQIWAxcYAxcYAxcZGwINDg8HlAlGAWYAAgFnAAQKdHlwZS11c2UtMQAZCnR5cGUtdXNlLTIAGgp0eXBlLXVzZS0zABsKdHlwZS11c2UtNAAcCnR5cGUtdXNlLTUAHQp0eXBlLXVzZS02AB4KdHlwZS11c2UtNwAfD2xvY2FsLWZpcnN0LWkzMgAkD2xvY2FsLWZpcnN0LWk2NAAlD2xvY2FsLWZpcnN0LWYzMgAmD2xvY2FsLWZpcnN0LWY2NAAnEGxvY2FsLXNlY29uZC1pMzIAKBBsb2NhbC1zZWNvbmQtaTY0ACkQbG9jYWwtc2Vjb25kLWYzMgAqEGxvY2FsLXNlY29uZC1mNjQAKwtsb2NhbC1taXhlZAAsD3BhcmFtLWZpcnN0LWkzMgAtD3BhcmFtLWZpcnN0LWk2NAAuD3BhcmFtLWZpcnN0LWYzMgAvD3BhcmFtLWZpcnN0LWY2NAAwEHBhcmFtLXNlY29uZC1pMzIAMRBwYXJhbS1zZWNvbmQtaTY0ADIQcGFyYW0tc2Vjb25kLWYzMgAzEHBhcmFtLXNlY29uZC1mNjQANAtwYXJhbS1taXhlZAA1BWVtcHR5ADYKdmFsdWUtdm9pZAA3CXZhbHVlLWkzMgA4CXZhbHVlLWk2NAA5CXZhbHVlLWYzMgA6CXZhbHVlLWY2NAA7DXZhbHVlLWkzMi1mNjQAPBF2YWx1ZS1pMzItaTMyLWkzMgA9EHZhbHVlLWJsb2NrLXZvaWQAPg92YWx1ZS1ibG9jay1pMzIAPxN2YWx1ZS1ibG9jay1pMzItaTY0AEAMcmV0dXJuLWVtcHR5AEEKcmV0dXJuLWkzMgBCCnJldHVybi1pNjQAQwpyZXR1cm4tZjMyAEQKcmV0dXJuLWY2NABFDnJldHVybi1pMzItZjY0AEYScmV0dXJuLWkzMi1pMzItaTMyAEcQcmV0dXJuLWJsb2NrLWkzMgBIFHJldHVybi1ibG9jay1pMzItaTY0AEkLYnJlYWstZW1wdHkASglicmVhay1pMzIASwlicmVhay1pNjQATAlicmVhay1mMzIATQlicmVhay1mNjQATg1icmVhay1pMzItZjY0AE8RYnJlYWstaTMyLWkzMi1pMzIAUA9icmVhay1ibG9jay1pMzIAURNicmVhay1ibG9jay1pMzItaTY0AFIRYnJlYWstYnJfaWYtZW1wdHkAUw9icmVhay1icl9pZi1udW0AVBNicmVhay1icl9pZi1udW0tbnVtAFUUYnJlYWstYnJfdGFibGUtZW1wdHkAVhJicmVhay1icl90YWJsZS1udW0AVxZicmVhay1icl90YWJsZS1udW0tbnVtAFgbYnJlYWstYnJfdGFibGUtbmVzdGVkLWVtcHR5AFkZYnJlYWstYnJfdGFibGUtbmVzdGVkLW51bQBaHWJyZWFrLWJyX3RhYmxlLW5lc3RlZC1udW0tbnVtAFsJbGFyZ2Utc2lnAFwOaW5pdC1sb2NhbC1pMzIAXQ5pbml0LWxvY2FsLWk2NABeDmluaXQtbG9jYWwtZjMyAF8OaW5pdC1sb2NhbC1mNjQAYAq3BmECAAsCAAsCAAsCAAsCAAsCAAsCAAsEAQF/CwQBAX8LCAMBfwF8AX4LBgIBfwF8CwwFAX8BfQF+AX8BfAsCAAsCAAsCAAsCAAsCAAsCAAsCAAsCAAsCAAsDAAALAwAACwMAAAsDAAALAgALBABBAAsCAAsEAEEACwQAQQALAgALBABBAAsCAAsCAAsQBgF9AX8BfgF/AXwBfwAACxAGAX0BfwF+AX8BfAF/AAALBgECfyAACwYBAn4gAAsGAQJ9IAALBgECfCAACwYBAn8gAQsGAQJ+IAELBgECfSABCwYBAnwgAQsoBgF9AX8BfgF/AXwBfyAAjBogAUUaIAJQGiADRRogBJoaIAVFGiAECwQAIAALBAAgAAsEACAACwQAIAALBAAgAQsEACABCwQAIAELBAAgAQscACAAjBogAUUaIAJQGiADRRogBJoaIAVFGiAECwIACwQAEAALBQBBzQALBQBC4TwLBwBDZmabQgsLAEThehSuR3FTQAsOAEHNAEQAAAAAAAAcQAsIAEEBQQJBAwsJAAJAEAAQAAsLCgACfxAAQc0ACwsLAAIWEABBAUICCwsDAA8LBgBBzgAPCwYAQsY9DwsIAENmZp1CDwsMAERSuB6F67FTQA8LDwBBzgBEUrgeheuxU0APCwkAQQFBAkEDDwsLAAJ/EABBzQALDwsMAAIWEABBAUICCw8LBAAMAAsHAEHPAAwACwcAQqs+DAALCQBDzcyfQgwACw0ARMP1KFyP8lNADAALEABBzwBEw/UoXI/yU0AMAAsKAEEBQQJBAwwACwwAAn8QAEHNAAsMAAsNAAIWEABBAUICCwwACwYAIAANAAsLAEEyIAANABpBMwsQAEEyQjMgAA0AGhpBM0I0CwkAIAAOAgAAAAsMAEEyIAAOAQAAQTMLEABBMkIzIAAOAQAAQTNCNAsMAAJAIAAOAgABAAsLEwACf0EyIAAOAgABAEEzC0ECagsXAAIaQTJBMyAADgIAAQBBM0F9C2pBNAsiACAFIAIgACAIIAcgASADIAkgBCAGIA0gCyAPIBAgDiAMCwYBAX8gAAsGAQF+IAALBgEBfSAACwYBAXwgAAs=", + "AGFzbQEAAAABDANgAX8AYAABfGAAAAMGBQEAAAECCikFCwBEAAAAAAAAAAALAgALAgALCwBEAAAAAAAAAAALCQBBABACEAMaCw==", + "AGFzbQEAAAABCgJgAAF/YAF/AX8DBQQBAQEABw0DAWYAAAFnAAIBcAADCiEEBgEBfyABCwYBAX8gAQsGACAAEAELCgEBf0EqIQAgAAs=", + "AGFzbQEAAAABHQRgAABgAABgCH5+fH58fn1/AGAIfH58fnx+fX8AAwwLAAMAAwMCAgAAAAAEBQFwAQcHB3cEGXNpZ25hdHVyZS1leHBsaWNpdC1yZXVzZWQABxlzaWduYXR1cmUtaW1wbGljaXQtcmV1c2VkAAgcc2lnbmF0dXJlLWV4cGxpY2l0LWR1cGxpY2F0ZQAJHHNpZ25hdHVyZS1pbXBsaWNpdC1kdXBsaWNhdGUACgkNAQBBAAsHBAIBBAAFBgqFAgsCAAsCAAsCAAsCAAsCAAsCAAsCAAsMAEEBEQAAQQQRAAALiQEARAAAAAAAAAAAQgBEAAAAAAAAAABCAEQAAAAAAAAAAEIAQwAAAABBAEEAEQMARAAAAAAAAAAAQgBEAAAAAAAAAABCAEQAAAAAAAAAAEIAQwAAAABBAEECEQMARAAAAAAAAAAAQgBEAAAAAAAAAABCAEQAAAAAAAAAAEIAQwAAAABBAEEDEQMACwcAQQERAQALTgBCAEIARAAAAAAAAAAAQgBEAAAAAAAAAABCAEMAAAAAQQBBBRECAEIAQgBEAAAAAAAAAABCAEQAAAAAAAAAAEIAQwAAAABBAEEGEQIACw==", + "AGFzbQEAAAABGwdgAABgAABgAABgAAF/YAABf2ABfwF/YAF/AAIWAQhzcGVjdGVzdAlwcmludF9pMzIABgMHBgABBAUFBgccBANvbmUAAwN0d28ABAV0aHJlZQAFBGZvdXIABgojBgIACwIACwQAQQ0LBwAgAEEBagsHACAAQQJrCwYAIAAQAAs=", + "AGFzbQEAAAABDgNgAAF/YAABf2ABfwF/AwgHAAAAAQECAgQFAXABBwcHEQIFY2FsbHQABQVjYWxsdQAGCQ0BAEEACwcAAQIDBAACCioHBABBAQsEAEECCwQAQQMLBABBBAsEAEEFCwcAIAARAAALBwAgABEBAAs=", + "AGFzbQEAAAABCgJgAAF/YAF/AX8DBAMAAAEEBQFwAQICBwkBBWNhbGx0AAIJCAEAQQALAgABChMDBABBAQsEAEECCwcAIAARAAAL", + "AGFzbQEAAAABXRBgAn9/AX9gAABgAAF/YAF/AGADf3x/A398f2ABfwF/YAF/An9/YAADf35/YAACf39gAAJ9fWABfwN/f35gAAN/f35gAn9/An9/YAN+fn8Cfn9gAn5+AX5gAX4BfgM2NQEDBQYABQUFBQUFBQUFBQUABQUFAwMFBQUFAwUFBQUFBQAFAAUFBQIFCgUFBQUFBQUNDgEBBAUBcAEBAQUDAQABBgYBfwFBCgsHzgYyBWVtcHR5AAEIc2luZ3VsYXIAAgVtdWx0aQADBm5lc3RlZAAED2FzLXNlbGVjdC1maXJzdAAFDWFzLXNlbGVjdC1taWQABg5hcy1zZWxlY3QtbGFzdAAHDWFzLWxvb3AtZmlyc3QACAthcy1sb29wLW1pZAAJDGFzLWxvb3AtbGFzdAAKD2FzLWlmLWNvbmRpdGlvbgALDmFzLWJyX2lmLWZpcnN0AAwNYXMtYnJfaWYtbGFzdAANEWFzLWJyX3RhYmxlLWZpcnN0AA4QYXMtYnJfdGFibGUtbGFzdAAPFmFzLWNhbGxfaW5kaXJlY3QtZmlyc3QAERRhcy1jYWxsX2luZGlyZWN0LW1pZAASFWFzLWNhbGxfaW5kaXJlY3QtbGFzdAATDmFzLXN0b3JlLWZpcnN0ABQNYXMtc3RvcmUtbGFzdAAVFGFzLW1lbW9yeS5ncm93LXZhbHVlABYNYXMtY2FsbC12YWx1ZQAYD2FzLXJldHVybi12YWx1ZQAZD2FzLWRyb3Atb3BlcmFuZAAaC2FzLWJyLXZhbHVlABsSYXMtbG9jYWwuc2V0LXZhbHVlABwSYXMtbG9jYWwudGVlLXZhbHVlAB0TYXMtZ2xvYmFsLnNldC12YWx1ZQAeD2FzLWxvYWQtb3BlcmFuZAAfEGFzLXVuYXJ5LW9wZXJhbmQAIBFhcy1iaW5hcnktb3BlcmFuZAAhD2FzLXRlc3Qtb3BlcmFuZAAiEmFzLWNvbXBhcmUtb3BlcmFuZAAjEmFzLWJpbmFyeS1vcGVyYW5kcwAkE2FzLWNvbXBhcmUtb3BlcmFuZHMAJRFhcy1taXhlZC1vcGVyYW5kcwAmCmJyZWFrLWJhcmUAJwticmVhay12YWx1ZQAoEWJyZWFrLW11bHRpLXZhbHVlACkFcGFyYW0AKgZwYXJhbXMAKwlwYXJhbXMtaWQALAtwYXJhbS1icmVhawAtDHBhcmFtcy1icmVhawAuD3BhcmFtcy1pZC1icmVhawAvB2VmZmVjdHMAMBJhZGQ2NF91X3dpdGhfY2FycnkAMRFhZGQ2NF91X3NhdHVyYXRlZAAyCHR5cGUtdXNlADMSYXR5cGljYWwtY29uZGl0aW9uADQJBwEAQQALARAKxgs1AgALFgAgAARACyAABEALIAAEQAsgAARACwsaACAABEABCyAABEABBQELIAAEf0EHBUEICwtnACAABEAQABAAEAALIAAEQAUQABAAEAALIAAEfxAAEABBCBAABRAAEABBCRAACyAABAcQABAAQQEQABAAEABCAhAAEAAQAEEDEAAFEAAQAEF/EAAQABAAQn4QABAAEABBfRAACxoaC1IAIAAEfyABBEAQAAJACwELIAEEQAUQAAJACwELIAEEfxAAQQkFEABBCgsFIAEEQBAAAkALAQsgAQRABRAAAkALAQsgAQR/EABBCgUQAEELCwsLFQAgAAR/EABBAQUQAEEAC0ECQQMbCxUAQQIgAAR/EABBAQUQAEEAC0EDGwsVAEECQQMgAAR/EABBAQUQAEEACxsLFwADfyAABH8QAEEBBRAAQQALEAAQAAsLFwADfxAAIAAEfxAAQQEFEABBAAsQAAsLFwADfxAAEAAgAAR/EABBAQUQAEEACwsLGAAgAAR/QQEFQQALBH8QAEECBRAAQQMLCxoAAn8gAAR/EABBAQUQAEEAC0ECDQBBAw8LCxoAAn9BAiAABH8QAEEBBRAAQQALDQBBAw8LCxkAAn8gAAR/EABBAQUQAEEAC0ECDgEAAAsLGQACf0ECIAAEfxAAQQEFEABBAAsOAQAACwsEACAACxoAAn8gAAR/EABBAQUQAEEAC0ECQQARAAALCxoAAn9BAiAABH8QAEEBBRAAQQALQQARAAALCxoAAn9BAkEAIAAEfxAAQQEFEABBAAsRAAALCxUAIAAEfxAAQQEFEABBAAtBAjYCAAsVAEECIAAEfxAAQQEFEABBAAs2AgALDgAgAAR/QQEFQQALQAALBAAgAAsOACAABH9BAQVBAAsQFwsNACAABH9BAQVBAAsPCw0AIAAEf0EBBUEACxoLEQACfyAABH9BAQVBAAsMAAsLEgEBfyAABH9BAQVBAAshACAACw4AIAAEf0EBBUEACyIACxAAIAAEf0EBBUEACyQAIwALDwAgAAR/QQsFQQoLKAIACxEAIAAEfxAAQQ0FEABBcwtoCx8AIAAEfxAAQQMFEABBfQsgAQR/EABBBAUQAEF7C2wLEQAgAAR/EABBDQUQAEEAC0ULKwAgAAR9EABDAABAQAUQAEMAAEDACyABBH0QAEMAAIBABRAAQwAAgMALXgsZACAABAgQAEEDEABBBAUQAEEDEABBfAtsCyUAIAAECRAAQwAAQEAQAEMAAEBABRAAQwAAAMAQAEMAAEDAC14LHAAgAAQIEABBAxAAQQQFEABBfRAAQXwLQQVqbAtnAEEBBEAMAAALQQEEQAwAAAUAC0EABEAABQwAAAtBAQRAQQENAAALQQEEQEEBDQAABQALQQAEQAAFQQENAAALQQEEQEEADgAAAAtBAQRAQQAOAAAABQALQQAEQAAFQQAOAAAAC0ETCxQAIAAEf0ESDABBEwVBFQwAQRQLCyQAIAAEC0ESQW5CEgwAQRNBbUITBUFuQRJCbgwAQW1BE0JtCwsQAEEBIAAEBUECagVBfmoLCw4AQQFBAiAABABqBWsLCwwAQQFBAiAABAwLagsUAEEBIAAEBUECagwABUF+agwACwsSAEEBQQIgAAQAagwABWsMAAsLDgBBAUECIAAEDAwAC2oLUQEBfwJ/QQEhASAACwRAIAFBA2whASABQQVrIQEgAUEHbCEBDAAgAUHkAGwhAQUgAUEFbCEBIAFBB2shASABQQNsIQEMACABQegHbCEBCyABCxcBAX4gACABfCACrXwhAyADIAMgAFQPCxAAIAAgAUEAEDEEDxpCfwsLVQBBAQRAC0EBBH9BAAVBAgtBAQQDGgUaC0EARAAAAAAAAAAAQQBBAQQECxoaGkEBBH9BAAVBAgtBAQQDGgUaC0EARAAAAAAAAAAAQQBBAQQECxoaGgsNAEEABEALQQFFBEALCw==", + "AGFzbQEAAAABCgJgAAF/YAF/AX8DExIAAAAAAQAAAAABAQAAAAAAAAAHmgESBWJsb2NrAAAFbG9vcDEAAQVsb29wMgACBWxvb3AzAAMFbG9vcDQABAVsb29wNQAFBWxvb3A2AAYCaWYABwNpZjIACAZzd2l0Y2gACQZyZXR1cm4ACgZicl9pZjAACwZicl9pZjEADAZicl9pZjIADQZicl9pZjMADgJicgAPCXNoYWRvd2luZwAQDHJlZGVmaW5pdGlvbgARCoMGEgsAAn9BAQwAQQALCyMBAX9BACEAAn8DfyAAQQFqIQAgAEEFRgRAIAAMAgsMAAsLCzQBAX9BACEAAn8DfyAAQQFqIQAgAEEFRgRADAELIABBCEYEQCAADAILIABBAWohAAwACwsLIwEBf0EAIQACfwN/IABBAWohACAAQQVGBEAgAAwCCyAACwsLIwEBf0EBIQECfwN/IAEgAWohASABIABLBEAgAQwCCwwACwsLCgADf0EBC0EBagsLAAN/QQANAEEDCwuEAQEBf0EAIQACQEEBBEAMAEGaBSEACyAAQQFqIQBBAQRADABBmgUhAAVB+AYhAAsgAEEBaiEAQQEEQAwAQZoFIQAFQfgGIQALIABBAWohAEEABEBB+AYhAAUMAEGaBSEACyAAQQFqIQBBAARAQfgGIQAFDABBmgUhAAsgAEEBaiEACyAAC4QBAQF/QQAhAAJAQQEEQAwAQZoFIQALIABBAWohAEEBBEAMAEGaBSEABUH4BiEACyAAQQFqIQBBAQRADABBmgUhAAVB+AYhAAsgAEEBaiEAQQAEQEH4BiEABQwAQZoFIQALIABBAWohAEEABEBB+AYhAAUMAEGaBSEACyAAQQFqIQALIAALLQACf0EKAn8CQAJAAkACQAJAIAAOBAQAAQIDCwtBAgwDC0EDDAMLC0EFC2wLCxgAAkACQAJAIAAOAQABDAILQQAPCwtBAgtWAQF/QQAhAAJ/AkBBAA0AIABBAXIhAEEBDQAgAEECciEACwJ/IABBBHIhACAAC0EADQAaIABBCHIhAAJ/IABBEHIhACAAC0EBDQAaIABBIHIhACAACwsTAAJ/An9BAQwAC0EBDQAaQQALCxgAAn9BAQRAAn9BAQwAC0EBDQEaC0EACwskAQF/An8Cf0EBIQAgAAsCf0ECIQAgAAsNABpBAAtBAGoaIAALIQACf0EBBEACf0EBDAALDAEFAkACf0EBDAALGgsLQQELCwwAAn9BAQwAQQJzCwsSAAJ/An9BAgsCf0EDDAALagsL", + "AGFzbQEAAAABTg5gAn9/AX9gAABgAAF/YAF/AGADf3x/A398f2AAA39+f2ABfwF/YAACf39gAAJ9fWAAA39/fmADf39+AGACf38Cf39gAX4BfmACfX0BfQM5OAEBAgICAgICAgECAgICAgIAAgICAQECBgICAQICAgICAgICAgICAgICCQICAgICAgICAgIMDA0BBAUBcAEBAQUDAQABBgYBfwFBAAsHyAY1BWVtcHR5AAEIc2luZ3VsYXIAAgVtdWx0aQADBm5lc3RlZAAEBGRlZXAABQ9hcy1zZWxlY3QtZmlyc3QABg1hcy1zZWxlY3QtbWlkAAcOYXMtc2VsZWN0LWxhc3QACA9hcy1pZi1jb25kaXRpb24ACQphcy1pZi10aGVuAAoKYXMtaWYtZWxzZQALDmFzLWJyX2lmLWZpcnN0AAwNYXMtYnJfaWYtbGFzdAANEWFzLWJyX3RhYmxlLWZpcnN0AA4QYXMtYnJfdGFibGUtbGFzdAAPFmFzLWNhbGxfaW5kaXJlY3QtZmlyc3QAERRhcy1jYWxsX2luZGlyZWN0LW1pZAASFWFzLWNhbGxfaW5kaXJlY3QtbGFzdAATDmFzLXN0b3JlLWZpcnN0ABQNYXMtc3RvcmUtbGFzdAAVFGFzLW1lbW9yeS5ncm93LXZhbHVlABYNYXMtY2FsbC12YWx1ZQAYD2FzLXJldHVybi12YWx1ZQAZD2FzLWRyb3Atb3BlcmFuZAAaC2FzLWJyLXZhbHVlABsSYXMtbG9jYWwuc2V0LXZhbHVlABwSYXMtbG9jYWwudGVlLXZhbHVlAB0TYXMtZ2xvYmFsLnNldC12YWx1ZQAeD2FzLWxvYWQtb3BlcmFuZAAfEGFzLXVuYXJ5LW9wZXJhbmQAIBFhcy1iaW5hcnktb3BlcmFuZAAhD2FzLXRlc3Qtb3BlcmFuZAAiEmFzLWNvbXBhcmUtb3BlcmFuZAAjEmFzLWJpbmFyeS1vcGVyYW5kcwAkE2FzLWNvbXBhcmUtb3BlcmFuZHMAJRFhcy1taXhlZC1vcGVyYW5kcwAmCmJyZWFrLWJhcmUAJwticmVhay12YWx1ZQAoEWJyZWFrLW11bHRpLXZhbHVlACkOYnJlYWstcmVwZWF0ZWQAKgticmVhay1pbm5lcgArCmNvbnQtaW5uZXIALAVwYXJhbQAtBnBhcmFtcwAuCXBhcmFtcy1pZAAvC3BhcmFtLWJyZWFrADAMcGFyYW1zLWJyZWFrADEPcGFyYW1zLWlkLWJyZWFrADIHZWZmZWN0cwAzBXdoaWxlADQDZm9yADUHbmVzdGluZwA2CHR5cGUtdXNlADcJBwEAQQALARAKgws4AgALCAADQAsDQAsLCwADQAELA39BBwsLPAADQBAAEAAQABAACwN/EAAQAEEIEAALGgMFEAAQABAAQQgQABAAEAAQAEIHEAAQABAAEABBCRAACxoaCxUAA38DQBAAAkALAQsDfxAAQQkLCwt/AAN/An8DfwJ/A38CfwN/An8DfwJ/A38CfwN/An8DfwJ/A38CfwN/An8DfwJ/A38CfwN/An8DfwJ/A38CfwN/An8DfwJ/A38CfwN/An8DfwJ/EABBlgELCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwwAA39BAQtBAkEDGwsMAEECA39BAQtBAxsLDABBAkEDA39BAQsbCwwAA39BAQsEQBAACwsPAEEBBH8Df0EBCwVBAgsLDwBBAQR/QQIFA39BAQsLCw4AAn8Df0EBC0ECDQALCw4AAn9BAgN/QQELDQALCxAAAn8Df0EBC0ECDgEAAAsLEAACf0ECA39BAQsOAQAACwsEACAACxEAAn8Df0EBC0ECQQARAAALCxEAAn9BAgN/QQELQQARAAALCxEAAn9BAUECA39BAAsRAAALCwwAA39BAQtBATYCAAsMAEEKA39BAQs2AgALCQADf0EBC0AACwQAIAALCQADf0EBCxAXCwgAA39BAQsPCwgAA39BAQsaCwwAAn8Df0EBCwwACwsNAQF/A39BAQshACAACwsBAX8Df0EBCyIACwsAA39BAQskACMACwoAA39BAQsoAgALCgADfxAAQQ0LaAsRAAN/EABBAwsDfxAAQQQLbAsKAAN/EABBDQtFCxcAA30QAEMAAEBACwN9EABDAABAQAteCw4AAwcQAEEDEABBBAtsCxQAAwgQAEMAAEBAEABDAABAQAteCxEAAwcQAEEDEABBBAtBBWpsCzQAAkADQAwBDAAACwsCQANAQQENAQALCwJAA0BBAA4AAQALCwJAA0BBAQ4CAQEBAAsLQRMLFwACf0EAAwMCQEESDAILQRQMAAtBEwsLJwACCUEAQQBCAAMKAkBBEkFuQhIMAgtBFEFsQhQMAAtBE0FtQhMLCzQAAn8Df0ESDAFBEwwBQRRBAA0BGkEUQQENARpBFQwBQRZBAA4AAUEXQQEOAgEBAUEVCwsLZgEBf0EAIQAgAAJ/A38Cf0EBDAILCwtqIQAgAAJ/A38Df0ECDAILCwtqIQAgAAJ/A38CfwN/QQQMAQsLCwtqIQAgAAJ/A39BCAwBaAsLaiEAIAACfwN/A39BEAwCC2gLC2ohACAACzABAX9BACEAIAADfwN/DAELC2ohACAAA38MAGgLaiEAIAADfwN/DAELaAtqIQAgAAsKAEEBAwZBAmoLCwoAQQFBAgMAagsLCgBBAUECAwsLagsVAQF/QQEDBkEEaiIAIABBCkkNAAsLGAEBf0EBQQIDAGoiAEEDIABBCkkNABoLCx4BAX9BACEAQQFBAgMLIABBAWohACAAQQpJDQALagsyAQF/AkADQEEBIQAgAEEDbCEAIABBBWshACAAQQdsIQAMASAAQeQAbCEACwsgAEFyRgslAQF+QgEhAQJAA0AgAFANASAAIAF+IQEgAEIBfSEADAALCyABCysBAn5CASEBQgIhAgJAA0AgAiAAVg0BIAEgAn4hASACQgF8IQIMAAsLIAELWgECfQJAA0AgAEMAAAAAWw0BIAEhAgJAA0AgAkMAAAAAWw0BIAJDAAAAAF0NAyADIAKSIQMgAkMAAABAkyECDAALCyADIACVIQMgAEMAAIA/kyEADAALCyADCz0AA0ALA39BAAsDAxoLQQBEAAAAAAAAAABBAAMECxoaGgN/QQALAwMaC0EARAAAAAAAAAAAQQADBAsaGhoL", + "AGFzbQEAAAABNgpgAAF/YAABfmAAAX1gAAF8YAF/AX9gAX4BfmABfQF9YAF8AXxgBX59fH9/AGAFfn18f38BfAMUEwABAgMEBQYHCAkEBAQEBAQEBAQHswITDnR5cGUtbG9jYWwtaTMyAAAOdHlwZS1sb2NhbC1pNjQAAQ50eXBlLWxvY2FsLWYzMgACDnR5cGUtbG9jYWwtZjY0AAMOdHlwZS1wYXJhbS1pMzIABA50eXBlLXBhcmFtLWk2NAAFDnR5cGUtcGFyYW0tZjMyAAYOdHlwZS1wYXJhbS1mNjQABwp0eXBlLW1peGVkAAgEcmVhZAAJDmFzLWJsb2NrLXZhbHVlAAoNYXMtbG9vcC12YWx1ZQALC2FzLWJyLXZhbHVlAAwOYXMtYnJfaWYtdmFsdWUADRNhcy1icl9pZi12YWx1ZS1jb25kAA4RYXMtYnJfdGFibGUtdmFsdWUADw9hcy1yZXR1cm4tdmFsdWUAEAphcy1pZi10aGVuABEKYXMtaWYtZWxzZQASCo4CEwYBAX8gAAsGAQF+IAALBgEBfSAACwYBAXwgAAsEACAACwQAIAALBAAgAAsEACAACywDAX0CfgF8IABQGiABjBogApoaIANFGiAERRogBYwaIAZQGiAHUBogCJoaCz8DAX0CfgF8QwAAsEAhBUIGIQZEAAAAAAAAIEAhCCAAuiABuyACIAO4IAS3IAW7IAa6IAe6IAigoKCgoKCgoAsHAAJ/IAALCwcAA38gAAsLCQACfyAADAALCwsAAn8gAEEBDQALCwsAAn8gACAADQALCx0AAkACQAJAIAAOAgABAkEADwtBAQ8LQQIPC0EDCwUAIAAPCwwAIAAEfyAABUEACwsMACAABH9BAQUgAAsL", + "AGFzbQEAAAABJQdgAABgAX8AYAF+AGABfQBgAXwAYAV+fXx/fwBgBX59fH9/AX4DFBMAAAAAAQIDBAUGAQEBAQEBAQEBB7QCEw50eXBlLWxvY2FsLWkzMgAADnR5cGUtbG9jYWwtaTY0AAEOdHlwZS1sb2NhbC1mMzIAAg50eXBlLWxvY2FsLWY2NAADDnR5cGUtcGFyYW0taTMyAAQOdHlwZS1wYXJhbS1pNjQABQ50eXBlLXBhcmFtLWYzMgAGDnR5cGUtcGFyYW0tZjY0AAcKdHlwZS1taXhlZAAIBXdyaXRlAAkOYXMtYmxvY2stdmFsdWUACg1hcy1sb29wLXZhbHVlAAsLYXMtYnItdmFsdWUADA5hcy1icl9pZi12YWx1ZQANE2FzLWJyX2lmLXZhbHVlLWNvbmQADhFhcy1icl90YWJsZS12YWx1ZQAPD2FzLXJldHVybi12YWx1ZQAQCmFzLWlmLXRoZW4AEQphcy1pZi1lbHNlABIK0gITCAEBf0EAIQALCAEBfkIAIQALCwEBfUMAAAAAIQALDwEBfEQAAAAAAAAAACEACwYAQQohAAsGAEILIQALCQBDmpkxQSEACw0ARGZmZmZmZihAIQALQAMBfQJ+AXxCACEAQwAAAAAhAUQAAAAAAAAAACECQQAhA0EAIQRDAAAAACEFQgAhBkIAIQdEAAAAAAAAAAAhCAtPAwF9An4BfEOamZm+IQFBKCEDQXkhBEMAALBAIQVCBiEGRAAAAAAAACBAIQggALogAbsgAiADuCAEtyAFuyAGuiAHuiAIoKCgoKCgoKCwCwkAAkBBASEACwsJAANAQQMhAAsLCwACQEEJIQAMAAsLDQACQEEIIQBBAQ0ACwsNAAJAQQZBCSEADQALCw4AAkBBCiEAQQEOAAALCwcAQQchAA8LCwAgAARAQQMhAAsLDAAgAARABUEBIQALCw==", + "AGFzbQEAAAABWBBgA39/fwF/YAABf2AAAX5gAAF9YAABfGABfwF/YAF+AX5gAX0BfWABfAF8YAV+fXx/fwBgBX59fH9/AX5gBX59fH9/AXxgAABgAX8AYAJ/fwF/YAF+AX8DOjkBAgMEBQYHCAkKCwwFBQUFBQUFDQUFDQUFBQUFBQ4OBQAFBQUFBQUFDAUMBQUNDQ0NBwUFBQUFDwUEBQFwAQEBBQMBAAEGBgF/AUEACwfPBzcOdHlwZS1sb2NhbC1pMzIAAA50eXBlLWxvY2FsLWk2NAABDnR5cGUtbG9jYWwtZjMyAAIOdHlwZS1sb2NhbC1mNjQAAw50eXBlLXBhcmFtLWkzMgAEDnR5cGUtcGFyYW0taTY0AAUOdHlwZS1wYXJhbS1mMzIABg50eXBlLXBhcmFtLWY2NAAHCnR5cGUtbWl4ZWQACAV3cml0ZQAJBnJlc3VsdAAKDmFzLWJsb2NrLWZpcnN0AAwMYXMtYmxvY2stbWlkAA0NYXMtYmxvY2stbGFzdAAODWFzLWxvb3AtZmlyc3QADwthcy1sb29wLW1pZAAQDGFzLWxvb3AtbGFzdAARC2FzLWJyLXZhbHVlABINYXMtYnJfaWYtY29uZAATDmFzLWJyX2lmLXZhbHVlABQTYXMtYnJfaWYtdmFsdWUtY29uZAAVEWFzLWJyX3RhYmxlLWluZGV4ABYRYXMtYnJfdGFibGUtdmFsdWUAFxdhcy1icl90YWJsZS12YWx1ZS1pbmRleAAYD2FzLXJldHVybi12YWx1ZQAZCmFzLWlmLWNvbmQAGgphcy1pZi10aGVuABsKYXMtaWYtZWxzZQAcD2FzLXNlbGVjdC1maXJzdAAdEGFzLXNlbGVjdC1zZWNvbmQAHg5hcy1zZWxlY3QtY29uZAAfDWFzLWNhbGwtZmlyc3QAIQthcy1jYWxsLW1pZAAiDGFzLWNhbGwtbGFzdAAjFmFzLWNhbGxfaW5kaXJlY3QtZmlyc3QAJBRhcy1jYWxsX2luZGlyZWN0LW1pZAAlFWFzLWNhbGxfaW5kaXJlY3QtbGFzdAAmFmFzLWNhbGxfaW5kaXJlY3QtaW5kZXgAJxJhcy1sb2NhbC5zZXQtdmFsdWUAKBJhcy1sb2NhbC50ZWUtdmFsdWUAKRNhcy1nbG9iYWwuc2V0LXZhbHVlACoPYXMtbG9hZC1hZGRyZXNzACsQYXMtbG9hZE4tYWRkcmVzcwAsEGFzLXN0b3JlLWFkZHJlc3MALQ5hcy1zdG9yZS12YWx1ZQAuEWFzLXN0b3JlTi1hZGRyZXNzAC8PYXMtc3RvcmVOLXZhbHVlADAQYXMtdW5hcnktb3BlcmFuZAAxDmFzLWJpbmFyeS1sZWZ0ADIPYXMtYmluYXJ5LXJpZ2h0ADMPYXMtdGVzdC1vcGVyYW5kADQPYXMtY29tcGFyZS1sZWZ0ADUQYXMtY29tcGFyZS1yaWdodAA2EmFzLWNvbnZlcnQtb3BlcmFuZAA3E2FzLW1lbW9yeS5ncm93LXNpemUAOAkHAQBBAAsBIAqDBzkIAQF/QQAiAAsIAQF+QgAiAAsLAQF9QwAAAAAiAAsPAQF8RAAAAAAAAAAAIgALBgBBCiIACwYAQgsiAAsJAEOamTFBIgALDQBEZmZmZmZmKEAiAAtSAwF9An4BfEIAIgBQGkMAAAAAIgGMGkQAAAAAAAAAACICmhpBACIDRRpBACIERRpDAAAAACIFjBpCACIGUBpCACIHUBpEAAAAAAAAAAAiCJoaC1UDAX0CfgF8Q5qZmb4iARpBKCIDGkF5IgQaQwAAsEAiBRpCBiIGGkQAAAAAAAAgQCIIGiAAuiABuyACIAO4IAS3IAW7IAa6IAe6IAigoKCgoKCgoLALTwMBfQJ+AXxCASIAukMAAABAIgG7RGZmZmZmZgpAIgJBBCIDuEEFIgS3QwAAsEAiBbtCBiIGukIAIge6RAAAAAAAACBAIgigoKCgoKCgoAsCAAsLAAJ/QQEiABALCwsNAAJ/EAtBASIAEAsLCw0AAn8QCxALQQEiAAsLCwADf0EDIgAQCwsLDQADfxALQQQiABALCwsNAAN/EAsQC0EFIgALCwsAAn9BCSIADAALCwsAAkBBASIADQALCxAAAn9BCCIAQQENABpBBwsLEAACf0EGQQkiAA0AGkEHCwsOAAJAQQAiAA4CAAAACwsSAAJ/QQoiAEEBDgIAAABBBwsLEQACf0EGQQsiAA4BAABBBwsLBwBBByIADwsOAEECIgAEf0EABUEBCwsOACAABH9BAyIABSAACwsOACAABH8gAAVBBCIACwsLAEEFIgAgACABGwsLACAAQQYiACABGwsLAEEAQQFBByIAGwsEAEF/CwwAQQwiAEECQQMQIAsMAEEBQQ0iAEEDECALDABBAUECQQ4iABAgCw8AQQEiAEECQQNBABEAAAsPAEEBQQIiAEEDQQARAAALDwBBAUECQQMiAEEAEQAACw8AQQFBAkEDQQAiABEAAAsKAQF/QQEiACEACwgAQQEiACIACwoBAX9BASIAJAALCQBBASIAKAIACwkAQQMiACwAAAsLAEEeIgBBBzYCAAsLAEECQQEiADYCAAsLAEEBIgBBBzoAAAsLAEECQQEiADsBAAsKAEPi8YB/IgCMCwkAQQMiAEEKagsJAEEKQQQiAGsLBwBBACIARQsJAEErIgBBCkwLCQBBCkEqIgBHCwcAQikiAKcLCABBKCIAQAAL", + "AGFzbQEAAAABIwdgAn9/AX9gAABgA39/fwF/YAABf2ABfwBgAX8Bf2ACf38AA1RTAQIDAwMDBAQEBQUFBQUDAwMDAwMDAwQEBAUFBQUFBQUFBQUFBQUFAgICAgIFBQUFBQUFBQUFBQUFBQUFBQADAwMDAwUFBQUFBQMDAwUFBQYGBgYEBQFwAQEBBQMBAAEGBgF/AUEACwfqC1ANYXMtZnVuYy1maXJzdAACC2FzLWZ1bmMtbWlkAAMMYXMtZnVuYy1sYXN0AAQSYXMtZnVuYy1ldmVyeXdoZXJlAAUNYXMtZHJvcC1maXJzdAAGDGFzLWRyb3AtbGFzdAAHEmFzLWRyb3AtZXZlcnl3aGVyZQAID2FzLXNlbGVjdC1maXJzdAAJDmFzLXNlbGVjdC1taWQxAAoOYXMtc2VsZWN0LW1pZDIACw5hcy1zZWxlY3QtbGFzdAAMFGFzLXNlbGVjdC1ldmVyeXdoZXJlAA0OYXMtYmxvY2stZmlyc3QADgxhcy1ibG9jay1taWQADw1hcy1ibG9jay1sYXN0ABATYXMtYmxvY2stZXZlcnl3aGVyZQARDWFzLWxvb3AtZmlyc3QAEgthcy1sb29wLW1pZAATDGFzLWxvb3AtbGFzdAAUEmFzLWxvb3AtZXZlcnl3aGVyZQAVD2FzLWlmLWNvbmRpdGlvbgAWCmFzLWlmLXRoZW4AFwphcy1pZi1lbHNlABgLYXMtYnItZmlyc3QAGQphcy1ici1sYXN0ABoQYXMtYnItZXZlcnl3aGVyZQAbDmFzLWJyX2lmLWZpcnN0ABwMYXMtYnJfaWYtbWlkAB0NYXMtYnJfaWYtbGFzdAAeE2FzLWJyX2lmLWV2ZXJ5d2hlcmUAHxFhcy1icl90YWJsZS1maXJzdAAgD2FzLWJyX3RhYmxlLW1pZAAhEGFzLWJyX3RhYmxlLWxhc3QAIhZhcy1icl90YWJsZS1ldmVyeXdoZXJlACMPYXMtcmV0dXJuLWZpcnN0ACQOYXMtcmV0dXJuLWxhc3QAJRRhcy1yZXR1cm4tZXZlcnl3aGVyZQAmDWFzLWNhbGwtZmlyc3QAJwxhcy1jYWxsLW1pZDEAKAxhcy1jYWxsLW1pZDIAKQxhcy1jYWxsLWxhc3QAKhJhcy1jYWxsLWV2ZXJ5d2hlcmUAKw5hcy11bmFyeS1maXJzdAAsDWFzLXVuYXJ5LWxhc3QALRNhcy11bmFyeS1ldmVyeXdoZXJlAC4PYXMtYmluYXJ5LWZpcnN0AC8NYXMtYmluYXJ5LW1pZAAwDmFzLWJpbmFyeS1sYXN0ADEUYXMtYmluYXJ5LWV2ZXJ5d2hlcmUAMg1hcy10ZXN0LWZpcnN0ADMMYXMtdGVzdC1sYXN0ADQSYXMtdGVzdC1ldmVyeXdoZXJlADUQYXMtY29tcGFyZS1maXJzdAA2DmFzLWNvbXBhcmUtbWlkADcPYXMtY29tcGFyZS1sYXN0ADgVYXMtY29tcGFyZS1ldmVyeXdoZXJlADkUYXMtbWVtb3J5Lmdyb3ctZmlyc3QAOhNhcy1tZW1vcnkuZ3Jvdy1sYXN0ADsZYXMtbWVtb3J5Lmdyb3ctZXZlcnl3aGVyZQA8FmFzLWNhbGxfaW5kaXJlY3QtZmlyc3QAPhVhcy1jYWxsX2luZGlyZWN0LW1pZDEAPxVhcy1jYWxsX2luZGlyZWN0LW1pZDIAQBVhcy1jYWxsX2luZGlyZWN0LWxhc3QAQRthcy1jYWxsX2luZGlyZWN0LWV2ZXJ5d2hlcmUAQhJhcy1sb2NhbC5zZXQtZmlyc3QAQxFhcy1sb2NhbC5zZXQtbGFzdABEF2FzLWxvY2FsLnNldC1ldmVyeXdoZXJlAEUSYXMtbG9jYWwudGVlLWZpcnN0AEYRYXMtbG9jYWwudGVlLWxhc3QARxdhcy1sb2NhbC50ZWUtZXZlcnl3aGVyZQBIE2FzLWdsb2JhbC5zZXQtZmlyc3QASRJhcy1nbG9iYWwuc2V0LWxhc3QAShhhcy1nbG9iYWwuc2V0LWV2ZXJ5d2hlcmUASw1hcy1sb2FkLWZpcnN0AEwMYXMtbG9hZC1sYXN0AE0SYXMtbG9hZC1ldmVyeXdoZXJlAE4OYXMtc3RvcmUtZmlyc3QATwxhcy1zdG9yZS1taWQAUA1hcy1zdG9yZS1sYXN0AFETYXMtc3RvcmUtZXZlcnl3aGVyZQBSCQcBAEEACwE9CqIHUwIACwoAIAAgASACa2oLBQABQQELBwAQAAFBAgsHABAAQQMBCwsAAQEQAAFBBAEBCwYAASAAGgsGACAAARoLCQABASAAAQEaCwoAASAAIAAgABsLCgAgAAEgACAAGwsKACAAIAABIAAbCwoAIAAgACAAARsLEAABIAABASAAAQEgAAEBGwsIAAJ/AUECCwsKAAJ/EAABQQILCwsAAn8BEABBAwELCw4AAn8BARAAAUEEAQELCwgAA38BQQILCwoAA38QAAFBAgsLCgADfxAAQQMBCwsOAAN/AQEQAAFBBAEBCwsKACAAAQRAEAALCwsAIAAEQAEFEAALCwsAIAAEQBAABQELCwoAAn8BIAAMAAsLCgACfyAAAQwACwsNAAJ/AQEgAAEBDAALCwwAAn8BIAAgAA0ACwsMAAJ/IAABIAANAAsLDAACfyAAIAABDQALCxEAAn8BASAAAQEgAAEBDQALCw4AAn8BIAAgAA4BAAALCw4AAn8gAAEgAA4BAAALCw4AAn8gACAAAQ4BAAALCxMAAn8BASAAAQEgAAEBDgEAAAsLBgABIAAPCwYAIAABDwsJAAEBIAABAQ8LCwABIAAgASACEAELCwAgAAEgASACEAELCwAgACABASACEAELCwAgACABIAIBEAELEgABASAAAQEgAQEBIAIBARABCwYAASAAaAsGACAAAWgLCQABASAAAQFoCwgAASAAIABqCwgAIAABIABqCwgAIAAgAAFqCwwAASAAAQEgAAEBagsGAAEgAEULBgAgAAFFCwkAAQEgAAEBRQsIAAEgACAARwsIACAAASAARwsIACAAIAABSQsMAAEgAAEBIAABAUwLBwABIABAAAsHACAAAUAACwoAAQEgAAEBQAALBAAgAAsPAAJ/AUEBQQJBABEAAAsLDwACf0EBAUECQQARAAALCw8AAn9BAUECAUEAEQAACwsPAAJ/QQFBAkEAAREAAAsLFgACfwEBQQEBAUECAQFBAAEBEQAACwsJAAFBAiEAIAALCQBBAgEhACAACwwAAQFBAgEBIQAgAAsHAAFBAiIACwcAQQIBIgALCgABAUECAQEiAAsJAAFBAiQAIwALCQBBAgEkACMACwwAAQFBAgEBJAAjAAsIAAEgACgCAAsIACAAASgCAAsLAAEBIAABASgCAAsKAAEgACABNgIACwoAIAABIAE2AgALCgAgACABATYCAAsPAAEBIAABASABAQE2AgAL", + "AGFzbQEAAAABIQdgA39/fwF/YAAAYAABf2AAAX5gAAF9YAABfGACf38BfwM+PQEBAQEBAgMEBQEFAgIBAgEBAQICAgICAQICAwICAwIGBgYGAgACAgICAgICAgICBAMCAgICBAIDAgICAgIEBQFwAQEBBQMBAAEGBgF/AUEACwf4BzsIdHlwZS1pMzIAAQh0eXBlLWk2NAACCHR5cGUtZjMyAAMIdHlwZS1mNjQABA50eXBlLWkzMi12YWx1ZQAFDnR5cGUtaTY0LXZhbHVlAAYOdHlwZS1mMzItdmFsdWUABw50eXBlLWY2NC12YWx1ZQAIB251bGxhcnkACQV1bmFyeQAKDWFzLWZ1bmMtZmlyc3QACwthcy1mdW5jLW1pZAAMDGFzLWZ1bmMtbGFzdAANDWFzLWZ1bmMtdmFsdWUADg5hcy1ibG9jay1maXJzdAAPDGFzLWJsb2NrLW1pZAAQDWFzLWJsb2NrLWxhc3QAEQ5hcy1ibG9jay12YWx1ZQASDWFzLWxvb3AtZmlyc3QAEwthcy1sb29wLW1pZAAUDGFzLWxvb3AtbGFzdAAVC2FzLWJyLXZhbHVlABYNYXMtYnJfaWYtY29uZAAXDmFzLWJyX2lmLXZhbHVlABgTYXMtYnJfaWYtdmFsdWUtY29uZAAZEWFzLWJyX3RhYmxlLWluZGV4ABoRYXMtYnJfdGFibGUtdmFsdWUAGxdhcy1icl90YWJsZS12YWx1ZS1pbmRleAAcD2FzLXJldHVybi12YWx1ZQAdCmFzLWlmLWNvbmQAHgphcy1pZi10aGVuAB8KYXMtaWYtZWxzZQAgD2FzLXNlbGVjdC1maXJzdAAhEGFzLXNlbGVjdC1zZWNvbmQAIg5hcy1zZWxlY3QtY29uZAAjDWFzLWNhbGwtZmlyc3QAJQthcy1jYWxsLW1pZAAmDGFzLWNhbGwtbGFzdAAnFWFzLWNhbGxfaW5kaXJlY3QtZnVuYwAoFmFzLWNhbGxfaW5kaXJlY3QtZmlyc3QAKRRhcy1jYWxsX2luZGlyZWN0LW1pZAAqFWFzLWNhbGxfaW5kaXJlY3QtbGFzdAArEmFzLWxvY2FsLnNldC12YWx1ZQAsEmFzLWxvY2FsLnRlZS12YWx1ZQAtE2FzLWdsb2JhbC5zZXQtdmFsdWUALg9hcy1sb2FkLWFkZHJlc3MALxBhcy1sb2FkTi1hZGRyZXNzADAQYXMtc3RvcmUtYWRkcmVzcwAxDmFzLXN0b3JlLXZhbHVlADIRYXMtc3RvcmVOLWFkZHJlc3MAMw9hcy1zdG9yZU4tdmFsdWUANBBhcy11bmFyeS1vcGVyYW5kADUOYXMtYmluYXJ5LWxlZnQANg9hcy1iaW5hcnktcmlnaHQANw9hcy10ZXN0LW9wZXJhbmQAOA9hcy1jb21wYXJlLWxlZnQAORBhcy1jb21wYXJlLXJpZ2h0ADoSYXMtY29udmVydC1vcGVyYW5kADsTYXMtbWVtb3J5Lmdyb3ctc2l6ZQA8CQcBAEEACwEkCqkFPQIACwUAD2gaCwUAD3oaCwUAD4waCwUAD5oaCwkAAn9BAQ9oCwsJAAJ+QgIPegsLDAACfUMAAEBAD4wLCxAAAnxEAAAAAAAAEEAPmgsLAwAPCwwARAAAAAAAAAhADwsHAEEBD0ECCwkAEABBAg9BAwsGAAEQAA8LCAABEABBAw8LCAACQA8QAAsLCgACQBAADxAACwsJAAJAARAADwsLCwACfwEQAEECDwsLCgADf0EDD0ECCwsMAAN/EABBBA9BAgsLCwADfwEQAEEFDwsLCgACf0EJDwwACwsIAAJADw0ACwsPAAJ/QQgPQQENABpBBwsLDwACf0EGQQkPDQAaQQcLCw8AAkBCCQ8OAgAAAAtCfwsRAAJ/QQoPQQEOAgAAAEEHCwsQAAJ/QQZBCw8OAQAAQQcLCwYAQgcPDwsNAEECDwR/QQAFQQELCw0AIAAEf0EDDwUgAQsLDQAgAAR/IAEFQQQPCwsKAEEFDyAAIAEbCwoAIABBBg8gARsLCgBBAEEBQQcPGwsEAEF/CwsAQQwPQQJBAxAkCwsAQQFBDQ9BAxAkCwsAQQFBAkEODxAkCw4AQRQPQQFBAkEDEQAACw4AQQBBFQ9BAkEDEQAACw4AQQBBAUEWD0EDEQAACw4AQQBBAUECQRcPEQAACwsBAX1BEQ8hAEF/CwkBAX9BAQ8iAAsHAEEBDyQACwsAQ5qZ2T8PKgIACwgAQh4PMAAACxMAQR4PRAAAAAAAABxAOQMAQX8LDABBAkEfDzcDAEF/CwwAQSAPQQc6AABBfwsMAEECQSEPPQEAQX8LCQBDmplZQA+MCwgAQQMPQQpqCwgAQgpCLQ99CwYAQSwPRQsPAEErD0QAAAAAAAAkQGULCwBDAAAgQUEqD1wLBgBBKQ+nCwcAQSgPQAAL", + "AGFzbQEAAAABPQpgAn9/AX9gAABgA39/fwF/YAN+fn8BfmADfX1/AX1gA3x8fwF8YANwcH8BcGADb29/AW9gAX8Bf2ABfwADLy4BAgMEBQIDBAUGBwgICAgICAkICAgICAgACAgICQkICAgICQgICAgICAgICAgIBAkCcAEBAXABAQEFAwEAAQYGAX8BQQoLB/AFKwpzZWxlY3QtaTMyAAEKc2VsZWN0LWk2NAACCnNlbGVjdC1mMzIAAwpzZWxlY3QtZjY0AAQMc2VsZWN0LWkzMi10AAUMc2VsZWN0LWk2NC10AAYMc2VsZWN0LWYzMi10AAcMc2VsZWN0LWY2NC10AAgOc2VsZWN0LWZ1bmNyZWYACRBzZWxlY3QtZXh0ZXJucmVmAAoPYXMtc2VsZWN0LWZpcnN0AAsNYXMtc2VsZWN0LW1pZAAMDmFzLXNlbGVjdC1sYXN0AA0NYXMtbG9vcC1maXJzdAAOC2FzLWxvb3AtbWlkAA8MYXMtbG9vcC1sYXN0ABAPYXMtaWYtY29uZGl0aW9uABEKYXMtaWYtdGhlbgASCmFzLWlmLWVsc2UAEw5hcy1icl9pZi1maXJzdAAUDWFzLWJyX2lmLWxhc3QAFRFhcy1icl90YWJsZS1maXJzdAAWEGFzLWJyX3RhYmxlLWxhc3QAFxZhcy1jYWxsX2luZGlyZWN0LWZpcnN0ABkUYXMtY2FsbF9pbmRpcmVjdC1taWQAGhVhcy1jYWxsX2luZGlyZWN0LWxhc3QAGw5hcy1zdG9yZS1maXJzdAAcDWFzLXN0b3JlLWxhc3QAHRRhcy1tZW1vcnkuZ3Jvdy12YWx1ZQAeDWFzLWNhbGwtdmFsdWUAIA9hcy1yZXR1cm4tdmFsdWUAIQ9hcy1kcm9wLW9wZXJhbmQAIgthcy1ici12YWx1ZQAjEmFzLWxvY2FsLnNldC12YWx1ZQAkEmFzLWxvY2FsLnRlZS12YWx1ZQAlE2FzLWdsb2JhbC5zZXQtdmFsdWUAJg9hcy1sb2FkLW9wZXJhbmQAJxBhcy11bmFyeS1vcGVyYW5kACgRYXMtYmluYXJ5LW9wZXJhbmQAKQ9hcy10ZXN0LW9wZXJhbmQAKg9hcy1jb21wYXJlLWxlZnQAKxBhcy1jb21wYXJlLXJpZ2h0ACwSYXMtY29udmVydC1vcGVyYW5kAC0JDwIAQQALAQACAUEACwABGAqABS4CAAsJACAAIAEgAhsLCQAgACABIAIbCwkAIAAgASACGwsJACAAIAEgAhsLCwAgACABIAIcAX8LCwAgACABIAIcAX4LCwAgACABIAIcAX0LCwAgACABIAIcAXwLCwAgACABIAIcAXALCwAgACABIAIcAW8LDgBBAEEBIAAbQQJBAxsLDgBBAkEAQQEgABtBAxsLDgBBAkEDQQBBASAAGxsLEAADf0ECQQMgABsQABAACwsQAAN/EABBAkEDIAAbEAALCxAAA38QABAAQQJBAyAAGwsLDgBBAkEDIAAbBEAQAAsLEQBBAQR/QQJBAyAAGwVBBAsLEQBBAAR/QQIFQQJBAyAAGwsLEAACf0ECQQMgABtBBA0ACwsQAAJ/QQJBAkEDIAAbDQALCxIAAn9BAkEDIAAbQQIOAQAACwsSAAJ/QQJBAkEDIAAbDgEAAAsLBAAgAAsTAAJ/QQJBAyAAG0EBQQARAAELCxMAAn9BAUECQQMgABtBABEAAQsLEwACf0EBQQRBAkEDIAAbEQABCwsOAEEAQQQgABtBATYCAAsOAEEIQQFBAiAAGzYCAAsLAEEBQQIgABtAAAsEACAACwsAQQFBAiAAGxAfCwoAQQFBAiAAGw8LCgBBAUECIAAbGgsOAAJ/QQFBAiAAGwwACwsPAQF/QQFBAiAAGyEAIAALCwBBAUECIAAbIgALDQBBAUECIAAbJAAjAAsMAEEAQQQgABsoAgALCgBBAEEBIAAbRQsRAEEBQQIgABtBAUECIAAbbAsNAAJ/QQBBASAAG0ULCw8AAn9BAUECIAAbQQFMCwsPAAJ/QQFBAEEBIAAbRwsLDQACf0IBQgAgABunCws=", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAAEBAFwAAEKUAsEAAAbCwUAABsBCwUAABsbCwUAABsbCwYAABsbGwsGAAAcAX8LBgAAHAF/CwcAABwBfxsLCQAAHAF/HAF/CwcAABsRAQALCAAAGxEBABsL", + "AGFzbQEAAAABDQNgAX4BfmAAAX9gAAADCQgAAAAAAAECAQYGAX8BQQALB1cGCGZhYy1leHByAAAJZmFjLXN0YWNrAAENZmFjLXN0YWNrLXJhdwACCWZhYy1taXhlZAADDWZhYy1taXhlZC1yYXcABBBub3QtcXVpdGUtYS10cmVlAAcKjwIILwECfiAAIQFCASECAkADQCABQgBRBEAMAgUgASACfiECIAFCAX0hAQsMAAsLIAILLwECfiAAIQFCASECAkADQCABQgBRBEAMAgUgASACfiECIAFCAX0hAQsMAAsLIAILLwECfiAAIQFCASECAkADQCABQgBRBEAMAgUgASACfiECIAFCAX0hAQsMAAsLIAILLwECfiAAIQFCASECAkADQCABQgBRBEAMAgUgASACfiECIAFCAX0hAQsMAAsLIAILLwECfiAAIQFCASECAkADQCABQgBRBEAMAgUgASACfiECIAFCAX0hAQsMAAsLIAILDQEBf0EBIwBqJAAjAAsFABAFGgsJABAFEAUQBmoL", + "AGFzbQEAAAABDANgAABgAX8AYAABfwMCAQAEBAFwAAEKtAMBsQMAAkBBABEAAAsDQEEAEQAAC0EABEBBABEAAAtBAARAQQARAAAFQQARAAALAkBBABEAAAsDQEEAEQAAC0EABEBBABEAAAtBAARAQQARAAAFQQARAAALAkBBAEEAEQEACwNAQQBBABEBAAtBAARAQQBBABEBAAtBAARAQQBBABEBAAVBAEEAEQEACwJ/QQARAgALGgN/QQARAgALGkEABH9BABECAAVBABECAAsaAkBBABEAAAsDQEEAEQAAC0EABEBBABEAAAtBAARAQQARAAAFQQARAAALAkBBABEAAAsDQEEAEQAAC0EABEBBABEAAAtBAARAQQARAAAFQQARAAALAkBBABEAAAsDQEEAEQAAC0EABEBBABEAAAtBAARAQQARAAAFQQARAAALAkBBAEEAEQEACwNAQQBBABEBAAtBAARAQQBBABEBAAtBAARAQQBBABEBAAVBAEEAEQEACwJ/QQARAgALGgN/QQARAgALGkEABH9BABECAAVBABECAAsaAkBBABEAAAsDQEEAEQAAC0EABEBBABEAAAtBAARAQQARAAAFQQARAAALQQARAAAL", + "AGFzbQEAAAABDwNgAX8Bf2ABfgF+YAABfwMFBAABAAIHHgQEc3RtdAAABGV4cHIAAQNhcmcAAgZjb3JuZXIAAwreAQRXAQF/QeQAIQECQAJAAkACQAJAAkACQAJAAkACQCAADggAAQIDBAUGCAcLIAAPCwELC0EAIABrIQEMBQsMBAtB5QAhAQwDC0HlACEBC0HmACEBCwsgAQ8LTAEBfkLkACEBAn4CQAJAAkACQAJAAkACQAJAAkAgAKcOCAABAgMGBQQIBwsgAA8LAQsLQgAgAH0MBQtC5QAhAQsLCyABDAELQnsLDwsqAAJ/QQoCf0HkAAJ/QegHAn9BAiAAbEEDIABxDgMBAgMAC2oLagtqCw8LDAACQEEADgAAC0EBCw==", + "AGFzbQEAAAABIAdgA39/fwBgAABgAAF/YAABfmAAAX1gAAF8YAJ/fwF/Az08AQACAwQFAgIBAgICAQICAgIBAgIBAgIBAgICAgMCBgYGBgYCAQEBAQEBAQEEBAQDAQEBAQQCAwICAgICBAUBcAEBAQUDAQABBgkBfQFDAAAAAAsHjAg6CHR5cGUtaTMyAAIIdHlwZS1pNjQAAwh0eXBlLWYzMgAECHR5cGUtZjY0AAUNYXMtZnVuYy1maXJzdAAGC2FzLWZ1bmMtbWlkAAcMYXMtZnVuYy1sYXN0AAgNYXMtZnVuYy12YWx1ZQAJDmFzLWJsb2NrLWZpcnN0AAoMYXMtYmxvY2stbWlkAAsNYXMtYmxvY2stbGFzdAAMDmFzLWJsb2NrLXZhbHVlAA0OYXMtYmxvY2stYnJva2UADg1hcy1sb29wLWZpcnN0AA8LYXMtbG9vcC1taWQAEAxhcy1sb29wLWxhc3QAEQ1hcy1sb29wLWJyb2tlABILYXMtYnItdmFsdWUAEw1hcy1icl9pZi1jb25kABQOYXMtYnJfaWYtdmFsdWUAFRNhcy1icl9pZi12YWx1ZS1jb25kABYRYXMtYnJfdGFibGUtaW5kZXgAFxFhcy1icl90YWJsZS12YWx1ZQAYE2FzLWJyX3RhYmxlLXZhbHVlLTIAGRdhcy1icl90YWJsZS12YWx1ZS1pbmRleAAaG2FzLWJyX3RhYmxlLXZhbHVlLWFuZC1pbmRleAAbD2FzLXJldHVybi12YWx1ZQAcCmFzLWlmLWNvbmQAHQphcy1pZi10aGVuAB4KYXMtaWYtZWxzZQAfEmFzLWlmLXRoZW4tbm8tZWxzZQAgD2FzLXNlbGVjdC1maXJzdAAhEGFzLXNlbGVjdC1zZWNvbmQAIg5hcy1zZWxlY3QtY29uZAAjDWFzLWNhbGwtZmlyc3QAJAthcy1jYWxsLW1pZAAlDGFzLWNhbGwtbGFzdAAmFWFzLWNhbGxfaW5kaXJlY3QtZnVuYwAnFmFzLWNhbGxfaW5kaXJlY3QtZmlyc3QAKBRhcy1jYWxsX2luZGlyZWN0LW1pZAApFWFzLWNhbGxfaW5kaXJlY3QtbGFzdAAqEmFzLWxvY2FsLnNldC12YWx1ZQArEmFzLWxvY2FsLnRlZS12YWx1ZQAsE2FzLWdsb2JhbC5zZXQtdmFsdWUALQ9hcy1sb2FkLWFkZHJlc3MALhBhcy1sb2FkTi1hZGRyZXNzAC8QYXMtc3RvcmUtYWRkcmVzcwAwDmFzLXN0b3JlLXZhbHVlADERYXMtc3RvcmVOLWFkZHJlc3MAMg9hcy1zdG9yZU4tdmFsdWUAMxBhcy11bmFyeS1vcGVyYW5kADQOYXMtYmluYXJ5LWxlZnQANQ9hcy1iaW5hcnktcmlnaHQANg9hcy10ZXN0LW9wZXJhbmQANw9hcy1jb21wYXJlLWxlZnQAOBBhcy1jb21wYXJlLXJpZ2h0ADkSYXMtY29udmVydC1vcGVyYW5kADoTYXMtbWVtb3J5Lmdyb3ctc2l6ZQA7CQcBAEEACwEBCrUEPAIACwIACwMAAAsDAAALAwAACwMAAAsFAABBfwsHABAAAEF/CwUAEAAACwUAEAAACwgAAn8AQQILCwoAAn8QAABBAgsLCQACQAEQAAALCwkAAn8BEAAACwsMAAJ/EABBAQwAAAsLCAADfwBBAgsLCgADfxAAAEECCwsJAANAARAAAAsLDwACfwN/EABBAQwBAAsLCwgAAn8ADAALCwgAAkAADQALCw0AAn8AQQENABpBBwsLDQACf0EGAA0AGkEHCwsLAAJAAA4CAAAACwsPAAJ/AEEBDgIAAABBBwsLDwACfwJ/AEEBDgEAAQsLCw4AAn9BBgAOAQAAQQcLCwwAAn8ADgEAAEEICwsEAAAPCwsAAAR/QQAFQQELCwsAIAAEfwAFIAELCwsAIAAEfyABBQALCwoAIAAEQAALIAELCAAAIAAgARsLCAAgAAAgARsLCABBAEEBABsLCQAAQQJBAxABCwkAQQEAQQMQAQsJAEEBQQIAEAELDAAAQQFBAkEDEQAACwwAQQAAQQJBAxEAAAsMAEEAQQEAQQMRAAALDABBAEEBQQIAEQAACwcBAX0AIQALBwEBfQAiAAsFAAAkAAsGAAAqAgALBgAAMAAACw8AAEQAAAAAAAAcQDkDAAsIAEECADcDAAsIAABBBzoAAAsIAEECAD0BAAsEAACMCwYAAEEKagsGAEIKAH0LBAAARQsNAABEAAAAAAAAJEBlCwkAQwAAIEEAXAsEAACnCwUAAEAACw==", + "AGFzbQEAAAABCAJgAABgAAF/AzIxAAABAAEAAQEAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQeUCjEaZnVuYy11bndpbmQtYnktdW5yZWFjaGFibGUAABFmdW5jLXVud2luZC1ieS1icgABF2Z1bmMtdW53aW5kLWJ5LWJyLXZhbHVlAAIUZnVuYy11bndpbmQtYnktYnJfaWYAAxpmdW5jLXVud2luZC1ieS1icl9pZi12YWx1ZQAEF2Z1bmMtdW53aW5kLWJ5LWJyX3RhYmxlAAUdZnVuYy11bndpbmQtYnktYnJfdGFibGUtdmFsdWUABhVmdW5jLXVud2luZC1ieS1yZXR1cm4ABxtibG9jay11bndpbmQtYnktdW5yZWFjaGFibGUACBJibG9jay11bndpbmQtYnktYnIACRhibG9jay11bndpbmQtYnktYnItdmFsdWUAChVibG9jay11bndpbmQtYnktYnJfaWYACxtibG9jay11bndpbmQtYnktYnJfaWYtdmFsdWUADBhibG9jay11bndpbmQtYnktYnJfdGFibGUADR5ibG9jay11bndpbmQtYnktYnJfdGFibGUtdmFsdWUADhZibG9jay11bndpbmQtYnktcmV0dXJuAA8iYmxvY2stbmVzdGVkLXVud2luZC1ieS11bnJlYWNoYWJsZQAQGWJsb2NrLW5lc3RlZC11bndpbmQtYnktYnIAER9ibG9jay1uZXN0ZWQtdW53aW5kLWJ5LWJyLXZhbHVlABIcYmxvY2stbmVzdGVkLXVud2luZC1ieS1icl9pZgATImJsb2NrLW5lc3RlZC11bndpbmQtYnktYnJfaWYtdmFsdWUAFB9ibG9jay1uZXN0ZWQtdW53aW5kLWJ5LWJyX3RhYmxlABUlYmxvY2stbmVzdGVkLXVud2luZC1ieS1icl90YWJsZS12YWx1ZQAWHWJsb2NrLW5lc3RlZC11bndpbmQtYnktcmV0dXJuABcXdW5hcnktYWZ0ZXItdW5yZWFjaGFibGUAGA51bmFyeS1hZnRlci1icgAZEXVuYXJ5LWFmdGVyLWJyX2lmABoUdW5hcnktYWZ0ZXItYnJfdGFibGUAGxJ1bmFyeS1hZnRlci1yZXR1cm4AHBhiaW5hcnktYWZ0ZXItdW5yZWFjaGFibGUAHQ9iaW5hcnktYWZ0ZXItYnIAHhJiaW5hcnktYWZ0ZXItYnJfaWYAHxViaW5hcnktYWZ0ZXItYnJfdGFibGUAIBNiaW5hcnktYWZ0ZXItcmV0dXJuACEYc2VsZWN0LWFmdGVyLXVucmVhY2hhYmxlACIPc2VsZWN0LWFmdGVyLWJyACMSc2VsZWN0LWFmdGVyLWJyX2lmACQVc2VsZWN0LWFmdGVyLWJyX3RhYmxlACUTc2VsZWN0LWFmdGVyLXJldHVybgAmHWJsb2NrLXZhbHVlLWFmdGVyLXVucmVhY2hhYmxlACcUYmxvY2stdmFsdWUtYWZ0ZXItYnIAKBdibG9jay12YWx1ZS1hZnRlci1icl9pZgApGmJsb2NrLXZhbHVlLWFmdGVyLWJyX3RhYmxlACoYYmxvY2stdmFsdWUtYWZ0ZXItcmV0dXJuACscbG9vcC12YWx1ZS1hZnRlci11bnJlYWNoYWJsZQAsE2xvb3AtdmFsdWUtYWZ0ZXItYnIALRZsb29wLXZhbHVlLWFmdGVyLWJyX2lmAC4ZbG9vcC12YWx1ZS1hZnRlci1icl90YWJsZQAvF2xvb3AtdmFsdWUtYWZ0ZXItcmV0dXJuADAKvAYxBwBBA0IBAAsIAEEDQgEMAAsKAEEDQgFBCQwACwwAQQNCAUEBDQAaGgsOAEEDQgFBCUEBDQAaGgsLAEEDQgFBAA4AAAsNAEEDQgFBCUEADgAACwkAQQNCAUEJDwsKAAJAQQNCAQALCw0AAkBBA0IBDAALQQkLDQACf0EDQgFBCQwACwsRAAJAQQNCAUEBDQAaGgtBCQsRAAJ/QQNCAUEJQQENABoaCwsQAAJAQQNCAUEADgAAC0EJCxAAAn9BA0IBQQlBAA4AAAsLDAACf0EDQgFBCQ8LCw0AAn9BAwJAQgEACwsLEQACQEEDAkBCAQwBCxoLQQkLEAACf0EDAkBCAUEJDAELCwsUAAJAQQMCQEIBQQENARoLGgtBCQsUAAJ/QQMCQEIBQQlBAQ0BGhoLCwsUAAJAQQMCQEIBQQEOAAELGgtBCQsTAAJ/QQMCQEIBQQlBAQ4AAQsLCw8AAn9BAwJAQgFBCQ8LCwsJAEMAAAAAAFALDwACf0MAAAAAQQkMAFALCw8AAn9CAEEJQQENABpQCwsTAAJ/QwAAAABBCUEADgEAAFALCwsAQwAAAABBCQ9QCxIAQwAAAABEAAAAAAAA8D8AUQsYAAJ/QwAAAABEAAAAAAAA8D9BCQwAUQsLEQACf0IAQgFBCUEBDQAaUQsLGwACf0MAAAAARAAAAAAAAPA/QQlBAA4AAFELCxQAQwAAAABEAAAAAAAA8D9BCQ9RCxQAQwAAAABEAAAAAAAA8D9CAAAbCxoAAn9DAAAAAEQAAAAAAADwP0IAQQkMABsLCxMAAn9BAEEBQQBBCUEBDQAaGwsLHQACf0MAAAAARAAAAAAAAPA/QgBBCUEADgAAGwsLFgBDAAAAAEQAAAAAAADwP0IBQQkPGwsLAAJ/QwAAAAAACwsOAAJ/QwAAAABBCQwACwsOAAJ/QQBBCUEBDQAaCwsSAAJ/QwAAAABBCUEADgEAAAsLDQACf0MAAAAAQQkPCwsLAAN/QwAAAAAACwsRAAJ/A39DAAAAAEEJDAELCwsRAAJ/A39BAEEJQQENARoLCwsVAAJ/A39DAAAAAEEJQQAOAQEBCwsLDQADf0MAAAAAQQkPCws=", + "AGFzbQEAAAABQA1gAn9/AX9gAn5+AX9gAn19AX9gAnx8AX9gAABgAAF/YAABfmAAAX1gAAF8YAJ/fwBgAn5+AGACfX0AYAJ8fAADhAGCAQAAAQECAgMDBAQFBQUFBQUGBgYFBQcHBwUFCAgIBQUJCgsMBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUEBQFwAQgIBQMBAAEHqghfB2kzMl9hZGQAIwdpMzJfc3ViACQHaTMyX211bAAlCWkzMl9kaXZfcwAmCWkzMl9kaXZfdQAnCWkzMl9yZW1fcwAoCWkzMl9yZW1fdQApB2kzMl9hbmQAKgZpMzJfb3IAKwdpMzJfeG9yACwHaTMyX3NobAAtCWkzMl9zaHJfdQAuCWkzMl9zaHJfcwAvBmkzMl9lcQAwBmkzMl9uZQAxCGkzMl9sdF9zADIIaTMyX2xlX3MAMwhpMzJfbHRfdQA0CGkzMl9sZV91ADUIaTMyX2d0X3MANghpMzJfZ2VfcwA3CGkzMl9ndF91ADgIaTMyX2dlX3UAOQlpMzJfc3RvcmUAOgppMzJfc3RvcmU4ADsLaTMyX3N0b3JlMTYAPAhpMzJfY2FsbAA9EWkzMl9jYWxsX2luZGlyZWN0AD4KaTMyX3NlbGVjdAA/B2k2NF9hZGQAQAdpNjRfc3ViAEEHaTY0X211bABCCWk2NF9kaXZfcwBDCWk2NF9kaXZfdQBECWk2NF9yZW1fcwBFCWk2NF9yZW1fdQBGB2k2NF9hbmQARwZpNjRfb3IASAdpNjRfeG9yAEkHaTY0X3NobABKCWk2NF9zaHJfdQBLCWk2NF9zaHJfcwBMBmk2NF9lcQBNBmk2NF9uZQBOCGk2NF9sdF9zAE8IaTY0X2xlX3MAUAhpNjRfbHRfdQBRCGk2NF9sZV91AFIIaTY0X2d0X3MAUwhpNjRfZ2VfcwBUCGk2NF9ndF91AFUIaTY0X2dlX3UAVglpNjRfc3RvcmUAVwppNjRfc3RvcmU4AFgLaTY0X3N0b3JlMTYAWQtpNjRfc3RvcmUzMgBaCGk2NF9jYWxsAFsRaTY0X2NhbGxfaW5kaXJlY3QAXAppNjRfc2VsZWN0AF0HZjMyX2FkZABeB2YzMl9zdWIAXwdmMzJfbXVsAGAHZjMyX2RpdgBhDGYzMl9jb3B5c2lnbgBiBmYzMl9lcQBjBmYzMl9uZQBkBmYzMl9sdABlBmYzMl9sZQBmBmYzMl9ndABnBmYzMl9nZQBoB2YzMl9taW4AaQdmMzJfbWF4AGoJZjMyX3N0b3JlAGsIZjMyX2NhbGwAbBFmMzJfY2FsbF9pbmRpcmVjdABtCmYzMl9zZWxlY3QAbgdmNjRfYWRkAG8HZjY0X3N1YgBwB2Y2NF9tdWwAcQdmNjRfZGl2AHIMZjY0X2NvcHlzaWduAHMGZjY0X2VxAHQGZjY0X25lAHUGZjY0X2x0AHYGZjY0X2xlAHcGZjY0X2d0AHgGZjY0X2dlAHkHZjY0X21pbgB6B2Y2NF9tYXgAewlmNjRfc3RvcmUAfAhmNjRfY2FsbAB9EWY2NF9jYWxsX2luZGlyZWN0AH4KZjY0X3NlbGVjdAB/BWJyX2lmAIABCGJyX3RhYmxlAIEBCQ4BAEEACwgAAQIDBAUGBwqqDYIBBABBfwsEAEF+CwQAQX8LBABBfgsEAEF/CwQAQX4LBABBfwsEAEF+CwkAQQhBADYCAAsnAEELQQotAAA6AABBCkEJLQAAOgAAQQlBCC0AADoAAEEIQX06AAALBwBBCCgCAAsNABAJQQhBAToAAEEACw0AEAlBCEECOgAAQQELDQAQCUEIQQM6AABBAQsNABAJQQhBBDoAAEEACw0AEAlBCEEFOgAAQQALDQAQCUEIQQE6AABCAAsNABAJQQhBAjoAAEIBCw0AEAlBCEEDOgAAQgELDQAQCUEIQQQ6AABBAgsNABAJQQhBBToAAEEACxAAEAlBCEEBOgAAQwAAAAALEAAQCUEIQQI6AABDAACAPwsQABAJQQhBAzoAAEMAAIA/Cw0AEAlBCEEEOgAAQQQLDQAQCUEIQQU6AABBAAsUABAJQQhBAToAAEQAAAAAAAAAAAsUABAJQQhBAjoAAEQAAAAAAADwPwsUABAJQQhBAzoAAEQAAAAAAADwPwsNABAJQQhBBDoAAEEGCw0AEAlBCEEFOgAAQQALAgALAgALAgALAgALDAAQCBALEAxqGhAKCwwAEAgQCxAMaxoQCgsMABAIEAsQDGwaEAoLDAAQCBALEAxtGhAKCwwAEAgQCxAMbhoQCgsMABAIEAsQDG8aEAoLDAAQCBALEAxwGhAKCwwAEAgQCxAMcRoQCgsMABAIEAsQDHIaEAoLDAAQCBALEAxzGhAKCwwAEAgQCxAMdBoQCgsMABAIEAsQDHYaEAoLDAAQCBALEAx1GhAKCwwAEAgQCxAMRhoQCgsMABAIEAsQDEcaEAoLDAAQCBALEAxIGhAKCwwAEAgQCxAMTBoQCgsMABAIEAsQDEkaEAoLDAAQCBALEAxNGhAKCwwAEAgQCxAMShoQCgsMABAIEAsQDE4aEAoLDAAQCBALEAxLGhAKCwwAEAgQCxAMTxoQCgsNABAIEAsQDDYCABAKCw0AEAgQCxAMOgAAEAoLDQAQCBALEAw7AQAQCgsMABAIEAsQDBAfEAoLEAAQCBALEAwQDhEAABoQCgsOABAIEAsQDBAPGxoQCgsMABAIEBAQEXwaEAoLDAAQCBAQEBF9GhAKCwwAEAgQEBARfhoQCgsMABAIEBAQEX8aEAoLDAAQCBAQEBGAGhAKCwwAEAgQEBARgRoQCgsMABAIEBAQEYIaEAoLDAAQCBAQEBGDGhAKCwwAEAgQEBARhBoQCgsMABAIEBAQEYUaEAoLDAAQCBAQEBGGGhAKCwwAEAgQEBARiBoQCgsMABAIEBAQEYcaEAoLDAAQCBAQEBFRGhAKCwwAEAgQEBARUhoQCgsMABAIEBAQEVMaEAoLDAAQCBAQEBFXGhAKCwwAEAgQEBARVBoQCgsMABAIEBAQEVgaEAoLDAAQCBAQEBFVGhAKCwwAEAgQEBARWRoQCgsMABAIEBAQEVYaEAoLDAAQCBAQEBFaGhAKCw0AEAgQCxARNwMAEAoLDQAQCBALEBE8AAAQCgsNABAIEAsQET0BABAKCw0AEAgQCxARPgIAEAoLDAAQCBAQEBEQIBAKCxAAEAgQEBAREBMRAQAaEAoLDgAQCBAQEBEQFBsaEAoLDAAQCBAVEBaSGhAKCwwAEAgQFRAWkxoQCgsMABAIEBUQFpQaEAoLDAAQCBAVEBaVGhAKCwwAEAgQFRAWmBoQCgsMABAIEBUQFlsaEAoLDAAQCBAVEBZcGhAKCwwAEAgQFRAWXRoQCgsMABAIEBUQFl8aEAoLDAAQCBAVEBZeGhAKCwwAEAgQFRAWYBoQCgsMABAIEBUQFpYaEAoLDAAQCBAVEBaXGhAKCw0AEAgQCxAWOAIAEAoLDAAQCBAVEBYQIRAKCxAAEAgQFRAWEBgRAgAaEAoLDgAQCBAVEBYQGRsaEAoLDAAQCBAaEBugGhAKCwwAEAgQGhAboRoQCgsMABAIEBoQG6IaEAoLDAAQCBAaEBujGhAKCwwAEAgQGhAbphoQCgsMABAIEBoQG2EaEAoLDAAQCBAaEBtiGhAKCwwAEAgQGhAbYxoQCgsMABAIEBoQG2UaEAoLDAAQCBAaEBtkGhAKCwwAEAgQGhAbZhoQCgsMABAIEBoQG6QaEAoLDAAQCBAaEBulGhAKCw0AEAgQCxAbOQMAEAoLDAAQCBAaEBsQIhAKCxAAEAgQGhAbEB0RAwAaEAoLDgAQCBAaEBsQHhsaEAoLEwACfxAIEAsQDEEAcQ0AGhAKCwsVAAJ/EAgCfxALEAwOAQEACxoQCgsL", + "AGFzbQEAAAABNw1gAn9/AX9gAAF/YAABfmAAAW9gAX8AYAF+AGABbwBgAAF9YAABfGABfQBgAXwAYAAAYAF/AX8CLwIIc3BlY3Rlc3QKZ2xvYmFsX2kzMgN/AAhzcGVjdGVzdApnbG9iYWxfaTY0A34AAzMyAQIDAwECAQIEBQYHCAcICQoLAQEBAQEBAQEBAQEBAQABAQELCwEBDAEBCwEMDAEBAQEEBQFwAQEBBQMBAAEGVg1/AEF+C30AQwAAQMALfABEAAAAAAAAEMALfgBCewt/AUF0C30BQwAAUMELfAFEAAAAAAAALMALfgFCcQt/ACMAC34AIwELbwDQbwtvAdBvC3AA0HALB7EFLwVnZXQtYQAABWdldC1iAAEFZ2V0LXIAAgZnZXQtbXIAAwVnZXQteAAEBWdldC15AAUGZ2V0LXoxAAYGZ2V0LXoyAAcFc2V0LXgACAVzZXQteQAJBnNldC1tcgAKBWdldC0zAAsFZ2V0LTQADAVnZXQtNwANBWdldC04AA4Fc2V0LTcADwVzZXQtOAAQD2FzLXNlbGVjdC1maXJzdAASDWFzLXNlbGVjdC1taWQAEw5hcy1zZWxlY3QtbGFzdAAUDWFzLWxvb3AtZmlyc3QAFQthcy1sb29wLW1pZAAWDGFzLWxvb3AtbGFzdAAXD2FzLWlmLWNvbmRpdGlvbgAYCmFzLWlmLXRoZW4AGQphcy1pZi1lbHNlABoOYXMtYnJfaWYtZmlyc3QAGw1hcy1icl9pZi1sYXN0ABwRYXMtYnJfdGFibGUtZmlyc3QAHRBhcy1icl90YWJsZS1sYXN0AB4WYXMtY2FsbF9pbmRpcmVjdC1maXJzdAAgFGFzLWNhbGxfaW5kaXJlY3QtbWlkACEVYXMtY2FsbF9pbmRpcmVjdC1sYXN0ACIOYXMtc3RvcmUtZmlyc3QAIw1hcy1zdG9yZS1sYXN0ACQPYXMtbG9hZC1vcGVyYW5kACUUYXMtbWVtb3J5Lmdyb3ctdmFsdWUAJg1hcy1jYWxsLXZhbHVlACgPYXMtcmV0dXJuLXZhbHVlACkPYXMtZHJvcC1vcGVyYW5kACoLYXMtYnItdmFsdWUAKxJhcy1sb2NhbC5zZXQtdmFsdWUALBJhcy1sb2NhbC50ZWUtdmFsdWUALRNhcy1nbG9iYWwuc2V0LXZhbHVlAC4QYXMtdW5hcnktb3BlcmFuZAAvEWFzLWJpbmFyeS1vcGVyYW5kADASYXMtY29tcGFyZS1vcGVyYW5kADEJBwEAQQALAR8KsAMyBAAjAgsEACMFCwQAIwwLBAAjDQsEACMGCwQAIwkLBAAjCgsEACMLCwYAIAAkBgsGACAAJAkLBgAgACQNCwQAIwMLBAAjBAsEACMHCwQAIwgLBgAgACQHCwYAIAAkCAsCAAsJACMGQQJBAxsLCQBBAiMGQQMbCwkAQQJBAyMGGwsLAAN/IwYQERARCwsLAAN/EBEjBhARCwsLAAN/EBEQESMGCwsQACMGBH8QEUECBRARQQMLCwwAQQEEfyMGBUECCwsMAEEABH9BAgUjBgsLDgACfyMGQQINAEEDDwsLDgACf0ECIwYNAEEDDwsLDQACfyMGQQIOAQAACwsNAAJ/QQIjBg4BAAALCwQAIAALDgACfyMGQQJBABEAAAsLDgACf0ECIwZBABEAAAsLDgACf0ECQQAjBhEAAAsLCQAjBkEBNgIACwkAQQAjBjYCAAsHACMGKAIACwYAIwZAAAsEACAACwYAIwYQJwsFACMGDwsFACMGGgsJAAJ/IwYMAAsLCAAjBiEAIAALBgAjBiIACwgAIwYkBiMGCwUAIwZFCwcAIwYjBmwLBwAjAEEBSws=", + "AGFzbQEAAAAGCQF9AUMAAAAACwcFAQFhAwA=", + "AGFzbQEAAAACGAEIc3BlY3Rlc3QKZ2xvYmFsX2kzMgN/AA==", + "AGFzbQEAAAAGBgF/AEEACw==", + "AGFzbQEAAAABCAJgAABgAAF/AwQDAAEABQQBAQEBBw0CA2luYwAAA2dldAABCAECCiMDDwBBAEEALQAAQQFqOgAACwgAQQAtAAAPCwgAEAAQABAACwsHAQBBAAsBQQ==", + "AGFzbQEAAAABCAJgAX8AYAAAAhYBCHNwZWN0ZXN0CXByaW50X2kzMgAAAwIBAQgBAQoIAQYAQQEQAAs=", + "AGFzbQEAAAABCAJgAX8AYAAAAhYBCHNwZWN0ZXN0CXByaW50X2kzMgAAAwIBAQgBAQoIAQYAQQIQAAs=", + "AGFzbQEAAAABBAFgAAACEgEIc3BlY3Rlc3QFcHJpbnQAAAgBAA==", + "AGFzbQEAAAABBAFgAAADAgEACAEACgUBAwAACw==", +]; +export const script = "[[\"file\",\"block.wast\"],[\"module\",3,0],[\"invoke\",353,0,\"empty\",[]],[\"invoke\",354,0,\"singular\",[]],[\"invoke\",355,0,\"multi\",[]],[\"invoke\",356,0,\"nested\",[]],[\"invoke\",357,0,\"deep\",[]],[\"invoke\",359,0,\"as-select-first\",[]],[\"invoke\",360,0,\"as-select-mid\",[]],[\"invoke\",361,0,\"as-select-last\",[]],[\"invoke\",363,0,\"as-loop-first\",[]],[\"invoke\",364,0,\"as-loop-mid\",[]],[\"invoke\",365,0,\"as-loop-last\",[]],[\"invoke\",367,0,\"as-if-condition\",[]],[\"invoke\",368,0,\"as-if-then\",[]],[\"invoke\",369,0,\"as-if-else\",[]],[\"invoke\",371,0,\"as-br_if-first\",[]],[\"invoke\",372,0,\"as-br_if-last\",[]],[\"invoke\",374,0,\"as-br_table-first\",[]],[\"invoke\",375,0,\"as-br_table-last\",[]],[\"invoke\",377,0,\"as-call_indirect-first\",[]],[\"invoke\",378,0,\"as-call_indirect-mid\",[]],[\"invoke\",379,0,\"as-call_indirect-last\",[]],[\"invoke\",381,0,\"as-store-first\",[]],[\"invoke\",382,0,\"as-store-last\",[]],[\"invoke\",384,0,\"as-memory.grow-value\",[]],[\"invoke\",385,0,\"as-call-value\",[]],[\"invoke\",386,0,\"as-return-value\",[]],[\"invoke\",387,0,\"as-drop-operand\",[]],[\"invoke\",388,0,\"as-br-value\",[]],[\"invoke\",389,0,\"as-local.set-value\",[]],[\"invoke\",390,0,\"as-local.tee-value\",[]],[\"invoke\",391,0,\"as-global.set-value\",[]],[\"invoke\",392,0,\"as-load-operand\",[]],[\"invoke\",394,0,\"as-unary-operand\",[]],[\"invoke\",395,0,\"as-binary-operand\",[]],[\"invoke\",396,0,\"as-test-operand\",[]],[\"invoke\",397,0,\"as-compare-operand\",[]],[\"invoke\",398,0,\"as-binary-operands\",[]],[\"invoke\",399,0,\"as-compare-operands\",[]],[\"invoke\",400,0,\"as-mixed-operands\",[]],[\"invoke\",402,0,\"break-bare\",[]],[\"invoke\",403,0,\"break-value\",[]],[\"invoke\",404,0,\"break-multi-value\",[]],[\"invoke\",407,0,\"break-repeated\",[]],[\"invoke\",408,0,\"break-inner\",[]],[\"invoke\",410,0,\"param\",[]],[\"invoke\",411,0,\"params\",[]],[\"invoke\",412,0,\"params-id\",[]],[\"invoke\",413,0,\"param-break\",[]],[\"invoke\",414,0,\"params-break\",[]],[\"invoke\",415,0,\"params-id-break\",[]],[\"invoke\",417,0,\"effects\",[]],[\"invoke\",419,0,\"type-use\",[]],[\"file\",\"br.wast\"],[\"module\",3,1],[\"invoke\",372,0,\"type-i32\",[]],[\"invoke\",373,0,\"type-i64\",[]],[\"invoke\",374,0,\"type-f32\",[]],[\"invoke\",375,0,\"type-f64\",[]],[\"invoke\",376,0,\"type-i32-i32\",[]],[\"invoke\",377,0,\"type-i64-i64\",[]],[\"invoke\",378,0,\"type-f32-f32\",[]],[\"invoke\",379,0,\"type-f64-f64\",[]],[\"invoke\",381,0,\"type-i32-value\",[]],[\"invoke\",382,0,\"type-i64-value\",[]],[\"invoke\",383,0,\"type-f32-value\",[]],[\"invoke\",384,0,\"type-f64-value\",[]],[\"invoke\",385,0,\"type-f64-f64-value\",[]],[\"invoke\",387,0,\"as-block-first\",[]],[\"invoke\",388,0,\"as-block-mid\",[]],[\"invoke\",389,0,\"as-block-last\",[]],[\"invoke\",390,0,\"as-block-value\",[]],[\"invoke\",392,0,\"as-loop-first\",[]],[\"invoke\",393,0,\"as-loop-mid\",[]],[\"invoke\",394,0,\"as-loop-last\",[]],[\"invoke\",396,0,\"as-br-value\",[]],[\"invoke\",398,0,\"as-br_if-cond\",[]],[\"invoke\",399,0,\"as-br_if-value\",[]],[\"invoke\",400,0,\"as-br_if-value-cond\",[]],[\"invoke\",402,0,\"as-br_table-index\",[]],[\"invoke\",403,0,\"as-br_table-value\",[]],[\"invoke\",404,0,\"as-br_table-value-index\",[]],[\"invoke\",406,0,\"as-return-value\",[]],[\"invoke\",407,0,\"as-return-values\",[]],[\"invoke\",409,0,\"as-if-cond\",[]],[\"invoke\",410,0,\"as-if-then\",[\"i32:1\",\"i32:6\"]],[\"invoke\",411,0,\"as-if-then\",[\"i32:0\",\"i32:6\"]],[\"invoke\",412,0,\"as-if-else\",[\"i32:0\",\"i32:6\"]],[\"invoke\",413,0,\"as-if-else\",[\"i32:1\",\"i32:6\"]],[\"invoke\",415,0,\"as-select-first\",[\"i32:0\",\"i32:6\"]],[\"invoke\",416,0,\"as-select-first\",[\"i32:1\",\"i32:6\"]],[\"invoke\",417,0,\"as-select-second\",[\"i32:0\",\"i32:6\"]],[\"invoke\",418,0,\"as-select-second\",[\"i32:1\",\"i32:6\"]],[\"invoke\",419,0,\"as-select-cond\",[]],[\"invoke\",420,0,\"as-select-all\",[]],[\"invoke\",422,0,\"as-call-first\",[]],[\"invoke\",423,0,\"as-call-mid\",[]],[\"invoke\",424,0,\"as-call-last\",[]],[\"invoke\",425,0,\"as-call-all\",[]],[\"invoke\",427,0,\"as-call_indirect-func\",[]],[\"invoke\",428,0,\"as-call_indirect-first\",[]],[\"invoke\",429,0,\"as-call_indirect-mid\",[]],[\"invoke\",430,0,\"as-call_indirect-last\",[]],[\"invoke\",431,0,\"as-call_indirect-all\",[]],[\"invoke\",433,0,\"as-local.set-value\",[]],[\"invoke\",434,0,\"as-local.tee-value\",[]],[\"invoke\",435,0,\"as-global.set-value\",[]],[\"invoke\",437,0,\"as-load-address\",[]],[\"invoke\",438,0,\"as-loadN-address\",[]],[\"invoke\",440,0,\"as-store-address\",[]],[\"invoke\",441,0,\"as-store-value\",[]],[\"invoke\",442,0,\"as-store-both\",[]],[\"invoke\",443,0,\"as-storeN-address\",[]],[\"invoke\",444,0,\"as-storeN-value\",[]],[\"invoke\",445,0,\"as-storeN-both\",[]],[\"invoke\",447,0,\"as-unary-operand\",[]],[\"invoke\",449,0,\"as-binary-left\",[]],[\"invoke\",450,0,\"as-binary-right\",[]],[\"invoke\",451,0,\"as-binary-both\",[]],[\"invoke\",453,0,\"as-test-operand\",[]],[\"invoke\",455,0,\"as-compare-left\",[]],[\"invoke\",456,0,\"as-compare-right\",[]],[\"invoke\",457,0,\"as-compare-both\",[]],[\"invoke\",459,0,\"as-convert-operand\",[]],[\"invoke\",461,0,\"as-memory.grow-size\",[]],[\"invoke\",463,0,\"nested-block-value\",[]],[\"invoke\",464,0,\"nested-br-value\",[]],[\"invoke\",465,0,\"nested-br_if-value\",[]],[\"invoke\",466,0,\"nested-br_if-value-cond\",[]],[\"invoke\",467,0,\"nested-br_table-value\",[]],[\"invoke\",468,0,\"nested-br_table-value-index\",[]],[\"file\",\"br_if.wast\"],[\"module\",3,2],[\"invoke\",372,0,\"type-i32\",[]],[\"invoke\",373,0,\"type-i64\",[]],[\"invoke\",374,0,\"type-f32\",[]],[\"invoke\",375,0,\"type-f64\",[]],[\"invoke\",377,0,\"type-i32-value\",[]],[\"invoke\",378,0,\"type-i64-value\",[]],[\"invoke\",379,0,\"type-f32-value\",[]],[\"invoke\",380,0,\"type-f64-value\",[]],[\"invoke\",382,0,\"as-block-first\",[\"i32:0\"]],[\"invoke\",383,0,\"as-block-first\",[\"i32:1\"]],[\"invoke\",384,0,\"as-block-mid\",[\"i32:0\"]],[\"invoke\",385,0,\"as-block-mid\",[\"i32:1\"]],[\"invoke\",386,0,\"as-block-last\",[\"i32:0\"]],[\"invoke\",387,0,\"as-block-last\",[\"i32:1\"]],[\"invoke\",389,0,\"as-block-first-value\",[\"i32:0\"]],[\"invoke\",390,0,\"as-block-first-value\",[\"i32:1\"]],[\"invoke\",391,0,\"as-block-mid-value\",[\"i32:0\"]],[\"invoke\",392,0,\"as-block-mid-value\",[\"i32:1\"]],[\"invoke\",393,0,\"as-block-last-value\",[\"i32:0\"]],[\"invoke\",394,0,\"as-block-last-value\",[\"i32:1\"]],[\"invoke\",396,0,\"as-loop-first\",[\"i32:0\"]],[\"invoke\",397,0,\"as-loop-first\",[\"i32:1\"]],[\"invoke\",398,0,\"as-loop-mid\",[\"i32:0\"]],[\"invoke\",399,0,\"as-loop-mid\",[\"i32:1\"]],[\"invoke\",400,0,\"as-loop-last\",[\"i32:0\"]],[\"invoke\",401,0,\"as-loop-last\",[\"i32:1\"]],[\"invoke\",403,0,\"as-br-value\",[]],[\"invoke\",405,0,\"as-br_if-cond\",[]],[\"invoke\",406,0,\"as-br_if-value\",[]],[\"invoke\",407,0,\"as-br_if-value-cond\",[\"i32:0\"]],[\"invoke\",408,0,\"as-br_if-value-cond\",[\"i32:1\"]],[\"invoke\",410,0,\"as-br_table-index\",[]],[\"invoke\",411,0,\"as-br_table-value\",[]],[\"invoke\",412,0,\"as-br_table-value-index\",[]],[\"invoke\",414,0,\"as-return-value\",[]],[\"invoke\",416,0,\"as-if-cond\",[\"i32:0\"]],[\"invoke\",417,0,\"as-if-cond\",[\"i32:1\"]],[\"invoke\",418,0,\"as-if-then\",[\"i32:0\",\"i32:0\"]],[\"invoke\",419,0,\"as-if-then\",[\"i32:4\",\"i32:0\"]],[\"invoke\",420,0,\"as-if-then\",[\"i32:0\",\"i32:1\"]],[\"invoke\",421,0,\"as-if-then\",[\"i32:4\",\"i32:1\"]],[\"invoke\",422,0,\"as-if-else\",[\"i32:0\",\"i32:0\"]],[\"invoke\",423,0,\"as-if-else\",[\"i32:3\",\"i32:0\"]],[\"invoke\",424,0,\"as-if-else\",[\"i32:0\",\"i32:1\"]],[\"invoke\",425,0,\"as-if-else\",[\"i32:3\",\"i32:1\"]],[\"invoke\",427,0,\"as-select-first\",[\"i32:0\"]],[\"invoke\",428,0,\"as-select-first\",[\"i32:1\"]],[\"invoke\",429,0,\"as-select-second\",[\"i32:0\"]],[\"invoke\",430,0,\"as-select-second\",[\"i32:1\"]],[\"invoke\",431,0,\"as-select-cond\",[]],[\"invoke\",433,0,\"as-call-first\",[]],[\"invoke\",434,0,\"as-call-mid\",[]],[\"invoke\",435,0,\"as-call-last\",[]],[\"invoke\",437,0,\"as-call_indirect-func\",[]],[\"invoke\",438,0,\"as-call_indirect-first\",[]],[\"invoke\",439,0,\"as-call_indirect-mid\",[]],[\"invoke\",440,0,\"as-call_indirect-last\",[]],[\"invoke\",442,0,\"as-local.set-value\",[\"i32:0\"]],[\"invoke\",443,0,\"as-local.set-value\",[\"i32:1\"]],[\"invoke\",445,0,\"as-local.tee-value\",[\"i32:0\"]],[\"invoke\",446,0,\"as-local.tee-value\",[\"i32:1\"]],[\"invoke\",448,0,\"as-global.set-value\",[\"i32:0\"]],[\"invoke\",449,0,\"as-global.set-value\",[\"i32:1\"]],[\"invoke\",451,0,\"as-load-address\",[]],[\"invoke\",452,0,\"as-loadN-address\",[]],[\"invoke\",454,0,\"as-store-address\",[]],[\"invoke\",455,0,\"as-store-value\",[]],[\"invoke\",456,0,\"as-storeN-address\",[]],[\"invoke\",457,0,\"as-storeN-value\",[]],[\"invoke\",459,0,\"as-unary-operand\",[]],[\"invoke\",460,0,\"as-binary-left\",[]],[\"invoke\",461,0,\"as-binary-right\",[]],[\"invoke\",462,0,\"as-test-operand\",[]],[\"invoke\",463,0,\"as-compare-left\",[]],[\"invoke\",464,0,\"as-compare-right\",[]],[\"invoke\",465,0,\"as-memory.grow-size\",[]],[\"invoke\",467,0,\"nested-block-value\",[\"i32:0\"]],[\"invoke\",468,0,\"nested-block-value\",[\"i32:1\"]],[\"invoke\",469,0,\"nested-br-value\",[\"i32:0\"]],[\"invoke\",470,0,\"nested-br-value\",[\"i32:1\"]],[\"invoke\",471,0,\"nested-br_if-value\",[\"i32:0\"]],[\"invoke\",472,0,\"nested-br_if-value\",[\"i32:1\"]],[\"invoke\",473,0,\"nested-br_if-value-cond\",[\"i32:0\"]],[\"invoke\",474,0,\"nested-br_if-value-cond\",[\"i32:1\"]],[\"invoke\",475,0,\"nested-br_table-value\",[\"i32:0\"]],[\"invoke\",476,0,\"nested-br_table-value\",[\"i32:1\"]],[\"invoke\",477,0,\"nested-br_table-value-index\",[\"i32:0\"]],[\"invoke\",478,0,\"nested-br_table-value-index\",[\"i32:1\"]],[\"file\",\"br_table.wast\"],[\"module\",3,3],[\"invoke\",1007,0,\"type-i32\",[]],[\"invoke\",1008,0,\"type-i64\",[]],[\"invoke\",1009,0,\"type-f32\",[]],[\"invoke\",1010,0,\"type-f64\",[]],[\"invoke\",1012,0,\"type-i32-value\",[]],[\"invoke\",1013,0,\"type-i64-value\",[]],[\"invoke\",1014,0,\"type-f32-value\",[]],[\"invoke\",1015,0,\"type-f64-value\",[]],[\"invoke\",1017,0,\"empty\",[\"i32:0\"]],[\"invoke\",1018,0,\"empty\",[\"i32:1\"]],[\"invoke\",1019,0,\"empty\",[\"i32:11\"]],[\"invoke\",1020,0,\"empty\",[\"i32:4294967295\"]],[\"invoke\",1021,0,\"empty\",[\"i32:4294967196\"]],[\"invoke\",1022,0,\"empty\",[\"i32:4294967295\"]],[\"invoke\",1024,0,\"empty-value\",[\"i32:0\"]],[\"invoke\",1025,0,\"empty-value\",[\"i32:1\"]],[\"invoke\",1026,0,\"empty-value\",[\"i32:11\"]],[\"invoke\",1027,0,\"empty-value\",[\"i32:4294967295\"]],[\"invoke\",1028,0,\"empty-value\",[\"i32:4294967196\"]],[\"invoke\",1029,0,\"empty-value\",[\"i32:4294967295\"]],[\"invoke\",1031,0,\"singleton\",[\"i32:0\"]],[\"invoke\",1032,0,\"singleton\",[\"i32:1\"]],[\"invoke\",1033,0,\"singleton\",[\"i32:11\"]],[\"invoke\",1034,0,\"singleton\",[\"i32:4294967295\"]],[\"invoke\",1035,0,\"singleton\",[\"i32:4294967196\"]],[\"invoke\",1036,0,\"singleton\",[\"i32:4294967295\"]],[\"invoke\",1038,0,\"singleton-value\",[\"i32:0\"]],[\"invoke\",1039,0,\"singleton-value\",[\"i32:1\"]],[\"invoke\",1040,0,\"singleton-value\",[\"i32:11\"]],[\"invoke\",1041,0,\"singleton-value\",[\"i32:4294967295\"]],[\"invoke\",1042,0,\"singleton-value\",[\"i32:4294967196\"]],[\"invoke\",1043,0,\"singleton-value\",[\"i32:4294967295\"]],[\"invoke\",1045,0,\"multiple\",[\"i32:0\"]],[\"invoke\",1046,0,\"multiple\",[\"i32:1\"]],[\"invoke\",1047,0,\"multiple\",[\"i32:2\"]],[\"invoke\",1048,0,\"multiple\",[\"i32:3\"]],[\"invoke\",1049,0,\"multiple\",[\"i32:4\"]],[\"invoke\",1050,0,\"multiple\",[\"i32:5\"]],[\"invoke\",1051,0,\"multiple\",[\"i32:6\"]],[\"invoke\",1052,0,\"multiple\",[\"i32:10\"]],[\"invoke\",1053,0,\"multiple\",[\"i32:4294967295\"]],[\"invoke\",1054,0,\"multiple\",[\"i32:4294967295\"]],[\"invoke\",1056,0,\"multiple-value\",[\"i32:0\"]],[\"invoke\",1057,0,\"multiple-value\",[\"i32:1\"]],[\"invoke\",1058,0,\"multiple-value\",[\"i32:2\"]],[\"invoke\",1059,0,\"multiple-value\",[\"i32:3\"]],[\"invoke\",1060,0,\"multiple-value\",[\"i32:4\"]],[\"invoke\",1061,0,\"multiple-value\",[\"i32:5\"]],[\"invoke\",1062,0,\"multiple-value\",[\"i32:6\"]],[\"invoke\",1063,0,\"multiple-value\",[\"i32:10\"]],[\"invoke\",1064,0,\"multiple-value\",[\"i32:4294967295\"]],[\"invoke\",1065,0,\"multiple-value\",[\"i32:4294967295\"]],[\"invoke\",1067,0,\"large\",[\"i32:0\"]],[\"invoke\",1068,0,\"large\",[\"i32:1\"]],[\"invoke\",1069,0,\"large\",[\"i32:100\"]],[\"invoke\",1070,0,\"large\",[\"i32:101\"]],[\"invoke\",1071,0,\"large\",[\"i32:10000\"]],[\"invoke\",1072,0,\"large\",[\"i32:10001\"]],[\"invoke\",1073,0,\"large\",[\"i32:1000000\"]],[\"invoke\",1074,0,\"large\",[\"i32:1000001\"]],[\"invoke\",1076,0,\"as-block-first\",[]],[\"invoke\",1077,0,\"as-block-mid\",[]],[\"invoke\",1078,0,\"as-block-last\",[]],[\"invoke\",1079,0,\"as-block-value\",[]],[\"invoke\",1081,0,\"as-loop-first\",[]],[\"invoke\",1082,0,\"as-loop-mid\",[]],[\"invoke\",1083,0,\"as-loop-last\",[]],[\"invoke\",1085,0,\"as-br-value\",[]],[\"invoke\",1087,0,\"as-br_if-cond\",[]],[\"invoke\",1088,0,\"as-br_if-value\",[]],[\"invoke\",1089,0,\"as-br_if-value-cond\",[]],[\"invoke\",1091,0,\"as-br_table-index\",[]],[\"invoke\",1092,0,\"as-br_table-value\",[]],[\"invoke\",1093,0,\"as-br_table-value-index\",[]],[\"invoke\",1095,0,\"as-return-value\",[]],[\"invoke\",1097,0,\"as-if-cond\",[]],[\"invoke\",1098,0,\"as-if-then\",[\"i32:1\",\"i32:6\"]],[\"invoke\",1099,0,\"as-if-then\",[\"i32:0\",\"i32:6\"]],[\"invoke\",1100,0,\"as-if-else\",[\"i32:0\",\"i32:6\"]],[\"invoke\",1101,0,\"as-if-else\",[\"i32:1\",\"i32:6\"]],[\"invoke\",1103,0,\"as-select-first\",[\"i32:0\",\"i32:6\"]],[\"invoke\",1104,0,\"as-select-first\",[\"i32:1\",\"i32:6\"]],[\"invoke\",1105,0,\"as-select-second\",[\"i32:0\",\"i32:6\"]],[\"invoke\",1106,0,\"as-select-second\",[\"i32:1\",\"i32:6\"]],[\"invoke\",1107,0,\"as-select-cond\",[]],[\"invoke\",1109,0,\"as-call-first\",[]],[\"invoke\",1110,0,\"as-call-mid\",[]],[\"invoke\",1111,0,\"as-call-last\",[]],[\"invoke\",1113,0,\"as-call_indirect-first\",[]],[\"invoke\",1114,0,\"as-call_indirect-mid\",[]],[\"invoke\",1115,0,\"as-call_indirect-last\",[]],[\"invoke\",1116,0,\"as-call_indirect-func\",[]],[\"invoke\",1118,0,\"as-local.set-value\",[]],[\"invoke\",1119,0,\"as-local.tee-value\",[]],[\"invoke\",1120,0,\"as-global.set-value\",[]],[\"invoke\",1122,0,\"as-load-address\",[]],[\"invoke\",1123,0,\"as-loadN-address\",[]],[\"invoke\",1125,0,\"as-store-address\",[]],[\"invoke\",1126,0,\"as-store-value\",[]],[\"invoke\",1127,0,\"as-storeN-address\",[]],[\"invoke\",1128,0,\"as-storeN-value\",[]],[\"invoke\",1130,0,\"as-unary-operand\",[]],[\"invoke\",1132,0,\"as-binary-left\",[]],[\"invoke\",1133,0,\"as-binary-right\",[]],[\"invoke\",1135,0,\"as-test-operand\",[]],[\"invoke\",1137,0,\"as-compare-left\",[]],[\"invoke\",1138,0,\"as-compare-right\",[]],[\"invoke\",1140,0,\"as-convert-operand\",[]],[\"invoke\",1142,0,\"as-memory.grow-size\",[]],[\"invoke\",1144,0,\"nested-block-value\",[\"i32:0\"]],[\"invoke\",1145,0,\"nested-block-value\",[\"i32:1\"]],[\"invoke\",1146,0,\"nested-block-value\",[\"i32:2\"]],[\"invoke\",1147,0,\"nested-block-value\",[\"i32:10\"]],[\"invoke\",1148,0,\"nested-block-value\",[\"i32:4294967295\"]],[\"invoke\",1149,0,\"nested-block-value\",[\"i32:100000\"]],[\"invoke\",1151,0,\"nested-br-value\",[\"i32:0\"]],[\"invoke\",1152,0,\"nested-br-value\",[\"i32:1\"]],[\"invoke\",1153,0,\"nested-br-value\",[\"i32:2\"]],[\"invoke\",1154,0,\"nested-br-value\",[\"i32:11\"]],[\"invoke\",1155,0,\"nested-br-value\",[\"i32:4294967292\"]],[\"invoke\",1156,0,\"nested-br-value\",[\"i32:10213210\"]],[\"invoke\",1158,0,\"nested-br_if-value\",[\"i32:0\"]],[\"invoke\",1159,0,\"nested-br_if-value\",[\"i32:1\"]],[\"invoke\",1160,0,\"nested-br_if-value\",[\"i32:2\"]],[\"invoke\",1161,0,\"nested-br_if-value\",[\"i32:9\"]],[\"invoke\",1162,0,\"nested-br_if-value\",[\"i32:4294967287\"]],[\"invoke\",1163,0,\"nested-br_if-value\",[\"i32:999999\"]],[\"invoke\",1165,0,\"nested-br_if-value-cond\",[\"i32:0\"]],[\"invoke\",1166,0,\"nested-br_if-value-cond\",[\"i32:1\"]],[\"invoke\",1167,0,\"nested-br_if-value-cond\",[\"i32:2\"]],[\"invoke\",1168,0,\"nested-br_if-value-cond\",[\"i32:3\"]],[\"invoke\",1169,0,\"nested-br_if-value-cond\",[\"i32:4293967296\"]],[\"invoke\",1170,0,\"nested-br_if-value-cond\",[\"i32:9423975\"]],[\"invoke\",1172,0,\"nested-br_table-value\",[\"i32:0\"]],[\"invoke\",1173,0,\"nested-br_table-value\",[\"i32:1\"]],[\"invoke\",1174,0,\"nested-br_table-value\",[\"i32:2\"]],[\"invoke\",1175,0,\"nested-br_table-value\",[\"i32:9\"]],[\"invoke\",1176,0,\"nested-br_table-value\",[\"i32:4294967287\"]],[\"invoke\",1177,0,\"nested-br_table-value\",[\"i32:999999\"]],[\"invoke\",1179,0,\"nested-br_table-value-index\",[\"i32:0\"]],[\"invoke\",1180,0,\"nested-br_table-value-index\",[\"i32:1\"]],[\"invoke\",1181,0,\"nested-br_table-value-index\",[\"i32:2\"]],[\"invoke\",1182,0,\"nested-br_table-value-index\",[\"i32:3\"]],[\"invoke\",1183,0,\"nested-br_table-value-index\",[\"i32:4293967296\"]],[\"invoke\",1184,0,\"nested-br_table-value-index\",[\"i32:9423975\"]],[\"invoke\",1186,0,\"nested-br_table-loop-block\",[\"i32:1\"]],[\"invoke\",1188,0,\"meet-externref\",[\"i32:0\",\"externref:1\"]],[\"invoke\",1189,0,\"meet-externref\",[\"i32:1\",\"externref:1\"]],[\"invoke\",1190,0,\"meet-externref\",[\"i32:2\",\"externref:1\"]],[\"file\",\"call.wast\"],[\"module\",3,4],[\"invoke\",285,0,\"type-i32\",[]],[\"invoke\",286,0,\"type-i64\",[]],[\"invoke\",287,0,\"type-f32\",[]],[\"invoke\",288,0,\"type-f64\",[]],[\"invoke\",289,0,\"type-i32-i64\",[]],[\"invoke\",291,0,\"type-first-i32\",[]],[\"invoke\",292,0,\"type-first-i64\",[]],[\"invoke\",293,0,\"type-first-f32\",[]],[\"invoke\",294,0,\"type-first-f64\",[]],[\"invoke\",296,0,\"type-second-i32\",[]],[\"invoke\",297,0,\"type-second-i64\",[]],[\"invoke\",298,0,\"type-second-f32\",[]],[\"invoke\",299,0,\"type-second-f64\",[]],[\"invoke\",301,0,\"type-all-i32-f64\",[]],[\"invoke\",302,0,\"type-all-i32-i32\",[]],[\"invoke\",303,0,\"type-all-f32-f64\",[]],[\"invoke\",304,0,\"type-all-f64-i32\",[]],[\"invoke\",306,0,\"as-binary-all-operands\",[]],[\"invoke\",307,0,\"as-mixed-operands\",[]],[\"invoke\",308,0,\"as-call-all-operands\",[]],[\"invoke\",310,0,\"fac\",[\"i64:0\"]],[\"invoke\",311,0,\"fac\",[\"i64:1\"]],[\"invoke\",312,0,\"fac\",[\"i64:5\"]],[\"invoke\",313,0,\"fac\",[\"i64:25\"]],[\"invoke\",314,0,\"fac-acc\",[\"i64:0\",\"i64:1\"]],[\"invoke\",315,0,\"fac-acc\",[\"i64:1\",\"i64:1\"]],[\"invoke\",316,0,\"fac-acc\",[\"i64:5\",\"i64:1\"]],[\"invoke\",318,0,\"fac-acc\",[\"i64:25\",\"i64:1\"]],[\"invoke\",322,0,\"fib\",[\"i64:0\"]],[\"invoke\",323,0,\"fib\",[\"i64:1\"]],[\"invoke\",324,0,\"fib\",[\"i64:2\"]],[\"invoke\",325,0,\"fib\",[\"i64:5\"]],[\"invoke\",326,0,\"fib\",[\"i64:20\"]],[\"invoke\",328,0,\"even\",[\"i64:0\"]],[\"invoke\",329,0,\"even\",[\"i64:1\"]],[\"invoke\",330,0,\"even\",[\"i64:100\"]],[\"invoke\",331,0,\"even\",[\"i64:77\"]],[\"invoke\",332,0,\"odd\",[\"i64:0\"]],[\"invoke\",333,0,\"odd\",[\"i64:1\"]],[\"invoke\",334,0,\"odd\",[\"i64:200\"]],[\"invoke\",335,0,\"odd\",[\"i64:77\"]],[\"invoke\",337,0,\"runaway\",[]],[\"invoke\",338,0,\"mutual-runaway\",[]],[\"invoke\",340,0,\"as-select-first\",[]],[\"invoke\",341,0,\"as-select-mid\",[]],[\"invoke\",342,0,\"as-select-last\",[]],[\"invoke\",344,0,\"as-if-condition\",[]],[\"invoke\",346,0,\"as-br_if-first\",[]],[\"invoke\",347,0,\"as-br_if-last\",[]],[\"invoke\",349,0,\"as-br_table-first\",[]],[\"invoke\",350,0,\"as-br_table-last\",[]],[\"invoke\",352,0,\"as-call_indirect-first\",[]],[\"invoke\",353,0,\"as-call_indirect-mid\",[]],[\"invoke\",354,0,\"as-call_indirect-last\",[]],[\"invoke\",356,0,\"as-store-first\",[]],[\"invoke\",357,0,\"as-store-last\",[]],[\"invoke\",359,0,\"as-memory.grow-value\",[]],[\"invoke\",360,0,\"as-return-value\",[]],[\"invoke\",361,0,\"as-drop-operand\",[]],[\"invoke\",362,0,\"as-br-value\",[]],[\"invoke\",363,0,\"as-local.set-value\",[]],[\"invoke\",364,0,\"as-local.tee-value\",[]],[\"invoke\",365,0,\"as-global.set-value\",[]],[\"invoke\",366,0,\"as-load-operand\",[]],[\"invoke\",368,0,\"as-unary-operand\",[]],[\"invoke\",369,0,\"as-binary-left\",[]],[\"invoke\",370,0,\"as-binary-right\",[]],[\"invoke\",371,0,\"as-test-operand\",[]],[\"invoke\",372,0,\"as-compare-left\",[]],[\"invoke\",373,0,\"as-compare-right\",[]],[\"invoke\",374,0,\"as-convert-operand\",[]],[\"invoke\",376,0,\"return-from-long-argument-list\",[\"i32:42\"]],[\"file\",\"call_indirect.wast\"],[\"module\",3,5],[\"invoke\",471,0,\"type-i32\",[]],[\"invoke\",472,0,\"type-i64\",[]],[\"invoke\",473,0,\"type-f32\",[]],[\"invoke\",474,0,\"type-f64\",[]],[\"invoke\",475,0,\"type-f64-i32\",[]],[\"invoke\",477,0,\"type-index\",[]],[\"invoke\",479,0,\"type-first-i32\",[]],[\"invoke\",480,0,\"type-first-i64\",[]],[\"invoke\",481,0,\"type-first-f32\",[]],[\"invoke\",482,0,\"type-first-f64\",[]],[\"invoke\",484,0,\"type-second-i32\",[]],[\"invoke\",485,0,\"type-second-i64\",[]],[\"invoke\",486,0,\"type-second-f32\",[]],[\"invoke\",487,0,\"type-second-f64\",[]],[\"invoke\",489,0,\"type-all-f64-i32\",[]],[\"invoke\",490,0,\"type-all-i32-f64\",[]],[\"invoke\",491,0,\"type-all-i32-i64\",[]],[\"invoke\",493,0,\"dispatch\",[\"i32:5\",\"i64:2\"]],[\"invoke\",494,0,\"dispatch\",[\"i32:5\",\"i64:5\"]],[\"invoke\",495,0,\"dispatch\",[\"i32:12\",\"i64:5\"]],[\"invoke\",496,0,\"dispatch\",[\"i32:13\",\"i64:5\"]],[\"invoke\",497,0,\"dispatch\",[\"i32:20\",\"i64:2\"]],[\"invoke\",498,0,\"dispatch\",[\"i32:0\",\"i64:2\"]],[\"invoke\",499,0,\"dispatch\",[\"i32:15\",\"i64:2\"]],[\"invoke\",500,0,\"dispatch\",[\"i32:32\",\"i64:2\"]],[\"invoke\",501,0,\"dispatch\",[\"i32:4294967295\",\"i64:2\"]],[\"invoke\",502,0,\"dispatch\",[\"i32:1213432423\",\"i64:2\"]],[\"invoke\",504,0,\"dispatch-structural-i64\",[\"i32:5\"]],[\"invoke\",505,0,\"dispatch-structural-i64\",[\"i32:12\"]],[\"invoke\",506,0,\"dispatch-structural-i64\",[\"i32:13\"]],[\"invoke\",507,0,\"dispatch-structural-i64\",[\"i32:20\"]],[\"invoke\",508,0,\"dispatch-structural-i64\",[\"i32:11\"]],[\"invoke\",509,0,\"dispatch-structural-i64\",[\"i32:22\"]],[\"invoke\",511,0,\"dispatch-structural-i32\",[\"i32:4\"]],[\"invoke\",512,0,\"dispatch-structural-i32\",[\"i32:23\"]],[\"invoke\",513,0,\"dispatch-structural-i32\",[\"i32:26\"]],[\"invoke\",514,0,\"dispatch-structural-i32\",[\"i32:19\"]],[\"invoke\",515,0,\"dispatch-structural-i32\",[\"i32:9\"]],[\"invoke\",516,0,\"dispatch-structural-i32\",[\"i32:21\"]],[\"invoke\",518,0,\"dispatch-structural-f32\",[\"i32:6\"]],[\"invoke\",519,0,\"dispatch-structural-f32\",[\"i32:24\"]],[\"invoke\",520,0,\"dispatch-structural-f32\",[\"i32:27\"]],[\"invoke\",521,0,\"dispatch-structural-f32\",[\"i32:21\"]],[\"invoke\",522,0,\"dispatch-structural-f32\",[\"i32:8\"]],[\"invoke\",523,0,\"dispatch-structural-f32\",[\"i32:19\"]],[\"invoke\",525,0,\"dispatch-structural-f64\",[\"i32:7\"]],[\"invoke\",526,0,\"dispatch-structural-f64\",[\"i32:25\"]],[\"invoke\",527,0,\"dispatch-structural-f64\",[\"i32:28\"]],[\"invoke\",528,0,\"dispatch-structural-f64\",[\"i32:22\"]],[\"invoke\",529,0,\"dispatch-structural-f64\",[\"i32:10\"]],[\"invoke\",530,0,\"dispatch-structural-f64\",[\"i32:18\"]],[\"invoke\",532,0,\"fac-i64\",[\"i64:0\"]],[\"invoke\",533,0,\"fac-i64\",[\"i64:1\"]],[\"invoke\",534,0,\"fac-i64\",[\"i64:5\"]],[\"invoke\",535,0,\"fac-i64\",[\"i64:25\"]],[\"invoke\",537,0,\"fac-i32\",[\"i32:0\"]],[\"invoke\",538,0,\"fac-i32\",[\"i32:1\"]],[\"invoke\",539,0,\"fac-i32\",[\"i32:5\"]],[\"invoke\",540,0,\"fac-i32\",[\"i32:10\"]],[\"invoke\",542,0,\"fac-f32\",[\"f32:0\"]],[\"invoke\",543,0,\"fac-f32\",[\"f32:1065353216\"]],[\"invoke\",544,0,\"fac-f32\",[\"f32:1084227584\"]],[\"invoke\",545,0,\"fac-f32\",[\"f32:1092616192\"]],[\"invoke\",547,0,\"fac-f64\",[\"f64:0\"]],[\"invoke\",548,0,\"fac-f64\",[\"f64:4607182418800017408\"]],[\"invoke\",549,0,\"fac-f64\",[\"f64:4617315517961601024\"]],[\"invoke\",550,0,\"fac-f64\",[\"f64:4621819117588971520\"]],[\"invoke\",552,0,\"fib-i64\",[\"i64:0\"]],[\"invoke\",553,0,\"fib-i64\",[\"i64:1\"]],[\"invoke\",554,0,\"fib-i64\",[\"i64:2\"]],[\"invoke\",555,0,\"fib-i64\",[\"i64:5\"]],[\"invoke\",556,0,\"fib-i64\",[\"i64:20\"]],[\"invoke\",558,0,\"fib-i32\",[\"i32:0\"]],[\"invoke\",559,0,\"fib-i32\",[\"i32:1\"]],[\"invoke\",560,0,\"fib-i32\",[\"i32:2\"]],[\"invoke\",561,0,\"fib-i32\",[\"i32:5\"]],[\"invoke\",562,0,\"fib-i32\",[\"i32:20\"]],[\"invoke\",564,0,\"fib-f32\",[\"f32:0\"]],[\"invoke\",565,0,\"fib-f32\",[\"f32:1065353216\"]],[\"invoke\",566,0,\"fib-f32\",[\"f32:1073741824\"]],[\"invoke\",567,0,\"fib-f32\",[\"f32:1084227584\"]],[\"invoke\",568,0,\"fib-f32\",[\"f32:1101004800\"]],[\"invoke\",570,0,\"fib-f64\",[\"f64:0\"]],[\"invoke\",571,0,\"fib-f64\",[\"f64:4607182418800017408\"]],[\"invoke\",572,0,\"fib-f64\",[\"f64:4611686018427387904\"]],[\"invoke\",573,0,\"fib-f64\",[\"f64:4617315517961601024\"]],[\"invoke\",574,0,\"fib-f64\",[\"f64:4626322717216342016\"]],[\"invoke\",576,0,\"even\",[\"i32:0\"]],[\"invoke\",577,0,\"even\",[\"i32:1\"]],[\"invoke\",578,0,\"even\",[\"i32:100\"]],[\"invoke\",579,0,\"even\",[\"i32:77\"]],[\"invoke\",580,0,\"odd\",[\"i32:0\"]],[\"invoke\",581,0,\"odd\",[\"i32:1\"]],[\"invoke\",582,0,\"odd\",[\"i32:200\"]],[\"invoke\",583,0,\"odd\",[\"i32:77\"]],[\"invoke\",585,0,\"runaway\",[]],[\"invoke\",586,0,\"mutual-runaway\",[]],[\"invoke\",588,0,\"as-select-first\",[]],[\"invoke\",589,0,\"as-select-mid\",[]],[\"invoke\",590,0,\"as-select-last\",[]],[\"invoke\",592,0,\"as-if-condition\",[]],[\"invoke\",594,0,\"as-br_if-first\",[]],[\"invoke\",595,0,\"as-br_if-last\",[]],[\"invoke\",597,0,\"as-br_table-first\",[]],[\"invoke\",598,0,\"as-br_table-last\",[]],[\"invoke\",600,0,\"as-store-first\",[]],[\"invoke\",601,0,\"as-store-last\",[]],[\"invoke\",603,0,\"as-memory.grow-value\",[]],[\"invoke\",604,0,\"as-return-value\",[]],[\"invoke\",605,0,\"as-drop-operand\",[]],[\"invoke\",606,0,\"as-br-value\",[]],[\"invoke\",607,0,\"as-local.set-value\",[]],[\"invoke\",608,0,\"as-local.tee-value\",[]],[\"invoke\",609,0,\"as-global.set-value\",[]],[\"invoke\",610,0,\"as-load-operand\",[]],[\"invoke\",612,0,\"as-unary-operand\",[]],[\"invoke\",613,0,\"as-binary-left\",[]],[\"invoke\",614,0,\"as-binary-right\",[]],[\"invoke\",615,0,\"as-test-operand\",[]],[\"invoke\",616,0,\"as-compare-left\",[]],[\"invoke\",617,0,\"as-compare-right\",[]],[\"invoke\",618,0,\"as-convert-operand\",[]],[\"module\",623,6],[\"invoke\",650,0,\"call-1\",[\"i32:2\",\"i32:3\",\"i32:0\"]],[\"invoke\",651,0,\"call-1\",[\"i32:2\",\"i32:3\",\"i32:1\"]],[\"invoke\",652,0,\"call-1\",[\"i32:2\",\"i32:3\",\"i32:2\"]],[\"invoke\",654,0,\"call-2\",[\"i32:2\",\"i32:3\",\"i32:0\"]],[\"invoke\",655,0,\"call-2\",[\"i32:2\",\"i32:3\",\"i32:1\"]],[\"invoke\",656,0,\"call-2\",[\"i32:2\",\"i32:3\",\"i32:2\"]],[\"invoke\",657,0,\"call-2\",[\"i32:2\",\"i32:3\",\"i32:3\"]],[\"invoke\",659,0,\"call-3\",[\"i32:2\",\"i32:3\",\"i32:0\"]],[\"invoke\",660,0,\"call-3\",[\"i32:2\",\"i32:3\",\"i32:1\"]],[\"invoke\",661,0,\"call-3\",[\"i32:2\",\"i32:3\",\"i32:2\"]],[\"invoke\",662,0,\"call-3\",[\"i32:2\",\"i32:3\",\"i32:3\"]],[\"invoke\",663,0,\"call-3\",[\"i32:2\",\"i32:3\",\"i32:4\"]],[\"module\",1047,7],[\"file\",\"fac.wast\"],[\"module\",1,8],[\"invoke\",102,0,\"fac-rec\",[\"i64:25\"]],[\"invoke\",103,0,\"fac-iter\",[\"i64:25\"]],[\"invoke\",104,0,\"fac-rec-named\",[\"i64:25\"]],[\"invoke\",105,0,\"fac-iter-named\",[\"i64:25\"]],[\"invoke\",106,0,\"fac-opt\",[\"i64:25\"]],[\"invoke\",107,0,\"fac-ssa\",[\"i64:25\"]],[\"invoke\",109,0,\"fac-rec\",[\"i64:1073741824\"]],[\"file\",\"forward.wast\"],[\"module\",1,9],[\"invoke\",17,0,\"even\",[\"i32:13\"]],[\"invoke\",18,0,\"even\",[\"i32:20\"]],[\"invoke\",19,0,\"odd\",[\"i32:13\"]],[\"invoke\",20,0,\"odd\",[\"i32:20\"]],[\"file\",\"func.wast\"],[\"module\",3,10],[\"invoke\",241,0,\"type-use-1\",[]],[\"invoke\",242,0,\"type-use-2\",[]],[\"invoke\",243,0,\"type-use-3\",[\"i32:1\"]],[\"invoke\",245,0,\"type-use-4\",[\"i32:1\",\"f64:4607182418800017408\",\"i32:1\"]],[\"invoke\",248,0,\"type-use-5\",[]],[\"invoke\",249,0,\"type-use-6\",[\"i32:1\"]],[\"invoke\",251,0,\"type-use-7\",[\"i32:1\",\"f64:4607182418800017408\",\"i32:1\"]],[\"invoke\",255,0,\"local-first-i32\",[]],[\"invoke\",256,0,\"local-first-i64\",[]],[\"invoke\",257,0,\"local-first-f32\",[]],[\"invoke\",258,0,\"local-first-f64\",[]],[\"invoke\",259,0,\"local-second-i32\",[]],[\"invoke\",260,0,\"local-second-i64\",[]],[\"invoke\",261,0,\"local-second-f32\",[]],[\"invoke\",262,0,\"local-second-f64\",[]],[\"invoke\",263,0,\"local-mixed\",[]],[\"invoke\",266,0,\"param-first-i32\",[\"i32:2\",\"i32:3\"]],[\"invoke\",269,0,\"param-first-i64\",[\"i64:2\",\"i64:3\"]],[\"invoke\",272,0,\"param-first-f32\",[\"f32:1073741824\",\"f32:1077936128\"]],[\"invoke\",275,0,\"param-first-f64\",[\"f64:4611686018427387904\",\"f64:4613937818241073152\"]],[\"invoke\",278,0,\"param-second-i32\",[\"i32:2\",\"i32:3\"]],[\"invoke\",281,0,\"param-second-i64\",[\"i64:2\",\"i64:3\"]],[\"invoke\",284,0,\"param-second-f32\",[\"f32:1073741824\",\"f32:1077936128\"]],[\"invoke\",287,0,\"param-second-f64\",[\"f64:4611686018427387904\",\"f64:4613937818241073152\"]],[\"invoke\",291,0,\"param-mixed\",[\"f32:1065353216\",\"i32:2\",\"i64:3\",\"i32:4\",\"f64:4617878467915022336\",\"i32:6\"]],[\"invoke\",298,0,\"empty\",[]],[\"invoke\",299,0,\"value-void\",[]],[\"invoke\",300,0,\"value-i32\",[]],[\"invoke\",301,0,\"value-i64\",[]],[\"invoke\",302,0,\"value-f32\",[]],[\"invoke\",303,0,\"value-f64\",[]],[\"invoke\",304,0,\"value-i32-f64\",[]],[\"invoke\",305,0,\"value-i32-i32-i32\",[]],[\"invoke\",308,0,\"value-block-void\",[]],[\"invoke\",309,0,\"value-block-i32\",[]],[\"invoke\",310,0,\"value-block-i32-i64\",[]],[\"invoke\",312,0,\"return-empty\",[]],[\"invoke\",313,0,\"return-i32\",[]],[\"invoke\",314,0,\"return-i64\",[]],[\"invoke\",315,0,\"return-f32\",[]],[\"invoke\",316,0,\"return-f64\",[]],[\"invoke\",317,0,\"return-i32-f64\",[]],[\"invoke\",318,0,\"return-i32-i32-i32\",[]],[\"invoke\",321,0,\"return-block-i32\",[]],[\"invoke\",322,0,\"return-block-i32-i64\",[]],[\"invoke\",324,0,\"break-empty\",[]],[\"invoke\",325,0,\"break-i32\",[]],[\"invoke\",326,0,\"break-i64\",[]],[\"invoke\",327,0,\"break-f32\",[]],[\"invoke\",328,0,\"break-f64\",[]],[\"invoke\",329,0,\"break-i32-f64\",[]],[\"invoke\",330,0,\"break-i32-i32-i32\",[]],[\"invoke\",333,0,\"break-block-i32\",[]],[\"invoke\",334,0,\"break-block-i32-i64\",[]],[\"invoke\",336,0,\"break-br_if-empty\",[\"i32:0\"]],[\"invoke\",337,0,\"break-br_if-empty\",[\"i32:2\"]],[\"invoke\",338,0,\"break-br_if-num\",[\"i32:0\"]],[\"invoke\",339,0,\"break-br_if-num\",[\"i32:1\"]],[\"invoke\",340,0,\"break-br_if-num-num\",[\"i32:0\"]],[\"invoke\",343,0,\"break-br_if-num-num\",[\"i32:1\"]],[\"invoke\",347,0,\"break-br_table-empty\",[\"i32:0\"]],[\"invoke\",348,0,\"break-br_table-empty\",[\"i32:1\"]],[\"invoke\",349,0,\"break-br_table-empty\",[\"i32:5\"]],[\"invoke\",350,0,\"break-br_table-empty\",[\"i32:4294967295\"]],[\"invoke\",351,0,\"break-br_table-num\",[\"i32:0\"]],[\"invoke\",352,0,\"break-br_table-num\",[\"i32:1\"]],[\"invoke\",353,0,\"break-br_table-num\",[\"i32:10\"]],[\"invoke\",354,0,\"break-br_table-num\",[\"i32:4294967196\"]],[\"invoke\",355,0,\"break-br_table-num-num\",[\"i32:0\"]],[\"invoke\",358,0,\"break-br_table-num-num\",[\"i32:1\"]],[\"invoke\",361,0,\"break-br_table-num-num\",[\"i32:10\"]],[\"invoke\",364,0,\"break-br_table-num-num\",[\"i32:4294967196\"]],[\"invoke\",367,0,\"break-br_table-nested-empty\",[\"i32:0\"]],[\"invoke\",368,0,\"break-br_table-nested-empty\",[\"i32:1\"]],[\"invoke\",369,0,\"break-br_table-nested-empty\",[\"i32:3\"]],[\"invoke\",370,0,\"break-br_table-nested-empty\",[\"i32:4294967294\"]],[\"invoke\",372,0,\"break-br_table-nested-num\",[\"i32:0\"]],[\"invoke\",375,0,\"break-br_table-nested-num\",[\"i32:1\"]],[\"invoke\",378,0,\"break-br_table-nested-num\",[\"i32:2\"]],[\"invoke\",381,0,\"break-br_table-nested-num\",[\"i32:4294967293\"]],[\"invoke\",384,0,\"break-br_table-nested-num-num\",[\"i32:0\"]],[\"invoke\",388,0,\"break-br_table-nested-num-num\",[\"i32:1\"]],[\"invoke\",392,0,\"break-br_table-nested-num-num\",[\"i32:2\"]],[\"invoke\",396,0,\"break-br_table-nested-num-num\",[\"i32:4294967293\"]],[\"invoke\",401,0,\"large-sig\",[\"i32:0\",\"i64:1\",\"f32:1073741824\",\"f32:1077936128\",\"i32:4\",\"f64:4617315517961601024\",\"f32:1086324736\",\"i32:7\",\"i32:8\",\"i32:9\",\"f32:1092616192\",\"f64:4622382067542392832\",\"f64:4622945017495814144\",\"f64:4623507967449235456\",\"i32:14\",\"i32:15\",\"f32:1098907648\"]],[\"invoke\",414,0,\"init-local-i32\",[]],[\"invoke\",415,0,\"init-local-i64\",[]],[\"invoke\",416,0,\"init-local-f32\",[]],[\"invoke\",417,0,\"init-local-f64\",[]],[\"module\",422,11],[\"module\",459,12],[\"invoke\",483,0,\"f\",[\"i32:42\"]],[\"invoke\",484,0,\"g\",[\"i32:42\"]],[\"invoke\",485,0,\"p\",[]],[\"module\",488,13],[\"invoke\",551,0,\"signature-explicit-reused\",[]],[\"invoke\",552,0,\"signature-implicit-reused\",[]],[\"invoke\",553,0,\"signature-explicit-duplicate\",[]],[\"invoke\",554,0,\"signature-implicit-duplicate\",[]],[\"file\",\"func_ptrs.wast\"],[\"module\",1,14],[\"invoke\",27,0,\"one\",[]],[\"invoke\",28,0,\"two\",[\"i32:13\"]],[\"invoke\",29,0,\"three\",[\"i32:13\"]],[\"invoke\",30,0,\"four\",[\"i32:83\"]],[\"module\",51,15],[\"invoke\",71,0,\"callt\",[\"i32:0\"]],[\"invoke\",72,0,\"callt\",[\"i32:1\"]],[\"invoke\",73,0,\"callt\",[\"i32:2\"]],[\"invoke\",74,0,\"callt\",[\"i32:3\"]],[\"invoke\",75,0,\"callt\",[\"i32:4\"]],[\"invoke\",76,0,\"callt\",[\"i32:5\"]],[\"invoke\",77,0,\"callt\",[\"i32:6\"]],[\"invoke\",78,0,\"callt\",[\"i32:7\"]],[\"invoke\",79,0,\"callt\",[\"i32:100\"]],[\"invoke\",80,0,\"callt\",[\"i32:4294967295\"]],[\"invoke\",82,0,\"callu\",[\"i32:0\"]],[\"invoke\",83,0,\"callu\",[\"i32:1\"]],[\"invoke\",84,0,\"callu\",[\"i32:2\"]],[\"invoke\",85,0,\"callu\",[\"i32:3\"]],[\"invoke\",86,0,\"callu\",[\"i32:4\"]],[\"invoke\",87,0,\"callu\",[\"i32:5\"]],[\"invoke\",88,0,\"callu\",[\"i32:6\"]],[\"invoke\",89,0,\"callu\",[\"i32:7\"]],[\"invoke\",90,0,\"callu\",[\"i32:100\"]],[\"invoke\",91,0,\"callu\",[\"i32:4294967295\"]],[\"module\",93,16],[\"invoke\",105,0,\"callt\",[\"i32:0\"]],[\"invoke\",106,0,\"callt\",[\"i32:1\"]],[\"file\",\"if.wast\"],[\"module\",3,17],[\"invoke\",537,0,\"empty\",[\"i32:0\"]],[\"invoke\",538,0,\"empty\",[\"i32:1\"]],[\"invoke\",539,0,\"empty\",[\"i32:100\"]],[\"invoke\",540,0,\"empty\",[\"i32:4294967294\"]],[\"invoke\",542,0,\"singular\",[\"i32:0\"]],[\"invoke\",543,0,\"singular\",[\"i32:1\"]],[\"invoke\",544,0,\"singular\",[\"i32:10\"]],[\"invoke\",545,0,\"singular\",[\"i32:4294967286\"]],[\"invoke\",547,0,\"multi\",[\"i32:0\"]],[\"invoke\",548,0,\"multi\",[\"i32:1\"]],[\"invoke\",549,0,\"multi\",[\"i32:13\"]],[\"invoke\",550,0,\"multi\",[\"i32:4294967291\"]],[\"invoke\",552,0,\"nested\",[\"i32:0\",\"i32:0\"]],[\"invoke\",553,0,\"nested\",[\"i32:1\",\"i32:0\"]],[\"invoke\",554,0,\"nested\",[\"i32:0\",\"i32:1\"]],[\"invoke\",555,0,\"nested\",[\"i32:3\",\"i32:2\"]],[\"invoke\",556,0,\"nested\",[\"i32:0\",\"i32:4294967196\"]],[\"invoke\",557,0,\"nested\",[\"i32:10\",\"i32:10\"]],[\"invoke\",558,0,\"nested\",[\"i32:0\",\"i32:4294967295\"]],[\"invoke\",559,0,\"nested\",[\"i32:4294967185\",\"i32:4294967294\"]],[\"invoke\",561,0,\"as-select-first\",[\"i32:0\"]],[\"invoke\",562,0,\"as-select-first\",[\"i32:1\"]],[\"invoke\",563,0,\"as-select-mid\",[\"i32:0\"]],[\"invoke\",564,0,\"as-select-mid\",[\"i32:1\"]],[\"invoke\",565,0,\"as-select-last\",[\"i32:0\"]],[\"invoke\",566,0,\"as-select-last\",[\"i32:1\"]],[\"invoke\",568,0,\"as-loop-first\",[\"i32:0\"]],[\"invoke\",569,0,\"as-loop-first\",[\"i32:1\"]],[\"invoke\",570,0,\"as-loop-mid\",[\"i32:0\"]],[\"invoke\",571,0,\"as-loop-mid\",[\"i32:1\"]],[\"invoke\",572,0,\"as-loop-last\",[\"i32:0\"]],[\"invoke\",573,0,\"as-loop-last\",[\"i32:1\"]],[\"invoke\",575,0,\"as-if-condition\",[\"i32:0\"]],[\"invoke\",576,0,\"as-if-condition\",[\"i32:1\"]],[\"invoke\",578,0,\"as-br_if-first\",[\"i32:0\"]],[\"invoke\",579,0,\"as-br_if-first\",[\"i32:1\"]],[\"invoke\",580,0,\"as-br_if-last\",[\"i32:0\"]],[\"invoke\",581,0,\"as-br_if-last\",[\"i32:1\"]],[\"invoke\",583,0,\"as-br_table-first\",[\"i32:0\"]],[\"invoke\",584,0,\"as-br_table-first\",[\"i32:1\"]],[\"invoke\",585,0,\"as-br_table-last\",[\"i32:0\"]],[\"invoke\",586,0,\"as-br_table-last\",[\"i32:1\"]],[\"invoke\",588,0,\"as-call_indirect-first\",[\"i32:0\"]],[\"invoke\",589,0,\"as-call_indirect-first\",[\"i32:1\"]],[\"invoke\",590,0,\"as-call_indirect-mid\",[\"i32:0\"]],[\"invoke\",591,0,\"as-call_indirect-mid\",[\"i32:1\"]],[\"invoke\",592,0,\"as-call_indirect-last\",[\"i32:0\"]],[\"invoke\",593,0,\"as-call_indirect-last\",[\"i32:1\"]],[\"invoke\",595,0,\"as-store-first\",[\"i32:0\"]],[\"invoke\",596,0,\"as-store-first\",[\"i32:1\"]],[\"invoke\",597,0,\"as-store-last\",[\"i32:0\"]],[\"invoke\",598,0,\"as-store-last\",[\"i32:1\"]],[\"invoke\",600,0,\"as-memory.grow-value\",[\"i32:0\"]],[\"invoke\",601,0,\"as-memory.grow-value\",[\"i32:1\"]],[\"invoke\",603,0,\"as-call-value\",[\"i32:0\"]],[\"invoke\",604,0,\"as-call-value\",[\"i32:1\"]],[\"invoke\",606,0,\"as-return-value\",[\"i32:0\"]],[\"invoke\",607,0,\"as-return-value\",[\"i32:1\"]],[\"invoke\",609,0,\"as-drop-operand\",[\"i32:0\"]],[\"invoke\",610,0,\"as-drop-operand\",[\"i32:1\"]],[\"invoke\",612,0,\"as-br-value\",[\"i32:0\"]],[\"invoke\",613,0,\"as-br-value\",[\"i32:1\"]],[\"invoke\",615,0,\"as-local.set-value\",[\"i32:0\"]],[\"invoke\",616,0,\"as-local.set-value\",[\"i32:1\"]],[\"invoke\",618,0,\"as-local.tee-value\",[\"i32:0\"]],[\"invoke\",619,0,\"as-local.tee-value\",[\"i32:1\"]],[\"invoke\",621,0,\"as-global.set-value\",[\"i32:0\"]],[\"invoke\",622,0,\"as-global.set-value\",[\"i32:1\"]],[\"invoke\",624,0,\"as-load-operand\",[\"i32:0\"]],[\"invoke\",625,0,\"as-load-operand\",[\"i32:1\"]],[\"invoke\",627,0,\"as-unary-operand\",[\"i32:0\"]],[\"invoke\",628,0,\"as-unary-operand\",[\"i32:1\"]],[\"invoke\",629,0,\"as-unary-operand\",[\"i32:4294967295\"]],[\"invoke\",631,0,\"as-binary-operand\",[\"i32:0\",\"i32:0\"]],[\"invoke\",632,0,\"as-binary-operand\",[\"i32:0\",\"i32:1\"]],[\"invoke\",633,0,\"as-binary-operand\",[\"i32:1\",\"i32:0\"]],[\"invoke\",634,0,\"as-binary-operand\",[\"i32:1\",\"i32:1\"]],[\"invoke\",636,0,\"as-test-operand\",[\"i32:0\"]],[\"invoke\",637,0,\"as-test-operand\",[\"i32:1\"]],[\"invoke\",639,0,\"as-compare-operand\",[\"i32:0\",\"i32:0\"]],[\"invoke\",640,0,\"as-compare-operand\",[\"i32:0\",\"i32:1\"]],[\"invoke\",641,0,\"as-compare-operand\",[\"i32:1\",\"i32:0\"]],[\"invoke\",642,0,\"as-compare-operand\",[\"i32:1\",\"i32:1\"]],[\"invoke\",644,0,\"as-binary-operands\",[\"i32:0\"]],[\"invoke\",645,0,\"as-binary-operands\",[\"i32:1\"]],[\"invoke\",647,0,\"as-compare-operands\",[\"i32:0\"]],[\"invoke\",648,0,\"as-compare-operands\",[\"i32:1\"]],[\"invoke\",650,0,\"as-mixed-operands\",[\"i32:0\"]],[\"invoke\",651,0,\"as-mixed-operands\",[\"i32:1\"]],[\"invoke\",653,0,\"break-bare\",[]],[\"invoke\",654,0,\"break-value\",[\"i32:1\"]],[\"invoke\",655,0,\"break-value\",[\"i32:0\"]],[\"invoke\",656,0,\"break-multi-value\",[\"i32:0\"]],[\"invoke\",659,0,\"break-multi-value\",[\"i32:1\"]],[\"invoke\",663,0,\"param\",[\"i32:0\"]],[\"invoke\",664,0,\"param\",[\"i32:1\"]],[\"invoke\",665,0,\"params\",[\"i32:0\"]],[\"invoke\",666,0,\"params\",[\"i32:1\"]],[\"invoke\",667,0,\"params-id\",[\"i32:0\"]],[\"invoke\",668,0,\"params-id\",[\"i32:1\"]],[\"invoke\",669,0,\"param-break\",[\"i32:0\"]],[\"invoke\",670,0,\"param-break\",[\"i32:1\"]],[\"invoke\",671,0,\"params-break\",[\"i32:0\"]],[\"invoke\",672,0,\"params-break\",[\"i32:1\"]],[\"invoke\",673,0,\"params-id-break\",[\"i32:0\"]],[\"invoke\",674,0,\"params-id-break\",[\"i32:1\"]],[\"invoke\",676,0,\"effects\",[\"i32:1\"]],[\"invoke\",677,0,\"effects\",[\"i32:0\"]],[\"invoke\",680,0,\"add64_u_with_carry\",[\"i64:0\",\"i64:0\",\"i32:0\"]],[\"invoke\",684,0,\"add64_u_with_carry\",[\"i64:100\",\"i64:124\",\"i32:0\"]],[\"invoke\",688,0,\"add64_u_with_carry\",[\"i64:18446744073709551615\",\"i64:0\",\"i32:0\"]],[\"invoke\",692,0,\"add64_u_with_carry\",[\"i64:18446744073709551615\",\"i64:1\",\"i32:0\"]],[\"invoke\",696,0,\"add64_u_with_carry\",[\"i64:18446744073709551615\",\"i64:18446744073709551615\",\"i32:0\"]],[\"invoke\",700,0,\"add64_u_with_carry\",[\"i64:18446744073709551615\",\"i64:0\",\"i32:1\"]],[\"invoke\",704,0,\"add64_u_with_carry\",[\"i64:18446744073709551615\",\"i64:1\",\"i32:1\"]],[\"invoke\",708,0,\"add64_u_with_carry\",[\"i64:9223372036854775808\",\"i64:9223372036854775808\",\"i32:0\"]],[\"invoke\",713,0,\"add64_u_saturated\",[\"i64:0\",\"i64:0\"]],[\"invoke\",716,0,\"add64_u_saturated\",[\"i64:1230\",\"i64:23\"]],[\"invoke\",719,0,\"add64_u_saturated\",[\"i64:18446744073709551615\",\"i64:0\"]],[\"invoke\",722,0,\"add64_u_saturated\",[\"i64:18446744073709551615\",\"i64:1\"]],[\"invoke\",725,0,\"add64_u_saturated\",[\"i64:18446744073709551615\",\"i64:18446744073709551615\"]],[\"invoke\",728,0,\"add64_u_saturated\",[\"i64:9223372036854775808\",\"i64:9223372036854775808\"]],[\"invoke\",731,0,\"type-use\",[]],[\"invoke\",733,0,\"atypical-condition\",[]],[\"file\",\"labels.wast\"],[\"module\",1,18],[\"invoke\",291,0,\"block\",[]],[\"invoke\",292,0,\"loop1\",[]],[\"invoke\",293,0,\"loop2\",[]],[\"invoke\",294,0,\"loop3\",[]],[\"invoke\",295,0,\"loop4\",[\"i32:8\"]],[\"invoke\",296,0,\"loop5\",[]],[\"invoke\",297,0,\"loop6\",[]],[\"invoke\",298,0,\"if\",[]],[\"invoke\",299,0,\"if2\",[]],[\"invoke\",300,0,\"switch\",[\"i32:0\"]],[\"invoke\",301,0,\"switch\",[\"i32:1\"]],[\"invoke\",302,0,\"switch\",[\"i32:2\"]],[\"invoke\",303,0,\"switch\",[\"i32:3\"]],[\"invoke\",304,0,\"switch\",[\"i32:4\"]],[\"invoke\",305,0,\"switch\",[\"i32:5\"]],[\"invoke\",306,0,\"return\",[\"i32:0\"]],[\"invoke\",307,0,\"return\",[\"i32:1\"]],[\"invoke\",308,0,\"return\",[\"i32:2\"]],[\"invoke\",309,0,\"br_if0\",[]],[\"invoke\",310,0,\"br_if1\",[]],[\"invoke\",311,0,\"br_if2\",[]],[\"invoke\",312,0,\"br_if3\",[]],[\"invoke\",313,0,\"br\",[]],[\"invoke\",314,0,\"shadowing\",[]],[\"invoke\",315,0,\"redefinition\",[]],[\"file\",\"loop.wast\"],[\"module\",3,19],[\"invoke\",430,0,\"empty\",[]],[\"invoke\",431,0,\"singular\",[]],[\"invoke\",432,0,\"multi\",[]],[\"invoke\",433,0,\"nested\",[]],[\"invoke\",434,0,\"deep\",[]],[\"invoke\",436,0,\"as-select-first\",[]],[\"invoke\",437,0,\"as-select-mid\",[]],[\"invoke\",438,0,\"as-select-last\",[]],[\"invoke\",440,0,\"as-if-condition\",[]],[\"invoke\",441,0,\"as-if-then\",[]],[\"invoke\",442,0,\"as-if-else\",[]],[\"invoke\",444,0,\"as-br_if-first\",[]],[\"invoke\",445,0,\"as-br_if-last\",[]],[\"invoke\",447,0,\"as-br_table-first\",[]],[\"invoke\",448,0,\"as-br_table-last\",[]],[\"invoke\",450,0,\"as-call_indirect-first\",[]],[\"invoke\",451,0,\"as-call_indirect-mid\",[]],[\"invoke\",452,0,\"as-call_indirect-last\",[]],[\"invoke\",454,0,\"as-store-first\",[]],[\"invoke\",455,0,\"as-store-last\",[]],[\"invoke\",457,0,\"as-memory.grow-value\",[]],[\"invoke\",458,0,\"as-call-value\",[]],[\"invoke\",459,0,\"as-return-value\",[]],[\"invoke\",460,0,\"as-drop-operand\",[]],[\"invoke\",461,0,\"as-br-value\",[]],[\"invoke\",462,0,\"as-local.set-value\",[]],[\"invoke\",463,0,\"as-local.tee-value\",[]],[\"invoke\",464,0,\"as-global.set-value\",[]],[\"invoke\",465,0,\"as-load-operand\",[]],[\"invoke\",467,0,\"as-unary-operand\",[]],[\"invoke\",468,0,\"as-binary-operand\",[]],[\"invoke\",469,0,\"as-test-operand\",[]],[\"invoke\",470,0,\"as-compare-operand\",[]],[\"invoke\",471,0,\"as-binary-operands\",[]],[\"invoke\",472,0,\"as-compare-operands\",[]],[\"invoke\",473,0,\"as-mixed-operands\",[]],[\"invoke\",475,0,\"break-bare\",[]],[\"invoke\",476,0,\"break-value\",[]],[\"invoke\",477,0,\"break-multi-value\",[]],[\"invoke\",480,0,\"break-repeated\",[]],[\"invoke\",481,0,\"break-inner\",[]],[\"invoke\",483,0,\"param\",[]],[\"invoke\",484,0,\"params\",[]],[\"invoke\",485,0,\"params-id\",[]],[\"invoke\",486,0,\"param-break\",[]],[\"invoke\",487,0,\"params-break\",[]],[\"invoke\",488,0,\"params-id-break\",[]],[\"invoke\",490,0,\"effects\",[]],[\"invoke\",492,0,\"while\",[\"i64:0\"]],[\"invoke\",493,0,\"while\",[\"i64:1\"]],[\"invoke\",494,0,\"while\",[\"i64:2\"]],[\"invoke\",495,0,\"while\",[\"i64:3\"]],[\"invoke\",496,0,\"while\",[\"i64:5\"]],[\"invoke\",497,0,\"while\",[\"i64:20\"]],[\"invoke\",499,0,\"for\",[\"i64:0\"]],[\"invoke\",500,0,\"for\",[\"i64:1\"]],[\"invoke\",501,0,\"for\",[\"i64:2\"]],[\"invoke\",502,0,\"for\",[\"i64:3\"]],[\"invoke\",503,0,\"for\",[\"i64:5\"]],[\"invoke\",504,0,\"for\",[\"i64:20\"]],[\"invoke\",506,0,\"nesting\",[\"f32:0\",\"f32:1088421888\"]],[\"invoke\",507,0,\"nesting\",[\"f32:1088421888\",\"f32:0\"]],[\"invoke\",508,0,\"nesting\",[\"f32:1065353216\",\"f32:1065353216\"]],[\"invoke\",509,0,\"nesting\",[\"f32:1065353216\",\"f32:1073741824\"]],[\"invoke\",510,0,\"nesting\",[\"f32:1065353216\",\"f32:1077936128\"]],[\"invoke\",511,0,\"nesting\",[\"f32:1065353216\",\"f32:1082130432\"]],[\"invoke\",512,0,\"nesting\",[\"f32:1065353216\",\"f32:1120403456\"]],[\"invoke\",513,0,\"nesting\",[\"f32:1065353216\",\"f32:1120534528\"]],[\"invoke\",514,0,\"nesting\",[\"f32:1073741824\",\"f32:1065353216\"]],[\"invoke\",515,0,\"nesting\",[\"f32:1077936128\",\"f32:1065353216\"]],[\"invoke\",516,0,\"nesting\",[\"f32:1092616192\",\"f32:1065353216\"]],[\"invoke\",517,0,\"nesting\",[\"f32:1073741824\",\"f32:1073741824\"]],[\"invoke\",518,0,\"nesting\",[\"f32:1073741824\",\"f32:1077936128\"]],[\"invoke\",519,0,\"nesting\",[\"f32:1088421888\",\"f32:1082130432\"]],[\"invoke\",520,0,\"nesting\",[\"f32:1088421888\",\"f32:1120403456\"]],[\"invoke\",521,0,\"nesting\",[\"f32:1088421888\",\"f32:1120534528\"]],[\"invoke\",523,0,\"type-use\",[]],[\"file\",\"local_get.wast\"],[\"module\",3,20],[\"invoke\",109,0,\"type-local-i32\",[]],[\"invoke\",110,0,\"type-local-i64\",[]],[\"invoke\",111,0,\"type-local-f32\",[]],[\"invoke\",112,0,\"type-local-f64\",[]],[\"invoke\",114,0,\"type-param-i32\",[\"i32:2\"]],[\"invoke\",115,0,\"type-param-i64\",[\"i64:3\"]],[\"invoke\",116,0,\"type-param-f32\",[\"f32:1082969293\"]],[\"invoke\",117,0,\"type-param-f64\",[\"f64:4617878467915022336\"]],[\"invoke\",119,0,\"as-block-value\",[\"i32:6\"]],[\"invoke\",120,0,\"as-loop-value\",[\"i32:7\"]],[\"invoke\",122,0,\"as-br-value\",[\"i32:8\"]],[\"invoke\",123,0,\"as-br_if-value\",[\"i32:9\"]],[\"invoke\",124,0,\"as-br_if-value-cond\",[\"i32:10\"]],[\"invoke\",125,0,\"as-br_table-value\",[\"i32:1\"]],[\"invoke\",127,0,\"as-return-value\",[\"i32:0\"]],[\"invoke\",129,0,\"as-if-then\",[\"i32:1\"]],[\"invoke\",130,0,\"as-if-else\",[\"i32:0\"]],[\"invoke\",133,0,\"type-mixed\",[\"i64:1\",\"f32:1074580685\",\"f64:4614613358185178726\",\"i32:4\",\"i32:5\"]],[\"invoke\",139,0,\"read\",[\"i64:1\",\"f32:1073741824\",\"f64:4614613358185178726\",\"i32:4\",\"i32:5\"]],[\"file\",\"local_set.wast\"],[\"module\",3,21],[\"invoke\",107,0,\"type-local-i32\",[]],[\"invoke\",108,0,\"type-local-i64\",[]],[\"invoke\",109,0,\"type-local-f32\",[]],[\"invoke\",110,0,\"type-local-f64\",[]],[\"invoke\",112,0,\"type-param-i32\",[\"i32:2\"]],[\"invoke\",113,0,\"type-param-i64\",[\"i64:3\"]],[\"invoke\",114,0,\"type-param-f32\",[\"f32:1082969293\"]],[\"invoke\",115,0,\"type-param-f64\",[\"f64:4617878467915022336\"]],[\"invoke\",117,0,\"as-block-value\",[\"i32:0\"]],[\"invoke\",118,0,\"as-loop-value\",[\"i32:0\"]],[\"invoke\",120,0,\"as-br-value\",[\"i32:0\"]],[\"invoke\",121,0,\"as-br_if-value\",[\"i32:0\"]],[\"invoke\",122,0,\"as-br_if-value-cond\",[\"i32:0\"]],[\"invoke\",123,0,\"as-br_table-value\",[\"i32:0\"]],[\"invoke\",125,0,\"as-return-value\",[\"i32:0\"]],[\"invoke\",127,0,\"as-if-then\",[\"i32:1\"]],[\"invoke\",128,0,\"as-if-else\",[\"i32:0\"]],[\"invoke\",131,0,\"type-mixed\",[\"i64:1\",\"f32:1074580685\",\"f64:4614613358185178726\",\"i32:4\",\"i32:5\"]],[\"invoke\",137,0,\"write\",[\"i64:1\",\"f32:1073741824\",\"f64:4614613358185178726\",\"i32:4\",\"i32:5\"]],[\"file\",\"local_tee.wast\"],[\"module\",3,22],[\"invoke\",280,0,\"type-local-i32\",[]],[\"invoke\",281,0,\"type-local-i64\",[]],[\"invoke\",282,0,\"type-local-f32\",[]],[\"invoke\",283,0,\"type-local-f64\",[]],[\"invoke\",285,0,\"type-param-i32\",[\"i32:2\"]],[\"invoke\",286,0,\"type-param-i64\",[\"i64:3\"]],[\"invoke\",287,0,\"type-param-f32\",[\"f32:1082969293\"]],[\"invoke\",288,0,\"type-param-f64\",[\"f64:4617878467915022336\"]],[\"invoke\",290,0,\"as-block-first\",[\"i32:0\"]],[\"invoke\",291,0,\"as-block-mid\",[\"i32:0\"]],[\"invoke\",292,0,\"as-block-last\",[\"i32:0\"]],[\"invoke\",294,0,\"as-loop-first\",[\"i32:0\"]],[\"invoke\",295,0,\"as-loop-mid\",[\"i32:0\"]],[\"invoke\",296,0,\"as-loop-last\",[\"i32:0\"]],[\"invoke\",298,0,\"as-br-value\",[\"i32:0\"]],[\"invoke\",300,0,\"as-br_if-cond\",[\"i32:0\"]],[\"invoke\",301,0,\"as-br_if-value\",[\"i32:0\"]],[\"invoke\",302,0,\"as-br_if-value-cond\",[\"i32:0\"]],[\"invoke\",304,0,\"as-br_table-index\",[\"i32:0\"]],[\"invoke\",305,0,\"as-br_table-value\",[\"i32:0\"]],[\"invoke\",306,0,\"as-br_table-value-index\",[\"i32:0\"]],[\"invoke\",308,0,\"as-return-value\",[\"i32:0\"]],[\"invoke\",310,0,\"as-if-cond\",[\"i32:0\"]],[\"invoke\",311,0,\"as-if-then\",[\"i32:1\"]],[\"invoke\",312,0,\"as-if-else\",[\"i32:0\"]],[\"invoke\",314,0,\"as-select-first\",[\"i32:0\",\"i32:1\"]],[\"invoke\",315,0,\"as-select-second\",[\"i32:0\",\"i32:0\"]],[\"invoke\",316,0,\"as-select-cond\",[\"i32:0\"]],[\"invoke\",318,0,\"as-call-first\",[\"i32:0\"]],[\"invoke\",319,0,\"as-call-mid\",[\"i32:0\"]],[\"invoke\",320,0,\"as-call-last\",[\"i32:0\"]],[\"invoke\",322,0,\"as-call_indirect-first\",[\"i32:0\"]],[\"invoke\",323,0,\"as-call_indirect-mid\",[\"i32:0\"]],[\"invoke\",324,0,\"as-call_indirect-last\",[\"i32:0\"]],[\"invoke\",325,0,\"as-call_indirect-index\",[\"i32:0\"]],[\"invoke\",327,0,\"as-local.set-value\",[]],[\"invoke\",328,0,\"as-local.tee-value\",[\"i32:0\"]],[\"invoke\",329,0,\"as-global.set-value\",[]],[\"invoke\",331,0,\"as-load-address\",[\"i32:0\"]],[\"invoke\",332,0,\"as-loadN-address\",[\"i32:0\"]],[\"invoke\",333,0,\"as-store-address\",[\"i32:0\"]],[\"invoke\",334,0,\"as-store-value\",[\"i32:0\"]],[\"invoke\",335,0,\"as-storeN-address\",[\"i32:0\"]],[\"invoke\",336,0,\"as-storeN-value\",[\"i32:0\"]],[\"invoke\",338,0,\"as-unary-operand\",[\"f32:0\"]],[\"invoke\",339,0,\"as-binary-left\",[\"i32:0\"]],[\"invoke\",340,0,\"as-binary-right\",[\"i32:0\"]],[\"invoke\",341,0,\"as-test-operand\",[\"i32:0\"]],[\"invoke\",342,0,\"as-compare-left\",[\"i32:0\"]],[\"invoke\",343,0,\"as-compare-right\",[\"i32:0\"]],[\"invoke\",344,0,\"as-convert-operand\",[\"i64:0\"]],[\"invoke\",345,0,\"as-memory.grow-size\",[\"i32:0\"]],[\"invoke\",348,0,\"type-mixed\",[\"i64:1\",\"f32:1074580685\",\"f64:4614613358185178726\",\"i32:4\",\"i32:5\"]],[\"invoke\",354,0,\"write\",[\"i64:1\",\"f32:1073741824\",\"f64:4614613358185178726\",\"i32:4\",\"i32:5\"]],[\"invoke\",361,0,\"result\",[\"i64:18446744073709551615\",\"f32:3221225472\",\"f64:13837985395039954534\",\"i32:4294967292\",\"i32:4294967291\"]],[\"file\",\"nop.wast\"],[\"module\",3,23],[\"invoke\",306,0,\"as-func-first\",[]],[\"invoke\",307,0,\"as-func-mid\",[]],[\"invoke\",308,0,\"as-func-last\",[]],[\"invoke\",309,0,\"as-func-everywhere\",[]],[\"invoke\",311,0,\"as-drop-first\",[\"i32:0\"]],[\"invoke\",312,0,\"as-drop-last\",[\"i32:0\"]],[\"invoke\",313,0,\"as-drop-everywhere\",[\"i32:0\"]],[\"invoke\",315,0,\"as-select-first\",[\"i32:3\"]],[\"invoke\",316,0,\"as-select-mid1\",[\"i32:3\"]],[\"invoke\",317,0,\"as-select-mid2\",[\"i32:3\"]],[\"invoke\",318,0,\"as-select-last\",[\"i32:3\"]],[\"invoke\",319,0,\"as-select-everywhere\",[\"i32:3\"]],[\"invoke\",321,0,\"as-block-first\",[]],[\"invoke\",322,0,\"as-block-mid\",[]],[\"invoke\",323,0,\"as-block-last\",[]],[\"invoke\",324,0,\"as-block-everywhere\",[]],[\"invoke\",326,0,\"as-loop-first\",[]],[\"invoke\",327,0,\"as-loop-mid\",[]],[\"invoke\",328,0,\"as-loop-last\",[]],[\"invoke\",329,0,\"as-loop-everywhere\",[]],[\"invoke\",331,0,\"as-if-condition\",[\"i32:0\"]],[\"invoke\",332,0,\"as-if-condition\",[\"i32:4294967295\"]],[\"invoke\",333,0,\"as-if-then\",[\"i32:0\"]],[\"invoke\",334,0,\"as-if-then\",[\"i32:4\"]],[\"invoke\",335,0,\"as-if-else\",[\"i32:0\"]],[\"invoke\",336,0,\"as-if-else\",[\"i32:3\"]],[\"invoke\",338,0,\"as-br-first\",[\"i32:5\"]],[\"invoke\",339,0,\"as-br-last\",[\"i32:6\"]],[\"invoke\",340,0,\"as-br-everywhere\",[\"i32:7\"]],[\"invoke\",342,0,\"as-br_if-first\",[\"i32:4\"]],[\"invoke\",343,0,\"as-br_if-mid\",[\"i32:5\"]],[\"invoke\",344,0,\"as-br_if-last\",[\"i32:6\"]],[\"invoke\",345,0,\"as-br_if-everywhere\",[\"i32:7\"]],[\"invoke\",347,0,\"as-br_table-first\",[\"i32:4\"]],[\"invoke\",348,0,\"as-br_table-mid\",[\"i32:5\"]],[\"invoke\",349,0,\"as-br_table-last\",[\"i32:6\"]],[\"invoke\",350,0,\"as-br_table-everywhere\",[\"i32:7\"]],[\"invoke\",352,0,\"as-return-first\",[\"i32:5\"]],[\"invoke\",353,0,\"as-return-last\",[\"i32:6\"]],[\"invoke\",354,0,\"as-return-everywhere\",[\"i32:7\"]],[\"invoke\",356,0,\"as-call-first\",[\"i32:3\",\"i32:1\",\"i32:2\"]],[\"invoke\",357,0,\"as-call-mid1\",[\"i32:3\",\"i32:1\",\"i32:2\"]],[\"invoke\",358,0,\"as-call-mid2\",[\"i32:0\",\"i32:3\",\"i32:1\"]],[\"invoke\",359,0,\"as-call-last\",[\"i32:10\",\"i32:9\",\"i32:4294967295\"]],[\"invoke\",360,0,\"as-call-everywhere\",[\"i32:2\",\"i32:1\",\"i32:5\"]],[\"invoke\",362,0,\"as-unary-first\",[\"i32:30\"]],[\"invoke\",363,0,\"as-unary-last\",[\"i32:30\"]],[\"invoke\",364,0,\"as-unary-everywhere\",[\"i32:12\"]],[\"invoke\",366,0,\"as-binary-first\",[\"i32:3\"]],[\"invoke\",367,0,\"as-binary-mid\",[\"i32:3\"]],[\"invoke\",368,0,\"as-binary-last\",[\"i32:3\"]],[\"invoke\",369,0,\"as-binary-everywhere\",[\"i32:3\"]],[\"invoke\",371,0,\"as-test-first\",[\"i32:0\"]],[\"invoke\",372,0,\"as-test-last\",[\"i32:0\"]],[\"invoke\",373,0,\"as-test-everywhere\",[\"i32:0\"]],[\"invoke\",375,0,\"as-compare-first\",[\"i32:3\"]],[\"invoke\",376,0,\"as-compare-mid\",[\"i32:3\"]],[\"invoke\",377,0,\"as-compare-last\",[\"i32:3\"]],[\"invoke\",378,0,\"as-compare-everywhere\",[\"i32:3\"]],[\"invoke\",380,0,\"as-memory.grow-first\",[\"i32:0\"]],[\"invoke\",381,0,\"as-memory.grow-last\",[\"i32:2\"]],[\"invoke\",382,0,\"as-memory.grow-everywhere\",[\"i32:12\"]],[\"invoke\",384,0,\"as-call_indirect-first\",[]],[\"invoke\",385,0,\"as-call_indirect-mid1\",[]],[\"invoke\",386,0,\"as-call_indirect-mid2\",[]],[\"invoke\",387,0,\"as-call_indirect-last\",[]],[\"invoke\",388,0,\"as-call_indirect-everywhere\",[]],[\"invoke\",390,0,\"as-local.set-first\",[\"i32:1\"]],[\"invoke\",391,0,\"as-local.set-last\",[\"i32:1\"]],[\"invoke\",392,0,\"as-local.set-everywhere\",[\"i32:1\"]],[\"invoke\",394,0,\"as-local.tee-first\",[\"i32:1\"]],[\"invoke\",395,0,\"as-local.tee-last\",[\"i32:1\"]],[\"invoke\",396,0,\"as-local.tee-everywhere\",[\"i32:1\"]],[\"invoke\",398,0,\"as-global.set-first\",[]],[\"invoke\",399,0,\"as-global.set-last\",[]],[\"invoke\",400,0,\"as-global.set-everywhere\",[]],[\"invoke\",402,0,\"as-load-first\",[\"i32:100\"]],[\"invoke\",403,0,\"as-load-last\",[\"i32:100\"]],[\"invoke\",404,0,\"as-load-everywhere\",[\"i32:100\"]],[\"invoke\",406,0,\"as-store-first\",[\"i32:0\",\"i32:1\"]],[\"invoke\",407,0,\"as-store-mid\",[\"i32:0\",\"i32:2\"]],[\"invoke\",408,0,\"as-store-last\",[\"i32:0\",\"i32:3\"]],[\"invoke\",409,0,\"as-store-everywhere\",[\"i32:0\",\"i32:4\"]],[\"file\",\"return.wast\"],[\"module\",3,24],[\"invoke\",224,0,\"type-i32\",[]],[\"invoke\",225,0,\"type-i64\",[]],[\"invoke\",226,0,\"type-f32\",[]],[\"invoke\",227,0,\"type-f64\",[]],[\"invoke\",229,0,\"type-i32-value\",[]],[\"invoke\",230,0,\"type-i64-value\",[]],[\"invoke\",231,0,\"type-f32-value\",[]],[\"invoke\",232,0,\"type-f64-value\",[]],[\"invoke\",234,0,\"nullary\",[]],[\"invoke\",235,0,\"unary\",[]],[\"invoke\",237,0,\"as-func-first\",[]],[\"invoke\",238,0,\"as-func-mid\",[]],[\"invoke\",239,0,\"as-func-last\",[]],[\"invoke\",240,0,\"as-func-value\",[]],[\"invoke\",242,0,\"as-block-first\",[]],[\"invoke\",243,0,\"as-block-mid\",[]],[\"invoke\",244,0,\"as-block-last\",[]],[\"invoke\",245,0,\"as-block-value\",[]],[\"invoke\",247,0,\"as-loop-first\",[]],[\"invoke\",248,0,\"as-loop-mid\",[]],[\"invoke\",249,0,\"as-loop-last\",[]],[\"invoke\",251,0,\"as-br-value\",[]],[\"invoke\",253,0,\"as-br_if-cond\",[]],[\"invoke\",254,0,\"as-br_if-value\",[]],[\"invoke\",255,0,\"as-br_if-value-cond\",[]],[\"invoke\",257,0,\"as-br_table-index\",[]],[\"invoke\",258,0,\"as-br_table-value\",[]],[\"invoke\",259,0,\"as-br_table-value-index\",[]],[\"invoke\",261,0,\"as-return-value\",[]],[\"invoke\",263,0,\"as-if-cond\",[]],[\"invoke\",264,0,\"as-if-then\",[\"i32:1\",\"i32:6\"]],[\"invoke\",265,0,\"as-if-then\",[\"i32:0\",\"i32:6\"]],[\"invoke\",266,0,\"as-if-else\",[\"i32:0\",\"i32:6\"]],[\"invoke\",267,0,\"as-if-else\",[\"i32:1\",\"i32:6\"]],[\"invoke\",269,0,\"as-select-first\",[\"i32:0\",\"i32:6\"]],[\"invoke\",270,0,\"as-select-first\",[\"i32:1\",\"i32:6\"]],[\"invoke\",271,0,\"as-select-second\",[\"i32:0\",\"i32:6\"]],[\"invoke\",272,0,\"as-select-second\",[\"i32:1\",\"i32:6\"]],[\"invoke\",273,0,\"as-select-cond\",[]],[\"invoke\",275,0,\"as-call-first\",[]],[\"invoke\",276,0,\"as-call-mid\",[]],[\"invoke\",277,0,\"as-call-last\",[]],[\"invoke\",279,0,\"as-call_indirect-func\",[]],[\"invoke\",280,0,\"as-call_indirect-first\",[]],[\"invoke\",281,0,\"as-call_indirect-mid\",[]],[\"invoke\",282,0,\"as-call_indirect-last\",[]],[\"invoke\",284,0,\"as-local.set-value\",[]],[\"invoke\",285,0,\"as-local.tee-value\",[]],[\"invoke\",286,0,\"as-global.set-value\",[]],[\"invoke\",288,0,\"as-load-address\",[]],[\"invoke\",289,0,\"as-loadN-address\",[]],[\"invoke\",291,0,\"as-store-address\",[]],[\"invoke\",292,0,\"as-store-value\",[]],[\"invoke\",293,0,\"as-storeN-address\",[]],[\"invoke\",294,0,\"as-storeN-value\",[]],[\"invoke\",296,0,\"as-unary-operand\",[]],[\"invoke\",298,0,\"as-binary-left\",[]],[\"invoke\",299,0,\"as-binary-right\",[]],[\"invoke\",301,0,\"as-test-operand\",[]],[\"invoke\",303,0,\"as-compare-left\",[]],[\"invoke\",304,0,\"as-compare-right\",[]],[\"invoke\",306,0,\"as-convert-operand\",[]],[\"invoke\",308,0,\"as-memory.grow-size\",[]],[\"file\",\"select.wast\"],[\"module\",1,25],[\"invoke\",183,0,\"select-i32\",[\"i32:1\",\"i32:2\",\"i32:1\"]],[\"invoke\",184,0,\"select-i64\",[\"i64:2\",\"i64:1\",\"i32:1\"]],[\"invoke\",185,0,\"select-f32\",[\"f32:1065353216\",\"f32:1073741824\",\"i32:1\"]],[\"invoke\",186,0,\"select-f64\",[\"f64:4607182418800017408\",\"f64:4611686018427387904\",\"i32:1\"]],[\"invoke\",188,0,\"select-i32\",[\"i32:1\",\"i32:2\",\"i32:0\"]],[\"invoke\",189,0,\"select-i32\",[\"i32:2\",\"i32:1\",\"i32:0\"]],[\"invoke\",190,0,\"select-i64\",[\"i64:2\",\"i64:1\",\"i32:4294967295\"]],[\"invoke\",191,0,\"select-i64\",[\"i64:2\",\"i64:1\",\"i32:4042322160\"]],[\"invoke\",193,0,\"select-f32\",[\"f32:2143289344\",\"f32:1065353216\",\"i32:1\"]],[\"invoke\",195,0,\"select-f32\",[\"f32:2143289344\",\"f32:1065353216\",\"i32:0\"]],[\"invoke\",197,0,\"select-f32\",[\"f32:1073741824\",\"f32:2143289344\",\"i32:1\"]],[\"invoke\",199,0,\"select-f32\",[\"f32:1073741824\",\"f32:2143289344\",\"i32:0\"]],[\"invoke\",202,0,\"select-f64\",[\"f64:9221120237041090560\",\"f64:4607182418800017408\",\"i32:1\"]],[\"invoke\",204,0,\"select-f64\",[\"f64:9221120237041090560\",\"f64:4607182418800017408\",\"i32:0\"]],[\"invoke\",206,0,\"select-f64\",[\"f64:4611686018427387904\",\"f64:9221120237041090560\",\"i32:1\"]],[\"invoke\",208,0,\"select-f64\",[\"f64:4611686018427387904\",\"f64:9221120237041090560\",\"i32:0\"]],[\"invoke\",211,0,\"select-i32-t\",[\"i32:1\",\"i32:2\",\"i32:1\"]],[\"invoke\",212,0,\"select-i64-t\",[\"i64:2\",\"i64:1\",\"i32:1\"]],[\"invoke\",213,0,\"select-f32-t\",[\"f32:1065353216\",\"f32:1073741824\",\"i32:1\"]],[\"invoke\",214,0,\"select-f64-t\",[\"f64:4607182418800017408\",\"f64:4611686018427387904\",\"i32:1\"]],[\"invoke\",215,0,\"select-funcref\",[\"funcref:null\",\"funcref:null\",\"i32:1\"]],[\"invoke\",216,0,\"select-externref\",[\"externref:1\",\"externref:2\",\"i32:1\"]],[\"invoke\",218,0,\"select-i32-t\",[\"i32:1\",\"i32:2\",\"i32:0\"]],[\"invoke\",219,0,\"select-i32-t\",[\"i32:2\",\"i32:1\",\"i32:0\"]],[\"invoke\",220,0,\"select-i64-t\",[\"i64:2\",\"i64:1\",\"i32:4294967295\"]],[\"invoke\",221,0,\"select-i64-t\",[\"i64:2\",\"i64:1\",\"i32:4042322160\"]],[\"invoke\",222,0,\"select-externref\",[\"externref:1\",\"externref:2\",\"i32:0\"]],[\"invoke\",223,0,\"select-externref\",[\"externref:2\",\"externref:1\",\"i32:0\"]],[\"invoke\",225,0,\"select-f32-t\",[\"f32:2143289344\",\"f32:1065353216\",\"i32:1\"]],[\"invoke\",227,0,\"select-f32-t\",[\"f32:2143289344\",\"f32:1065353216\",\"i32:0\"]],[\"invoke\",229,0,\"select-f32-t\",[\"f32:1073741824\",\"f32:2143289344\",\"i32:1\"]],[\"invoke\",231,0,\"select-f32-t\",[\"f32:1073741824\",\"f32:2143289344\",\"i32:0\"]],[\"invoke\",234,0,\"select-f64-t\",[\"f64:9221120237041090560\",\"f64:4607182418800017408\",\"i32:1\"]],[\"invoke\",236,0,\"select-f64-t\",[\"f64:9221120237041090560\",\"f64:4607182418800017408\",\"i32:0\"]],[\"invoke\",238,0,\"select-f64-t\",[\"f64:4611686018427387904\",\"f64:9221120237041090560\",\"i32:1\"]],[\"invoke\",240,0,\"select-f64-t\",[\"f64:4611686018427387904\",\"f64:9221120237041090560\",\"i32:0\"]],[\"invoke\",243,0,\"as-select-first\",[\"i32:0\"]],[\"invoke\",244,0,\"as-select-first\",[\"i32:1\"]],[\"invoke\",245,0,\"as-select-mid\",[\"i32:0\"]],[\"invoke\",246,0,\"as-select-mid\",[\"i32:1\"]],[\"invoke\",247,0,\"as-select-last\",[\"i32:0\"]],[\"invoke\",248,0,\"as-select-last\",[\"i32:1\"]],[\"invoke\",250,0,\"as-loop-first\",[\"i32:0\"]],[\"invoke\",251,0,\"as-loop-first\",[\"i32:1\"]],[\"invoke\",252,0,\"as-loop-mid\",[\"i32:0\"]],[\"invoke\",253,0,\"as-loop-mid\",[\"i32:1\"]],[\"invoke\",254,0,\"as-loop-last\",[\"i32:0\"]],[\"invoke\",255,0,\"as-loop-last\",[\"i32:1\"]],[\"invoke\",257,0,\"as-if-condition\",[\"i32:0\"]],[\"invoke\",258,0,\"as-if-condition\",[\"i32:1\"]],[\"invoke\",259,0,\"as-if-then\",[\"i32:0\"]],[\"invoke\",260,0,\"as-if-then\",[\"i32:1\"]],[\"invoke\",261,0,\"as-if-else\",[\"i32:0\"]],[\"invoke\",262,0,\"as-if-else\",[\"i32:1\"]],[\"invoke\",264,0,\"as-br_if-first\",[\"i32:0\"]],[\"invoke\",265,0,\"as-br_if-first\",[\"i32:1\"]],[\"invoke\",266,0,\"as-br_if-last\",[\"i32:0\"]],[\"invoke\",267,0,\"as-br_if-last\",[\"i32:1\"]],[\"invoke\",269,0,\"as-br_table-first\",[\"i32:0\"]],[\"invoke\",270,0,\"as-br_table-first\",[\"i32:1\"]],[\"invoke\",271,0,\"as-br_table-last\",[\"i32:0\"]],[\"invoke\",272,0,\"as-br_table-last\",[\"i32:1\"]],[\"invoke\",274,0,\"as-call_indirect-first\",[\"i32:0\"]],[\"invoke\",275,0,\"as-call_indirect-first\",[\"i32:1\"]],[\"invoke\",276,0,\"as-call_indirect-mid\",[\"i32:0\"]],[\"invoke\",277,0,\"as-call_indirect-mid\",[\"i32:1\"]],[\"invoke\",278,0,\"as-call_indirect-last\",[\"i32:0\"]],[\"invoke\",279,0,\"as-call_indirect-last\",[\"i32:1\"]],[\"invoke\",281,0,\"as-store-first\",[\"i32:0\"]],[\"invoke\",282,0,\"as-store-first\",[\"i32:1\"]],[\"invoke\",283,0,\"as-store-last\",[\"i32:0\"]],[\"invoke\",284,0,\"as-store-last\",[\"i32:1\"]],[\"invoke\",286,0,\"as-memory.grow-value\",[\"i32:0\"]],[\"invoke\",287,0,\"as-memory.grow-value\",[\"i32:1\"]],[\"invoke\",289,0,\"as-call-value\",[\"i32:0\"]],[\"invoke\",290,0,\"as-call-value\",[\"i32:1\"]],[\"invoke\",291,0,\"as-return-value\",[\"i32:0\"]],[\"invoke\",292,0,\"as-return-value\",[\"i32:1\"]],[\"invoke\",293,0,\"as-drop-operand\",[\"i32:0\"]],[\"invoke\",294,0,\"as-drop-operand\",[\"i32:1\"]],[\"invoke\",295,0,\"as-br-value\",[\"i32:0\"]],[\"invoke\",296,0,\"as-br-value\",[\"i32:1\"]],[\"invoke\",297,0,\"as-local.set-value\",[\"i32:0\"]],[\"invoke\",298,0,\"as-local.set-value\",[\"i32:1\"]],[\"invoke\",299,0,\"as-local.tee-value\",[\"i32:0\"]],[\"invoke\",300,0,\"as-local.tee-value\",[\"i32:1\"]],[\"invoke\",301,0,\"as-global.set-value\",[\"i32:0\"]],[\"invoke\",302,0,\"as-global.set-value\",[\"i32:1\"]],[\"invoke\",303,0,\"as-load-operand\",[\"i32:0\"]],[\"invoke\",304,0,\"as-load-operand\",[\"i32:1\"]],[\"invoke\",306,0,\"as-unary-operand\",[\"i32:0\"]],[\"invoke\",307,0,\"as-unary-operand\",[\"i32:1\"]],[\"invoke\",308,0,\"as-binary-operand\",[\"i32:0\"]],[\"invoke\",309,0,\"as-binary-operand\",[\"i32:1\"]],[\"invoke\",310,0,\"as-test-operand\",[\"i32:0\"]],[\"invoke\",311,0,\"as-test-operand\",[\"i32:1\"]],[\"invoke\",312,0,\"as-compare-left\",[\"i32:0\"]],[\"invoke\",313,0,\"as-compare-left\",[\"i32:1\"]],[\"invoke\",314,0,\"as-compare-right\",[\"i32:0\"]],[\"invoke\",315,0,\"as-compare-right\",[\"i32:1\"]],[\"invoke\",316,0,\"as-convert-operand\",[\"i32:0\"]],[\"invoke\",317,0,\"as-convert-operand\",[\"i32:1\"]],[\"module\",518,26],[\"file\",\"stack.wast\"],[\"module\",1,27],[\"invoke\",146,0,\"fac-expr\",[\"i64:25\"]],[\"invoke\",147,0,\"fac-stack\",[\"i64:25\"]],[\"invoke\",148,0,\"fac-mixed\",[\"i64:25\"]],[\"invoke\",150,0,\"not-quite-a-tree\",[]],[\"invoke\",151,0,\"not-quite-a-tree\",[]],[\"module\",156,28],[\"file\",\"switch.wast\"],[\"module\",1,29],[\"invoke\",120,0,\"stmt\",[\"i32:0\"]],[\"invoke\",121,0,\"stmt\",[\"i32:1\"]],[\"invoke\",122,0,\"stmt\",[\"i32:2\"]],[\"invoke\",123,0,\"stmt\",[\"i32:3\"]],[\"invoke\",124,0,\"stmt\",[\"i32:4\"]],[\"invoke\",125,0,\"stmt\",[\"i32:5\"]],[\"invoke\",126,0,\"stmt\",[\"i32:6\"]],[\"invoke\",127,0,\"stmt\",[\"i32:7\"]],[\"invoke\",128,0,\"stmt\",[\"i32:4294967286\"]],[\"invoke\",130,0,\"expr\",[\"i64:0\"]],[\"invoke\",131,0,\"expr\",[\"i64:1\"]],[\"invoke\",132,0,\"expr\",[\"i64:2\"]],[\"invoke\",133,0,\"expr\",[\"i64:3\"]],[\"invoke\",134,0,\"expr\",[\"i64:6\"]],[\"invoke\",135,0,\"expr\",[\"i64:7\"]],[\"invoke\",136,0,\"expr\",[\"i64:18446744073709551606\"]],[\"invoke\",138,0,\"arg\",[\"i32:0\"]],[\"invoke\",139,0,\"arg\",[\"i32:1\"]],[\"invoke\",140,0,\"arg\",[\"i32:2\"]],[\"invoke\",141,0,\"arg\",[\"i32:3\"]],[\"invoke\",142,0,\"arg\",[\"i32:4\"]],[\"invoke\",143,0,\"arg\",[\"i32:5\"]],[\"invoke\",144,0,\"arg\",[\"i32:6\"]],[\"invoke\",145,0,\"arg\",[\"i32:7\"]],[\"invoke\",146,0,\"arg\",[\"i32:8\"]],[\"invoke\",148,0,\"corner\",[]],[\"file\",\"unreachable.wast\"],[\"module\",3,30],[\"invoke\",221,0,\"type-i32\",[]],[\"invoke\",222,0,\"type-i64\",[]],[\"invoke\",223,0,\"type-f32\",[]],[\"invoke\",224,0,\"type-f64\",[]],[\"invoke\",226,0,\"as-func-first\",[]],[\"invoke\",227,0,\"as-func-mid\",[]],[\"invoke\",228,0,\"as-func-last\",[]],[\"invoke\",229,0,\"as-func-value\",[]],[\"invoke\",231,0,\"as-block-first\",[]],[\"invoke\",232,0,\"as-block-mid\",[]],[\"invoke\",233,0,\"as-block-last\",[]],[\"invoke\",234,0,\"as-block-value\",[]],[\"invoke\",235,0,\"as-block-broke\",[]],[\"invoke\",237,0,\"as-loop-first\",[]],[\"invoke\",238,0,\"as-loop-mid\",[]],[\"invoke\",239,0,\"as-loop-last\",[]],[\"invoke\",240,0,\"as-loop-broke\",[]],[\"invoke\",242,0,\"as-br-value\",[]],[\"invoke\",244,0,\"as-br_if-cond\",[]],[\"invoke\",245,0,\"as-br_if-value\",[]],[\"invoke\",246,0,\"as-br_if-value-cond\",[]],[\"invoke\",248,0,\"as-br_table-index\",[]],[\"invoke\",249,0,\"as-br_table-value\",[]],[\"invoke\",250,0,\"as-br_table-value-2\",[]],[\"invoke\",251,0,\"as-br_table-value-index\",[]],[\"invoke\",252,0,\"as-br_table-value-and-index\",[]],[\"invoke\",254,0,\"as-return-value\",[]],[\"invoke\",256,0,\"as-if-cond\",[]],[\"invoke\",257,0,\"as-if-then\",[\"i32:1\",\"i32:6\"]],[\"invoke\",258,0,\"as-if-then\",[\"i32:0\",\"i32:6\"]],[\"invoke\",259,0,\"as-if-else\",[\"i32:0\",\"i32:6\"]],[\"invoke\",260,0,\"as-if-else\",[\"i32:1\",\"i32:6\"]],[\"invoke\",261,0,\"as-if-then-no-else\",[\"i32:1\",\"i32:6\"]],[\"invoke\",262,0,\"as-if-then-no-else\",[\"i32:0\",\"i32:6\"]],[\"invoke\",264,0,\"as-select-first\",[\"i32:0\",\"i32:6\"]],[\"invoke\",265,0,\"as-select-first\",[\"i32:1\",\"i32:6\"]],[\"invoke\",266,0,\"as-select-second\",[\"i32:0\",\"i32:6\"]],[\"invoke\",267,0,\"as-select-second\",[\"i32:1\",\"i32:6\"]],[\"invoke\",268,0,\"as-select-cond\",[]],[\"invoke\",270,0,\"as-call-first\",[]],[\"invoke\",271,0,\"as-call-mid\",[]],[\"invoke\",272,0,\"as-call-last\",[]],[\"invoke\",274,0,\"as-call_indirect-func\",[]],[\"invoke\",275,0,\"as-call_indirect-first\",[]],[\"invoke\",276,0,\"as-call_indirect-mid\",[]],[\"invoke\",277,0,\"as-call_indirect-last\",[]],[\"invoke\",279,0,\"as-local.set-value\",[]],[\"invoke\",280,0,\"as-local.tee-value\",[]],[\"invoke\",281,0,\"as-global.set-value\",[]],[\"invoke\",283,0,\"as-load-address\",[]],[\"invoke\",284,0,\"as-loadN-address\",[]],[\"invoke\",286,0,\"as-store-address\",[]],[\"invoke\",287,0,\"as-store-value\",[]],[\"invoke\",288,0,\"as-storeN-address\",[]],[\"invoke\",289,0,\"as-storeN-value\",[]],[\"invoke\",291,0,\"as-unary-operand\",[]],[\"invoke\",293,0,\"as-binary-left\",[]],[\"invoke\",294,0,\"as-binary-right\",[]],[\"invoke\",296,0,\"as-test-operand\",[]],[\"invoke\",298,0,\"as-compare-left\",[]],[\"invoke\",299,0,\"as-compare-right\",[]],[\"invoke\",301,0,\"as-convert-operand\",[]],[\"invoke\",303,0,\"as-memory.grow-size\",[]],[\"file\",\"unwind.wast\"],[\"module\",3,31],[\"invoke\",212,0,\"func-unwind-by-unreachable\",[]],[\"invoke\",213,0,\"func-unwind-by-br\",[]],[\"invoke\",214,0,\"func-unwind-by-br-value\",[]],[\"invoke\",215,0,\"func-unwind-by-br_if\",[]],[\"invoke\",216,0,\"func-unwind-by-br_if-value\",[]],[\"invoke\",217,0,\"func-unwind-by-br_table\",[]],[\"invoke\",218,0,\"func-unwind-by-br_table-value\",[]],[\"invoke\",219,0,\"func-unwind-by-return\",[]],[\"invoke\",221,0,\"block-unwind-by-unreachable\",[]],[\"invoke\",222,0,\"block-unwind-by-br\",[]],[\"invoke\",223,0,\"block-unwind-by-br-value\",[]],[\"invoke\",224,0,\"block-unwind-by-br_if\",[]],[\"invoke\",225,0,\"block-unwind-by-br_if-value\",[]],[\"invoke\",226,0,\"block-unwind-by-br_table\",[]],[\"invoke\",227,0,\"block-unwind-by-br_table-value\",[]],[\"invoke\",228,0,\"block-unwind-by-return\",[]],[\"invoke\",230,0,\"block-nested-unwind-by-unreachable\",[]],[\"invoke\",231,0,\"block-nested-unwind-by-br\",[]],[\"invoke\",232,0,\"block-nested-unwind-by-br-value\",[]],[\"invoke\",233,0,\"block-nested-unwind-by-br_if\",[]],[\"invoke\",234,0,\"block-nested-unwind-by-br_if-value\",[]],[\"invoke\",235,0,\"block-nested-unwind-by-br_table\",[]],[\"invoke\",236,0,\"block-nested-unwind-by-br_table-value\",[]],[\"invoke\",237,0,\"block-nested-unwind-by-return\",[]],[\"invoke\",239,0,\"unary-after-unreachable\",[]],[\"invoke\",240,0,\"unary-after-br\",[]],[\"invoke\",241,0,\"unary-after-br_if\",[]],[\"invoke\",242,0,\"unary-after-br_table\",[]],[\"invoke\",243,0,\"unary-after-return\",[]],[\"invoke\",245,0,\"binary-after-unreachable\",[]],[\"invoke\",246,0,\"binary-after-br\",[]],[\"invoke\",247,0,\"binary-after-br_if\",[]],[\"invoke\",248,0,\"binary-after-br_table\",[]],[\"invoke\",249,0,\"binary-after-return\",[]],[\"invoke\",251,0,\"select-after-unreachable\",[]],[\"invoke\",252,0,\"select-after-br\",[]],[\"invoke\",253,0,\"select-after-br_if\",[]],[\"invoke\",254,0,\"select-after-br_table\",[]],[\"invoke\",255,0,\"select-after-return\",[]],[\"invoke\",257,0,\"block-value-after-unreachable\",[]],[\"invoke\",258,0,\"block-value-after-br\",[]],[\"invoke\",259,0,\"block-value-after-br_if\",[]],[\"invoke\",260,0,\"block-value-after-br_table\",[]],[\"invoke\",261,0,\"block-value-after-return\",[]],[\"invoke\",263,0,\"loop-value-after-unreachable\",[]],[\"invoke\",264,0,\"loop-value-after-br\",[]],[\"invoke\",265,0,\"loop-value-after-br_if\",[]],[\"invoke\",266,0,\"loop-value-after-br_table\",[]],[\"invoke\",267,0,\"loop-value-after-return\",[]],[\"file\",\"left-to-right.wast\"],[\"module\",1,32],[\"invoke\",181,0,\"i32_add\",[]],[\"invoke\",181,0,\"i64_add\",[]],[\"invoke\",182,0,\"i32_sub\",[]],[\"invoke\",182,0,\"i64_sub\",[]],[\"invoke\",183,0,\"i32_mul\",[]],[\"invoke\",183,0,\"i64_mul\",[]],[\"invoke\",184,0,\"i32_div_s\",[]],[\"invoke\",184,0,\"i64_div_s\",[]],[\"invoke\",185,0,\"i32_div_u\",[]],[\"invoke\",185,0,\"i64_div_u\",[]],[\"invoke\",186,0,\"i32_rem_s\",[]],[\"invoke\",186,0,\"i64_rem_s\",[]],[\"invoke\",187,0,\"i32_rem_u\",[]],[\"invoke\",187,0,\"i64_rem_u\",[]],[\"invoke\",188,0,\"i32_and\",[]],[\"invoke\",188,0,\"i64_and\",[]],[\"invoke\",189,0,\"i32_or\",[]],[\"invoke\",189,0,\"i64_or\",[]],[\"invoke\",190,0,\"i32_xor\",[]],[\"invoke\",190,0,\"i64_xor\",[]],[\"invoke\",191,0,\"i32_shl\",[]],[\"invoke\",191,0,\"i64_shl\",[]],[\"invoke\",192,0,\"i32_shr_u\",[]],[\"invoke\",192,0,\"i64_shr_u\",[]],[\"invoke\",193,0,\"i32_shr_s\",[]],[\"invoke\",193,0,\"i64_shr_s\",[]],[\"invoke\",194,0,\"i32_eq\",[]],[\"invoke\",194,0,\"i64_eq\",[]],[\"invoke\",195,0,\"i32_ne\",[]],[\"invoke\",195,0,\"i64_ne\",[]],[\"invoke\",196,0,\"i32_lt_s\",[]],[\"invoke\",196,0,\"i64_lt_s\",[]],[\"invoke\",197,0,\"i32_le_s\",[]],[\"invoke\",197,0,\"i64_le_s\",[]],[\"invoke\",198,0,\"i32_lt_u\",[]],[\"invoke\",198,0,\"i64_lt_u\",[]],[\"invoke\",199,0,\"i32_le_u\",[]],[\"invoke\",199,0,\"i64_le_u\",[]],[\"invoke\",200,0,\"i32_gt_s\",[]],[\"invoke\",200,0,\"i64_gt_s\",[]],[\"invoke\",201,0,\"i32_ge_s\",[]],[\"invoke\",201,0,\"i64_ge_s\",[]],[\"invoke\",202,0,\"i32_gt_u\",[]],[\"invoke\",202,0,\"i64_gt_u\",[]],[\"invoke\",203,0,\"i32_ge_u\",[]],[\"invoke\",203,0,\"i64_ge_u\",[]],[\"invoke\",204,0,\"i32_store\",[]],[\"invoke\",204,0,\"i64_store\",[]],[\"invoke\",205,0,\"i32_store8\",[]],[\"invoke\",205,0,\"i64_store8\",[]],[\"invoke\",206,0,\"i32_store16\",[]],[\"invoke\",206,0,\"i64_store16\",[]],[\"invoke\",207,0,\"i64_store32\",[]],[\"invoke\",208,0,\"i32_call\",[]],[\"invoke\",208,0,\"i64_call\",[]],[\"invoke\",209,0,\"i32_call_indirect\",[]],[\"invoke\",210,0,\"i64_call_indirect\",[]],[\"invoke\",211,0,\"i32_select\",[]],[\"invoke\",211,0,\"i64_select\",[]],[\"invoke\",213,0,\"f32_add\",[]],[\"invoke\",213,0,\"f64_add\",[]],[\"invoke\",214,0,\"f32_sub\",[]],[\"invoke\",214,0,\"f64_sub\",[]],[\"invoke\",215,0,\"f32_mul\",[]],[\"invoke\",215,0,\"f64_mul\",[]],[\"invoke\",216,0,\"f32_div\",[]],[\"invoke\",216,0,\"f64_div\",[]],[\"invoke\",217,0,\"f32_copysign\",[]],[\"invoke\",217,0,\"f64_copysign\",[]],[\"invoke\",218,0,\"f32_eq\",[]],[\"invoke\",218,0,\"f64_eq\",[]],[\"invoke\",219,0,\"f32_ne\",[]],[\"invoke\",219,0,\"f64_ne\",[]],[\"invoke\",220,0,\"f32_lt\",[]],[\"invoke\",220,0,\"f64_lt\",[]],[\"invoke\",221,0,\"f32_le\",[]],[\"invoke\",221,0,\"f64_le\",[]],[\"invoke\",222,0,\"f32_gt\",[]],[\"invoke\",222,0,\"f64_gt\",[]],[\"invoke\",223,0,\"f32_ge\",[]],[\"invoke\",223,0,\"f64_ge\",[]],[\"invoke\",224,0,\"f32_min\",[]],[\"invoke\",224,0,\"f64_min\",[]],[\"invoke\",225,0,\"f32_max\",[]],[\"invoke\",225,0,\"f64_max\",[]],[\"invoke\",226,0,\"f32_store\",[]],[\"invoke\",226,0,\"f64_store\",[]],[\"invoke\",227,0,\"f32_call\",[]],[\"invoke\",227,0,\"f64_call\",[]],[\"invoke\",228,0,\"f32_call_indirect\",[]],[\"invoke\",229,0,\"f64_call_indirect\",[]],[\"invoke\",230,0,\"f32_select\",[]],[\"invoke\",230,0,\"f64_select\",[]],[\"invoke\",232,0,\"br_if\",[]],[\"invoke\",233,0,\"br_table\",[]],[\"file\",\"global.wast\"],[\"module\",3,33],[\"invoke\",196,0,\"get-a\",[]],[\"invoke\",197,0,\"get-b\",[]],[\"invoke\",198,0,\"get-r\",[]],[\"invoke\",199,0,\"get-mr\",[]],[\"invoke\",200,0,\"get-x\",[]],[\"invoke\",201,0,\"get-y\",[]],[\"invoke\",202,0,\"get-z1\",[]],[\"invoke\",203,0,\"get-z2\",[]],[\"invoke\",205,0,\"get-3\",[]],[\"invoke\",206,0,\"get-4\",[]],[\"invoke\",207,0,\"get-7\",[]],[\"invoke\",208,0,\"get-8\",[]],[\"invoke\",210,0,\"set-x\",[\"i32:6\"]],[\"invoke\",211,0,\"set-y\",[\"i64:7\"]],[\"invoke\",213,0,\"set-7\",[\"f32:1090519040\"]],[\"invoke\",214,0,\"set-8\",[\"f64:4621256167635550208\"]],[\"invoke\",216,0,\"get-x\",[]],[\"invoke\",217,0,\"get-y\",[]],[\"invoke\",218,0,\"get-7\",[]],[\"invoke\",219,0,\"get-8\",[]],[\"invoke\",221,0,\"set-7\",[\"f32:1090519040\"]],[\"invoke\",222,0,\"set-8\",[\"f64:4621256167635550208\"]],[\"invoke\",223,0,\"set-mr\",[\"externref:10\"]],[\"invoke\",225,0,\"get-x\",[]],[\"invoke\",226,0,\"get-y\",[]],[\"invoke\",227,0,\"get-7\",[]],[\"invoke\",228,0,\"get-8\",[]],[\"invoke\",229,0,\"get-mr\",[]],[\"invoke\",231,0,\"as-select-first\",[]],[\"invoke\",232,0,\"as-select-mid\",[]],[\"invoke\",233,0,\"as-select-last\",[]],[\"invoke\",235,0,\"as-loop-first\",[]],[\"invoke\",236,0,\"as-loop-mid\",[]],[\"invoke\",237,0,\"as-loop-last\",[]],[\"invoke\",239,0,\"as-if-condition\",[]],[\"invoke\",240,0,\"as-if-then\",[]],[\"invoke\",241,0,\"as-if-else\",[]],[\"invoke\",243,0,\"as-br_if-first\",[]],[\"invoke\",244,0,\"as-br_if-last\",[]],[\"invoke\",246,0,\"as-br_table-first\",[]],[\"invoke\",247,0,\"as-br_table-last\",[]],[\"invoke\",249,0,\"as-call_indirect-first\",[]],[\"invoke\",250,0,\"as-call_indirect-mid\",[]],[\"invoke\",251,0,\"as-call_indirect-last\",[]],[\"invoke\",253,0,\"as-store-first\",[]],[\"invoke\",254,0,\"as-store-last\",[]],[\"invoke\",255,0,\"as-load-operand\",[]],[\"invoke\",256,0,\"as-memory.grow-value\",[]],[\"invoke\",258,0,\"as-call-value\",[]],[\"invoke\",260,0,\"as-return-value\",[]],[\"invoke\",261,0,\"as-drop-operand\",[]],[\"invoke\",262,0,\"as-br-value\",[]],[\"invoke\",264,0,\"as-local.set-value\",[\"i32:1\"]],[\"invoke\",265,0,\"as-local.tee-value\",[\"i32:1\"]],[\"invoke\",266,0,\"as-global.set-value\",[]],[\"invoke\",268,0,\"as-unary-operand\",[]],[\"invoke\",269,0,\"as-binary-operand\",[]],[\"invoke\",270,0,\"as-compare-operand\",[]],[\"module\",283,34],[\"module\",284,34],[\"module\",375,35],[\"module\",405,36],[\"file\",\"start.wast\"],[\"module\",21,37],[\"invoke\",45,0,\"get\",[]],[\"invoke\",46,0,\"inc\",[]],[\"invoke\",47,0,\"get\",[]],[\"invoke\",48,0,\"inc\",[]],[\"invoke\",49,0,\"get\",[]],[\"module\",51,37],[\"invoke\",74,0,\"get\",[]],[\"invoke\",75,0,\"inc\",[]],[\"invoke\",76,0,\"get\",[]],[\"invoke\",77,0,\"inc\",[]],[\"invoke\",78,0,\"get\",[]],[\"module\",80,38],[\"module\",86,39],[\"module\",92,40],[\"instantiate\",98,41]]"; diff --git a/tests/corpus/webassembly-spec-control/main.mjs b/tests/corpus/webassembly-spec-control/main.mjs new file mode 100644 index 000000000..2451a5a1c --- /dev/null +++ b/tests/corpus/webassembly-spec-control/main.mjs @@ -0,0 +1,5 @@ +// Executes WebAssembly spec tests (26 files); see tests/wasm-spec/runner.mjs. +import { run } from "../../wasm-spec/runner.mjs"; +import { modules, script } from "./data.mjs"; + +run(modules, script); diff --git a/tests/corpus/webassembly-spec-float/data.mjs b/tests/corpus/webassembly-spec-float/data.mjs new file mode 100644 index 000000000..814d702e3 --- /dev/null +++ b/tests/corpus/webassembly-spec-float/data.mjs @@ -0,0 +1,113 @@ +/* Generated by tests/wasm-spec/gen.mjs from the WebAssembly 2.0 core test + * suite (WebAssembly/spec wg-2.0, Apache-2.0) with wabt 1.0.37 wast2json: + * f32.wast, f64.wast, f32_cmp.wast, f64_cmp.wast, f32_bitwise.wast, f64_bitwise.wast, float_exprs.wast, float_literals.wast, float_misc.wast. Do not edit. */ +export const modules = [ + "AGFzbQEAAAABDAJgAn19AX1gAX0BfQMMCwAAAAABAAABAQEBB00LA2FkZAAAA3N1YgABA211bAACA2RpdgADBHNxcnQABANtaW4ABQNtYXgABgRjZWlsAAcFZmxvb3IACAV0cnVuYwAJB25lYXJlc3QACgpPCwcAIAAgAZILBwAgACABkwsHACAAIAGUCwcAIAAgAZULBQAgAJELBwAgACABlgsHACAAIAGXCwUAIACNCwUAIACOCwUAIACPCwUAIACQCw==", + "AGFzbQEAAAABDAJgAnx8AXxgAXwBfAMMCwAAAAABAAABAQEBB00LA2FkZAAAA3N1YgABA211bAACA2RpdgADBHNxcnQABANtaW4ABQNtYXgABgRjZWlsAAcFZmxvb3IACAV0cnVuYwAJB25lYXJlc3QACgpPCwcAIAAgAaALBwAgACABoQsHACAAIAGiCwcAIAAgAaMLBQAgAJ8LBwAgACABpAsHACAAIAGlCwUAIACbCwUAIACcCwUAIACdCwUAIACeCw==", + "AGFzbQEAAAABBwFgAn19AX8DBwYAAAAAAAAHHwYCZXEAAAJuZQABAmx0AAICbGUAAwJndAAEAmdlAAUKMQYHACAAIAFbCwcAIAAgAVwLBwAgACABXQsHACAAIAFfCwcAIAAgAV4LBwAgACABYAs=", + "AGFzbQEAAAABBwFgAnx8AX8DBwYAAAAAAAAHHwYCZXEAAAJuZQABAmx0AAICbGUAAwJndAAEAmdlAAUKMQYHACAAIAFhCwcAIAAgAWILBwAgACABYwsHACAAIAFlCwcAIAAgAWQLBwAgACABZgs=", + "AGFzbQEAAAABDAJgAX0BfWACfX0BfQMEAwAAAQcYAwNhYnMAAANuZWcAAQhjb3B5c2lnbgACChUDBQAgAIsLBQAgAIwLBwAgACABmAs=", + "AGFzbQEAAAABDAJgAXwBfGACfHwBfAMEAwAAAQcYAwNhYnMAAANuZWcAAQhjb3B5c2lnbgACChUDBQAgAJkLBQAgAJoLBwAgACABpgs=", + "AGFzbQEAAAABCAFgA3x8fAF8AwIBAAcWARJmNjQubm9fY29udHJhY3Rpb24AAAoMAQoAIAAgAaIgAqAL", + "AGFzbQEAAAABDwJgA319fQF9YAN8fHwBfAMDAgABBxsCCmYzMi5ub19mbWEAAApmNjQubm9fZm1hAAEKFwIKACAAIAGUIAKSCwoAIAAgAaIgAqAL", + "AGFzbQEAAAABCwJgAX0BfWABfAF8AwMCAAEHLwIUZjMyLm5vX2ZvbGRfYWRkX3plcm8AABRmNjQubm9fZm9sZF9hZGRfemVybwABChsCCgAgAEMAAAAAkgsOACAARAAAAAAAAAAAoAs=", + "AGFzbQEAAAABCwJgAX0BfWABfAF8AwMCAAEHLwIUZjMyLm5vX2ZvbGRfemVyb19zdWIAABRmNjQubm9fZm9sZF96ZXJvX3N1YgABChsCCgBDAAAAACAAkwsOAEQAAAAAAAAAACAAoQs=", + "AGFzbQEAAAABCwJgAX0BfWABfAF8AwMCAAEHLwIUZjMyLm5vX2ZvbGRfc3ViX3plcm8AABRmNjQubm9fZm9sZF9zdWJfemVybwABChsCCgAgAEMAAAAAkwsOACAARAAAAAAAAAAAoQs=", + "AGFzbQEAAAABCwJgAX0BfWABfAF8AwMCAAEHLwIUZjMyLm5vX2ZvbGRfbXVsX3plcm8AABRmNjQubm9fZm9sZF9tdWxfemVybwABChsCCgAgAEMAAAAAlAsOACAARAAAAAAAAAAAogs=", + "AGFzbQEAAAABCwJgAX0BfWABfAF8AwMCAAEHLQITZjMyLm5vX2ZvbGRfbXVsX29uZQAAE2Y2NC5ub19mb2xkX211bF9vbmUAAQobAgoAIABDAACAP5QLDgAgAEQAAAAAAADwP6IL", + "AGFzbQEAAAABCwJgAX0BfWABfAF8AwMCAAEHLwIUZjMyLm5vX2ZvbGRfemVyb19kaXYAABRmNjQubm9fZm9sZF96ZXJvX2RpdgABChsCCgBDAAAAACAAlQsOAEQAAAAAAAAAACAAows=", + "AGFzbQEAAAABCwJgAX0BfWABfAF8AwMCAAEHLQITZjMyLm5vX2ZvbGRfZGl2X29uZQAAE2Y2NC5ub19mb2xkX2Rpdl9vbmUAAQobAgoAIABDAACAP5ULDgAgAEQAAAAAAADwP6ML", + "AGFzbQEAAAABCwJgAX0BfWABfAF8AwMCAAEHLwIUZjMyLm5vX2ZvbGRfZGl2X25lZzEAABRmNjQubm9fZm9sZF9kaXZfbmVnMQABChsCCgAgAEMAAIC/lQsOACAARAAAAAAAAPC/ows=", + "AGFzbQEAAAABCwJgAX0BfWABfAF8AwMCAAEHLwIUZjMyLm5vX2ZvbGRfbmVnMF9zdWIAABRmNjQubm9fZm9sZF9uZWcwX3N1YgABChsCCgBDAAAAgCAAkwsOAEQAAAAAAAAAgCAAoQs=", + "AGFzbQEAAAABCwJgAX0BfWABfAF8AwMCAAEHLwIUZjMyLm5vX2ZvbGRfbmVnMV9tdWwAABRmNjQubm9fZm9sZF9uZWcxX211bAABChsCCgBDAACAvyAAlAsOAEQAAAAAAADwvyAAogs=", + "AGFzbQEAAAABCwJgAX0Bf2ABfAF/AwMCAAEHLQITZjMyLm5vX2ZvbGRfZXFfc2VsZgAAE2Y2NC5ub19mb2xkX2VxX3NlbGYAAQoRAgcAIAAgAFsLBwAgACAAYQs=", + "AGFzbQEAAAABCwJgAX0Bf2ABfAF/AwMCAAEHLQITZjMyLm5vX2ZvbGRfbmVfc2VsZgAAE2Y2NC5ub19mb2xkX25lX3NlbGYAAQoRAgcAIAAgAFwLBwAgACAAYgs=", + "AGFzbQEAAAABCwJgAX0BfWABfAF8AwMCAAEHLwIUZjMyLm5vX2ZvbGRfc3ViX3NlbGYAABRmNjQubm9fZm9sZF9zdWJfc2VsZgABChECBwAgACAAkwsHACAAIAChCw==", + "AGFzbQEAAAABCwJgAX0BfWABfAF8AwMCAAEHLwIUZjMyLm5vX2ZvbGRfZGl2X3NlbGYAABRmNjQubm9fZm9sZF9kaXZfc2VsZgABChECBwAgACAAlQsHACAAIACjCw==", + "AGFzbQEAAAABCwJgAX0BfWABfAF8AwMCAAEHKQIRZjMyLm5vX2ZvbGRfZGl2XzMAABFmNjQubm9fZm9sZF9kaXZfMwABChsCCgAgAEMAAEBAlQsOACAARAAAAAAAAAhAows=", + "AGFzbQEAAAABDwJgA319fQF9YAN8fHwBfAMDAgABByECDWYzMi5ub19mYWN0b3IAAA1mNjQubm9fZmFjdG9yAAEKHQINACAAIAKUIAEgApSSCw0AIAAgAqIgASACoqAL", + "AGFzbQEAAAABDwJgA319fQF9YAN8fHwBfAMDAgABBykCEWYzMi5ub19kaXN0cmlidXRlAAARZjY0Lm5vX2Rpc3RyaWJ1dGUAAQoXAgoAIAAgAZIgApQLCgAgACABoCACogs=", + "AGFzbQEAAAABDwJgA319fQF9YAN8fHwBfAMDAgABBzMCFmYzMi5ub19yZWdyb3VwX2Rpdl9tdWwAABZmNjQubm9fcmVncm91cF9kaXZfbXVsAAEKFwIKACAAIAEgApWUCwoAIAAgASACo6IL", + "AGFzbQEAAAABDwJgA319fQF9YAN8fHwBfAMDAgABBzMCFmYzMi5ub19yZWdyb3VwX211bF9kaXYAABZmNjQubm9fcmVncm91cF9tdWxfZGl2AAEKFwIKACAAIAGUIAKVCwoAIAAgAaIgAqML", + "AGFzbQEAAAABEQJgBH19fX0BfWAEfHx8fAF8AwMCAAEHMwIWZjMyLm5vX3JlYXNzb2NpYXRlX2FkZAAAFmY2NC5ub19yZWFzc29jaWF0ZV9hZGQAAQodAg0AIAAgAZIgApIgA5ILDQAgACABoCACoCADoAs=", + "AGFzbQEAAAABEQJgBH19fX0BfWAEfHx8fAF8AwMCAAEHMwIWZjMyLm5vX3JlYXNzb2NpYXRlX211bAAAFmY2NC5ub19yZWFzc29jaWF0ZV9tdWwAAQodAg0AIAAgAZQgApQgA5QLDQAgACABoiACoiADogs=", + "AGFzbQEAAAABCwJgAX0BfWABfAF8AwMCAAEHKQIRZjMyLm5vX2ZvbGRfZGl2XzAAABFmNjQubm9fZm9sZF9kaXZfMAABChsCCgAgAEMAAAAAlQsOACAARAAAAAAAAAAAows=", + "AGFzbQEAAAABCwJgAX0BfWABfAF8AwMCAAEHLwIUZjMyLm5vX2ZvbGRfZGl2X25lZzAAABRmNjQubm9fZm9sZF9kaXZfbmVnMAABChsCCgAgAEMAAACAlQsOACAARAAAAAAAAACAows=", + "AGFzbQEAAAABDQJgAn19AX1gAnx8AXwDAwIAAQcvAhRmMzIubm9fZm9sZF90b19oeXBvdAAAFGY2NC5ub19mb2xkX3RvX2h5cG90AAEKHwIOACAAIACUIAEgAZSSkQsOACAAIACiIAEgAaKgnws=", + "AGFzbQEAAAABBgFgAX0BfQMCAQAHIQEdZjMyLm5vX2FwcHJveGltYXRlX3JlY2lwcm9jYWwAAAoMAQoAQwAAgD8gAJUL", + "AGFzbQEAAAABCwJgAX0BfWABfAF8AwMCAAEHRAIiZjMyLm5vX2FwcHJveGltYXRlX3JlY2lwcm9jYWxfc3FydAAAG2Y2NC5ub19mdXNlX3JlY2lwcm9jYWxfc3FydAABCh0CCwBDAACAPyAAkZULDwBEAAAAAAAA8D8gAJ+jCw==", + "AGFzbQEAAAABBgFgAX0BfQMCAQAHJgEiZjMyLm5vX2FwcHJveGltYXRlX3NxcnRfcmVjaXByb2NhbAAACg0BCwBDAACAPyAAlZEL", + "AGFzbQEAAAABCwJgAX8Bf2ABfgF+AwUEAAABAQdRBBFpMzIubm9fZm9sZF9mMzJfcwAAEWkzMi5ub19mb2xkX2YzMl91AAERaTY0Lm5vX2ZvbGRfZjY0X3MAAhFpNjQubm9fZm9sZF9mNjRfdQADCh0EBgAgALKoCwYAIACzqQsGACAAubALBgAgALqxCw==", + "AGFzbQEAAAABDQJgAn19AX1gAnx8AXwDAwIAAQctAhNmMzIubm9fZm9sZF9hZGRfc3ViAAATZjY0Lm5vX2ZvbGRfYWRkX3N1YgABChcCCgAgACABkiABkwsKACAAIAGgIAGhCw==", + "AGFzbQEAAAABDQJgAn19AX1gAnx8AXwDAwIAAQctAhNmMzIubm9fZm9sZF9zdWJfYWRkAAATZjY0Lm5vX2ZvbGRfc3ViX2FkZAABChcCCgAgACABkyABkgsKACAAIAGhIAGgCw==", + "AGFzbQEAAAABDQJgAn19AX1gAnx8AXwDAwIAAQctAhNmMzIubm9fZm9sZF9tdWxfZGl2AAATZjY0Lm5vX2ZvbGRfbXVsX2RpdgABChcCCgAgACABlCABlQsKACAAIAGiIAGjCw==", + "AGFzbQEAAAABDQJgAn19AX1gAnx8AXwDAwIAAQctAhNmMzIubm9fZm9sZF9kaXZfbXVsAAATZjY0Lm5vX2ZvbGRfZGl2X211bAABChcCCgAgACABlSABlAsKACAAIAGjIAGiCw==", + "AGFzbQEAAAABCwJgAX0BfWABfAF8AwMCAAEHMQIVZjMyLm5vX2ZvbGRfZGl2Ml9tdWwyAAAVZjY0Lm5vX2ZvbGRfZGl2Ml9tdWwyAAEKKwIQACAAQwAAAECVQwAAAECUCxgAIABEAAAAAAAAAECjRAAAAAAAAABAogs=", + "AGFzbQEAAAABBgFgAXwBfAMCAQAHGgEWbm9fZm9sZF9kZW1vdGVfcHJvbW90ZQAACggBBgAgALa7Cw==", + "AGFzbQEAAAABBgFgAX0BfQMCAQAHGgEWbm9fZm9sZF9wcm9tb3RlX2RlbW90ZQAACggBBgAgALu2Cw==", + "AGFzbQEAAAABDQJgAnx9AX1gAn18AX0DAwIAAQc2AhNub19kZW1vdGVfbWl4ZWRfYWRkAAAcbm9fZGVtb3RlX21peGVkX2FkZF9jb21tdXRlZAABChUCCQAgACABu6C2CwkAIAC7IAGgtgs=", + "AGFzbQEAAAABDQJgAnx9AX1gAn18AX0DAwIAAQc2AhNub19kZW1vdGVfbWl4ZWRfc3ViAAAcbm9fZGVtb3RlX21peGVkX3N1Yl9jb21tdXRlZAABChUCCQAgACABu6G2CwkAIAC7IAGhtgs=", + "AGFzbQEAAAABDQJgAnx9AX1gAn18AX0DAwIAAQc2AhNub19kZW1vdGVfbWl4ZWRfbXVsAAAcbm9fZGVtb3RlX21peGVkX211bF9jb21tdXRlZAABChUCCQAgACABu6K2CwkAIAC7IAGitgs=", + "AGFzbQEAAAABDQJgAnx9AX1gAn18AX0DAwIAAQc2AhNub19kZW1vdGVfbWl4ZWRfZGl2AAAcbm9fZGVtb3RlX21peGVkX2Rpdl9jb21tdXRlZAABChUCCQAgACABu6O2CwkAIAC7IAGjtgs=", + "AGFzbQEAAAABCwJgAX0BfWABfAF8AxEQAAAAAAEBAQEAAAAAAQEBAQfBBBAhZjMyLmkzMi5ub19mb2xkX3RydW5jX3NfY29udmVydF9zAAAhZjMyLmkzMi5ub19mb2xkX3RydW5jX3VfY29udmVydF9zAAEhZjMyLmkzMi5ub19mb2xkX3RydW5jX3NfY29udmVydF91AAIhZjMyLmkzMi5ub19mb2xkX3RydW5jX3VfY29udmVydF91AAMhZjY0LmkzMi5ub19mb2xkX3RydW5jX3NfY29udmVydF9zAAQhZjY0LmkzMi5ub19mb2xkX3RydW5jX3VfY29udmVydF9zAAUhZjY0LmkzMi5ub19mb2xkX3RydW5jX3NfY29udmVydF91AAYhZjY0LmkzMi5ub19mb2xkX3RydW5jX3VfY29udmVydF91AAchZjMyLmk2NC5ub19mb2xkX3RydW5jX3NfY29udmVydF9zAAghZjMyLmk2NC5ub19mb2xkX3RydW5jX3VfY29udmVydF9zAAkhZjMyLmk2NC5ub19mb2xkX3RydW5jX3NfY29udmVydF91AAohZjMyLmk2NC5ub19mb2xkX3RydW5jX3VfY29udmVydF91AAshZjY0Lmk2NC5ub19mb2xkX3RydW5jX3NfY29udmVydF9zAAwhZjY0Lmk2NC5ub19mb2xkX3RydW5jX3VfY29udmVydF9zAA0hZjY0Lmk2NC5ub19mb2xkX3RydW5jX3NfY29udmVydF91AA4hZjY0Lmk2NC5ub19mb2xkX3RydW5jX3VfY29udmVydF91AA8KcRAGACAAqLILBgAgAKmyCwYAIACoswsGACAAqbMLBgAgAKq3CwYAIACrtwsGACAAqrgLBgAgAKu4CwYAIACutAsGACAAr7QLBgAgAK61CwYAIACvtQsGACAAsLkLBgAgALG5CwYAIACwugsGACAAsboL", + "AGFzbQEAAAABCwJgAn99AGABfwF9AwQDAAABBQQBAQEBBxYDBGluaXQAAANydW4AAQVjaGVjawACCjkDCQAgACABOAIACyUBAX8CQANAIAIgAioCACABlTgCACACQQRqIQIgAiAASQ0ACwsLBwAgACoCAAs=", + "AGFzbQEAAAABCwJgAn98AGABfwF8AwQDAAABBQQBAQEBBxYDBGluaXQAAANydW4AAQVjaGVjawACCjkDCQAgACABOQMACyUBAX8CQANAIAIgAisDACABozkDACACQQhqIQIgAiAASQ0ACwsLBwAgACsDAAs=", + "AGFzbQEAAAABDQJgAn19AX9gAnx8AX8DCQgAAAAAAQEBAQdRCAdmMzIudWx0AAAHZjMyLnVsZQABB2YzMi51Z3QAAgdmMzIudWdlAAMHZjY0LnVsdAAEB2Y2NC51bGUABQdmNjQudWd0AAYHZjY0LnVnZQAHCkkICAAgACABYEULCAAgACABXkULCAAgACABX0ULCAAgACABXUULCAAgACABZkULCAAgACABZEULCAAgACABZUULCAAgACABY0UL", + "AGFzbQEAAAABDQJgAn19AX1gAnx8AXwDCQgAAAAAAQEBAQfBAQgVZjMyLm5vX2ZvbGRfbHRfc2VsZWN0AAAVZjMyLm5vX2ZvbGRfbGVfc2VsZWN0AAEVZjMyLm5vX2ZvbGRfZ3Rfc2VsZWN0AAIVZjMyLm5vX2ZvbGRfZ2Vfc2VsZWN0AAMVZjY0Lm5vX2ZvbGRfbHRfc2VsZWN0AAQVZjY0Lm5vX2ZvbGRfbGVfc2VsZWN0AAUVZjY0Lm5vX2ZvbGRfZ3Rfc2VsZWN0AAYVZjY0Lm5vX2ZvbGRfZ2Vfc2VsZWN0AAcKaQgMACAAIAEgACABXRsLDAAgACABIAAgAV8bCwwAIAAgASAAIAFeGwsMACAAIAEgACABYBsLDAAgACABIAAgAWMbCwwAIAAgASAAIAFlGwsMACAAIAEgACABZBsLDAAgACABIAAgAWYbCw==", + "AGFzbQEAAAABDQJgAn19AX1gAnx8AXwDCQgAAAAAAQEBAQehAQgRZjMyLm5vX2ZvbGRfbHRfaWYAABFmMzIubm9fZm9sZF9sZV9pZgABEWYzMi5ub19mb2xkX2d0X2lmAAIRZjMyLm5vX2ZvbGRfZ2VfaWYAAxFmNjQubm9fZm9sZF9sdF9pZgAEEWY2NC5ub19mb2xkX2xlX2lmAAURZjY0Lm5vX2ZvbGRfZ3RfaWYABhFmNjQubm9fZm9sZF9nZV9pZgAHCoEBCA8AIAAgAV0EfSAABSABCwsPACAAIAFfBH0gAAUgAQsLDwAgACABXgR9IAAFIAELCw8AIAAgAWAEfSAABSABCwsPACAAIAFjBHwgAAUgAQsLDwAgACABZQR8IAAFIAELCw8AIAAgAWQEfCAABSABCwsPACAAIAFmBHwgAAUgAQsL", + "AGFzbQEAAAABCwJgAX0BfWABfAF8AwkIAAAAAAEBAQEH+QEIHGYzMi5ub19mb2xkX2x0X3NlbGVjdF90b19hYnMAABxmMzIubm9fZm9sZF9sZV9zZWxlY3RfdG9fYWJzAAEcZjMyLm5vX2ZvbGRfZ3Rfc2VsZWN0X3RvX2FicwACHGYzMi5ub19mb2xkX2dlX3NlbGVjdF90b19hYnMAAxxmNjQubm9fZm9sZF9sdF9zZWxlY3RfdG9fYWJzAAQcZjY0Lm5vX2ZvbGRfbGVfc2VsZWN0X3RvX2FicwAFHGY2NC5ub19mb2xkX2d0X3NlbGVjdF90b19hYnMABhxmNjQubm9fZm9sZF9nZV9zZWxlY3RfdG9fYWJzAAcKmQEIEAAgAIwgACAAQwAAAABdGwsQACAAjCAAIABDAAAAgF8bCxAAIAAgAIwgAEMAAACAXhsLEAAgACAAjCAAQwAAAABgGwsUACAAmiAAIABEAAAAAAAAAABjGwsUACAAmiAAIABEAAAAAAAAAIBlGwsUACAAIACaIABEAAAAAAAAAIBkGwsUACAAIACaIABEAAAAAAAAAABmGws=", + "AGFzbQEAAAABCwJgAX0BfWABfAF8AwkIAAAAAAEBAQEH2QEIGGYzMi5ub19mb2xkX2x0X2lmX3RvX2FicwAAGGYzMi5ub19mb2xkX2xlX2lmX3RvX2FicwABGGYzMi5ub19mb2xkX2d0X2lmX3RvX2FicwACGGYzMi5ub19mb2xkX2dlX2lmX3RvX2FicwADGGY2NC5ub19mb2xkX2x0X2lmX3RvX2FicwAEGGY2NC5ub19mb2xkX2xlX2lmX3RvX2FicwAFGGY2NC5ub19mb2xkX2d0X2lmX3RvX2FicwAGGGY2NC5ub19mb2xkX2dlX2lmX3RvX2FicwAHCrEBCBMAIABDAAAAAF0EfSAAjAUgAAsLEwAgAEMAAACAXwR9IACMBSAACwsTACAAQwAAAIBeBH0gAAUgAIwLCxMAIABDAAAAAGAEfSAABSAAjAsLFwAgAEQAAAAAAAAAAGMEfCAAmgUgAAsLFwAgAEQAAAAAAAAAgGUEfCAAmgUgAAsLFwAgAEQAAAAAAAAAgGQEfCAABSAAmgsLFwAgAEQAAAAAAAAAAGYEfCAABSAAmgsL", + "AGFzbQEAAAABCQJgAAF9YAABfAMDAgABBzcCGGYzMi5pbmNvcnJlY3RfY29ycmVjdGlvbgAAGGY2NC5pbmNvcnJlY3RfY29ycmVjdGlvbgABCkUCGQBDvp+qP0PNzJw/kkO+n6o/k0PNzJw/kwspAESHFtnO91P1P0SamZmZmZnzP6BEhxbZzvdT9T+hRJqZmZmZmfM/oQs=", + "AGFzbQEAAAABBQFgAAF9AwIBAAcNAQljYWxjdWxhdGUAAApKAUgBBX1DAEAcQyEAQ1VVUEMhAUMVx+M/IQIgAYwgAJQgACAClCABk5UhAyABjCAAlCAAIAKUIAGTlSEEAkAgAyAEWw0AAAsgBAs=", + "AGFzbQEAAAABBQFgAAF8AwIBAAcNAQljYWxjdWxhdGUAAApWAVQBBXxEAAAAAACIY0AhAERLBquqqgpqQCEBRJbQ46riePw/IQIgAZogAKIgACACoiABoaMhAyABmiAAoiAAIAKiIAGhoyEEAkAgAyAEYQ0AAAsgBAs=", + "AGFzbQEAAAABBgFgAX0BfQMCAQAHEAEMbGx2bV9wcjI2NzQ2AAAKEgEQAEMAAAAAQwAAAIAgAJOTCw==", + "AGFzbQEAAAABBgFgAX8BfQMCAQAHEAEMbGx2bV9wcjI3MTUzAAAKFAESACAAQf////8AcbJDAAAAy5IL", + "AGFzbQEAAAABBwFgAn9/AX0DAgEABxABDGxsdm1fcHIyNzAzNgAAChcBFQAgAEHL/4d0crIgAUGAgJAHcbKSCw==", + "AGFzbQEAAAABFwNgBHx8fHwBfGADfHx8AXxgA319fQF9AwQDAAECByIDCHRoZXBhc3QwAAAIdGhlcGFzdDEAAQh0aGVwYXN0MgACCiUDDQAgACABoiACIAOiowsKACAAIAGiIAKhCwoAIAAgAZQgApQL", + "AGFzbQEAAAABBgFgAX0BfQMCAQAHCwEHaW52ZXJzZQAACgwBCgBDAACAPyAAlQs=", + "AGFzbQEAAAABCwJgAX0BfWABfAF8AwMCAAEHJwIQZjMyX3NxcnRfbWludXNfMgAAEGY2NF9zcXJ0X21pbnVzXzIAAQodAgsAIACRQwAAAECTCw8AIACfRAAAAAAAAABAoQs=", + "AGFzbQEAAAABCwJgAX0BfWABfAF8AwMCAAEHNQIXZjMyLm5vX2ZvbGRfcmVjaXBfcmVjaXAAABdmNjQubm9fZm9sZF9yZWNpcF9yZWNpcAABCisCEABDAACAP0MAAIA/IACVlQsYAEQAAAAAAADwP0QAAAAAAADwPyAAo6ML", + "AGFzbQEAAAABDQJgAn19AX1gAnx8AXwDAwIAAQc7AhpmMzIubm9fYWxnZWJyYWljX2ZhY3RvcmluZwAAGmY2NC5ub19hbGdlYnJhaWNfZmFjdG9yaW5nAAEKHQINACAAIAGSIAAgAZOUCw0AIAAgAaAgACABoaIL", + "AGFzbQEAAAABDQJgAn19AX1gAnx8AXwDAwIAAQc7AhpmMzIubm9fYWxnZWJyYWljX2ZhY3RvcmluZwAAGmY2NC5ub19hbGdlYnJhaWNfZmFjdG9yaW5nAAEKHQINACAAIACUIAEgAZSTCw0AIAAgAKIgASABoqEL", + "AGFzbQEAAAABDAJgA39/fwBgAX8BfQMDAgABBQQBAQEBByACEWYzMi5zaW1wbGVfeDRfc3VtAAAIZjMyLmxvYWQAAQpuAmQBCH0gACoCACEDIAAqAgQhBCAAKgIIIQUgACoCDCEGIAEqAgAhByABKgIEIQggASoCCCEJIAEqAgwhCiACIAMgB5I4AgAgAiAEIAiSOAIEIAIgBSAJkjgCCCACIAYgCpI4AgwLBwAgACoCAAsLNgEAQQALMAEAAAABAACAAQAAAAEAAIABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", + "AGFzbQEAAAABDAJgA39/fwBgAX8BfAMDAgABBQQBAQEBByACEWY2NC5zaW1wbGVfeDRfc3VtAAAIZjY0LmxvYWQAAQpuAmQBCHwgACsDACEDIAArAwghBCAAKwMQIQUgACsDGCEGIAErAwAhByABKwMIIQggASsDECEJIAErAxghCiACIAMgB6A5AwAgAiAEIAigOQMIIAIgBSAJoDkDECACIAYgCqA5AxgLBwAgACsDAAsLZgEAQQALYAEAAAAAAAAAAQAAAAAAAIABAAAAAAAAAAEAAAAAAACAAQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", + "AGFzbQEAAAABBwFgAn9/AX0DAwIAAAUEAQEBAQchAg1mMzIua2FoYW5fc3VtAAANZjMyLnBsYWluX3N1bQABCmICNwEDfQJAA0AgAyAAKgIAIASTIgSSIgIgA5MgBJMhBCAAQQRqIQAgAiEDIAFBf2oiAQ0ACwsgAgsoAQF9AkADQCACIAAqAgCSIQIgAEEEaiEAIAFBf2ohASABDQALCyACCwuHCAEAQQALgAjExVckpYTIC224Sy7ydhccykpWHhtucSJdFx5uv80UXMchVVE5nB+yUfCjk9fBLK5+qCg6ASH0CliT+EJ3n4M5al+69wrYUWo0yq3GNA7YJtxMMxztKZCoeA/RznYxI4O4NejyRLDTofy7MuGwumlECdbZff8uwFo2FDMUPqn6h22LvM6dp/3E6YU/3dfhGKZQJnJuP3MP+BKTIzRhdhJIwJsFk+usht6UPlXojOjd5PyVR75WAyEgTOa/e/Z/1bpzHMEUj8QnlrO9M/94QV/AWs72Z25zmhdmcAP4ziejUrKfO7/7ru3TWvg3V/D1bu+xTXA9VKcBmoUISJH1nQxgh1vZVB5RbYiOCIylcTpWCGdGj48TKizsLB+0YitvQQrEZUKiMWssfT67dayGlzDZSM2aH1bExuQSwJ377gKMzhzyHqF4I9vEHkkD03HMCFDF2Fzt1bVlrLXJIdLJKXbe8DAaWzzyO9s6OYI6FghvqPG+aWmZcaYF0xSTKhbyLxHHfiC7kUTu+OQBU8C5f/C/8AOcbbHfokQBbWtxK1yzIRlGXo/bkdN8eGu3EgCP672K9dQuxMEe33NjWUdJAwq3zyTPnA5Eep4U+0K/nTkwnqCrL9GunmqDQ+NVfYW/Y4r4lhAf/m3nIhvhaUaKRMjI+QwrGQelAj7yMBCahYpf74FFoHexAxBzS66YnUe/mi061Q8DZuM9U9lAzh9vMi8hKyMhbGLUpz6ozigxLQA9Z16voM8u0rlrhOtpCDxiNr4S/TZ/iD6tvAvAQcRQtuNQMejO4pZlVZwWRuawLTrogQWwvzT3vBAc+8w88YWXQp/rFI08v9cXiEmdiyuyOoPRTwSeoQ+tCJ1Ur9GCw+wyLwKPBSEtorfk9G8ugSsLnPzL/nQC+dv08+oAqOzRmXQm3dY01SWxRt2cqnH1YLCIyOALWVolTylm+eMu/una5RhPJ2L0zqQhlXTHV2Qnmkz9VH1hzsOsh0ac+v8JynmXZyR0ytQhgyYlGRI3ZBnlZeB0dY7dyO90x9ghK3kEUUZlYANd+tj0ZaSeXSPa14qSgKTeeDzxV0Jtzckv1aSeq0D0yxvXo8r866cBspppTkabGE7deaeqplI5Hu8wzJu9W+5MIW0wAHKwRl8Iz8W54D7CswzcjmTeGUJ5z0PqQ12OiPerFdw/yGcg27hksUcf3vLLP1mf2EaQ3K4vIvniMYnZnBxM06lKV4Scn+osPK48wx6L5U4XASXbNEZfFeoFDHzZRYwZ0HOKlhbdRPkFt1txsOYhNl91iZFzdat9rtNz7DfG6lV17+qri3sR3G0asmrEJc+q459JSYnLN5sKpwFgcNy3yIPhQvW+rWKUrY2h", + "AGFzbQEAAAABBwFgAn9/AXwDAwIAAAUEAQEBAQchAg1mNjQua2FoYW5fc3VtAAANZjY0LnBsYWluX3N1bQABCmICNwEDfAJAA0AgAyAAKwMAIAShIgSgIgIgA6EgBKEhBCAAQQhqIQAgAiEDIAFBf2oiAQ0ACwsgAgsoAQF8AkADQCACIAArAwCgIQIgAEEIaiEAIAFBf2ohASABDQALCyACCwuHEAEAQQALgBATBYRCXaIsxkPbVanN2lXjc/xY1rrVAP2DNUKIixNdOEpHDXJzoRrvxEUXV9jJRuCNbOE3cMiDW1VeWi1zHlbI4W1pFHgKilpkOgnHqIfF8NNd5gP8k74mytapkWC9sO2u9zB+kjpvp1mOqn2/Z1gqVPhO/u01WKZRv0LlS2YnJG1/Qi0okhjsCK7nVdqxpmWlclBHG7ipVNemBlsPQliDiheCxhBDoMAubbxahVNyf61EvDA8VbIkmnQ6nuHYD3D8qTrNk0vs437dXSfN+KCdHBHAVy79yBMyzDoafaNBVe3DgkkqBB7vc7kuLuNf9N/msjMMOT9vRGoDwUK5+rHI7aVYmX/ttHKeeev7Q4JFqruV0v8onvahrZXWVZUNb2ARx3g+SfJ+SPSicdATjrPemVLjRXTqdg4bKsjuFAHEUFs2PO+6cqKmCPh7Np357wvHVi1c8J1d3vy4rQ9kDpcVMibCMeYFHu/LFxttFQt0XdMu+GuGtLpzUlOZqXYgRclAgGsU7aH6gEbmJtLmmMRXv8QcpJB6NpQUuhWJbuacN4z03hIiXaF5UGcNPXrp1Koufyp6MD3qXRJI/uEYzaRXooc+tpqL29qdeJzPjbFPkLQ04J32yv5MO3htClwYn2G53bTgD3bgG2kNXlhzcF4OLaF9/yDrkTSSrDhyKh+OcS5q8a/HJ3DZxFf30jwduPDwZM/crr6jzD4ifU5pIWMX7QMCVJoPUE4TWjWhIqTfhsJ0eRa4aWmgUl0RZL1bk/xpoPQT0IFR3foMFcN6yWJ6qR3J5lqzW5cCPGQiEjwikGQtMFRMtKEiCVciXo44KwKorva+DSvyA636EAFxdyowApX2AD7QxI00GVAhCrxQ2jww1joxlI06/u8UV51LkwCWJAxv/bwjdgJs61JygBF+gDoTEjgdOEmVQCeKRHvo3G2MjI48tbMYDvYIGoRBNf+LuJNA6uFRHYmljUJoKeovwXpS65BdTdaA49d1SM7t0wEcjVullA14z/EGEy+YAqRtLmzy1XQpiUz5A/XHGK168Gj4XNZZh27WPwa+hiDjQZEi826L8GgcV6f8sHyemQuWGolf5g18CFGgomeaRwCTa/ko8GjbYvHgZSxTM+CnyhFCMPavAcFlPTIBb6suvtOLvhTD/+z78PnFDAVvAQlr4zQxDB9mpkK8GodJFhaMsJANNIwK4QleEKRrVszwybvcuFzO9syNdX6zB4gEL7ReyeNKI3MZYmyaA3ZEhpxg/Ntyjyeg3bPF2v/57Gqxe9PPUDfJengM5Dq29eb0mG5CfTVzi0XAVpfNbc7PrTGzw1T679XA9GpfVOdJPjMKMDj92QX/pT9XRhS1kRfKa5gjemWzbAK0zHldWNiz1ZSu9G11ZfeSv35HTDzu26zxMl37b0EcNMiDT8JYAb4FPmYWpgRtXU+GCSeCJRLNOs3Oa7zKrCib7molhp5FcMbSvTt9QuUnr8cd9IHIs3aKqDajrirmGOE2Iq32JXKwOYsBmiJ7hMMtX3KkmKwVcOfUGOJ90jB8MwjNysQihYh1gcZKdFiN4Oisxat1WvQoEvAYRVLyl7KTQW+Nf9tw+6NdH6eNmCArIp86AbWLG9LLFAMOFBTSGVofzl7NgXkVAcrec3SMViCfdy0lFvZhUR2kjpuYpcbsqEVXgll4DZC031Gww4KUzLNTCRVtlmw6QEe3SnoFL6EejJ2gIIj7Uref8/O7X+eKYachsaz6CaqkbLwkgLoq6WX/cP/M+mWHdvPFFc7L6EIxAAyRV9ngnTVUJK2k2PkIZ2PIz4HdkKLXxAdK5hBvZ+cn1CNZGPKonV/YlDCqVIZPh52CtSbKppa/z1X5nTcBGUhDxZRs83SXWEw8nQjoBMJYMHbhoPjq6cWuz3ieqQyss0RC4LxdG5xJWEocGUnBOur16zuBqUtwDMyeGtMvt1IvIDvrZFEdoC2yPr4ThUiSMi7bXKHnjEWRNQEKk8LrCc7z0iIk0IzMHZ04yE3jgsxkFQYt5wEvq7u1BEySHHrWP+hfMRUM3OQxtMQlPiqqAJ7I5SF6fynxwK8dXuhjOa34fmzIxX/CqJcnCtn0IWrqAwn795Y7g3lffEswn1Y13rRz1JXwFMN0Lw2jHU6NMSSzGoSFYlp7PBQ5F+Zt6zfCAFhbC+M8imLh+DVLVuKHYIu+pziRd1SpWiQlkJ+lQnfzXDnf/3QHdqHNH2ILgYForwXBwH8m7sCRo2p9KWFFJ+VXiNwNlwQaM6lEitoCEEU/jlWmdoxN4/GJg8jQ+JtQd59H30ycZg2qGLhfT8QBztyErEaeaeF2RWthieRdlLsRg5942ArS9X5dQ+q8EPE6yeJk+1Nl0Me0p/vUBVMl0M0piABWJSR9XbTzQZ/ptfeuZCzjyW3VhDpyErh62RsJ6DjaJk8EzgNxbopEe1yBWZzS5MO6WablKKePmuTVTrnKf8t1uCtDPrMVRrGlvJ2eOBXxvRshqvGCAJX8p3dHOaczQ5LXUkBLBoGKoL3xa5mEQlviO8VeElwoTbYOTshc6AGKxefknULuXZzE6+toCSeSlZoRVHPEEoD7ff7FCGB/NkHgELrWK2zxtBf+JjTjS/io45G+Tyr82oG45/7VJlBH8xplMoHgBbhPMjEmAEpTl8LDDi6hJlSrBY5WL32vIoRopYuX9qT9qMx1QZaG/Sc9KYaNf0zUjnNB9B7i3Vgnl86clM96BC/c7Q==", + "AGFzbQEAAAABDQJgAn19AX1gAnx8AXwDAwIAAQctAhNmMzIubm9fZm9sZF9uZWdfc3ViAAATZjY0Lm5vX2ZvbGRfbmVnX3N1YgABChMCCAAgACABk4wLCAAgACABoZoL", + "AGFzbQEAAAABDQJgAn19AX1gAnx8AXwDAwIAAQctAhNmMzIubm9fZm9sZF9uZWdfYWRkAAATZjY0Lm5vX2ZvbGRfbmVnX2FkZAABChMCCAAgACABkowLCAAgACABoJoL", + "AGFzbQEAAAABDQJgAn19AX1gAnx8AXwDAwIAAQc1AhdmMzIubm9fZm9sZF9hZGRfbmVnX25lZwAAF2Y2NC5ub19mb2xkX2FkZF9uZWdfbmVnAAEKFQIJACAAjCABjJILCQAgAJogAZqgCw==", + "AGFzbQEAAAABCwJgAX0BfWABfAF8AwMCAAEHLQITZjMyLm5vX2ZvbGRfYWRkX25lZwAAE2Y2NC5ub19mb2xkX2FkZF9uZWcAAQoTAggAIACMIACSCwgAIACaIACgCw==", + "AGFzbQEAAAABCwJgAX0BfWABfAF8AwMCAAEHMwIWZjMyLm5vX2ZvbGRfNnhfdmlhX2FkZAAAFmY2NC5ub19mb2xkXzZ4X3ZpYV9hZGQAAQopAhMAIAAgAJIgAJIgAJIgAJIgAJILEwAgACAAoCAAoCAAoCAAoCAAoAs=", + "AGFzbQEAAAABDwJgA319fQF9YAN8fHwBfAMDAgABBy0CE2YzMi5ub19mb2xkX2Rpdl9kaXYAABNmNjQubm9fZm9sZF9kaXZfZGl2AAEKFwIKACAAIAGVIAKVCwoAIAAgAaMgAqML", + "AGFzbQEAAAABEQJgBH19fX0BfWAEfHx8fAF8AwMCAAEHLwIUZjMyLm5vX2ZvbGRfbXVsX2RpdnMAABRmNjQubm9fZm9sZF9tdWxfZGl2cwABCh0CDQAgACABlSACIAOVlAsNACAAIAGjIAIgA6OiCw==", + "AGFzbQEAAAABDwJgA319fQF9YAN8fHwBfAMDAgABBy8CFGYzMi5ub19mb2xkX2FkZF9kaXZzAAAUZjY0Lm5vX2ZvbGRfYWRkX2RpdnMAAQodAg0AIAAgApUgASAClZILDQAgACACoyABIAKjoAs=", + "AGFzbQEAAAABCwJgAX0BfWABfAF8AwMCAAEHNQIXZjMyLm5vX2ZvbGRfc3FydF9zcXVhcmUAABdmNjQubm9fZm9sZF9zcXJ0X3NxdWFyZQABChMCCAAgACAAlJELCAAgACAAop8L", + "AGFzbQEAAAABDQJgAn19AX1gAnx8AXwDAwIAAQcxAhVmMzIubm9fZm9sZF9tdWxfc3FydHMAABVmNjQubm9fZm9sZF9tdWxfc3FydHMAAQoVAgkAIACRIAGRlAsJACAAnyABn6IL", + "AGFzbQEAAAABDQJgAn19AX1gAnx8AXwDAwIAAQcxAhVmMzIubm9fZm9sZF9kaXZfc3FydHMAABVmNjQubm9fZm9sZF9kaXZfc3FydHMAAQoVAgkAIACRIAGRlQsJACAAnyABn6ML", + "AGFzbQEAAAABDQJgAn19AX1gAnx8AXwDAwIAAQc3AhhmMzIubm9fZm9sZF9tdWxfc3FydF9kaXYAABhmNjQubm9fZm9sZF9tdWxfc3FydF9kaXYAAQoZAgsAIAAgAZGUIAGVCwsAIAAgAZ+iIAGjCw==", + "AGFzbQEAAAABDwJgA319fQF9YAN8fHwBfAMDAgABB00CI2YzMi5ub19mbHVzaF9pbnRlcm1lZGlhdGVfc3Vibm9ybWFsAAAjZjY0Lm5vX2ZsdXNoX2ludGVybWVkaWF0ZV9zdWJub3JtYWwAAQoXAgoAIAAgAZQgApQLCgAgACABoiACogs=", + "AGFzbQEAAAABEwNgAn19AX9gAnx8AX9gAnx9AX0DCAcAAAABAQECB38HD2YzMi5yZWNvZGluZ19lcQAAD2YzMi5yZWNvZGluZ19sZQABD2YzMi5yZWNvZGluZ19sdAACD2Y2NC5yZWNvZGluZ19lcQADD2Y2NC5yZWNvZGluZ19sZQAED2Y2NC5yZWNvZGluZ19sdAAFD3JlY29kaW5nX2RlbW90ZQAGCkwHCgAgACABlCAAWwsKACAAIAGUIABfCwoAIAAgAZQgAF0LCgAgACABoiAAYQsKACAAIAGiIABlCwoAIAAgAaIgAGMLCAAgALYgAZQL", + "AGFzbQEAAAABDwJgA319fQF/YAN8fHwBfwMDAgABB0ECHWYzMi5ub19leHRlbmRlZF9wcmVjaXNpb25fZGl2AAAdZjY0Lm5vX2V4dGVuZGVkX3ByZWNpc2lvbl9kaXYAAQoXAgoAIAAgAZUgAlsLCgAgACABoyACYQs=", + "AGFzbQEAAAABCwJgAX0BfWABfAF8AwMCAAEHNQIXZjMyLm5vX2Rpc3RyaWJ1dGVfZXhhY3QAABdmNjQubm9fZGlzdHJpYnV0ZV9leGFjdAABCjECEwBDAAAAwSAAlEMAAABBIACUkgsbAEQAAAAAAAAgwCAAokQAAAAAAAAgQCAAoqAL", + "AGFzbQEAAAABKQZgAX0BfWAEfX19fQF9YAN9fX0BfWABfAF8YAR8fHx8AXxgA3x8fAF8AwsKAAECAgEDBAUFBAe1AQoIZjMyLnNxcnQAAA9mMzIueGtjZF9zcXJ0XzIAAQ9mMzIueGtjZF9zcXJ0XzMAAg9mMzIueGtjZF9zcXJ0XzUAAxZmMzIueGtjZF9iZXR0ZXJfc3FydF81AAQIZjY0LnNxcnQABQ9mNjQueGtjZF9zcXJ0XzIABg9mNjQueGtjZF9zcXJ0XzMABw9mNjQueGtjZF9zcXJ0XzUACBZmNjQueGtjZF9iZXR0ZXJfc3FydF81AAkKiQEKBQAgAJELEAAgACABlSACIAMgApOVkgsKACAAIAGUIAKVCw0AIAAgAZUgAiAAlZILEwAgACABIAKUkiADIAEgApSTlQsFACAAnwsQACAAIAGjIAIgAyACoaOgCwoAIAAgAaIgAqMLDQAgACABoyACIACjoAsTACAAIAEgAqKgIAMgASACoqGjCw==", + "AGFzbQEAAAABDQJgAn19AX1gAnx8AXwDAwIAAQcpAhFmMzIuY29tcHV0ZV9yYWRpeAAAEWY2NC5jb21wdXRlX3JhZGl4AAEKnQECQwADQCAAIACSIgBDAACAP5IgAJNDAACAv5JDAAAAAFsNAAsDQCAAIAFDAACAP5IiAZIgAJMgAZNDAAAAAFwNAAsgAQtXAANAIAAgAKAiAEQAAAAAAADwP6AgAKFEAAAAAAAA8L+gRAAAAAAAAAAAYQ0ACwNAIAAgAUQAAAAAAADwP6AiAaAgAKEgAaFEAAAAAAAAAABiDQALIAEL", + "AGFzbQEAAAABDQJgAn19AX1gAnx8AXwDAwIAAQc3AhhmMzIubm9fZm9sZF9zdWIxX211bF9hZGQAABhmNjQubm9fZm9sZF9zdWIxX211bF9hZGQAAQonAhAAIABDAACAP5MgAZQgAZILFAAgAEQAAAAAAADwP6EgAaIgAaAL", + "AGFzbQEAAAABDwJgA319fQF/YAN8fHwBfwMFBAAAAQEHiQEEH2YzMi5ub19mb2xkX2FkZF9sZV9tb25vdG9uaWNpdHkAAB9mMzIubm9fZm9sZF9hZGRfZ2VfbW9ub3RvbmljaXR5AAEfZjY0Lm5vX2ZvbGRfYWRkX2xlX21vbm90b25pY2l0eQACH2Y2NC5ub19mb2xkX2FkZF9nZV9tb25vdG9uaWNpdHkAAwo5BA0AIAAgApIgASACkl8LDQAgACACkiABIAKSYAsNACAAIAKgIAEgAqBlCw0AIAAgAqAgASACoGYL", + "AGFzbQEAAAABDQJgAn19AX9gAnx8AX8DCQgAAAAAAQEBAQdpCApmMzIubm90X2x0AAAKZjMyLm5vdF9sZQABCmYzMi5ub3RfZ3QAAgpmMzIubm90X2dlAAMKZjY0Lm5vdF9sdAAECmY2NC5ub3RfbGUABQpmNjQubm90X2d0AAYKZjY0Lm5vdF9nZQAHCkkICAAgACABXUULCAAgACABX0ULCAAgACABXkULCAAgACABYEULCAAgACABY0ULCAAgACABZUULCAAgACABZEULCAAgACABZkUL", + "AGFzbQEAAAABCQJgAAF9YAABfAMDAgABBx0CC2YzMi5lcHNpbG9uAAALZjY0LmVwc2lsb24AAQpVAh8AQwAAgD9DAABAQEMAAIBAQwAAQECVQwAAgD+TlJMLMwBEAAAAAAAA8D9EAAAAAAAACEBEAAAAAAAAEEBEAAAAAAAACECjRAAAAAAAAPA/oaKhCw==", + "AGFzbQEAAAABCQJgAAF9YAABfAMDAgABBx0CC2YzMi5lcHNpbG9uAAALZjY0LmVwc2lsb24AAQpnAioBAn1DAACAPyEAA0AgACIBQwAAAD+UIgBDAACAP5JDAACAP14NAAsgAQs6AQJ8RAAAAAAAAPA/IQADQCAAIgFEAAAAAAAA4D+iIgBEAAAAAAAA8D+gRAAAAAAAAPA/ZA0ACyABCw==", + "AGFzbQEAAAABDQJgAn19AX9gAnx8AX8DCQgAAAAAAQEBAQe5AQgUZjMyLm5vX3RyaWNob3RvbXlfbHQAABRmMzIubm9fdHJpY2hvdG9teV9sZQABFGYzMi5ub190cmljaG90b215X2d0AAIUZjMyLm5vX3RyaWNob3RvbXlfZ2UAAxRmNjQubm9fdHJpY2hvdG9teV9sdAAEFGY2NC5ub190cmljaG90b215X2xlAAUUZjY0Lm5vX3RyaWNob3RvbXlfZ3QABhRmNjQubm9fdHJpY2hvdG9teV9nZQAHCnEIDQAgACABXSAAIAFgcgsNACAAIAFfIAAgAV5yCw0AIAAgAV4gACABX3ILDQAgACABYCAAIAFdcgsNACAAIAFjIAAgAWZyCw0AIAAgAWUgACABZHILDQAgACABZCAAIAFlcgsNACAAIAFmIAAgAWNyCw==", + "AGFzbQEAAAABFwRgAn9/AX9gAX8Bf2ACfn4BfmABfgF+AxQTAAABAgIDAQEBAQEBAwMDAwMDAQfuAxMdZjMyLmFyaXRobWV0aWNfbmFuX2JpdHBhdHRlcm4AABxmMzIuY2Fub25pY2FsX25hbl9iaXRwYXR0ZXJuAAEgZjMyLm5vbmFyaXRobWV0aWNfbmFuX2JpdHBhdHRlcm4AAh1mNjQuYXJpdGhtZXRpY19uYW5fYml0cGF0dGVybgADHGY2NC5jYW5vbmljYWxfbmFuX2JpdHBhdHRlcm4ABCBmNjQubm9uYXJpdGhtZXRpY19uYW5fYml0cGF0dGVybgAFFGYzMi5ub19mb2xkX3N1Yl96ZXJvAAYUZjMyLm5vX2ZvbGRfbmVnMF9zdWIABxNmMzIubm9fZm9sZF9tdWxfb25lAAgUZjMyLm5vX2ZvbGRfbmVnMV9tdWwACRNmMzIubm9fZm9sZF9kaXZfb25lAAoUZjMyLm5vX2ZvbGRfZGl2X25lZzEACxRmNjQubm9fZm9sZF9zdWJfemVybwAMFGY2NC5ub19mb2xkX25lZzBfc3ViAA0TZjY0Lm5vX2ZvbGRfbXVsX29uZQAOFGY2NC5ub19mb2xkX25lZzFfbXVsAA8TZjY0Lm5vX2ZvbGRfZGl2X29uZQAQFGY2NC5ub19mb2xkX2Rpdl9uZWcxABEWbm9fZm9sZF9wcm9tb3RlX2RlbW90ZQASCpkDExEAIAC+IAG+lbxBgICA/gdxCxEAIAC+IAG+lbxB/////wdxCwcAIAC+jLwLFgAgAL8gAb+jvUKAgICAgICA/P8AgwsWACAAvyABv6O9Qv///////////wCDCwcAIAC/mr0LEwAgAL5DAAAAAJO8QYCAgP4HcQsTAEMAAACAIAC+k7xBgICA/gdxCxMAIAC+QwAAgD+UvEGAgID+B3ELEwBDAACAvyAAvpS8QYCAgP4HcQsTACAAvkMAAIA/lbxBgICA/gdxCxMAIAC+QwAAgL+VvEGAgID+B3ELHAAgAL9EAAAAAAAAAAChvUKAgICAgICA/P8AgwscAEQAAAAAAAAAgCAAv6G9QoCAgICAgID8/wCDCxwAIAC/RAAAAAAAAPA/or1CgICAgICAgPz/AIMLHABEAAAAAAAA8L8gAL+ivUKAgICAgICA/P8AgwscACAAv0QAAAAAAADwP6O9QoCAgICAgID8/wCDCxwAIAC/RAAAAAAAAPC/o71CgICAgICAgPz/AIMLDwAgAL67trxBgICA/gdxCw==", + "AGFzbQEAAAABDQFgCHx8fHx8fHx8AXwDAwIAAAcyAhNkb3RfcHJvZHVjdF9leGFtcGxlAAAYd2l0aF9iaW5hcnlfc3VtX2NvbGxhcHNlAAEKNQIZACAAIASiIAEgBaKgIAIgBqKgIAMgB6KgCxkAIAAgBKIgASAFoqAgAiAGoiADIAeioKAL", + "AGFzbQEAAAABDQJgAn19AX1gAnx8AXwDAwIAAQcnAhBmMzIuY29udHJhY3QyZm1hAAAQZjY0LmNvbnRyYWN0MmZtYQABCh8CDgAgACAAlCABIAGUk5ELDgAgACAAoiABIAGioZ8L", + "AGFzbQEAAAABDwJgA319fQF9YAN8fHwBfAMDAgABBz8CHGYzMi5kaXZpc2lvbl9ieV9zbWFsbF9udW1iZXIAABxmNjQuZGl2aXNpb25fYnlfc21hbGxfbnVtYmVyAAEKFwIKACAAIAEgApWTCwoAIAAgASACo6EL", + "AGFzbQEAAAABDwJgA319fQF9YAN8fHwBfAMDAgABBycCEGYzMi5nb2xkZW5fcmF0aW8AABBmNjQuZ29sZGVuX3JhdGlvAAEKGQILACAAIAEgApGSlAsLACAAIAEgAp+gogs=", + "AGFzbQEAAAABCwJgAX0BfWABfAF8AwMCAAEHJwIQZjMyLnNpbHZlcl9tZWFucwAAEGY2NC5zaWx2ZXJfbWVhbnMAAQo5AhcAQwAAAD8gACAAIACUQwAAgECSkZKUCx8ARAAAAAAAAOA/IAAgACAAokQAAAAAAAAQQKCfoKIL", + "AGFzbQEAAAABBwFgAnx8AX8DAgEABw4BCnBvaW50X2ZvdXIAAAoTAREAIAAgAaNEmpmZmZmZ2T9jCw==", + "AGFzbQEAAAABBgFgAX8BfAMCAQAHBwEDdGF1AAAKywEByAEBBHxEAAAAAAAAAAAhAQJAIABBAUgNAEQAAAAAAADwPyECRAAAAAAAAAAAIQMDQCABIAJEAAAAAAAAIEAgA0QAAAAAAAAgQKIiBEQAAAAAAADwP6CjRAAAAAAAABBAIAREAAAAAAAAEECgo6FEAAAAAAAAAEAgBEQAAAAAAAAUQKCjoUQAAAAAAAAAQCAERAAAAAAAABhAoKOhoqAhASADRAAAAAAAAPA/oCEDIAJEAAAAAAAAsD+iIQIgAEF/aiIADQALCyABCw==", + "AGFzbQEAAAABDQJgAn19AX1gAnx8AXwDAwIAAQc9AhtmMzIubm9fZm9sZF9jb25kaXRpb25hbF9pbmMAABtmNjQubm9fZm9sZF9jb25kaXRpb25hbF9pbmMAAQo1AhUAIAAgAEMAAIA/kiABQwAAAABdGwsdACAAIABEAAAAAAAA8D+gIAFEAAAAAAAAAABjGws=", + "AGFzbQEAAAABEQRgAAF/YAABfmAAAX1gAAF8A2NiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAgICAgICAgICAgMDAwMDAwMDAwMHmQ5iB2YzMi5uYW4AABBmMzIucG9zaXRpdmVfbmFuAAEQZjMyLm5lZ2F0aXZlX25hbgACDWYzMi5wbGFpbl9uYW4AAyJmMzIuaW5mb3JtYWxseV9rbm93bl9hc19wbGFpbl9zbmFuAAQQZjMyLmFsbF9vbmVzX25hbgAFDGYzMi5taXNjX25hbgAGFWYzMi5taXNjX3Bvc2l0aXZlX25hbgAHFWYzMi5taXNjX25lZ2F0aXZlX25hbgAIDGYzMi5pbmZpbml0eQAJFWYzMi5wb3NpdGl2ZV9pbmZpbml0eQAKFWYzMi5uZWdhdGl2ZV9pbmZpbml0eQALCGYzMi56ZXJvAAwRZjMyLnBvc2l0aXZlX3plcm8ADRFmMzIubmVnYXRpdmVfemVybwAOCGYzMi5taXNjAA8QZjMyLm1pbl9wb3NpdGl2ZQAQDmYzMi5taW5fbm9ybWFsABEOZjMyLm1heF9maW5pdGUAEhFmMzIubWF4X3N1Ym5vcm1hbAATEGYzMi50cmFpbGluZ19kb3QAFAxmMzIubWlzY19pbnQAFQ1mMzIubGFyZ2VfaW50ABYNZjMyLm1pbl9pbnQzMgAXDWYzMi5taW5faW50NjQAGAxmMzJfZGVjLnplcm8AGRVmMzJfZGVjLnBvc2l0aXZlX3plcm8AGhVmMzJfZGVjLm5lZ2F0aXZlX3plcm8AGwxmMzJfZGVjLm1pc2MAHBRmMzJfZGVjLm1pbl9wb3NpdGl2ZQAdEmYzMl9kZWMubWluX25vcm1hbAAeFWYzMl9kZWMubWF4X3N1Ym5vcm1hbAAfEmYzMl9kZWMubWF4X2Zpbml0ZQAgFGYzMl9kZWMudHJhaWxpbmdfZG90ACEQZjMyX2RlYy5taXNjX2ludAAiEWYzMl9kZWMubGFyZ2VfaW50ACMRZjMyX2RlYy5taW5faW50MzIAJBFmMzJfZGVjLm1pbl9pbnQ2NAAlF2YzMl9kZWMucm9vdF9iZWVyX2Zsb2F0ACYHZjY0Lm5hbgAnEGY2NC5wb3NpdGl2ZV9uYW4AKBBmNjQubmVnYXRpdmVfbmFuACkNZjY0LnBsYWluX25hbgAqImY2NC5pbmZvcm1hbGx5X2tub3duX2FzX3BsYWluX3NuYW4AKxBmNjQuYWxsX29uZXNfbmFuACwMZjY0Lm1pc2NfbmFuAC0VZjY0Lm1pc2NfcG9zaXRpdmVfbmFuAC4VZjY0Lm1pc2NfbmVnYXRpdmVfbmFuAC8MZjY0LmluZmluaXR5ADAVZjY0LnBvc2l0aXZlX2luZmluaXR5ADEVZjY0Lm5lZ2F0aXZlX2luZmluaXR5ADIIZjY0Lnplcm8AMxFmNjQucG9zaXRpdmVfemVybwA0EWY2NC5uZWdhdGl2ZV96ZXJvADUIZjY0Lm1pc2MANhBmNjQubWluX3Bvc2l0aXZlADcOZjY0Lm1pbl9ub3JtYWwAOBFmNjQubWF4X3N1Ym5vcm1hbAA5DmY2NC5tYXhfZmluaXRlADoQZjY0LnRyYWlsaW5nX2RvdAA7DGY2NC5taXNjX2ludAA8DWY2NC5sYXJnZV9pbnQAPQ1mNjQubWluX2ludDMyAD4NZjY0Lm1pbl9pbnQ2NAA/DGY2NF9kZWMuemVybwBAFWY2NF9kZWMucG9zaXRpdmVfemVybwBBFWY2NF9kZWMubmVnYXRpdmVfemVybwBCDGY2NF9kZWMubWlzYwBDFGY2NF9kZWMubWluX3Bvc2l0aXZlAEQSZjY0X2RlYy5taW5fbm9ybWFsAEUVZjY0X2RlYy5tYXhfc3Vibm9ybWFsAEYSZjY0X2RlYy5tYXhfZmluaXRlAEcUZjY0X2RlYy50cmFpbGluZ19kb3QASBBmNjRfZGVjLm1pc2NfaW50AEkRZjY0X2RlYy5sYXJnZV9pbnQAShFmNjRfZGVjLm1pbl9pbnQzMgBLEWY2NF9kZWMubWluX2ludDY0AEwXZjY0X2RlYy5yb290X2JlZXJfZmxvYXQATQxmMzItZGVjLXNlcDEATgxmMzItZGVjLXNlcDIATwxmMzItZGVjLXNlcDMAUAxmMzItZGVjLXNlcDQAUQxmMzItZGVjLXNlcDUAUgxmMzItaGV4LXNlcDEAUwxmMzItaGV4LXNlcDIAVAxmMzItaGV4LXNlcDMAVQxmMzItaGV4LXNlcDQAVgxmMzItaGV4LXNlcDUAVwxmNjQtZGVjLXNlcDEAWAxmNjQtZGVjLXNlcDIAWQxmNjQtZGVjLXNlcDMAWgxmNjQtZGVjLXNlcDQAWwxmNjQtZGVjLXNlcDUAXAxmNjQtaGV4LXNlcDEAXQxmNjQtaGV4LXNlcDIAXgxmNjQtaGV4LXNlcDMAXwxmNjQtaGV4LXNlcDQAYAxmNjQtaGV4LXNlcDUAYQqjCGIIAEMAAMB/vAsIAEMAAMB/vAsIAEMAAMD/vAsIAEMAAMB/vAsIAEMAAKB/vAsIAEP/////vAsIAENFI4F/vAsIAENQQLB/vAsIAEPevKr/vAsIAEMAAIB/vAsIAEMAAIB/vAsIAEMAAID/vAsIAEMAAAAAvAsIAEMAAAAAvAsIAEMAAACAvAsIAEPbD8lAvAsIAEMBAAAAvAsIAEMAAIAAvAsIAEP//39/vAsIAEP//38AvAsIAEMAAIBEvAsIAEOAopFHvAsIAEMAAIBnvAsIAEMAAADPvAsIAEMAAADfvAsIAEMAAAAAvAsIAEMAAAAAvAsIAEMAAACAvAsIAEPbD8lAvAsIAEMBAAAAvAsIAEMAAIAAvAsIAEP//38AvAsIAEP//39/vAsIAEP5AhVQvAsIAEMA5EBGvAsIAEPseK1gvAsIAEMAAADPvAsIAEMAAADfvAsIAEMBAIA/vAsMAEQAAAAAAAD4f70LDABEAAAAAAAA+H+9CwwARAAAAAAAAPj/vQsMAEQAAAAAAAD4f70LDABEAAAAAAAA9H+9CwwARP//////////vQsMAES8mnhWNBLwf70LDABECQgHBgUE83+9CwwAREUjAe/Nq/L/vQsMAEQAAAAAAADwf70LDABEAAAAAAAA8H+9CwwARAAAAAAAAPD/vQsMAEQAAAAAAAAAAL0LDABEAAAAAAAAAAC9CwwARAAAAAAAAACAvQsMAEQYLURU+yEZQL0LDABEAQAAAAAAAAC9CwwARAAAAAAAABAAvQsMAET///////8PAL0LDABE////////73+9CwwARAAAAAAAADBGvQsMAEQAAAAAUDTyQL0LDABEAAAAAAAA8ES9CwwARAAAAAAAAODBvQsMAEQAAAAAAADgw70LDABEAAAAAAAAAAC9CwwARAAAAAAAAAAAvQsMAEQAAAAAAAAAgL0LDABEGC1EVPshGUC9CwwARAEAAAAAAAAAvQsMAEQAAAAAAAAQAL0LDABE////////DwC9CwwARP///////+9/vQsMAER9w5QlrUmyVL0LDABEAAAAAIAcyEC9CwwARECMtXgdrxVEvQsMAEQAAAAAAADgwb0LDABEAAAAAAAA4MO9CwwARCSe8R8AAPA/vQsHAEMAJHRJCwcAQwAAekQLBwBDEMl6RAsHAEOWGWFYCwcAQ3SuHW4LBwBDCvAgTQsHAEOAB9VHCwcAQ/H/IEcLBwBDAADwSQsHAEMowKtVCwsARAAAAACAhC5BCwsARAAAAAAAQI9ACwsARACL/PohWY9ACwsAREXMt1tzXNAmCwsARJzeW3LOtcNFCwsARDIzAQAe4CVDCwsARAAAAADwoPpACwsARECzNCj+H+RACwsARAAAAAAAAD5BCwsARABwnw8FeLVCCw==", + "AGFzbQEAAAABhYCAgAABYAABfAOCgICAAAEAB46AgIAAAQo0Mjk0OTY3MjQ5AAAKkYCAgAABi4CAgAAARAAAIPr//+9BCw==", + "AGFzbQEAAAABFwRgAn19AX1gAX0BfWACfHwBfGABfAF8Ax0cAAAAAAEBAQABAQEBAAACAgICAwMDAgMDAwMCAge3AhwHZjMyLmFkZAAAB2YzMi5zdWIAAQdmMzIubXVsAAIHZjMyLmRpdgADCGYzMi5zcXJ0AAQHZjMyLmFicwAFB2YzMi5uZWcABgxmMzIuY29weXNpZ24ABwhmMzIuY2VpbAAICWYzMi5mbG9vcgAJCWYzMi50cnVuYwAKC2YzMi5uZWFyZXN0AAsHZjMyLm1pbgAMB2YzMi5tYXgADQdmNjQuYWRkAA4HZjY0LnN1YgAPB2Y2NC5tdWwAEAdmNjQuZGl2ABEIZjY0LnNxcnQAEgdmNjQuYWJzABMHZjY0Lm5lZwAUDGY2NC5jb3B5c2lnbgAVCGY2NC5jZWlsABYJZjY0LmZsb29yABcJZjY0LnRydW5jABgLZjY0Lm5lYXJlc3QAGQdmNjQubWluABoHZjY0Lm1heAAbCsUBHAcAIAAgAZILBwAgACABkwsHACAAIAGUCwcAIAAgAZULBQAgAJELBQAgAIsLBQAgAIwLBwAgACABmAsFACAAjQsFACAAjgsFACAAjwsFACAAkAsHACAAIAGWCwcAIAAgAZcLBwAgACABoAsHACAAIAGhCwcAIAAgAaILBwAgACABowsFACAAnwsFACAAmQsFACAAmgsHACAAIAGmCwUAIACbCwUAIACcCwUAIACdCwUAIACeCwcAIAAgAaQLBwAgACABpQs=", +]; +export const script = "[[\"file\",\"f32.wast\"],[\"module\",5,0],[\"invoke\",19,0,\"add\",[\"f32:2147483648\",\"f32:2147483648\"]],[\"invoke\",25,0,\"add\",[\"f32:0\",\"f32:2147483649\"]],[\"invoke\",30,0,\"add\",[\"f32:0\",\"f32:8388608\"]],[\"invoke\",35,0,\"add\",[\"f32:2147483648\",\"f32:3212836864\"]],[\"invoke\",40,0,\"add\",[\"f32:2147483648\",\"f32:1086918619\"]],[\"invoke\",45,0,\"add\",[\"f32:0\",\"f32:4286578687\"]],[\"invoke\",50,0,\"add\",[\"f32:0\",\"f32:2139095040\"]],[\"invoke\",59,0,\"add\",[\"f32:2147483649\",\"f32:2147483648\"]],[\"invoke\",64,0,\"add\",[\"f32:2147483649\",\"f32:1\"]],[\"invoke\",69,0,\"add\",[\"f32:1\",\"f32:2155872256\"]],[\"invoke\",74,0,\"add\",[\"f32:1\",\"f32:1056964608\"]],[\"invoke\",79,0,\"add\",[\"f32:2147483649\",\"f32:3234402267\"]],[\"invoke\",85,0,\"add\",[\"f32:1\",\"f32:4286578687\"]],[\"invoke\",90,0,\"add\",[\"f32:1\",\"f32:2139095040\"]],[\"invoke\",99,0,\"add\",[\"f32:2155872256\",\"f32:2147483648\"]],[\"invoke\",104,0,\"add\",[\"f32:2155872256\",\"f32:1\"]],[\"invoke\",109,0,\"add\",[\"f32:8388608\",\"f32:2155872256\"]],[\"invoke\",114,0,\"add\",[\"f32:8388608\",\"f32:1056964608\"]],[\"invoke\",119,0,\"add\",[\"f32:2155872256\",\"f32:3234402267\"]],[\"invoke\",124,0,\"add\",[\"f32:2155872256\",\"f32:2139095039\"]],[\"invoke\",129,0,\"add\",[\"f32:8388608\",\"f32:4286578688\"]],[\"invoke\",137,0,\"add\",[\"f32:8388608\",\"f32:2143289344\"]],[\"invoke\",143,0,\"add\",[\"f32:3204448256\",\"f32:2147483649\"]],[\"invoke\",148,0,\"add\",[\"f32:3204448256\",\"f32:8388608\"]],[\"invoke\",154,0,\"add\",[\"f32:1056964608\",\"f32:1056964608\"]],[\"invoke\",159,0,\"add\",[\"f32:3204448256\",\"f32:3234402267\"]],[\"invoke\",164,0,\"add\",[\"f32:3204448256\",\"f32:2139095039\"]],[\"invoke\",169,0,\"add\",[\"f32:1056964608\",\"f32:4286578688\"]],[\"invoke\",177,0,\"add\",[\"f32:1056964608\",\"f32:2143289344\"]],[\"invoke\",183,0,\"add\",[\"f32:3212836864\",\"f32:2147483649\"]],[\"invoke\",188,0,\"add\",[\"f32:3212836864\",\"f32:8388608\"]],[\"invoke\",193,0,\"add\",[\"f32:1065353216\",\"f32:3204448256\"]],[\"invoke\",198,0,\"add\",[\"f32:1065353216\",\"f32:1065353216\"]],[\"invoke\",203,0,\"add\",[\"f32:3212836864\",\"f32:4286578687\"]],[\"invoke\",208,0,\"add\",[\"f32:3212836864\",\"f32:2139095040\"]],[\"invoke\",215,0,\"add\",[\"f32:1065353216\",\"f32:4290772992\"]],[\"invoke\",223,0,\"add\",[\"f32:3234402267\",\"f32:2147483649\"]],[\"invoke\",228,0,\"add\",[\"f32:3234402267\",\"f32:8388608\"]],[\"invoke\",233,0,\"add\",[\"f32:1086918619\",\"f32:3204448256\"]],[\"invoke\",238,0,\"add\",[\"f32:1086918619\",\"f32:1065353216\"]],[\"invoke\",243,0,\"add\",[\"f32:3234402267\",\"f32:4286578687\"]],[\"invoke\",248,0,\"add\",[\"f32:3234402267\",\"f32:2139095040\"]],[\"invoke\",255,0,\"add\",[\"f32:1086918619\",\"f32:4290772992\"]],[\"invoke\",262,0,\"add\",[\"f32:2139095039\",\"f32:0\"]],[\"invoke\",267,0,\"add\",[\"f32:4286578687\",\"f32:2155872256\"]],[\"invoke\",272,0,\"add\",[\"f32:4286578687\",\"f32:1056964608\"]],[\"invoke\",277,0,\"add\",[\"f32:2139095039\",\"f32:3212836864\"]],[\"invoke\",282,0,\"add\",[\"f32:2139095039\",\"f32:1086918619\"]],[\"invoke\",288,0,\"add\",[\"f32:4286578687\",\"f32:2139095040\"]],[\"invoke\",295,0,\"add\",[\"f32:2139095039\",\"f32:4290772992\"]],[\"invoke\",302,0,\"add\",[\"f32:2139095040\",\"f32:0\"]],[\"invoke\",307,0,\"add\",[\"f32:4286578688\",\"f32:2155872256\"]],[\"invoke\",312,0,\"add\",[\"f32:4286578688\",\"f32:1056964608\"]],[\"invoke\",317,0,\"add\",[\"f32:2139095040\",\"f32:3212836864\"]],[\"invoke\",322,0,\"add\",[\"f32:2139095040\",\"f32:1086918619\"]],[\"invoke\",327,0,\"add\",[\"f32:4286578688\",\"f32:4286578688\"]],[\"invoke\",333,0,\"add\",[\"f32:4286578688\",\"f32:2143289344\"]],[\"invoke\",343,0,\"add\",[\"f32:2143289344\",\"f32:2147483648\"]],[\"invoke\",353,0,\"add\",[\"f32:2143289344\",\"f32:1\"]],[\"invoke\",365,0,\"add\",[\"f32:4290772992\",\"f32:1056964608\"]],[\"invoke\",375,0,\"add\",[\"f32:2143289344\",\"f32:3212836864\"]],[\"invoke\",385,0,\"add\",[\"f32:2143289344\",\"f32:1086918619\"]],[\"invoke\",395,0,\"add\",[\"f32:4290772992\",\"f32:4286578688\"]],[\"invoke\",407,0,\"add\",[\"f32:4290772992\",\"f32:2143289344\"]],[\"invoke\",421,0,\"sub\",[\"f32:0\",\"f32:2147483648\"]],[\"invoke\",426,0,\"sub\",[\"f32:0\",\"f32:1\"]],[\"invoke\",431,0,\"sub\",[\"f32:2147483648\",\"f32:3204448256\"]],[\"invoke\",436,0,\"sub\",[\"f32:2147483648\",\"f32:1065353216\"]],[\"invoke\",441,0,\"sub\",[\"f32:0\",\"f32:3234402267\"]],[\"invoke\",446,0,\"sub\",[\"f32:0\",\"f32:2139095039\"]],[\"invoke\",451,0,\"sub\",[\"f32:2147483648\",\"f32:4290772992\"]],[\"invoke\",461,0,\"sub\",[\"f32:1\",\"f32:2147483648\"]],[\"invoke\",466,0,\"sub\",[\"f32:1\",\"f32:1\"]],[\"invoke\",471,0,\"sub\",[\"f32:2147483649\",\"f32:3204448256\"]],[\"invoke\",476,0,\"sub\",[\"f32:2147483649\",\"f32:1065353216\"]],[\"invoke\",481,0,\"sub\",[\"f32:1\",\"f32:3234402267\"]],[\"invoke\",486,0,\"sub\",[\"f32:1\",\"f32:2139095039\"]],[\"invoke\",491,0,\"sub\",[\"f32:2147483649\",\"f32:4290772992\"]],[\"invoke\",500,0,\"sub\",[\"f32:2155872256\",\"f32:0\"]],[\"invoke\",505,0,\"sub\",[\"f32:8388608\",\"f32:2147483649\"]],[\"invoke\",510,0,\"sub\",[\"f32:8388608\",\"f32:8388608\"]],[\"invoke\",515,0,\"sub\",[\"f32:2155872256\",\"f32:3212836864\"]],[\"invoke\",520,0,\"sub\",[\"f32:2155872256\",\"f32:1086918619\"]],[\"invoke\",526,0,\"sub\",[\"f32:8388608\",\"f32:2139095039\"]],[\"invoke\",531,0,\"sub\",[\"f32:2155872256\",\"f32:4290772992\"]],[\"invoke\",540,0,\"sub\",[\"f32:3204448256\",\"f32:0\"]],[\"invoke\",545,0,\"sub\",[\"f32:1056964608\",\"f32:2147483649\"]],[\"invoke\",550,0,\"sub\",[\"f32:1056964608\",\"f32:8388608\"]],[\"invoke\",555,0,\"sub\",[\"f32:3204448256\",\"f32:3212836864\"]],[\"invoke\",560,0,\"sub\",[\"f32:3204448256\",\"f32:1086918619\"]],[\"invoke\",565,0,\"sub\",[\"f32:1056964608\",\"f32:4286578687\"]],[\"invoke\",570,0,\"sub\",[\"f32:1056964608\",\"f32:2139095040\"]],[\"invoke\",579,0,\"sub\",[\"f32:3212836864\",\"f32:2147483648\"]],[\"invoke\",584,0,\"sub\",[\"f32:3212836864\",\"f32:1\"]],[\"invoke\",589,0,\"sub\",[\"f32:1065353216\",\"f32:2155872256\"]],[\"invoke\",595,0,\"sub\",[\"f32:3212836864\",\"f32:3212836864\"]],[\"invoke\",600,0,\"sub\",[\"f32:3212836864\",\"f32:1086918619\"]],[\"invoke\",605,0,\"sub\",[\"f32:1065353216\",\"f32:4286578687\"]],[\"invoke\",610,0,\"sub\",[\"f32:1065353216\",\"f32:2139095040\"]],[\"invoke\",619,0,\"sub\",[\"f32:3234402267\",\"f32:2147483648\"]],[\"invoke\",624,0,\"sub\",[\"f32:3234402267\",\"f32:1\"]],[\"invoke\",629,0,\"sub\",[\"f32:1086918619\",\"f32:2155872256\"]],[\"invoke\",634,0,\"sub\",[\"f32:1086918619\",\"f32:1056964608\"]],[\"invoke\",639,0,\"sub\",[\"f32:3234402267\",\"f32:3234402267\"]],[\"invoke\",644,0,\"sub\",[\"f32:3234402267\",\"f32:2139095039\"]],[\"invoke\",649,0,\"sub\",[\"f32:1086918619\",\"f32:4286578688\"]],[\"invoke\",659,0,\"sub\",[\"f32:4286578687\",\"f32:2147483648\"]],[\"invoke\",664,0,\"sub\",[\"f32:4286578687\",\"f32:1\"]],[\"invoke\",669,0,\"sub\",[\"f32:2139095039\",\"f32:2155872256\"]],[\"invoke\",674,0,\"sub\",[\"f32:2139095039\",\"f32:1056964608\"]],[\"invoke\",679,0,\"sub\",[\"f32:4286578687\",\"f32:3234402267\"]],[\"invoke\",684,0,\"sub\",[\"f32:4286578687\",\"f32:2139095039\"]],[\"invoke\",689,0,\"sub\",[\"f32:2139095039\",\"f32:4286578688\"]],[\"invoke\",697,0,\"sub\",[\"f32:2139095039\",\"f32:2143289344\"]],[\"invoke\",703,0,\"sub\",[\"f32:4286578688\",\"f32:2147483649\"]],[\"invoke\",708,0,\"sub\",[\"f32:4286578688\",\"f32:8388608\"]],[\"invoke\",713,0,\"sub\",[\"f32:2139095040\",\"f32:3204448256\"]],[\"invoke\",718,0,\"sub\",[\"f32:2139095040\",\"f32:1065353216\"]],[\"invoke\",724,0,\"sub\",[\"f32:4286578688\",\"f32:2139095039\"]],[\"invoke\",729,0,\"sub\",[\"f32:2139095040\",\"f32:4286578688\"]],[\"invoke\",737,0,\"sub\",[\"f32:2139095040\",\"f32:2143289344\"]],[\"invoke\",747,0,\"sub\",[\"f32:4290772992\",\"f32:2147483649\"]],[\"invoke\",757,0,\"sub\",[\"f32:4290772992\",\"f32:8388608\"]],[\"invoke\",767,0,\"sub\",[\"f32:2143289344\",\"f32:3204448256\"]],[\"invoke\",777,0,\"sub\",[\"f32:2143289344\",\"f32:1065353216\"]],[\"invoke\",787,0,\"sub\",[\"f32:4290772992\",\"f32:4286578687\"]],[\"invoke\",797,0,\"sub\",[\"f32:4290772992\",\"f32:2139095040\"]],[\"invoke\",811,0,\"sub\",[\"f32:2143289344\",\"f32:4290772992\"]],[\"invoke\",822,0,\"mul\",[\"f32:0\",\"f32:0\"]],[\"invoke\",827,0,\"mul\",[\"f32:2147483648\",\"f32:2155872256\"]],[\"invoke\",833,0,\"mul\",[\"f32:0\",\"f32:3204448256\"]],[\"invoke\",838,0,\"mul\",[\"f32:0\",\"f32:1065353216\"]],[\"invoke\",843,0,\"mul\",[\"f32:2147483648\",\"f32:4286578687\"]],[\"invoke\",848,0,\"mul\",[\"f32:2147483648\",\"f32:2139095040\"]],[\"invoke\",855,0,\"mul\",[\"f32:0\",\"f32:4290772992\"]],[\"invoke\",862,0,\"mul\",[\"f32:1\",\"f32:0\"]],[\"invoke\",867,0,\"mul\",[\"f32:2147483649\",\"f32:2155872256\"]],[\"invoke\",872,0,\"mul\",[\"f32:2147483649\",\"f32:1056964608\"]],[\"invoke\",877,0,\"mul\",[\"f32:1\",\"f32:3212836864\"]],[\"invoke\",882,0,\"mul\",[\"f32:1\",\"f32:1086918619\"]],[\"invoke\",887,0,\"mul\",[\"f32:2147483649\",\"f32:4286578688\"]],[\"invoke\",893,0,\"mul\",[\"f32:2147483649\",\"f32:2143289344\"]],[\"invoke\",902,0,\"mul\",[\"f32:8388608\",\"f32:0\"]],[\"invoke\",907,0,\"mul\",[\"f32:2155872256\",\"f32:2155872256\"]],[\"invoke\",912,0,\"mul\",[\"f32:2155872256\",\"f32:1056964608\"]],[\"invoke\",917,0,\"mul\",[\"f32:8388608\",\"f32:3212836864\"]],[\"invoke\",922,0,\"mul\",[\"f32:8388608\",\"f32:1086918619\"]],[\"invoke\",927,0,\"mul\",[\"f32:2155872256\",\"f32:4286578688\"]],[\"invoke\",933,0,\"mul\",[\"f32:2155872256\",\"f32:2143289344\"]],[\"invoke\",941,0,\"mul\",[\"f32:1056964608\",\"f32:2147483648\"]],[\"invoke\",946,0,\"mul\",[\"f32:1056964608\",\"f32:1\"]],[\"invoke\",951,0,\"mul\",[\"f32:3204448256\",\"f32:3204448256\"]],[\"invoke\",956,0,\"mul\",[\"f32:3204448256\",\"f32:1065353216\"]],[\"invoke\",962,0,\"mul\",[\"f32:1056964608\",\"f32:1086918619\"]],[\"invoke\",967,0,\"mul\",[\"f32:3204448256\",\"f32:4286578688\"]],[\"invoke\",973,0,\"mul\",[\"f32:3204448256\",\"f32:2143289344\"]],[\"invoke\",981,0,\"mul\",[\"f32:1065353216\",\"f32:2147483648\"]],[\"invoke\",986,0,\"mul\",[\"f32:1065353216\",\"f32:1\"]],[\"invoke\",991,0,\"mul\",[\"f32:3212836864\",\"f32:3204448256\"]],[\"invoke\",996,0,\"mul\",[\"f32:3212836864\",\"f32:1065353216\"]],[\"invoke\",1001,0,\"mul\",[\"f32:1065353216\",\"f32:3234402267\"]],[\"invoke\",1006,0,\"mul\",[\"f32:1065353216\",\"f32:2139095039\"]],[\"invoke\",1011,0,\"mul\",[\"f32:3212836864\",\"f32:4290772992\"]],[\"invoke\",1020,0,\"mul\",[\"f32:3234402267\",\"f32:0\"]],[\"invoke\",1025,0,\"mul\",[\"f32:1086918619\",\"f32:2147483649\"]],[\"invoke\",1031,0,\"mul\",[\"f32:3234402267\",\"f32:3204448256\"]],[\"invoke\",1036,0,\"mul\",[\"f32:3234402267\",\"f32:1065353216\"]],[\"invoke\",1041,0,\"mul\",[\"f32:1086918619\",\"f32:3234402267\"]],[\"invoke\",1046,0,\"mul\",[\"f32:1086918619\",\"f32:2139095039\"]],[\"invoke\",1051,0,\"mul\",[\"f32:3234402267\",\"f32:4290772992\"]],[\"invoke\",1060,0,\"mul\",[\"f32:4286578687\",\"f32:0\"]],[\"invoke\",1065,0,\"mul\",[\"f32:2139095039\",\"f32:2147483649\"]],[\"invoke\",1070,0,\"mul\",[\"f32:2139095039\",\"f32:8388608\"]],[\"invoke\",1075,0,\"mul\",[\"f32:4286578687\",\"f32:3212836864\"]],[\"invoke\",1080,0,\"mul\",[\"f32:4286578687\",\"f32:1086918619\"]],[\"invoke\",1085,0,\"mul\",[\"f32:2139095039\",\"f32:4286578687\"]],[\"invoke\",1090,0,\"mul\",[\"f32:2139095039\",\"f32:2139095040\"]],[\"invoke\",1100,0,\"mul\",[\"f32:4286578688\",\"f32:0\"]],[\"invoke\",1105,0,\"mul\",[\"f32:2139095040\",\"f32:2147483649\"]],[\"invoke\",1110,0,\"mul\",[\"f32:2139095040\",\"f32:8388608\"]],[\"invoke\",1115,0,\"mul\",[\"f32:4286578688\",\"f32:3212836864\"]],[\"invoke\",1120,0,\"mul\",[\"f32:4286578688\",\"f32:1086918619\"]],[\"invoke\",1125,0,\"mul\",[\"f32:2139095040\",\"f32:4286578687\"]],[\"invoke\",1130,0,\"mul\",[\"f32:2139095040\",\"f32:2139095040\"]],[\"invoke\",1139,0,\"mul\",[\"f32:4290772992\",\"f32:2147483648\"]],[\"invoke\",1149,0,\"mul\",[\"f32:4290772992\",\"f32:1\"]],[\"invoke\",1159,0,\"mul\",[\"f32:2143289344\",\"f32:2155872256\"]],[\"invoke\",1169,0,\"mul\",[\"f32:2143289344\",\"f32:1056964608\"]],[\"invoke\",1179,0,\"mul\",[\"f32:4290772992\",\"f32:3234402267\"]],[\"invoke\",1191,0,\"mul\",[\"f32:2143289344\",\"f32:4286578687\"]],[\"invoke\",1201,0,\"mul\",[\"f32:2143289344\",\"f32:2139095040\"]],[\"invoke\",1219,0,\"div\",[\"f32:2147483648\",\"f32:2147483648\"]],[\"invoke\",1224,0,\"div\",[\"f32:2147483648\",\"f32:1\"]],[\"invoke\",1229,0,\"div\",[\"f32:0\",\"f32:2155872256\"]],[\"invoke\",1234,0,\"div\",[\"f32:0\",\"f32:1056964608\"]],[\"invoke\",1239,0,\"div\",[\"f32:2147483648\",\"f32:3234402267\"]],[\"invoke\",1244,0,\"div\",[\"f32:2147483648\",\"f32:2139095039\"]],[\"invoke\",1249,0,\"div\",[\"f32:0\",\"f32:4286578688\"]],[\"invoke\",1257,0,\"div\",[\"f32:0\",\"f32:2143289344\"]],[\"invoke\",1263,0,\"div\",[\"f32:2147483649\",\"f32:2147483649\"]],[\"invoke\",1269,0,\"div\",[\"f32:1\",\"f32:2155872256\"]],[\"invoke\",1274,0,\"div\",[\"f32:1\",\"f32:1056964608\"]],[\"invoke\",1279,0,\"div\",[\"f32:2147483649\",\"f32:3234402267\"]],[\"invoke\",1284,0,\"div\",[\"f32:2147483649\",\"f32:2139095039\"]],[\"invoke\",1289,0,\"div\",[\"f32:1\",\"f32:4286578688\"]],[\"invoke\",1297,0,\"div\",[\"f32:1\",\"f32:2143289344\"]],[\"invoke\",1303,0,\"div\",[\"f32:2155872256\",\"f32:2147483649\"]],[\"invoke\",1308,0,\"div\",[\"f32:2155872256\",\"f32:8388608\"]],[\"invoke\",1313,0,\"div\",[\"f32:8388608\",\"f32:3204448256\"]],[\"invoke\",1318,0,\"div\",[\"f32:8388608\",\"f32:1065353216\"]],[\"invoke\",1323,0,\"div\",[\"f32:2155872256\",\"f32:4286578687\"]],[\"invoke\",1328,0,\"div\",[\"f32:2155872256\",\"f32:2139095040\"]],[\"invoke\",1337,0,\"div\",[\"f32:8388608\",\"f32:2143289344\"]],[\"invoke\",1343,0,\"div\",[\"f32:3204448256\",\"f32:2147483649\"]],[\"invoke\",1348,0,\"div\",[\"f32:3204448256\",\"f32:8388608\"]],[\"invoke\",1353,0,\"div\",[\"f32:1056964608\",\"f32:3204448256\"]],[\"invoke\",1358,0,\"div\",[\"f32:1056964608\",\"f32:1065353216\"]],[\"invoke\",1363,0,\"div\",[\"f32:3204448256\",\"f32:4286578687\"]],[\"invoke\",1368,0,\"div\",[\"f32:3204448256\",\"f32:2139095040\"]],[\"invoke\",1375,0,\"div\",[\"f32:1056964608\",\"f32:4290772992\"]],[\"invoke\",1382,0,\"div\",[\"f32:1065353216\",\"f32:0\"]],[\"invoke\",1387,0,\"div\",[\"f32:3212836864\",\"f32:2155872256\"]],[\"invoke\",1392,0,\"div\",[\"f32:3212836864\",\"f32:1056964608\"]],[\"invoke\",1397,0,\"div\",[\"f32:1065353216\",\"f32:3212836864\"]],[\"invoke\",1403,0,\"div\",[\"f32:3212836864\",\"f32:4286578687\"]],[\"invoke\",1408,0,\"div\",[\"f32:3212836864\",\"f32:2139095040\"]],[\"invoke\",1415,0,\"div\",[\"f32:1065353216\",\"f32:4290772992\"]],[\"invoke\",1422,0,\"div\",[\"f32:1086918619\",\"f32:0\"]],[\"invoke\",1427,0,\"div\",[\"f32:3234402267\",\"f32:2155872256\"]],[\"invoke\",1432,0,\"div\",[\"f32:3234402267\",\"f32:1056964608\"]],[\"invoke\",1437,0,\"div\",[\"f32:1086918619\",\"f32:3212836864\"]],[\"invoke\",1442,0,\"div\",[\"f32:1086918619\",\"f32:1086918619\"]],[\"invoke\",1447,0,\"div\",[\"f32:3234402267\",\"f32:4286578688\"]],[\"invoke\",1453,0,\"div\",[\"f32:3234402267\",\"f32:2143289344\"]],[\"invoke\",1461,0,\"div\",[\"f32:2139095039\",\"f32:2147483648\"]],[\"invoke\",1466,0,\"div\",[\"f32:2139095039\",\"f32:1\"]],[\"invoke\",1472,0,\"div\",[\"f32:4286578687\",\"f32:1056964608\"]],[\"invoke\",1477,0,\"div\",[\"f32:2139095039\",\"f32:3212836864\"]],[\"invoke\",1482,0,\"div\",[\"f32:2139095039\",\"f32:1086918619\"]],[\"invoke\",1487,0,\"div\",[\"f32:4286578687\",\"f32:4286578688\"]],[\"invoke\",1493,0,\"div\",[\"f32:4286578687\",\"f32:2143289344\"]],[\"invoke\",1501,0,\"div\",[\"f32:2139095040\",\"f32:2147483648\"]],[\"invoke\",1506,0,\"div\",[\"f32:2139095040\",\"f32:1\"]],[\"invoke\",1511,0,\"div\",[\"f32:4286578688\",\"f32:3204448256\"]],[\"invoke\",1516,0,\"div\",[\"f32:4286578688\",\"f32:1065353216\"]],[\"invoke\",1521,0,\"div\",[\"f32:2139095040\",\"f32:3234402267\"]],[\"invoke\",1526,0,\"div\",[\"f32:2139095040\",\"f32:2139095039\"]],[\"invoke\",1531,0,\"div\",[\"f32:4286578688\",\"f32:4290772992\"]],[\"invoke\",1543,0,\"div\",[\"f32:2143289344\",\"f32:2147483648\"]],[\"invoke\",1553,0,\"div\",[\"f32:2143289344\",\"f32:1\"]],[\"invoke\",1563,0,\"div\",[\"f32:4290772992\",\"f32:3204448256\"]],[\"invoke\",1573,0,\"div\",[\"f32:4290772992\",\"f32:1065353216\"]],[\"invoke\",1583,0,\"div\",[\"f32:2143289344\",\"f32:3234402267\"]],[\"invoke\",1593,0,\"div\",[\"f32:2143289344\",\"f32:2139095039\"]],[\"invoke\",1603,0,\"div\",[\"f32:4290772992\",\"f32:4290772992\"]],[\"invoke\",1620,0,\"min\",[\"f32:2147483648\",\"f32:0\"]],[\"invoke\",1625,0,\"min\",[\"f32:0\",\"f32:2147483649\"]],[\"invoke\",1630,0,\"min\",[\"f32:0\",\"f32:8388608\"]],[\"invoke\",1635,0,\"min\",[\"f32:2147483648\",\"f32:3212836864\"]],[\"invoke\",1641,0,\"min\",[\"f32:0\",\"f32:3234402267\"]],[\"invoke\",1646,0,\"min\",[\"f32:0\",\"f32:2139095039\"]],[\"invoke\",1651,0,\"min\",[\"f32:2147483648\",\"f32:4290772992\"]],[\"invoke\",1660,0,\"min\",[\"f32:2147483649\",\"f32:0\"]],[\"invoke\",1665,0,\"min\",[\"f32:1\",\"f32:2147483649\"]],[\"invoke\",1670,0,\"min\",[\"f32:1\",\"f32:8388608\"]],[\"invoke\",1675,0,\"min\",[\"f32:2147483649\",\"f32:3212836864\"]],[\"invoke\",1680,0,\"min\",[\"f32:2147483649\",\"f32:1086918619\"]],[\"invoke\",1685,0,\"min\",[\"f32:1\",\"f32:4286578687\"]],[\"invoke\",1690,0,\"min\",[\"f32:1\",\"f32:2139095040\"]],[\"invoke\",1699,0,\"min\",[\"f32:2155872256\",\"f32:2147483648\"]],[\"invoke\",1704,0,\"min\",[\"f32:2155872256\",\"f32:1\"]],[\"invoke\",1710,0,\"min\",[\"f32:8388608\",\"f32:8388608\"]],[\"invoke\",1715,0,\"min\",[\"f32:2155872256\",\"f32:3212836864\"]],[\"invoke\",1720,0,\"min\",[\"f32:2155872256\",\"f32:1086918619\"]],[\"invoke\",1725,0,\"min\",[\"f32:8388608\",\"f32:4286578687\"]],[\"invoke\",1730,0,\"min\",[\"f32:8388608\",\"f32:2139095040\"]],[\"invoke\",1739,0,\"min\",[\"f32:3204448256\",\"f32:2147483648\"]],[\"invoke\",1744,0,\"min\",[\"f32:3204448256\",\"f32:1\"]],[\"invoke\",1749,0,\"min\",[\"f32:1056964608\",\"f32:2155872256\"]],[\"invoke\",1754,0,\"min\",[\"f32:1056964608\",\"f32:1056964608\"]],[\"invoke\",1759,0,\"min\",[\"f32:3204448256\",\"f32:3234402267\"]],[\"invoke\",1764,0,\"min\",[\"f32:3204448256\",\"f32:2139095039\"]],[\"invoke\",1769,0,\"min\",[\"f32:1056964608\",\"f32:4286578688\"]],[\"invoke\",1779,0,\"min\",[\"f32:3212836864\",\"f32:2147483648\"]],[\"invoke\",1784,0,\"min\",[\"f32:3212836864\",\"f32:1\"]],[\"invoke\",1789,0,\"min\",[\"f32:1065353216\",\"f32:2155872256\"]],[\"invoke\",1794,0,\"min\",[\"f32:1065353216\",\"f32:1056964608\"]],[\"invoke\",1799,0,\"min\",[\"f32:3212836864\",\"f32:3234402267\"]],[\"invoke\",1804,0,\"min\",[\"f32:3212836864\",\"f32:2139095039\"]],[\"invoke\",1809,0,\"min\",[\"f32:1065353216\",\"f32:4286578688\"]],[\"invoke\",1817,0,\"min\",[\"f32:1065353216\",\"f32:2143289344\"]],[\"invoke\",1823,0,\"min\",[\"f32:3234402267\",\"f32:2147483649\"]],[\"invoke\",1828,0,\"min\",[\"f32:3234402267\",\"f32:8388608\"]],[\"invoke\",1833,0,\"min\",[\"f32:1086918619\",\"f32:3204448256\"]],[\"invoke\",1838,0,\"min\",[\"f32:1086918619\",\"f32:1065353216\"]],[\"invoke\",1844,0,\"min\",[\"f32:3234402267\",\"f32:2139095039\"]],[\"invoke\",1849,0,\"min\",[\"f32:1086918619\",\"f32:4286578688\"]],[\"invoke\",1857,0,\"min\",[\"f32:1086918619\",\"f32:2143289344\"]],[\"invoke\",1863,0,\"min\",[\"f32:4286578687\",\"f32:2147483649\"]],[\"invoke\",1868,0,\"min\",[\"f32:4286578687\",\"f32:8388608\"]],[\"invoke\",1873,0,\"min\",[\"f32:2139095039\",\"f32:3204448256\"]],[\"invoke\",1878,0,\"min\",[\"f32:2139095039\",\"f32:1065353216\"]],[\"invoke\",1883,0,\"min\",[\"f32:4286578687\",\"f32:4286578687\"]],[\"invoke\",1888,0,\"min\",[\"f32:4286578687\",\"f32:2139095040\"]],[\"invoke\",1895,0,\"min\",[\"f32:2139095039\",\"f32:4290772992\"]],[\"invoke\",1902,0,\"min\",[\"f32:2139095040\",\"f32:0\"]],[\"invoke\",1908,0,\"min\",[\"f32:4286578688\",\"f32:8388608\"]],[\"invoke\",1913,0,\"min\",[\"f32:2139095040\",\"f32:3204448256\"]],[\"invoke\",1918,0,\"min\",[\"f32:2139095040\",\"f32:1065353216\"]],[\"invoke\",1923,0,\"min\",[\"f32:4286578688\",\"f32:4286578687\"]],[\"invoke\",1928,0,\"min\",[\"f32:4286578688\",\"f32:2139095040\"]],[\"invoke\",1935,0,\"min\",[\"f32:2139095040\",\"f32:4290772992\"]],[\"invoke\",1945,0,\"min\",[\"f32:2143289344\",\"f32:0\"]],[\"invoke\",1955,0,\"min\",[\"f32:4290772992\",\"f32:2155872256\"]],[\"invoke\",1965,0,\"min\",[\"f32:4290772992\",\"f32:1056964608\"]],[\"invoke\",1975,0,\"min\",[\"f32:2143289344\",\"f32:3212836864\"]],[\"invoke\",1985,0,\"min\",[\"f32:2143289344\",\"f32:1086918619\"]],[\"invoke\",1995,0,\"min\",[\"f32:4290772992\",\"f32:4286578688\"]],[\"invoke\",2011,0,\"min\",[\"f32:2143289344\",\"f32:4290772992\"]],[\"invoke\",2022,0,\"max\",[\"f32:0\",\"f32:0\"]],[\"invoke\",2027,0,\"max\",[\"f32:2147483648\",\"f32:2155872256\"]],[\"invoke\",2032,0,\"max\",[\"f32:2147483648\",\"f32:1056964608\"]],[\"invoke\",2037,0,\"max\",[\"f32:0\",\"f32:3212836864\"]],[\"invoke\",2042,0,\"max\",[\"f32:0\",\"f32:1086918619\"]],[\"invoke\",2047,0,\"max\",[\"f32:2147483648\",\"f32:4286578688\"]],[\"invoke\",2053,0,\"max\",[\"f32:2147483648\",\"f32:2143289344\"]],[\"invoke\",2061,0,\"max\",[\"f32:1\",\"f32:2147483648\"]],[\"invoke\",2066,0,\"max\",[\"f32:1\",\"f32:1\"]],[\"invoke\",2071,0,\"max\",[\"f32:2147483649\",\"f32:3204448256\"]],[\"invoke\",2076,0,\"max\",[\"f32:2147483649\",\"f32:1065353216\"]],[\"invoke\",2082,0,\"max\",[\"f32:1\",\"f32:1086918619\"]],[\"invoke\",2087,0,\"max\",[\"f32:2147483649\",\"f32:4286578688\"]],[\"invoke\",2093,0,\"max\",[\"f32:2147483649\",\"f32:2143289344\"]],[\"invoke\",2101,0,\"max\",[\"f32:8388608\",\"f32:2147483648\"]],[\"invoke\",2106,0,\"max\",[\"f32:8388608\",\"f32:1\"]],[\"invoke\",2111,0,\"max\",[\"f32:2155872256\",\"f32:3204448256\"]],[\"invoke\",2116,0,\"max\",[\"f32:2155872256\",\"f32:1065353216\"]],[\"invoke\",2121,0,\"max\",[\"f32:8388608\",\"f32:3234402267\"]],[\"invoke\",2126,0,\"max\",[\"f32:8388608\",\"f32:2139095039\"]],[\"invoke\",2131,0,\"max\",[\"f32:2155872256\",\"f32:4290772992\"]],[\"invoke\",2140,0,\"max\",[\"f32:3204448256\",\"f32:0\"]],[\"invoke\",2145,0,\"max\",[\"f32:1056964608\",\"f32:2147483649\"]],[\"invoke\",2151,0,\"max\",[\"f32:3204448256\",\"f32:3204448256\"]],[\"invoke\",2156,0,\"max\",[\"f32:3204448256\",\"f32:1065353216\"]],[\"invoke\",2161,0,\"max\",[\"f32:1056964608\",\"f32:3234402267\"]],[\"invoke\",2166,0,\"max\",[\"f32:1056964608\",\"f32:2139095039\"]],[\"invoke\",2171,0,\"max\",[\"f32:3204448256\",\"f32:4290772992\"]],[\"invoke\",2180,0,\"max\",[\"f32:3212836864\",\"f32:0\"]],[\"invoke\",2185,0,\"max\",[\"f32:1065353216\",\"f32:2147483649\"]],[\"invoke\",2190,0,\"max\",[\"f32:1065353216\",\"f32:8388608\"]],[\"invoke\",2195,0,\"max\",[\"f32:3212836864\",\"f32:3212836864\"]],[\"invoke\",2200,0,\"max\",[\"f32:3212836864\",\"f32:1086918619\"]],[\"invoke\",2205,0,\"max\",[\"f32:1065353216\",\"f32:4286578687\"]],[\"invoke\",2211,0,\"max\",[\"f32:3212836864\",\"f32:4290772992\"]],[\"invoke\",2220,0,\"max\",[\"f32:3234402267\",\"f32:0\"]],[\"invoke\",2225,0,\"max\",[\"f32:1086918619\",\"f32:2147483649\"]],[\"invoke\",2230,0,\"max\",[\"f32:1086918619\",\"f32:8388608\"]],[\"invoke\",2235,0,\"max\",[\"f32:3234402267\",\"f32:3212836864\"]],[\"invoke\",2240,0,\"max\",[\"f32:3234402267\",\"f32:1086918619\"]],[\"invoke\",2245,0,\"max\",[\"f32:1086918619\",\"f32:4286578687\"]],[\"invoke\",2250,0,\"max\",[\"f32:1086918619\",\"f32:2139095040\"]],[\"invoke\",2259,0,\"max\",[\"f32:4286578687\",\"f32:2147483648\"]],[\"invoke\",2264,0,\"max\",[\"f32:4286578687\",\"f32:1\"]],[\"invoke\",2269,0,\"max\",[\"f32:2139095039\",\"f32:2155872256\"]],[\"invoke\",2274,0,\"max\",[\"f32:2139095039\",\"f32:1056964608\"]],[\"invoke\",2280,0,\"max\",[\"f32:4286578687\",\"f32:1086918619\"]],[\"invoke\",2285,0,\"max\",[\"f32:2139095039\",\"f32:4286578687\"]],[\"invoke\",2290,0,\"max\",[\"f32:2139095039\",\"f32:2139095040\"]],[\"invoke\",2299,0,\"max\",[\"f32:4286578688\",\"f32:2147483648\"]],[\"invoke\",2304,0,\"max\",[\"f32:4286578688\",\"f32:1\"]],[\"invoke\",2309,0,\"max\",[\"f32:2139095040\",\"f32:2155872256\"]],[\"invoke\",2314,0,\"max\",[\"f32:2139095040\",\"f32:1056964608\"]],[\"invoke\",2319,0,\"max\",[\"f32:4286578688\",\"f32:3234402267\"]],[\"invoke\",2324,0,\"max\",[\"f32:4286578688\",\"f32:2139095039\"]],[\"invoke\",2329,0,\"max\",[\"f32:2139095040\",\"f32:4286578688\"]],[\"invoke\",2337,0,\"max\",[\"f32:2139095040\",\"f32:2143289344\"]],[\"invoke\",2347,0,\"max\",[\"f32:4290772992\",\"f32:2147483649\"]],[\"invoke\",2359,0,\"max\",[\"f32:2143289344\",\"f32:2155872256\"]],[\"invoke\",2369,0,\"max\",[\"f32:2143289344\",\"f32:1056964608\"]],[\"invoke\",2379,0,\"max\",[\"f32:4290772992\",\"f32:3234402267\"]],[\"invoke\",2389,0,\"max\",[\"f32:4290772992\",\"f32:2139095039\"]],[\"invoke\",2399,0,\"max\",[\"f32:2143289344\",\"f32:4286578688\"]],[\"invoke\",2415,0,\"max\",[\"f32:2143289344\",\"f32:2143289344\"]],[\"invoke\",2423,0,\"sqrt\",[\"f32:2155872256\"]],[\"invoke\",2428,0,\"sqrt\",[\"f32:1065353216\"]],[\"invoke\",2433,0,\"sqrt\",[\"f32:4286578688\"]],[\"invoke\",2440,0,\"floor\",[\"f32:0\"]],[\"invoke\",2445,0,\"floor\",[\"f32:3204448256\"]],[\"invoke\",2450,0,\"floor\",[\"f32:1086918619\"]],[\"invoke\",2457,0,\"floor\",[\"f32:2143289344\"]],[\"invoke\",2463,0,\"ceil\",[\"f32:2155872256\"]],[\"invoke\",2468,0,\"ceil\",[\"f32:1065353216\"]],[\"invoke\",2473,0,\"ceil\",[\"f32:4286578688\"]],[\"invoke\",2480,0,\"trunc\",[\"f32:0\"]],[\"invoke\",2485,0,\"trunc\",[\"f32:3204448256\"]],[\"invoke\",2490,0,\"trunc\",[\"f32:1086918619\"]],[\"invoke\",2495,0,\"trunc\",[\"f32:4290772992\"]],[\"invoke\",2502,0,\"nearest\",[\"f32:1\"]],[\"invoke\",2507,0,\"nearest\",[\"f32:3212836864\"]],[\"invoke\",2512,0,\"nearest\",[\"f32:2139095039\"]],[\"file\",\"f64.wast\"],[\"module\",5,1],[\"invoke\",19,0,\"add\",[\"f64:9223372036854775808\",\"f64:9223372036854775808\"]],[\"invoke\",25,0,\"add\",[\"f64:0\",\"f64:9223372036854775809\"]],[\"invoke\",30,0,\"add\",[\"f64:0\",\"f64:4503599627370496\"]],[\"invoke\",35,0,\"add\",[\"f64:9223372036854775808\",\"f64:13830554455654793216\"]],[\"invoke\",40,0,\"add\",[\"f64:9223372036854775808\",\"f64:4618760256179416344\"]],[\"invoke\",45,0,\"add\",[\"f64:0\",\"f64:18442240474082181119\"]],[\"invoke\",50,0,\"add\",[\"f64:0\",\"f64:9218868437227405312\"]],[\"invoke\",59,0,\"add\",[\"f64:9223372036854775809\",\"f64:9223372036854775808\"]],[\"invoke\",64,0,\"add\",[\"f64:9223372036854775809\",\"f64:1\"]],[\"invoke\",69,0,\"add\",[\"f64:1\",\"f64:9227875636482146304\"]],[\"invoke\",74,0,\"add\",[\"f64:1\",\"f64:4602678819172646912\"]],[\"invoke\",79,0,\"add\",[\"f64:9223372036854775809\",\"f64:13842132293034192152\"]],[\"invoke\",85,0,\"add\",[\"f64:1\",\"f64:18442240474082181119\"]],[\"invoke\",90,0,\"add\",[\"f64:1\",\"f64:9218868437227405312\"]],[\"invoke\",99,0,\"add\",[\"f64:9227875636482146304\",\"f64:9223372036854775808\"]],[\"invoke\",104,0,\"add\",[\"f64:9227875636482146304\",\"f64:1\"]],[\"invoke\",109,0,\"add\",[\"f64:4503599627370496\",\"f64:9227875636482146304\"]],[\"invoke\",114,0,\"add\",[\"f64:4503599627370496\",\"f64:4602678819172646912\"]],[\"invoke\",119,0,\"add\",[\"f64:9227875636482146304\",\"f64:13842132293034192152\"]],[\"invoke\",124,0,\"add\",[\"f64:9227875636482146304\",\"f64:9218868437227405311\"]],[\"invoke\",129,0,\"add\",[\"f64:4503599627370496\",\"f64:18442240474082181120\"]],[\"invoke\",137,0,\"add\",[\"f64:4503599627370496\",\"f64:9221120237041090560\"]],[\"invoke\",143,0,\"add\",[\"f64:13826050856027422720\",\"f64:9223372036854775809\"]],[\"invoke\",148,0,\"add\",[\"f64:13826050856027422720\",\"f64:4503599627370496\"]],[\"invoke\",154,0,\"add\",[\"f64:4602678819172646912\",\"f64:4602678819172646912\"]],[\"invoke\",159,0,\"add\",[\"f64:13826050856027422720\",\"f64:13842132293034192152\"]],[\"invoke\",164,0,\"add\",[\"f64:13826050856027422720\",\"f64:9218868437227405311\"]],[\"invoke\",169,0,\"add\",[\"f64:4602678819172646912\",\"f64:18442240474082181120\"]],[\"invoke\",177,0,\"add\",[\"f64:4602678819172646912\",\"f64:9221120237041090560\"]],[\"invoke\",183,0,\"add\",[\"f64:13830554455654793216\",\"f64:9223372036854775809\"]],[\"invoke\",188,0,\"add\",[\"f64:13830554455654793216\",\"f64:4503599627370496\"]],[\"invoke\",193,0,\"add\",[\"f64:4607182418800017408\",\"f64:13826050856027422720\"]],[\"invoke\",198,0,\"add\",[\"f64:4607182418800017408\",\"f64:4607182418800017408\"]],[\"invoke\",203,0,\"add\",[\"f64:13830554455654793216\",\"f64:18442240474082181119\"]],[\"invoke\",208,0,\"add\",[\"f64:13830554455654793216\",\"f64:9218868437227405312\"]],[\"invoke\",215,0,\"add\",[\"f64:4607182418800017408\",\"f64:18444492273895866368\"]],[\"invoke\",223,0,\"add\",[\"f64:13842132293034192152\",\"f64:9223372036854775809\"]],[\"invoke\",228,0,\"add\",[\"f64:13842132293034192152\",\"f64:4503599627370496\"]],[\"invoke\",233,0,\"add\",[\"f64:4618760256179416344\",\"f64:13826050856027422720\"]],[\"invoke\",238,0,\"add\",[\"f64:4618760256179416344\",\"f64:4607182418800017408\"]],[\"invoke\",243,0,\"add\",[\"f64:13842132293034192152\",\"f64:18442240474082181119\"]],[\"invoke\",248,0,\"add\",[\"f64:13842132293034192152\",\"f64:9218868437227405312\"]],[\"invoke\",255,0,\"add\",[\"f64:4618760256179416344\",\"f64:18444492273895866368\"]],[\"invoke\",262,0,\"add\",[\"f64:9218868437227405311\",\"f64:0\"]],[\"invoke\",267,0,\"add\",[\"f64:18442240474082181119\",\"f64:9227875636482146304\"]],[\"invoke\",272,0,\"add\",[\"f64:18442240474082181119\",\"f64:4602678819172646912\"]],[\"invoke\",277,0,\"add\",[\"f64:9218868437227405311\",\"f64:13830554455654793216\"]],[\"invoke\",282,0,\"add\",[\"f64:9218868437227405311\",\"f64:4618760256179416344\"]],[\"invoke\",288,0,\"add\",[\"f64:18442240474082181119\",\"f64:9218868437227405312\"]],[\"invoke\",295,0,\"add\",[\"f64:9218868437227405311\",\"f64:18444492273895866368\"]],[\"invoke\",302,0,\"add\",[\"f64:9218868437227405312\",\"f64:0\"]],[\"invoke\",307,0,\"add\",[\"f64:18442240474082181120\",\"f64:9227875636482146304\"]],[\"invoke\",312,0,\"add\",[\"f64:18442240474082181120\",\"f64:4602678819172646912\"]],[\"invoke\",317,0,\"add\",[\"f64:9218868437227405312\",\"f64:13830554455654793216\"]],[\"invoke\",322,0,\"add\",[\"f64:9218868437227405312\",\"f64:4618760256179416344\"]],[\"invoke\",327,0,\"add\",[\"f64:18442240474082181120\",\"f64:18442240474082181120\"]],[\"invoke\",333,0,\"add\",[\"f64:18442240474082181120\",\"f64:9221120237041090560\"]],[\"invoke\",343,0,\"add\",[\"f64:9221120237041090560\",\"f64:9223372036854775808\"]],[\"invoke\",353,0,\"add\",[\"f64:9221120237041090560\",\"f64:1\"]],[\"invoke\",365,0,\"add\",[\"f64:18444492273895866368\",\"f64:4602678819172646912\"]],[\"invoke\",375,0,\"add\",[\"f64:9221120237041090560\",\"f64:13830554455654793216\"]],[\"invoke\",385,0,\"add\",[\"f64:9221120237041090560\",\"f64:4618760256179416344\"]],[\"invoke\",395,0,\"add\",[\"f64:18444492273895866368\",\"f64:18442240474082181120\"]],[\"invoke\",407,0,\"add\",[\"f64:18444492273895866368\",\"f64:9221120237041090560\"]],[\"invoke\",421,0,\"sub\",[\"f64:0\",\"f64:9223372036854775808\"]],[\"invoke\",426,0,\"sub\",[\"f64:0\",\"f64:1\"]],[\"invoke\",431,0,\"sub\",[\"f64:9223372036854775808\",\"f64:13826050856027422720\"]],[\"invoke\",436,0,\"sub\",[\"f64:9223372036854775808\",\"f64:4607182418800017408\"]],[\"invoke\",441,0,\"sub\",[\"f64:0\",\"f64:13842132293034192152\"]],[\"invoke\",446,0,\"sub\",[\"f64:0\",\"f64:9218868437227405311\"]],[\"invoke\",451,0,\"sub\",[\"f64:9223372036854775808\",\"f64:18444492273895866368\"]],[\"invoke\",461,0,\"sub\",[\"f64:1\",\"f64:9223372036854775808\"]],[\"invoke\",466,0,\"sub\",[\"f64:1\",\"f64:1\"]],[\"invoke\",471,0,\"sub\",[\"f64:9223372036854775809\",\"f64:13826050856027422720\"]],[\"invoke\",476,0,\"sub\",[\"f64:9223372036854775809\",\"f64:4607182418800017408\"]],[\"invoke\",481,0,\"sub\",[\"f64:1\",\"f64:13842132293034192152\"]],[\"invoke\",486,0,\"sub\",[\"f64:1\",\"f64:9218868437227405311\"]],[\"invoke\",491,0,\"sub\",[\"f64:9223372036854775809\",\"f64:18444492273895866368\"]],[\"invoke\",500,0,\"sub\",[\"f64:9227875636482146304\",\"f64:0\"]],[\"invoke\",505,0,\"sub\",[\"f64:4503599627370496\",\"f64:9223372036854775809\"]],[\"invoke\",510,0,\"sub\",[\"f64:4503599627370496\",\"f64:4503599627370496\"]],[\"invoke\",515,0,\"sub\",[\"f64:9227875636482146304\",\"f64:13830554455654793216\"]],[\"invoke\",520,0,\"sub\",[\"f64:9227875636482146304\",\"f64:4618760256179416344\"]],[\"invoke\",526,0,\"sub\",[\"f64:4503599627370496\",\"f64:9218868437227405311\"]],[\"invoke\",531,0,\"sub\",[\"f64:9227875636482146304\",\"f64:18444492273895866368\"]],[\"invoke\",540,0,\"sub\",[\"f64:13826050856027422720\",\"f64:0\"]],[\"invoke\",545,0,\"sub\",[\"f64:4602678819172646912\",\"f64:9223372036854775809\"]],[\"invoke\",550,0,\"sub\",[\"f64:4602678819172646912\",\"f64:4503599627370496\"]],[\"invoke\",555,0,\"sub\",[\"f64:13826050856027422720\",\"f64:13830554455654793216\"]],[\"invoke\",560,0,\"sub\",[\"f64:13826050856027422720\",\"f64:4618760256179416344\"]],[\"invoke\",565,0,\"sub\",[\"f64:4602678819172646912\",\"f64:18442240474082181119\"]],[\"invoke\",570,0,\"sub\",[\"f64:4602678819172646912\",\"f64:9218868437227405312\"]],[\"invoke\",579,0,\"sub\",[\"f64:13830554455654793216\",\"f64:9223372036854775808\"]],[\"invoke\",584,0,\"sub\",[\"f64:13830554455654793216\",\"f64:1\"]],[\"invoke\",589,0,\"sub\",[\"f64:4607182418800017408\",\"f64:9227875636482146304\"]],[\"invoke\",595,0,\"sub\",[\"f64:13830554455654793216\",\"f64:13830554455654793216\"]],[\"invoke\",600,0,\"sub\",[\"f64:13830554455654793216\",\"f64:4618760256179416344\"]],[\"invoke\",605,0,\"sub\",[\"f64:4607182418800017408\",\"f64:18442240474082181119\"]],[\"invoke\",610,0,\"sub\",[\"f64:4607182418800017408\",\"f64:9218868437227405312\"]],[\"invoke\",619,0,\"sub\",[\"f64:13842132293034192152\",\"f64:9223372036854775808\"]],[\"invoke\",624,0,\"sub\",[\"f64:13842132293034192152\",\"f64:1\"]],[\"invoke\",629,0,\"sub\",[\"f64:4618760256179416344\",\"f64:9227875636482146304\"]],[\"invoke\",634,0,\"sub\",[\"f64:4618760256179416344\",\"f64:4602678819172646912\"]],[\"invoke\",639,0,\"sub\",[\"f64:13842132293034192152\",\"f64:13842132293034192152\"]],[\"invoke\",644,0,\"sub\",[\"f64:13842132293034192152\",\"f64:9218868437227405311\"]],[\"invoke\",649,0,\"sub\",[\"f64:4618760256179416344\",\"f64:18442240474082181120\"]],[\"invoke\",659,0,\"sub\",[\"f64:18442240474082181119\",\"f64:9223372036854775808\"]],[\"invoke\",664,0,\"sub\",[\"f64:18442240474082181119\",\"f64:1\"]],[\"invoke\",669,0,\"sub\",[\"f64:9218868437227405311\",\"f64:9227875636482146304\"]],[\"invoke\",674,0,\"sub\",[\"f64:9218868437227405311\",\"f64:4602678819172646912\"]],[\"invoke\",679,0,\"sub\",[\"f64:18442240474082181119\",\"f64:13842132293034192152\"]],[\"invoke\",684,0,\"sub\",[\"f64:18442240474082181119\",\"f64:9218868437227405311\"]],[\"invoke\",689,0,\"sub\",[\"f64:9218868437227405311\",\"f64:18442240474082181120\"]],[\"invoke\",697,0,\"sub\",[\"f64:9218868437227405311\",\"f64:9221120237041090560\"]],[\"invoke\",703,0,\"sub\",[\"f64:18442240474082181120\",\"f64:9223372036854775809\"]],[\"invoke\",708,0,\"sub\",[\"f64:18442240474082181120\",\"f64:4503599627370496\"]],[\"invoke\",713,0,\"sub\",[\"f64:9218868437227405312\",\"f64:13826050856027422720\"]],[\"invoke\",718,0,\"sub\",[\"f64:9218868437227405312\",\"f64:4607182418800017408\"]],[\"invoke\",724,0,\"sub\",[\"f64:18442240474082181120\",\"f64:9218868437227405311\"]],[\"invoke\",729,0,\"sub\",[\"f64:9218868437227405312\",\"f64:18442240474082181120\"]],[\"invoke\",737,0,\"sub\",[\"f64:9218868437227405312\",\"f64:9221120237041090560\"]],[\"invoke\",747,0,\"sub\",[\"f64:18444492273895866368\",\"f64:9223372036854775809\"]],[\"invoke\",757,0,\"sub\",[\"f64:18444492273895866368\",\"f64:4503599627370496\"]],[\"invoke\",767,0,\"sub\",[\"f64:9221120237041090560\",\"f64:13826050856027422720\"]],[\"invoke\",777,0,\"sub\",[\"f64:9221120237041090560\",\"f64:4607182418800017408\"]],[\"invoke\",787,0,\"sub\",[\"f64:18444492273895866368\",\"f64:18442240474082181119\"]],[\"invoke\",797,0,\"sub\",[\"f64:18444492273895866368\",\"f64:9218868437227405312\"]],[\"invoke\",811,0,\"sub\",[\"f64:9221120237041090560\",\"f64:18444492273895866368\"]],[\"invoke\",822,0,\"mul\",[\"f64:0\",\"f64:0\"]],[\"invoke\",827,0,\"mul\",[\"f64:9223372036854775808\",\"f64:9227875636482146304\"]],[\"invoke\",833,0,\"mul\",[\"f64:0\",\"f64:13826050856027422720\"]],[\"invoke\",838,0,\"mul\",[\"f64:0\",\"f64:4607182418800017408\"]],[\"invoke\",843,0,\"mul\",[\"f64:9223372036854775808\",\"f64:18442240474082181119\"]],[\"invoke\",848,0,\"mul\",[\"f64:9223372036854775808\",\"f64:9218868437227405312\"]],[\"invoke\",855,0,\"mul\",[\"f64:0\",\"f64:18444492273895866368\"]],[\"invoke\",862,0,\"mul\",[\"f64:1\",\"f64:0\"]],[\"invoke\",867,0,\"mul\",[\"f64:9223372036854775809\",\"f64:9227875636482146304\"]],[\"invoke\",872,0,\"mul\",[\"f64:9223372036854775809\",\"f64:4602678819172646912\"]],[\"invoke\",877,0,\"mul\",[\"f64:1\",\"f64:13830554455654793216\"]],[\"invoke\",882,0,\"mul\",[\"f64:1\",\"f64:4618760256179416344\"]],[\"invoke\",887,0,\"mul\",[\"f64:9223372036854775809\",\"f64:18442240474082181120\"]],[\"invoke\",893,0,\"mul\",[\"f64:9223372036854775809\",\"f64:9221120237041090560\"]],[\"invoke\",902,0,\"mul\",[\"f64:4503599627370496\",\"f64:0\"]],[\"invoke\",907,0,\"mul\",[\"f64:9227875636482146304\",\"f64:9227875636482146304\"]],[\"invoke\",912,0,\"mul\",[\"f64:9227875636482146304\",\"f64:4602678819172646912\"]],[\"invoke\",917,0,\"mul\",[\"f64:4503599627370496\",\"f64:13830554455654793216\"]],[\"invoke\",922,0,\"mul\",[\"f64:4503599627370496\",\"f64:4618760256179416344\"]],[\"invoke\",927,0,\"mul\",[\"f64:9227875636482146304\",\"f64:18442240474082181120\"]],[\"invoke\",933,0,\"mul\",[\"f64:9227875636482146304\",\"f64:9221120237041090560\"]],[\"invoke\",941,0,\"mul\",[\"f64:4602678819172646912\",\"f64:9223372036854775808\"]],[\"invoke\",946,0,\"mul\",[\"f64:4602678819172646912\",\"f64:1\"]],[\"invoke\",951,0,\"mul\",[\"f64:13826050856027422720\",\"f64:13826050856027422720\"]],[\"invoke\",956,0,\"mul\",[\"f64:13826050856027422720\",\"f64:4607182418800017408\"]],[\"invoke\",962,0,\"mul\",[\"f64:4602678819172646912\",\"f64:4618760256179416344\"]],[\"invoke\",967,0,\"mul\",[\"f64:13826050856027422720\",\"f64:18442240474082181120\"]],[\"invoke\",973,0,\"mul\",[\"f64:13826050856027422720\",\"f64:9221120237041090560\"]],[\"invoke\",981,0,\"mul\",[\"f64:4607182418800017408\",\"f64:9223372036854775808\"]],[\"invoke\",986,0,\"mul\",[\"f64:4607182418800017408\",\"f64:1\"]],[\"invoke\",991,0,\"mul\",[\"f64:13830554455654793216\",\"f64:13826050856027422720\"]],[\"invoke\",996,0,\"mul\",[\"f64:13830554455654793216\",\"f64:4607182418800017408\"]],[\"invoke\",1001,0,\"mul\",[\"f64:4607182418800017408\",\"f64:13842132293034192152\"]],[\"invoke\",1006,0,\"mul\",[\"f64:4607182418800017408\",\"f64:9218868437227405311\"]],[\"invoke\",1011,0,\"mul\",[\"f64:13830554455654793216\",\"f64:18444492273895866368\"]],[\"invoke\",1020,0,\"mul\",[\"f64:13842132293034192152\",\"f64:0\"]],[\"invoke\",1025,0,\"mul\",[\"f64:4618760256179416344\",\"f64:9223372036854775809\"]],[\"invoke\",1031,0,\"mul\",[\"f64:13842132293034192152\",\"f64:13826050856027422720\"]],[\"invoke\",1036,0,\"mul\",[\"f64:13842132293034192152\",\"f64:4607182418800017408\"]],[\"invoke\",1041,0,\"mul\",[\"f64:4618760256179416344\",\"f64:13842132293034192152\"]],[\"invoke\",1046,0,\"mul\",[\"f64:4618760256179416344\",\"f64:9218868437227405311\"]],[\"invoke\",1051,0,\"mul\",[\"f64:13842132293034192152\",\"f64:18444492273895866368\"]],[\"invoke\",1060,0,\"mul\",[\"f64:18442240474082181119\",\"f64:0\"]],[\"invoke\",1065,0,\"mul\",[\"f64:9218868437227405311\",\"f64:9223372036854775809\"]],[\"invoke\",1070,0,\"mul\",[\"f64:9218868437227405311\",\"f64:4503599627370496\"]],[\"invoke\",1075,0,\"mul\",[\"f64:18442240474082181119\",\"f64:13830554455654793216\"]],[\"invoke\",1080,0,\"mul\",[\"f64:18442240474082181119\",\"f64:4618760256179416344\"]],[\"invoke\",1085,0,\"mul\",[\"f64:9218868437227405311\",\"f64:18442240474082181119\"]],[\"invoke\",1090,0,\"mul\",[\"f64:9218868437227405311\",\"f64:9218868437227405312\"]],[\"invoke\",1100,0,\"mul\",[\"f64:18442240474082181120\",\"f64:0\"]],[\"invoke\",1105,0,\"mul\",[\"f64:9218868437227405312\",\"f64:9223372036854775809\"]],[\"invoke\",1110,0,\"mul\",[\"f64:9218868437227405312\",\"f64:4503599627370496\"]],[\"invoke\",1115,0,\"mul\",[\"f64:18442240474082181120\",\"f64:13830554455654793216\"]],[\"invoke\",1120,0,\"mul\",[\"f64:18442240474082181120\",\"f64:4618760256179416344\"]],[\"invoke\",1125,0,\"mul\",[\"f64:9218868437227405312\",\"f64:18442240474082181119\"]],[\"invoke\",1130,0,\"mul\",[\"f64:9218868437227405312\",\"f64:9218868437227405312\"]],[\"invoke\",1139,0,\"mul\",[\"f64:18444492273895866368\",\"f64:9223372036854775808\"]],[\"invoke\",1149,0,\"mul\",[\"f64:18444492273895866368\",\"f64:1\"]],[\"invoke\",1159,0,\"mul\",[\"f64:9221120237041090560\",\"f64:9227875636482146304\"]],[\"invoke\",1169,0,\"mul\",[\"f64:9221120237041090560\",\"f64:4602678819172646912\"]],[\"invoke\",1179,0,\"mul\",[\"f64:18444492273895866368\",\"f64:13842132293034192152\"]],[\"invoke\",1191,0,\"mul\",[\"f64:9221120237041090560\",\"f64:18442240474082181119\"]],[\"invoke\",1201,0,\"mul\",[\"f64:9221120237041090560\",\"f64:9218868437227405312\"]],[\"invoke\",1219,0,\"div\",[\"f64:9223372036854775808\",\"f64:9223372036854775808\"]],[\"invoke\",1224,0,\"div\",[\"f64:9223372036854775808\",\"f64:1\"]],[\"invoke\",1229,0,\"div\",[\"f64:0\",\"f64:9227875636482146304\"]],[\"invoke\",1234,0,\"div\",[\"f64:0\",\"f64:4602678819172646912\"]],[\"invoke\",1239,0,\"div\",[\"f64:9223372036854775808\",\"f64:13842132293034192152\"]],[\"invoke\",1244,0,\"div\",[\"f64:9223372036854775808\",\"f64:9218868437227405311\"]],[\"invoke\",1249,0,\"div\",[\"f64:0\",\"f64:18442240474082181120\"]],[\"invoke\",1257,0,\"div\",[\"f64:0\",\"f64:9221120237041090560\"]],[\"invoke\",1263,0,\"div\",[\"f64:9223372036854775809\",\"f64:9223372036854775809\"]],[\"invoke\",1269,0,\"div\",[\"f64:1\",\"f64:9227875636482146304\"]],[\"invoke\",1274,0,\"div\",[\"f64:1\",\"f64:4602678819172646912\"]],[\"invoke\",1279,0,\"div\",[\"f64:9223372036854775809\",\"f64:13842132293034192152\"]],[\"invoke\",1284,0,\"div\",[\"f64:9223372036854775809\",\"f64:9218868437227405311\"]],[\"invoke\",1289,0,\"div\",[\"f64:1\",\"f64:18442240474082181120\"]],[\"invoke\",1297,0,\"div\",[\"f64:1\",\"f64:9221120237041090560\"]],[\"invoke\",1303,0,\"div\",[\"f64:9227875636482146304\",\"f64:9223372036854775809\"]],[\"invoke\",1308,0,\"div\",[\"f64:9227875636482146304\",\"f64:4503599627370496\"]],[\"invoke\",1313,0,\"div\",[\"f64:4503599627370496\",\"f64:13826050856027422720\"]],[\"invoke\",1318,0,\"div\",[\"f64:4503599627370496\",\"f64:4607182418800017408\"]],[\"invoke\",1323,0,\"div\",[\"f64:9227875636482146304\",\"f64:18442240474082181119\"]],[\"invoke\",1328,0,\"div\",[\"f64:9227875636482146304\",\"f64:9218868437227405312\"]],[\"invoke\",1337,0,\"div\",[\"f64:4503599627370496\",\"f64:9221120237041090560\"]],[\"invoke\",1343,0,\"div\",[\"f64:13826050856027422720\",\"f64:9223372036854775809\"]],[\"invoke\",1348,0,\"div\",[\"f64:13826050856027422720\",\"f64:4503599627370496\"]],[\"invoke\",1353,0,\"div\",[\"f64:4602678819172646912\",\"f64:13826050856027422720\"]],[\"invoke\",1358,0,\"div\",[\"f64:4602678819172646912\",\"f64:4607182418800017408\"]],[\"invoke\",1363,0,\"div\",[\"f64:13826050856027422720\",\"f64:18442240474082181119\"]],[\"invoke\",1368,0,\"div\",[\"f64:13826050856027422720\",\"f64:9218868437227405312\"]],[\"invoke\",1375,0,\"div\",[\"f64:4602678819172646912\",\"f64:18444492273895866368\"]],[\"invoke\",1382,0,\"div\",[\"f64:4607182418800017408\",\"f64:0\"]],[\"invoke\",1387,0,\"div\",[\"f64:13830554455654793216\",\"f64:9227875636482146304\"]],[\"invoke\",1392,0,\"div\",[\"f64:13830554455654793216\",\"f64:4602678819172646912\"]],[\"invoke\",1397,0,\"div\",[\"f64:4607182418800017408\",\"f64:13830554455654793216\"]],[\"invoke\",1403,0,\"div\",[\"f64:13830554455654793216\",\"f64:18442240474082181119\"]],[\"invoke\",1408,0,\"div\",[\"f64:13830554455654793216\",\"f64:9218868437227405312\"]],[\"invoke\",1415,0,\"div\",[\"f64:4607182418800017408\",\"f64:18444492273895866368\"]],[\"invoke\",1422,0,\"div\",[\"f64:4618760256179416344\",\"f64:0\"]],[\"invoke\",1427,0,\"div\",[\"f64:13842132293034192152\",\"f64:9227875636482146304\"]],[\"invoke\",1432,0,\"div\",[\"f64:13842132293034192152\",\"f64:4602678819172646912\"]],[\"invoke\",1437,0,\"div\",[\"f64:4618760256179416344\",\"f64:13830554455654793216\"]],[\"invoke\",1442,0,\"div\",[\"f64:4618760256179416344\",\"f64:4618760256179416344\"]],[\"invoke\",1447,0,\"div\",[\"f64:13842132293034192152\",\"f64:18442240474082181120\"]],[\"invoke\",1453,0,\"div\",[\"f64:13842132293034192152\",\"f64:9221120237041090560\"]],[\"invoke\",1461,0,\"div\",[\"f64:9218868437227405311\",\"f64:9223372036854775808\"]],[\"invoke\",1466,0,\"div\",[\"f64:9218868437227405311\",\"f64:1\"]],[\"invoke\",1472,0,\"div\",[\"f64:18442240474082181119\",\"f64:4602678819172646912\"]],[\"invoke\",1477,0,\"div\",[\"f64:9218868437227405311\",\"f64:13830554455654793216\"]],[\"invoke\",1482,0,\"div\",[\"f64:9218868437227405311\",\"f64:4618760256179416344\"]],[\"invoke\",1487,0,\"div\",[\"f64:18442240474082181119\",\"f64:18442240474082181120\"]],[\"invoke\",1493,0,\"div\",[\"f64:18442240474082181119\",\"f64:9221120237041090560\"]],[\"invoke\",1501,0,\"div\",[\"f64:9218868437227405312\",\"f64:9223372036854775808\"]],[\"invoke\",1506,0,\"div\",[\"f64:9218868437227405312\",\"f64:1\"]],[\"invoke\",1511,0,\"div\",[\"f64:18442240474082181120\",\"f64:13826050856027422720\"]],[\"invoke\",1516,0,\"div\",[\"f64:18442240474082181120\",\"f64:4607182418800017408\"]],[\"invoke\",1521,0,\"div\",[\"f64:9218868437227405312\",\"f64:13842132293034192152\"]],[\"invoke\",1526,0,\"div\",[\"f64:9218868437227405312\",\"f64:9218868437227405311\"]],[\"invoke\",1531,0,\"div\",[\"f64:18442240474082181120\",\"f64:18444492273895866368\"]],[\"invoke\",1543,0,\"div\",[\"f64:9221120237041090560\",\"f64:9223372036854775808\"]],[\"invoke\",1553,0,\"div\",[\"f64:9221120237041090560\",\"f64:1\"]],[\"invoke\",1563,0,\"div\",[\"f64:18444492273895866368\",\"f64:13826050856027422720\"]],[\"invoke\",1573,0,\"div\",[\"f64:18444492273895866368\",\"f64:4607182418800017408\"]],[\"invoke\",1583,0,\"div\",[\"f64:9221120237041090560\",\"f64:13842132293034192152\"]],[\"invoke\",1593,0,\"div\",[\"f64:9221120237041090560\",\"f64:9218868437227405311\"]],[\"invoke\",1603,0,\"div\",[\"f64:18444492273895866368\",\"f64:18444492273895866368\"]],[\"invoke\",1620,0,\"min\",[\"f64:9223372036854775808\",\"f64:0\"]],[\"invoke\",1625,0,\"min\",[\"f64:0\",\"f64:9223372036854775809\"]],[\"invoke\",1630,0,\"min\",[\"f64:0\",\"f64:4503599627370496\"]],[\"invoke\",1635,0,\"min\",[\"f64:9223372036854775808\",\"f64:13830554455654793216\"]],[\"invoke\",1641,0,\"min\",[\"f64:0\",\"f64:13842132293034192152\"]],[\"invoke\",1646,0,\"min\",[\"f64:0\",\"f64:9218868437227405311\"]],[\"invoke\",1651,0,\"min\",[\"f64:9223372036854775808\",\"f64:18444492273895866368\"]],[\"invoke\",1660,0,\"min\",[\"f64:9223372036854775809\",\"f64:0\"]],[\"invoke\",1665,0,\"min\",[\"f64:1\",\"f64:9223372036854775809\"]],[\"invoke\",1670,0,\"min\",[\"f64:1\",\"f64:4503599627370496\"]],[\"invoke\",1675,0,\"min\",[\"f64:9223372036854775809\",\"f64:13830554455654793216\"]],[\"invoke\",1680,0,\"min\",[\"f64:9223372036854775809\",\"f64:4618760256179416344\"]],[\"invoke\",1685,0,\"min\",[\"f64:1\",\"f64:18442240474082181119\"]],[\"invoke\",1690,0,\"min\",[\"f64:1\",\"f64:9218868437227405312\"]],[\"invoke\",1699,0,\"min\",[\"f64:9227875636482146304\",\"f64:9223372036854775808\"]],[\"invoke\",1704,0,\"min\",[\"f64:9227875636482146304\",\"f64:1\"]],[\"invoke\",1710,0,\"min\",[\"f64:4503599627370496\",\"f64:4503599627370496\"]],[\"invoke\",1715,0,\"min\",[\"f64:9227875636482146304\",\"f64:13830554455654793216\"]],[\"invoke\",1720,0,\"min\",[\"f64:9227875636482146304\",\"f64:4618760256179416344\"]],[\"invoke\",1725,0,\"min\",[\"f64:4503599627370496\",\"f64:18442240474082181119\"]],[\"invoke\",1730,0,\"min\",[\"f64:4503599627370496\",\"f64:9218868437227405312\"]],[\"invoke\",1739,0,\"min\",[\"f64:13826050856027422720\",\"f64:9223372036854775808\"]],[\"invoke\",1744,0,\"min\",[\"f64:13826050856027422720\",\"f64:1\"]],[\"invoke\",1749,0,\"min\",[\"f64:4602678819172646912\",\"f64:9227875636482146304\"]],[\"invoke\",1754,0,\"min\",[\"f64:4602678819172646912\",\"f64:4602678819172646912\"]],[\"invoke\",1759,0,\"min\",[\"f64:13826050856027422720\",\"f64:13842132293034192152\"]],[\"invoke\",1764,0,\"min\",[\"f64:13826050856027422720\",\"f64:9218868437227405311\"]],[\"invoke\",1769,0,\"min\",[\"f64:4602678819172646912\",\"f64:18442240474082181120\"]],[\"invoke\",1779,0,\"min\",[\"f64:13830554455654793216\",\"f64:9223372036854775808\"]],[\"invoke\",1784,0,\"min\",[\"f64:13830554455654793216\",\"f64:1\"]],[\"invoke\",1789,0,\"min\",[\"f64:4607182418800017408\",\"f64:9227875636482146304\"]],[\"invoke\",1794,0,\"min\",[\"f64:4607182418800017408\",\"f64:4602678819172646912\"]],[\"invoke\",1799,0,\"min\",[\"f64:13830554455654793216\",\"f64:13842132293034192152\"]],[\"invoke\",1804,0,\"min\",[\"f64:13830554455654793216\",\"f64:9218868437227405311\"]],[\"invoke\",1809,0,\"min\",[\"f64:4607182418800017408\",\"f64:18442240474082181120\"]],[\"invoke\",1817,0,\"min\",[\"f64:4607182418800017408\",\"f64:9221120237041090560\"]],[\"invoke\",1823,0,\"min\",[\"f64:13842132293034192152\",\"f64:9223372036854775809\"]],[\"invoke\",1828,0,\"min\",[\"f64:13842132293034192152\",\"f64:4503599627370496\"]],[\"invoke\",1833,0,\"min\",[\"f64:4618760256179416344\",\"f64:13826050856027422720\"]],[\"invoke\",1838,0,\"min\",[\"f64:4618760256179416344\",\"f64:4607182418800017408\"]],[\"invoke\",1844,0,\"min\",[\"f64:13842132293034192152\",\"f64:9218868437227405311\"]],[\"invoke\",1849,0,\"min\",[\"f64:4618760256179416344\",\"f64:18442240474082181120\"]],[\"invoke\",1857,0,\"min\",[\"f64:4618760256179416344\",\"f64:9221120237041090560\"]],[\"invoke\",1863,0,\"min\",[\"f64:18442240474082181119\",\"f64:9223372036854775809\"]],[\"invoke\",1868,0,\"min\",[\"f64:18442240474082181119\",\"f64:4503599627370496\"]],[\"invoke\",1873,0,\"min\",[\"f64:9218868437227405311\",\"f64:13826050856027422720\"]],[\"invoke\",1878,0,\"min\",[\"f64:9218868437227405311\",\"f64:4607182418800017408\"]],[\"invoke\",1883,0,\"min\",[\"f64:18442240474082181119\",\"f64:18442240474082181119\"]],[\"invoke\",1888,0,\"min\",[\"f64:18442240474082181119\",\"f64:9218868437227405312\"]],[\"invoke\",1895,0,\"min\",[\"f64:9218868437227405311\",\"f64:18444492273895866368\"]],[\"invoke\",1902,0,\"min\",[\"f64:9218868437227405312\",\"f64:0\"]],[\"invoke\",1908,0,\"min\",[\"f64:18442240474082181120\",\"f64:4503599627370496\"]],[\"invoke\",1913,0,\"min\",[\"f64:9218868437227405312\",\"f64:13826050856027422720\"]],[\"invoke\",1918,0,\"min\",[\"f64:9218868437227405312\",\"f64:4607182418800017408\"]],[\"invoke\",1923,0,\"min\",[\"f64:18442240474082181120\",\"f64:18442240474082181119\"]],[\"invoke\",1928,0,\"min\",[\"f64:18442240474082181120\",\"f64:9218868437227405312\"]],[\"invoke\",1935,0,\"min\",[\"f64:9218868437227405312\",\"f64:18444492273895866368\"]],[\"invoke\",1945,0,\"min\",[\"f64:9221120237041090560\",\"f64:0\"]],[\"invoke\",1955,0,\"min\",[\"f64:18444492273895866368\",\"f64:9227875636482146304\"]],[\"invoke\",1965,0,\"min\",[\"f64:18444492273895866368\",\"f64:4602678819172646912\"]],[\"invoke\",1975,0,\"min\",[\"f64:9221120237041090560\",\"f64:13830554455654793216\"]],[\"invoke\",1985,0,\"min\",[\"f64:9221120237041090560\",\"f64:4618760256179416344\"]],[\"invoke\",1995,0,\"min\",[\"f64:18444492273895866368\",\"f64:18442240474082181120\"]],[\"invoke\",2011,0,\"min\",[\"f64:9221120237041090560\",\"f64:18444492273895866368\"]],[\"invoke\",2022,0,\"max\",[\"f64:0\",\"f64:0\"]],[\"invoke\",2027,0,\"max\",[\"f64:9223372036854775808\",\"f64:9227875636482146304\"]],[\"invoke\",2032,0,\"max\",[\"f64:9223372036854775808\",\"f64:4602678819172646912\"]],[\"invoke\",2037,0,\"max\",[\"f64:0\",\"f64:13830554455654793216\"]],[\"invoke\",2042,0,\"max\",[\"f64:0\",\"f64:4618760256179416344\"]],[\"invoke\",2047,0,\"max\",[\"f64:9223372036854775808\",\"f64:18442240474082181120\"]],[\"invoke\",2053,0,\"max\",[\"f64:9223372036854775808\",\"f64:9221120237041090560\"]],[\"invoke\",2061,0,\"max\",[\"f64:1\",\"f64:9223372036854775808\"]],[\"invoke\",2066,0,\"max\",[\"f64:1\",\"f64:1\"]],[\"invoke\",2071,0,\"max\",[\"f64:9223372036854775809\",\"f64:13826050856027422720\"]],[\"invoke\",2076,0,\"max\",[\"f64:9223372036854775809\",\"f64:4607182418800017408\"]],[\"invoke\",2082,0,\"max\",[\"f64:1\",\"f64:4618760256179416344\"]],[\"invoke\",2087,0,\"max\",[\"f64:9223372036854775809\",\"f64:18442240474082181120\"]],[\"invoke\",2093,0,\"max\",[\"f64:9223372036854775809\",\"f64:9221120237041090560\"]],[\"invoke\",2101,0,\"max\",[\"f64:4503599627370496\",\"f64:9223372036854775808\"]],[\"invoke\",2106,0,\"max\",[\"f64:4503599627370496\",\"f64:1\"]],[\"invoke\",2111,0,\"max\",[\"f64:9227875636482146304\",\"f64:13826050856027422720\"]],[\"invoke\",2116,0,\"max\",[\"f64:9227875636482146304\",\"f64:4607182418800017408\"]],[\"invoke\",2121,0,\"max\",[\"f64:4503599627370496\",\"f64:13842132293034192152\"]],[\"invoke\",2126,0,\"max\",[\"f64:4503599627370496\",\"f64:9218868437227405311\"]],[\"invoke\",2131,0,\"max\",[\"f64:9227875636482146304\",\"f64:18444492273895866368\"]],[\"invoke\",2140,0,\"max\",[\"f64:13826050856027422720\",\"f64:0\"]],[\"invoke\",2145,0,\"max\",[\"f64:4602678819172646912\",\"f64:9223372036854775809\"]],[\"invoke\",2151,0,\"max\",[\"f64:13826050856027422720\",\"f64:13826050856027422720\"]],[\"invoke\",2156,0,\"max\",[\"f64:13826050856027422720\",\"f64:4607182418800017408\"]],[\"invoke\",2161,0,\"max\",[\"f64:4602678819172646912\",\"f64:13842132293034192152\"]],[\"invoke\",2166,0,\"max\",[\"f64:4602678819172646912\",\"f64:9218868437227405311\"]],[\"invoke\",2171,0,\"max\",[\"f64:13826050856027422720\",\"f64:18444492273895866368\"]],[\"invoke\",2180,0,\"max\",[\"f64:13830554455654793216\",\"f64:0\"]],[\"invoke\",2185,0,\"max\",[\"f64:4607182418800017408\",\"f64:9223372036854775809\"]],[\"invoke\",2190,0,\"max\",[\"f64:4607182418800017408\",\"f64:4503599627370496\"]],[\"invoke\",2195,0,\"max\",[\"f64:13830554455654793216\",\"f64:13830554455654793216\"]],[\"invoke\",2200,0,\"max\",[\"f64:13830554455654793216\",\"f64:4618760256179416344\"]],[\"invoke\",2205,0,\"max\",[\"f64:4607182418800017408\",\"f64:18442240474082181119\"]],[\"invoke\",2211,0,\"max\",[\"f64:13830554455654793216\",\"f64:18444492273895866368\"]],[\"invoke\",2220,0,\"max\",[\"f64:13842132293034192152\",\"f64:0\"]],[\"invoke\",2225,0,\"max\",[\"f64:4618760256179416344\",\"f64:9223372036854775809\"]],[\"invoke\",2230,0,\"max\",[\"f64:4618760256179416344\",\"f64:4503599627370496\"]],[\"invoke\",2235,0,\"max\",[\"f64:13842132293034192152\",\"f64:13830554455654793216\"]],[\"invoke\",2240,0,\"max\",[\"f64:13842132293034192152\",\"f64:4618760256179416344\"]],[\"invoke\",2245,0,\"max\",[\"f64:4618760256179416344\",\"f64:18442240474082181119\"]],[\"invoke\",2250,0,\"max\",[\"f64:4618760256179416344\",\"f64:9218868437227405312\"]],[\"invoke\",2259,0,\"max\",[\"f64:18442240474082181119\",\"f64:9223372036854775808\"]],[\"invoke\",2264,0,\"max\",[\"f64:18442240474082181119\",\"f64:1\"]],[\"invoke\",2269,0,\"max\",[\"f64:9218868437227405311\",\"f64:9227875636482146304\"]],[\"invoke\",2274,0,\"max\",[\"f64:9218868437227405311\",\"f64:4602678819172646912\"]],[\"invoke\",2280,0,\"max\",[\"f64:18442240474082181119\",\"f64:4618760256179416344\"]],[\"invoke\",2285,0,\"max\",[\"f64:9218868437227405311\",\"f64:18442240474082181119\"]],[\"invoke\",2290,0,\"max\",[\"f64:9218868437227405311\",\"f64:9218868437227405312\"]],[\"invoke\",2299,0,\"max\",[\"f64:18442240474082181120\",\"f64:9223372036854775808\"]],[\"invoke\",2304,0,\"max\",[\"f64:18442240474082181120\",\"f64:1\"]],[\"invoke\",2309,0,\"max\",[\"f64:9218868437227405312\",\"f64:9227875636482146304\"]],[\"invoke\",2314,0,\"max\",[\"f64:9218868437227405312\",\"f64:4602678819172646912\"]],[\"invoke\",2319,0,\"max\",[\"f64:18442240474082181120\",\"f64:13842132293034192152\"]],[\"invoke\",2324,0,\"max\",[\"f64:18442240474082181120\",\"f64:9218868437227405311\"]],[\"invoke\",2329,0,\"max\",[\"f64:9218868437227405312\",\"f64:18442240474082181120\"]],[\"invoke\",2337,0,\"max\",[\"f64:9218868437227405312\",\"f64:9221120237041090560\"]],[\"invoke\",2347,0,\"max\",[\"f64:18444492273895866368\",\"f64:9223372036854775809\"]],[\"invoke\",2359,0,\"max\",[\"f64:9221120237041090560\",\"f64:9227875636482146304\"]],[\"invoke\",2369,0,\"max\",[\"f64:9221120237041090560\",\"f64:4602678819172646912\"]],[\"invoke\",2379,0,\"max\",[\"f64:18444492273895866368\",\"f64:13842132293034192152\"]],[\"invoke\",2389,0,\"max\",[\"f64:18444492273895866368\",\"f64:9218868437227405311\"]],[\"invoke\",2399,0,\"max\",[\"f64:9221120237041090560\",\"f64:18442240474082181120\"]],[\"invoke\",2415,0,\"max\",[\"f64:9221120237041090560\",\"f64:9221120237041090560\"]],[\"invoke\",2423,0,\"sqrt\",[\"f64:9227875636482146304\"]],[\"invoke\",2428,0,\"sqrt\",[\"f64:4607182418800017408\"]],[\"invoke\",2433,0,\"sqrt\",[\"f64:18442240474082181120\"]],[\"invoke\",2440,0,\"floor\",[\"f64:0\"]],[\"invoke\",2445,0,\"floor\",[\"f64:13826050856027422720\"]],[\"invoke\",2450,0,\"floor\",[\"f64:4618760256179416344\"]],[\"invoke\",2457,0,\"floor\",[\"f64:9221120237041090560\"]],[\"invoke\",2463,0,\"ceil\",[\"f64:9227875636482146304\"]],[\"invoke\",2468,0,\"ceil\",[\"f64:4607182418800017408\"]],[\"invoke\",2473,0,\"ceil\",[\"f64:18442240474082181120\"]],[\"invoke\",2480,0,\"trunc\",[\"f64:0\"]],[\"invoke\",2485,0,\"trunc\",[\"f64:13826050856027422720\"]],[\"invoke\",2490,0,\"trunc\",[\"f64:4618760256179416344\"]],[\"invoke\",2495,0,\"trunc\",[\"f64:18444492273895866368\"]],[\"invoke\",2502,0,\"nearest\",[\"f64:1\"]],[\"invoke\",2507,0,\"nearest\",[\"f64:13830554455654793216\"]],[\"invoke\",2512,0,\"nearest\",[\"f64:9218868437227405311\"]],[\"file\",\"f32_cmp.wast\"],[\"module\",4,2],[\"invoke\",13,0,\"eq\",[\"f32:2147483648\",\"f32:2147483648\"]],[\"invoke\",18,0,\"eq\",[\"f32:2147483648\",\"f32:1\"]],[\"invoke\",23,0,\"eq\",[\"f32:0\",\"f32:2155872256\"]],[\"invoke\",28,0,\"eq\",[\"f32:0\",\"f32:1056964608\"]],[\"invoke\",33,0,\"eq\",[\"f32:2147483648\",\"f32:3234402267\"]],[\"invoke\",38,0,\"eq\",[\"f32:2147483648\",\"f32:2139095039\"]],[\"invoke\",43,0,\"eq\",[\"f32:0\",\"f32:4286578688\"]],[\"invoke\",51,0,\"eq\",[\"f32:0\",\"f32:2143289344\"]],[\"invoke\",56,0,\"eq\",[\"f32:1\",\"f32:0\"]],[\"invoke\",61,0,\"eq\",[\"f32:2147483649\",\"f32:2155872256\"]],[\"invoke\",66,0,\"eq\",[\"f32:2147483649\",\"f32:1056964608\"]],[\"invoke\",71,0,\"eq\",[\"f32:1\",\"f32:3212836864\"]],[\"invoke\",76,0,\"eq\",[\"f32:1\",\"f32:1086918619\"]],[\"invoke\",81,0,\"eq\",[\"f32:2147483649\",\"f32:4286578688\"]],[\"invoke\",87,0,\"eq\",[\"f32:2147483649\",\"f32:2143289344\"]],[\"invoke\",94,0,\"eq\",[\"f32:2155872256\",\"f32:0\"]],[\"invoke\",99,0,\"eq\",[\"f32:8388608\",\"f32:2147483649\"]],[\"invoke\",104,0,\"eq\",[\"f32:8388608\",\"f32:8388608\"]],[\"invoke\",109,0,\"eq\",[\"f32:2155872256\",\"f32:3212836864\"]],[\"invoke\",114,0,\"eq\",[\"f32:2155872256\",\"f32:1086918619\"]],[\"invoke\",119,0,\"eq\",[\"f32:8388608\",\"f32:4286578687\"]],[\"invoke\",124,0,\"eq\",[\"f32:8388608\",\"f32:2139095040\"]],[\"invoke\",131,0,\"eq\",[\"f32:8388608\",\"f32:2143289344\"]],[\"invoke\",137,0,\"eq\",[\"f32:3204448256\",\"f32:2147483649\"]],[\"invoke\",142,0,\"eq\",[\"f32:3204448256\",\"f32:8388608\"]],[\"invoke\",147,0,\"eq\",[\"f32:1056964608\",\"f32:3204448256\"]],[\"invoke\",152,0,\"eq\",[\"f32:1056964608\",\"f32:1065353216\"]],[\"invoke\",157,0,\"eq\",[\"f32:3204448256\",\"f32:4286578687\"]],[\"invoke\",162,0,\"eq\",[\"f32:3204448256\",\"f32:2139095040\"]],[\"invoke\",167,0,\"eq\",[\"f32:3204448256\",\"f32:2143289344\"]],[\"invoke\",175,0,\"eq\",[\"f32:1065353216\",\"f32:2147483648\"]],[\"invoke\",180,0,\"eq\",[\"f32:1065353216\",\"f32:1\"]],[\"invoke\",185,0,\"eq\",[\"f32:3212836864\",\"f32:3204448256\"]],[\"invoke\",190,0,\"eq\",[\"f32:3212836864\",\"f32:1065353216\"]],[\"invoke\",195,0,\"eq\",[\"f32:1065353216\",\"f32:3234402267\"]],[\"invoke\",200,0,\"eq\",[\"f32:1065353216\",\"f32:2139095039\"]],[\"invoke\",204,0,\"eq\",[\"f32:1065353216\",\"f32:2139095040\"]],[\"invoke\",213,0,\"eq\",[\"f32:3234402267\",\"f32:2147483648\"]],[\"invoke\",218,0,\"eq\",[\"f32:3234402267\",\"f32:1\"]],[\"invoke\",223,0,\"eq\",[\"f32:1086918619\",\"f32:2155872256\"]],[\"invoke\",228,0,\"eq\",[\"f32:1086918619\",\"f32:1056964608\"]],[\"invoke\",233,0,\"eq\",[\"f32:3234402267\",\"f32:3234402267\"]],[\"invoke\",238,0,\"eq\",[\"f32:3234402267\",\"f32:2139095039\"]],[\"invoke\",242,0,\"eq\",[\"f32:3234402267\",\"f32:2139095040\"]],[\"invoke\",249,0,\"eq\",[\"f32:1086918619\",\"f32:4290772992\"]],[\"invoke\",256,0,\"eq\",[\"f32:2139095039\",\"f32:0\"]],[\"invoke\",261,0,\"eq\",[\"f32:4286578687\",\"f32:2155872256\"]],[\"invoke\",266,0,\"eq\",[\"f32:4286578687\",\"f32:1056964608\"]],[\"invoke\",271,0,\"eq\",[\"f32:2139095039\",\"f32:3212836864\"]],[\"invoke\",276,0,\"eq\",[\"f32:2139095039\",\"f32:1086918619\"]],[\"invoke\",281,0,\"eq\",[\"f32:4286578687\",\"f32:4286578688\"]],[\"invoke\",285,0,\"eq\",[\"f32:4286578687\",\"f32:4290772992\"]],[\"invoke\",294,0,\"eq\",[\"f32:4286578688\",\"f32:0\"]],[\"invoke\",299,0,\"eq\",[\"f32:2139095040\",\"f32:2147483649\"]],[\"invoke\",304,0,\"eq\",[\"f32:2139095040\",\"f32:8388608\"]],[\"invoke\",309,0,\"eq\",[\"f32:4286578688\",\"f32:3212836864\"]],[\"invoke\",314,0,\"eq\",[\"f32:4286578688\",\"f32:1086918619\"]],[\"invoke\",319,0,\"eq\",[\"f32:2139095040\",\"f32:4286578687\"]],[\"invoke\",323,0,\"eq\",[\"f32:2139095040\",\"f32:4286578688\"]],[\"invoke\",331,0,\"eq\",[\"f32:2139095040\",\"f32:2143289344\"]],[\"invoke\",341,0,\"eq\",[\"f32:4290772992\",\"f32:2147483649\"]],[\"invoke\",351,0,\"eq\",[\"f32:4290772992\",\"f32:8388608\"]],[\"invoke\",361,0,\"eq\",[\"f32:2143289344\",\"f32:3204448256\"]],[\"invoke\",371,0,\"eq\",[\"f32:2143289344\",\"f32:1065353216\"]],[\"invoke\",381,0,\"eq\",[\"f32:4290772992\",\"f32:4286578687\"]],[\"invoke\",389,0,\"eq\",[\"f32:4290772992\",\"f32:4286578688\"]],[\"invoke\",401,0,\"eq\",[\"f32:4290772992\",\"f32:2143289344\"]],[\"invoke\",415,0,\"ne\",[\"f32:0\",\"f32:2147483648\"]],[\"invoke\",420,0,\"ne\",[\"f32:0\",\"f32:1\"]],[\"invoke\",425,0,\"ne\",[\"f32:2147483648\",\"f32:3204448256\"]],[\"invoke\",430,0,\"ne\",[\"f32:2147483648\",\"f32:1065353216\"]],[\"invoke\",435,0,\"ne\",[\"f32:0\",\"f32:3234402267\"]],[\"invoke\",439,0,\"ne\",[\"f32:0\",\"f32:4286578687\"]],[\"invoke\",444,0,\"ne\",[\"f32:0\",\"f32:2139095040\"]],[\"invoke\",453,0,\"ne\",[\"f32:2147483649\",\"f32:2147483648\"]],[\"invoke\",458,0,\"ne\",[\"f32:2147483649\",\"f32:1\"]],[\"invoke\",463,0,\"ne\",[\"f32:1\",\"f32:2155872256\"]],[\"invoke\",468,0,\"ne\",[\"f32:1\",\"f32:1056964608\"]],[\"invoke\",473,0,\"ne\",[\"f32:2147483649\",\"f32:3234402267\"]],[\"invoke\",477,0,\"ne\",[\"f32:2147483649\",\"f32:4286578687\"]],[\"invoke\",482,0,\"ne\",[\"f32:2147483649\",\"f32:2139095040\"]],[\"invoke\",489,0,\"ne\",[\"f32:1\",\"f32:4290772992\"]],[\"invoke\",496,0,\"ne\",[\"f32:8388608\",\"f32:0\"]],[\"invoke\",501,0,\"ne\",[\"f32:2155872256\",\"f32:2155872256\"]],[\"invoke\",506,0,\"ne\",[\"f32:2155872256\",\"f32:1056964608\"]],[\"invoke\",511,0,\"ne\",[\"f32:8388608\",\"f32:3212836864\"]],[\"invoke\",515,0,\"ne\",[\"f32:8388608\",\"f32:3234402267\"]],[\"invoke\",520,0,\"ne\",[\"f32:8388608\",\"f32:2139095039\"]],[\"invoke\",525,0,\"ne\",[\"f32:2155872256\",\"f32:4290772992\"]],[\"invoke\",534,0,\"ne\",[\"f32:3204448256\",\"f32:0\"]],[\"invoke\",539,0,\"ne\",[\"f32:1056964608\",\"f32:2147483649\"]],[\"invoke\",544,0,\"ne\",[\"f32:1056964608\",\"f32:8388608\"]],[\"invoke\",549,0,\"ne\",[\"f32:3204448256\",\"f32:3212836864\"]],[\"invoke\",553,0,\"ne\",[\"f32:3204448256\",\"f32:3234402267\"]],[\"invoke\",558,0,\"ne\",[\"f32:3204448256\",\"f32:2139095039\"]],[\"invoke\",563,0,\"ne\",[\"f32:1056964608\",\"f32:4286578688\"]],[\"invoke\",571,0,\"ne\",[\"f32:1056964608\",\"f32:2143289344\"]],[\"invoke\",577,0,\"ne\",[\"f32:3212836864\",\"f32:2147483649\"]],[\"invoke\",582,0,\"ne\",[\"f32:3212836864\",\"f32:8388608\"]],[\"invoke\",587,0,\"ne\",[\"f32:1065353216\",\"f32:3204448256\"]],[\"invoke\",592,0,\"ne\",[\"f32:1065353216\",\"f32:1065353216\"]],[\"invoke\",596,0,\"ne\",[\"f32:1065353216\",\"f32:1086918619\"]],[\"invoke\",601,0,\"ne\",[\"f32:3212836864\",\"f32:4286578688\"]],[\"invoke\",607,0,\"ne\",[\"f32:3212836864\",\"f32:2143289344\"]],[\"invoke\",615,0,\"ne\",[\"f32:1086918619\",\"f32:2147483648\"]],[\"invoke\",620,0,\"ne\",[\"f32:1086918619\",\"f32:1\"]],[\"invoke\",625,0,\"ne\",[\"f32:3234402267\",\"f32:3204448256\"]],[\"invoke\",630,0,\"ne\",[\"f32:3234402267\",\"f32:1065353216\"]],[\"invoke\",634,0,\"ne\",[\"f32:3234402267\",\"f32:1086918619\"]],[\"invoke\",639,0,\"ne\",[\"f32:1086918619\",\"f32:4286578687\"]],[\"invoke\",644,0,\"ne\",[\"f32:1086918619\",\"f32:2139095040\"]],[\"invoke\",653,0,\"ne\",[\"f32:4286578687\",\"f32:2147483648\"]],[\"invoke\",658,0,\"ne\",[\"f32:4286578687\",\"f32:1\"]],[\"invoke\",663,0,\"ne\",[\"f32:2139095039\",\"f32:2155872256\"]],[\"invoke\",668,0,\"ne\",[\"f32:2139095039\",\"f32:1056964608\"]],[\"invoke\",672,0,\"ne\",[\"f32:2139095039\",\"f32:1065353216\"]],[\"invoke\",677,0,\"ne\",[\"f32:4286578687\",\"f32:4286578687\"]],[\"invoke\",682,0,\"ne\",[\"f32:4286578687\",\"f32:2139095040\"]],[\"invoke\",689,0,\"ne\",[\"f32:2139095039\",\"f32:4290772992\"]],[\"invoke\",696,0,\"ne\",[\"f32:2139095040\",\"f32:0\"]],[\"invoke\",701,0,\"ne\",[\"f32:4286578688\",\"f32:2155872256\"]],[\"invoke\",706,0,\"ne\",[\"f32:4286578688\",\"f32:1056964608\"]],[\"invoke\",710,0,\"ne\",[\"f32:4286578688\",\"f32:1065353216\"]],[\"invoke\",715,0,\"ne\",[\"f32:2139095040\",\"f32:3234402267\"]],[\"invoke\",720,0,\"ne\",[\"f32:2139095040\",\"f32:2139095039\"]],[\"invoke\",725,0,\"ne\",[\"f32:4286578688\",\"f32:4290772992\"]],[\"invoke\",735,0,\"ne\",[\"f32:4290772992\",\"f32:0\"]],[\"invoke\",745,0,\"ne\",[\"f32:2143289344\",\"f32:2147483649\"]],[\"invoke\",755,0,\"ne\",[\"f32:2143289344\",\"f32:8388608\"]],[\"invoke\",763,0,\"ne\",[\"f32:2143289344\",\"f32:1056964608\"]],[\"invoke\",773,0,\"ne\",[\"f32:4290772992\",\"f32:3234402267\"]],[\"invoke\",783,0,\"ne\",[\"f32:4290772992\",\"f32:2139095039\"]],[\"invoke\",793,0,\"ne\",[\"f32:2143289344\",\"f32:4286578688\"]],[\"invoke\",809,0,\"ne\",[\"f32:2143289344\",\"f32:2143289344\"]],[\"invoke\",817,0,\"lt\",[\"f32:2147483648\",\"f32:2147483649\"]],[\"invoke\",822,0,\"lt\",[\"f32:2147483648\",\"f32:8388608\"]],[\"invoke\",826,0,\"lt\",[\"f32:2147483648\",\"f32:1056964608\"]],[\"invoke\",831,0,\"lt\",[\"f32:0\",\"f32:3212836864\"]],[\"invoke\",836,0,\"lt\",[\"f32:0\",\"f32:1086918619\"]],[\"invoke\",841,0,\"lt\",[\"f32:2147483648\",\"f32:4286578688\"]],[\"invoke\",847,0,\"lt\",[\"f32:2147483648\",\"f32:2143289344\"]],[\"invoke\",855,0,\"lt\",[\"f32:1\",\"f32:2147483648\"]],[\"invoke\",860,0,\"lt\",[\"f32:1\",\"f32:1\"]],[\"invoke\",864,0,\"lt\",[\"f32:1\",\"f32:8388608\"]],[\"invoke\",869,0,\"lt\",[\"f32:2147483649\",\"f32:3212836864\"]],[\"invoke\",874,0,\"lt\",[\"f32:2147483649\",\"f32:1086918619\"]],[\"invoke\",879,0,\"lt\",[\"f32:1\",\"f32:4286578687\"]],[\"invoke\",884,0,\"lt\",[\"f32:1\",\"f32:2139095040\"]],[\"invoke\",893,0,\"lt\",[\"f32:2155872256\",\"f32:2147483648\"]],[\"invoke\",898,0,\"lt\",[\"f32:2155872256\",\"f32:1\"]],[\"invoke\",903,0,\"lt\",[\"f32:8388608\",\"f32:2155872256\"]],[\"invoke\",907,0,\"lt\",[\"f32:8388608\",\"f32:3204448256\"]],[\"invoke\",912,0,\"lt\",[\"f32:8388608\",\"f32:1065353216\"]],[\"invoke\",917,0,\"lt\",[\"f32:2155872256\",\"f32:4286578687\"]],[\"invoke\",922,0,\"lt\",[\"f32:2155872256\",\"f32:2139095040\"]],[\"invoke\",929,0,\"lt\",[\"f32:8388608\",\"f32:4290772992\"]],[\"invoke\",936,0,\"lt\",[\"f32:1056964608\",\"f32:0\"]],[\"invoke\",941,0,\"lt\",[\"f32:3204448256\",\"f32:2155872256\"]],[\"invoke\",945,0,\"lt\",[\"f32:3204448256\",\"f32:3204448256\"]],[\"invoke\",950,0,\"lt\",[\"f32:3204448256\",\"f32:1065353216\"]],[\"invoke\",955,0,\"lt\",[\"f32:1056964608\",\"f32:3234402267\"]],[\"invoke\",960,0,\"lt\",[\"f32:1056964608\",\"f32:2139095039\"]],[\"invoke\",965,0,\"lt\",[\"f32:3204448256\",\"f32:4290772992\"]],[\"invoke\",974,0,\"lt\",[\"f32:3212836864\",\"f32:0\"]],[\"invoke\",979,0,\"lt\",[\"f32:1065353216\",\"f32:2147483649\"]],[\"invoke\",983,0,\"lt\",[\"f32:1065353216\",\"f32:2155872256\"]],[\"invoke\",988,0,\"lt\",[\"f32:1065353216\",\"f32:1056964608\"]],[\"invoke\",993,0,\"lt\",[\"f32:3212836864\",\"f32:3234402267\"]],[\"invoke\",998,0,\"lt\",[\"f32:3212836864\",\"f32:2139095039\"]],[\"invoke\",1003,0,\"lt\",[\"f32:1065353216\",\"f32:4286578688\"]],[\"invoke\",1011,0,\"lt\",[\"f32:1065353216\",\"f32:2143289344\"]],[\"invoke\",1017,0,\"lt\",[\"f32:3234402267\",\"f32:2147483649\"]],[\"invoke\",1021,0,\"lt\",[\"f32:3234402267\",\"f32:2155872256\"]],[\"invoke\",1026,0,\"lt\",[\"f32:3234402267\",\"f32:1056964608\"]],[\"invoke\",1031,0,\"lt\",[\"f32:1086918619\",\"f32:3212836864\"]],[\"invoke\",1036,0,\"lt\",[\"f32:1086918619\",\"f32:1086918619\"]],[\"invoke\",1041,0,\"lt\",[\"f32:3234402267\",\"f32:4286578688\"]],[\"invoke\",1047,0,\"lt\",[\"f32:3234402267\",\"f32:2143289344\"]],[\"invoke\",1055,0,\"lt\",[\"f32:2139095039\",\"f32:2147483648\"]],[\"invoke\",1059,0,\"lt\",[\"f32:2139095039\",\"f32:2147483649\"]],[\"invoke\",1064,0,\"lt\",[\"f32:2139095039\",\"f32:8388608\"]],[\"invoke\",1069,0,\"lt\",[\"f32:4286578687\",\"f32:3212836864\"]],[\"invoke\",1074,0,\"lt\",[\"f32:4286578687\",\"f32:1086918619\"]],[\"invoke\",1079,0,\"lt\",[\"f32:2139095039\",\"f32:4286578687\"]],[\"invoke\",1084,0,\"lt\",[\"f32:2139095039\",\"f32:2139095040\"]],[\"invoke\",1093,0,\"lt\",[\"f32:4286578688\",\"f32:2147483648\"]],[\"invoke\",1097,0,\"lt\",[\"f32:4286578688\",\"f32:2147483649\"]],[\"invoke\",1102,0,\"lt\",[\"f32:4286578688\",\"f32:8388608\"]],[\"invoke\",1107,0,\"lt\",[\"f32:2139095040\",\"f32:3204448256\"]],[\"invoke\",1112,0,\"lt\",[\"f32:2139095040\",\"f32:1065353216\"]],[\"invoke\",1117,0,\"lt\",[\"f32:4286578688\",\"f32:4286578687\"]],[\"invoke\",1122,0,\"lt\",[\"f32:4286578688\",\"f32:2139095040\"]],[\"invoke\",1129,0,\"lt\",[\"f32:2139095040\",\"f32:4290772992\"]],[\"invoke\",1137,0,\"lt\",[\"f32:2143289344\",\"f32:2147483648\"]],[\"invoke\",1147,0,\"lt\",[\"f32:2143289344\",\"f32:1\"]],[\"invoke\",1157,0,\"lt\",[\"f32:4290772992\",\"f32:3204448256\"]],[\"invoke\",1167,0,\"lt\",[\"f32:4290772992\",\"f32:1065353216\"]],[\"invoke\",1177,0,\"lt\",[\"f32:2143289344\",\"f32:3234402267\"]],[\"invoke\",1187,0,\"lt\",[\"f32:2143289344\",\"f32:2139095039\"]],[\"invoke\",1197,0,\"lt\",[\"f32:4290772992\",\"f32:4290772992\"]],[\"invoke\",1214,0,\"le\",[\"f32:2147483648\",\"f32:0\"]],[\"invoke\",1218,0,\"le\",[\"f32:2147483648\",\"f32:1\"]],[\"invoke\",1223,0,\"le\",[\"f32:0\",\"f32:2155872256\"]],[\"invoke\",1228,0,\"le\",[\"f32:0\",\"f32:1056964608\"]],[\"invoke\",1233,0,\"le\",[\"f32:2147483648\",\"f32:3234402267\"]],[\"invoke\",1238,0,\"le\",[\"f32:2147483648\",\"f32:2139095039\"]],[\"invoke\",1243,0,\"le\",[\"f32:0\",\"f32:4286578688\"]],[\"invoke\",1251,0,\"le\",[\"f32:0\",\"f32:2143289344\"]],[\"invoke\",1256,0,\"le\",[\"f32:1\",\"f32:0\"]],[\"invoke\",1261,0,\"le\",[\"f32:2147483649\",\"f32:2155872256\"]],[\"invoke\",1266,0,\"le\",[\"f32:2147483649\",\"f32:1056964608\"]],[\"invoke\",1271,0,\"le\",[\"f32:1\",\"f32:3212836864\"]],[\"invoke\",1276,0,\"le\",[\"f32:1\",\"f32:1086918619\"]],[\"invoke\",1281,0,\"le\",[\"f32:2147483649\",\"f32:4286578688\"]],[\"invoke\",1287,0,\"le\",[\"f32:2147483649\",\"f32:2143289344\"]],[\"invoke\",1294,0,\"le\",[\"f32:2155872256\",\"f32:0\"]],[\"invoke\",1299,0,\"le\",[\"f32:8388608\",\"f32:2147483649\"]],[\"invoke\",1304,0,\"le\",[\"f32:8388608\",\"f32:8388608\"]],[\"invoke\",1309,0,\"le\",[\"f32:2155872256\",\"f32:3212836864\"]],[\"invoke\",1314,0,\"le\",[\"f32:2155872256\",\"f32:1086918619\"]],[\"invoke\",1319,0,\"le\",[\"f32:8388608\",\"f32:4286578687\"]],[\"invoke\",1324,0,\"le\",[\"f32:8388608\",\"f32:2139095040\"]],[\"invoke\",1331,0,\"le\",[\"f32:8388608\",\"f32:2143289344\"]],[\"invoke\",1337,0,\"le\",[\"f32:3204448256\",\"f32:2147483649\"]],[\"invoke\",1342,0,\"le\",[\"f32:3204448256\",\"f32:8388608\"]],[\"invoke\",1347,0,\"le\",[\"f32:1056964608\",\"f32:3204448256\"]],[\"invoke\",1352,0,\"le\",[\"f32:1056964608\",\"f32:1065353216\"]],[\"invoke\",1357,0,\"le\",[\"f32:3204448256\",\"f32:4286578687\"]],[\"invoke\",1362,0,\"le\",[\"f32:3204448256\",\"f32:2139095040\"]],[\"invoke\",1367,0,\"le\",[\"f32:3204448256\",\"f32:2143289344\"]],[\"invoke\",1375,0,\"le\",[\"f32:1065353216\",\"f32:2147483648\"]],[\"invoke\",1380,0,\"le\",[\"f32:1065353216\",\"f32:1\"]],[\"invoke\",1385,0,\"le\",[\"f32:3212836864\",\"f32:3204448256\"]],[\"invoke\",1390,0,\"le\",[\"f32:3212836864\",\"f32:1065353216\"]],[\"invoke\",1395,0,\"le\",[\"f32:1065353216\",\"f32:3234402267\"]],[\"invoke\",1400,0,\"le\",[\"f32:1065353216\",\"f32:2139095039\"]],[\"invoke\",1404,0,\"le\",[\"f32:1065353216\",\"f32:2139095040\"]],[\"invoke\",1413,0,\"le\",[\"f32:3234402267\",\"f32:2147483648\"]],[\"invoke\",1418,0,\"le\",[\"f32:3234402267\",\"f32:1\"]],[\"invoke\",1423,0,\"le\",[\"f32:1086918619\",\"f32:2155872256\"]],[\"invoke\",1428,0,\"le\",[\"f32:1086918619\",\"f32:1056964608\"]],[\"invoke\",1433,0,\"le\",[\"f32:3234402267\",\"f32:3234402267\"]],[\"invoke\",1438,0,\"le\",[\"f32:3234402267\",\"f32:2139095039\"]],[\"invoke\",1442,0,\"le\",[\"f32:3234402267\",\"f32:2139095040\"]],[\"invoke\",1449,0,\"le\",[\"f32:1086918619\",\"f32:4290772992\"]],[\"invoke\",1456,0,\"le\",[\"f32:2139095039\",\"f32:0\"]],[\"invoke\",1461,0,\"le\",[\"f32:4286578687\",\"f32:2155872256\"]],[\"invoke\",1466,0,\"le\",[\"f32:4286578687\",\"f32:1056964608\"]],[\"invoke\",1471,0,\"le\",[\"f32:2139095039\",\"f32:3212836864\"]],[\"invoke\",1476,0,\"le\",[\"f32:2139095039\",\"f32:1086918619\"]],[\"invoke\",1480,0,\"le\",[\"f32:2139095039\",\"f32:2139095039\"]],[\"invoke\",1485,0,\"le\",[\"f32:4286578687\",\"f32:4290772992\"]],[\"invoke\",1494,0,\"le\",[\"f32:4286578688\",\"f32:0\"]],[\"invoke\",1499,0,\"le\",[\"f32:2139095040\",\"f32:2147483649\"]],[\"invoke\",1504,0,\"le\",[\"f32:2139095040\",\"f32:8388608\"]],[\"invoke\",1509,0,\"le\",[\"f32:4286578688\",\"f32:3212836864\"]],[\"invoke\",1514,0,\"le\",[\"f32:4286578688\",\"f32:1086918619\"]],[\"invoke\",1519,0,\"le\",[\"f32:2139095040\",\"f32:4286578687\"]],[\"invoke\",1523,0,\"le\",[\"f32:2139095040\",\"f32:4286578688\"]],[\"invoke\",1531,0,\"le\",[\"f32:2139095040\",\"f32:2143289344\"]],[\"invoke\",1541,0,\"le\",[\"f32:4290772992\",\"f32:2147483649\"]],[\"invoke\",1551,0,\"le\",[\"f32:4290772992\",\"f32:8388608\"]],[\"invoke\",1561,0,\"le\",[\"f32:2143289344\",\"f32:3204448256\"]],[\"invoke\",1571,0,\"le\",[\"f32:2143289344\",\"f32:1065353216\"]],[\"invoke\",1581,0,\"le\",[\"f32:4290772992\",\"f32:4286578687\"]],[\"invoke\",1589,0,\"le\",[\"f32:4290772992\",\"f32:4286578688\"]],[\"invoke\",1601,0,\"le\",[\"f32:4290772992\",\"f32:2143289344\"]],[\"invoke\",1615,0,\"gt\",[\"f32:0\",\"f32:2147483648\"]],[\"invoke\",1620,0,\"gt\",[\"f32:0\",\"f32:1\"]],[\"invoke\",1625,0,\"gt\",[\"f32:2147483648\",\"f32:3204448256\"]],[\"invoke\",1630,0,\"gt\",[\"f32:2147483648\",\"f32:1065353216\"]],[\"invoke\",1635,0,\"gt\",[\"f32:0\",\"f32:3234402267\"]],[\"invoke\",1639,0,\"gt\",[\"f32:0\",\"f32:4286578687\"]],[\"invoke\",1644,0,\"gt\",[\"f32:0\",\"f32:2139095040\"]],[\"invoke\",1653,0,\"gt\",[\"f32:2147483649\",\"f32:2147483648\"]],[\"invoke\",1658,0,\"gt\",[\"f32:2147483649\",\"f32:1\"]],[\"invoke\",1663,0,\"gt\",[\"f32:1\",\"f32:2155872256\"]],[\"invoke\",1668,0,\"gt\",[\"f32:1\",\"f32:1056964608\"]],[\"invoke\",1673,0,\"gt\",[\"f32:2147483649\",\"f32:3234402267\"]],[\"invoke\",1677,0,\"gt\",[\"f32:2147483649\",\"f32:4286578687\"]],[\"invoke\",1682,0,\"gt\",[\"f32:2147483649\",\"f32:2139095040\"]],[\"invoke\",1689,0,\"gt\",[\"f32:1\",\"f32:4290772992\"]],[\"invoke\",1696,0,\"gt\",[\"f32:8388608\",\"f32:0\"]],[\"invoke\",1701,0,\"gt\",[\"f32:2155872256\",\"f32:2155872256\"]],[\"invoke\",1706,0,\"gt\",[\"f32:2155872256\",\"f32:1056964608\"]],[\"invoke\",1711,0,\"gt\",[\"f32:8388608\",\"f32:3212836864\"]],[\"invoke\",1715,0,\"gt\",[\"f32:8388608\",\"f32:3234402267\"]],[\"invoke\",1720,0,\"gt\",[\"f32:8388608\",\"f32:2139095039\"]],[\"invoke\",1725,0,\"gt\",[\"f32:2155872256\",\"f32:4290772992\"]],[\"invoke\",1734,0,\"gt\",[\"f32:3204448256\",\"f32:0\"]],[\"invoke\",1739,0,\"gt\",[\"f32:1056964608\",\"f32:2147483649\"]],[\"invoke\",1744,0,\"gt\",[\"f32:1056964608\",\"f32:8388608\"]],[\"invoke\",1749,0,\"gt\",[\"f32:3204448256\",\"f32:3212836864\"]],[\"invoke\",1753,0,\"gt\",[\"f32:3204448256\",\"f32:3234402267\"]],[\"invoke\",1758,0,\"gt\",[\"f32:3204448256\",\"f32:2139095039\"]],[\"invoke\",1763,0,\"gt\",[\"f32:1056964608\",\"f32:4286578688\"]],[\"invoke\",1771,0,\"gt\",[\"f32:1056964608\",\"f32:2143289344\"]],[\"invoke\",1777,0,\"gt\",[\"f32:3212836864\",\"f32:2147483649\"]],[\"invoke\",1782,0,\"gt\",[\"f32:3212836864\",\"f32:8388608\"]],[\"invoke\",1787,0,\"gt\",[\"f32:1065353216\",\"f32:3204448256\"]],[\"invoke\",1791,0,\"gt\",[\"f32:1065353216\",\"f32:3212836864\"]],[\"invoke\",1796,0,\"gt\",[\"f32:1065353216\",\"f32:1086918619\"]],[\"invoke\",1801,0,\"gt\",[\"f32:3212836864\",\"f32:4286578688\"]],[\"invoke\",1807,0,\"gt\",[\"f32:3212836864\",\"f32:2143289344\"]],[\"invoke\",1815,0,\"gt\",[\"f32:1086918619\",\"f32:2147483648\"]],[\"invoke\",1820,0,\"gt\",[\"f32:1086918619\",\"f32:1\"]],[\"invoke\",1825,0,\"gt\",[\"f32:3234402267\",\"f32:3204448256\"]],[\"invoke\",1830,0,\"gt\",[\"f32:3234402267\",\"f32:1065353216\"]],[\"invoke\",1834,0,\"gt\",[\"f32:3234402267\",\"f32:1086918619\"]],[\"invoke\",1839,0,\"gt\",[\"f32:1086918619\",\"f32:4286578687\"]],[\"invoke\",1844,0,\"gt\",[\"f32:1086918619\",\"f32:2139095040\"]],[\"invoke\",1853,0,\"gt\",[\"f32:4286578687\",\"f32:2147483648\"]],[\"invoke\",1858,0,\"gt\",[\"f32:4286578687\",\"f32:1\"]],[\"invoke\",1863,0,\"gt\",[\"f32:2139095039\",\"f32:2155872256\"]],[\"invoke\",1868,0,\"gt\",[\"f32:2139095039\",\"f32:1056964608\"]],[\"invoke\",1872,0,\"gt\",[\"f32:2139095039\",\"f32:1065353216\"]],[\"invoke\",1877,0,\"gt\",[\"f32:4286578687\",\"f32:4286578687\"]],[\"invoke\",1882,0,\"gt\",[\"f32:4286578687\",\"f32:2139095040\"]],[\"invoke\",1889,0,\"gt\",[\"f32:2139095039\",\"f32:4290772992\"]],[\"invoke\",1896,0,\"gt\",[\"f32:2139095040\",\"f32:0\"]],[\"invoke\",1901,0,\"gt\",[\"f32:4286578688\",\"f32:2155872256\"]],[\"invoke\",1906,0,\"gt\",[\"f32:4286578688\",\"f32:1056964608\"]],[\"invoke\",1910,0,\"gt\",[\"f32:4286578688\",\"f32:1065353216\"]],[\"invoke\",1915,0,\"gt\",[\"f32:2139095040\",\"f32:3234402267\"]],[\"invoke\",1920,0,\"gt\",[\"f32:2139095040\",\"f32:2139095039\"]],[\"invoke\",1925,0,\"gt\",[\"f32:4286578688\",\"f32:4290772992\"]],[\"invoke\",1935,0,\"gt\",[\"f32:4290772992\",\"f32:0\"]],[\"invoke\",1945,0,\"gt\",[\"f32:2143289344\",\"f32:2147483649\"]],[\"invoke\",1955,0,\"gt\",[\"f32:2143289344\",\"f32:8388608\"]],[\"invoke\",1963,0,\"gt\",[\"f32:2143289344\",\"f32:1056964608\"]],[\"invoke\",1973,0,\"gt\",[\"f32:4290772992\",\"f32:3234402267\"]],[\"invoke\",1983,0,\"gt\",[\"f32:4290772992\",\"f32:2139095039\"]],[\"invoke\",1993,0,\"gt\",[\"f32:2143289344\",\"f32:4286578688\"]],[\"invoke\",2009,0,\"gt\",[\"f32:2143289344\",\"f32:2143289344\"]],[\"invoke\",2017,0,\"ge\",[\"f32:2147483648\",\"f32:2147483649\"]],[\"invoke\",2022,0,\"ge\",[\"f32:2147483648\",\"f32:8388608\"]],[\"invoke\",2026,0,\"ge\",[\"f32:2147483648\",\"f32:1056964608\"]],[\"invoke\",2031,0,\"ge\",[\"f32:0\",\"f32:3212836864\"]],[\"invoke\",2036,0,\"ge\",[\"f32:0\",\"f32:1086918619\"]],[\"invoke\",2041,0,\"ge\",[\"f32:2147483648\",\"f32:4286578688\"]],[\"invoke\",2047,0,\"ge\",[\"f32:2147483648\",\"f32:2143289344\"]],[\"invoke\",2055,0,\"ge\",[\"f32:1\",\"f32:2147483648\"]],[\"invoke\",2060,0,\"ge\",[\"f32:1\",\"f32:1\"]],[\"invoke\",2064,0,\"ge\",[\"f32:1\",\"f32:8388608\"]],[\"invoke\",2069,0,\"ge\",[\"f32:2147483649\",\"f32:3212836864\"]],[\"invoke\",2074,0,\"ge\",[\"f32:2147483649\",\"f32:1086918619\"]],[\"invoke\",2079,0,\"ge\",[\"f32:1\",\"f32:4286578687\"]],[\"invoke\",2084,0,\"ge\",[\"f32:1\",\"f32:2139095040\"]],[\"invoke\",2093,0,\"ge\",[\"f32:2155872256\",\"f32:2147483648\"]],[\"invoke\",2098,0,\"ge\",[\"f32:2155872256\",\"f32:1\"]],[\"invoke\",2102,0,\"ge\",[\"f32:2155872256\",\"f32:8388608\"]],[\"invoke\",2107,0,\"ge\",[\"f32:8388608\",\"f32:3204448256\"]],[\"invoke\",2112,0,\"ge\",[\"f32:8388608\",\"f32:1065353216\"]],[\"invoke\",2117,0,\"ge\",[\"f32:2155872256\",\"f32:4286578687\"]],[\"invoke\",2122,0,\"ge\",[\"f32:2155872256\",\"f32:2139095040\"]],[\"invoke\",2129,0,\"ge\",[\"f32:8388608\",\"f32:4290772992\"]],[\"invoke\",2136,0,\"ge\",[\"f32:1056964608\",\"f32:0\"]],[\"invoke\",2141,0,\"ge\",[\"f32:3204448256\",\"f32:2155872256\"]],[\"invoke\",2145,0,\"ge\",[\"f32:3204448256\",\"f32:3204448256\"]],[\"invoke\",2150,0,\"ge\",[\"f32:3204448256\",\"f32:1065353216\"]],[\"invoke\",2155,0,\"ge\",[\"f32:1056964608\",\"f32:3234402267\"]],[\"invoke\",2160,0,\"ge\",[\"f32:1056964608\",\"f32:2139095039\"]],[\"invoke\",2165,0,\"ge\",[\"f32:3204448256\",\"f32:4290772992\"]],[\"invoke\",2174,0,\"ge\",[\"f32:3212836864\",\"f32:0\"]],[\"invoke\",2179,0,\"ge\",[\"f32:1065353216\",\"f32:2147483649\"]],[\"invoke\",2183,0,\"ge\",[\"f32:1065353216\",\"f32:2155872256\"]],[\"invoke\",2188,0,\"ge\",[\"f32:1065353216\",\"f32:1056964608\"]],[\"invoke\",2193,0,\"ge\",[\"f32:3212836864\",\"f32:3234402267\"]],[\"invoke\",2198,0,\"ge\",[\"f32:3212836864\",\"f32:2139095039\"]],[\"invoke\",2203,0,\"ge\",[\"f32:1065353216\",\"f32:4286578688\"]],[\"invoke\",2211,0,\"ge\",[\"f32:1065353216\",\"f32:2143289344\"]],[\"invoke\",2217,0,\"ge\",[\"f32:3234402267\",\"f32:2147483649\"]],[\"invoke\",2221,0,\"ge\",[\"f32:3234402267\",\"f32:2155872256\"]],[\"invoke\",2226,0,\"ge\",[\"f32:3234402267\",\"f32:1056964608\"]],[\"invoke\",2231,0,\"ge\",[\"f32:1086918619\",\"f32:3212836864\"]],[\"invoke\",2236,0,\"ge\",[\"f32:1086918619\",\"f32:1086918619\"]],[\"invoke\",2241,0,\"ge\",[\"f32:3234402267\",\"f32:4286578688\"]],[\"invoke\",2247,0,\"ge\",[\"f32:3234402267\",\"f32:2143289344\"]],[\"invoke\",2255,0,\"ge\",[\"f32:2139095039\",\"f32:2147483648\"]],[\"invoke\",2259,0,\"ge\",[\"f32:2139095039\",\"f32:2147483649\"]],[\"invoke\",2264,0,\"ge\",[\"f32:2139095039\",\"f32:8388608\"]],[\"invoke\",2269,0,\"ge\",[\"f32:4286578687\",\"f32:3212836864\"]],[\"invoke\",2274,0,\"ge\",[\"f32:4286578687\",\"f32:1086918619\"]],[\"invoke\",2279,0,\"ge\",[\"f32:2139095039\",\"f32:4286578687\"]],[\"invoke\",2284,0,\"ge\",[\"f32:2139095039\",\"f32:2139095040\"]],[\"invoke\",2293,0,\"ge\",[\"f32:4286578688\",\"f32:2147483648\"]],[\"invoke\",2297,0,\"ge\",[\"f32:4286578688\",\"f32:2147483649\"]],[\"invoke\",2302,0,\"ge\",[\"f32:4286578688\",\"f32:8388608\"]],[\"invoke\",2307,0,\"ge\",[\"f32:2139095040\",\"f32:3204448256\"]],[\"invoke\",2312,0,\"ge\",[\"f32:2139095040\",\"f32:1065353216\"]],[\"invoke\",2317,0,\"ge\",[\"f32:4286578688\",\"f32:4286578687\"]],[\"invoke\",2322,0,\"ge\",[\"f32:4286578688\",\"f32:2139095040\"]],[\"invoke\",2329,0,\"ge\",[\"f32:2139095040\",\"f32:4290772992\"]],[\"invoke\",2337,0,\"ge\",[\"f32:2143289344\",\"f32:2147483648\"]],[\"invoke\",2347,0,\"ge\",[\"f32:2143289344\",\"f32:1\"]],[\"invoke\",2357,0,\"ge\",[\"f32:4290772992\",\"f32:3204448256\"]],[\"invoke\",2367,0,\"ge\",[\"f32:4290772992\",\"f32:1065353216\"]],[\"invoke\",2377,0,\"ge\",[\"f32:2143289344\",\"f32:3234402267\"]],[\"invoke\",2387,0,\"ge\",[\"f32:2143289344\",\"f32:2139095039\"]],[\"invoke\",2397,0,\"ge\",[\"f32:4290772992\",\"f32:4290772992\"]],[\"file\",\"f64_cmp.wast\"],[\"module\",4,3],[\"invoke\",13,0,\"eq\",[\"f64:9223372036854775808\",\"f64:9223372036854775808\"]],[\"invoke\",18,0,\"eq\",[\"f64:9223372036854775808\",\"f64:1\"]],[\"invoke\",23,0,\"eq\",[\"f64:0\",\"f64:9227875636482146304\"]],[\"invoke\",28,0,\"eq\",[\"f64:0\",\"f64:4602678819172646912\"]],[\"invoke\",33,0,\"eq\",[\"f64:9223372036854775808\",\"f64:13842132293034192152\"]],[\"invoke\",38,0,\"eq\",[\"f64:9223372036854775808\",\"f64:9218868437227405311\"]],[\"invoke\",43,0,\"eq\",[\"f64:0\",\"f64:18442240474082181120\"]],[\"invoke\",51,0,\"eq\",[\"f64:0\",\"f64:9221120237041090560\"]],[\"invoke\",56,0,\"eq\",[\"f64:1\",\"f64:0\"]],[\"invoke\",61,0,\"eq\",[\"f64:9223372036854775809\",\"f64:9227875636482146304\"]],[\"invoke\",66,0,\"eq\",[\"f64:9223372036854775809\",\"f64:4602678819172646912\"]],[\"invoke\",71,0,\"eq\",[\"f64:1\",\"f64:13830554455654793216\"]],[\"invoke\",76,0,\"eq\",[\"f64:1\",\"f64:4618760256179416344\"]],[\"invoke\",81,0,\"eq\",[\"f64:9223372036854775809\",\"f64:18442240474082181120\"]],[\"invoke\",87,0,\"eq\",[\"f64:9223372036854775809\",\"f64:9221120237041090560\"]],[\"invoke\",94,0,\"eq\",[\"f64:9227875636482146304\",\"f64:0\"]],[\"invoke\",99,0,\"eq\",[\"f64:4503599627370496\",\"f64:9223372036854775809\"]],[\"invoke\",104,0,\"eq\",[\"f64:4503599627370496\",\"f64:4503599627370496\"]],[\"invoke\",109,0,\"eq\",[\"f64:9227875636482146304\",\"f64:13830554455654793216\"]],[\"invoke\",114,0,\"eq\",[\"f64:9227875636482146304\",\"f64:4618760256179416344\"]],[\"invoke\",119,0,\"eq\",[\"f64:4503599627370496\",\"f64:18442240474082181119\"]],[\"invoke\",124,0,\"eq\",[\"f64:4503599627370496\",\"f64:9218868437227405312\"]],[\"invoke\",131,0,\"eq\",[\"f64:4503599627370496\",\"f64:9221120237041090560\"]],[\"invoke\",137,0,\"eq\",[\"f64:13826050856027422720\",\"f64:9223372036854775809\"]],[\"invoke\",142,0,\"eq\",[\"f64:13826050856027422720\",\"f64:4503599627370496\"]],[\"invoke\",147,0,\"eq\",[\"f64:4602678819172646912\",\"f64:13826050856027422720\"]],[\"invoke\",152,0,\"eq\",[\"f64:4602678819172646912\",\"f64:4607182418800017408\"]],[\"invoke\",157,0,\"eq\",[\"f64:13826050856027422720\",\"f64:18442240474082181119\"]],[\"invoke\",162,0,\"eq\",[\"f64:13826050856027422720\",\"f64:9218868437227405312\"]],[\"invoke\",167,0,\"eq\",[\"f64:13826050856027422720\",\"f64:9221120237041090560\"]],[\"invoke\",175,0,\"eq\",[\"f64:4607182418800017408\",\"f64:9223372036854775808\"]],[\"invoke\",180,0,\"eq\",[\"f64:4607182418800017408\",\"f64:1\"]],[\"invoke\",185,0,\"eq\",[\"f64:13830554455654793216\",\"f64:13826050856027422720\"]],[\"invoke\",190,0,\"eq\",[\"f64:13830554455654793216\",\"f64:4607182418800017408\"]],[\"invoke\",195,0,\"eq\",[\"f64:4607182418800017408\",\"f64:13842132293034192152\"]],[\"invoke\",200,0,\"eq\",[\"f64:4607182418800017408\",\"f64:9218868437227405311\"]],[\"invoke\",204,0,\"eq\",[\"f64:4607182418800017408\",\"f64:9218868437227405312\"]],[\"invoke\",213,0,\"eq\",[\"f64:13842132293034192152\",\"f64:9223372036854775808\"]],[\"invoke\",218,0,\"eq\",[\"f64:13842132293034192152\",\"f64:1\"]],[\"invoke\",223,0,\"eq\",[\"f64:4618760256179416344\",\"f64:9227875636482146304\"]],[\"invoke\",228,0,\"eq\",[\"f64:4618760256179416344\",\"f64:4602678819172646912\"]],[\"invoke\",233,0,\"eq\",[\"f64:13842132293034192152\",\"f64:13842132293034192152\"]],[\"invoke\",238,0,\"eq\",[\"f64:13842132293034192152\",\"f64:9218868437227405311\"]],[\"invoke\",242,0,\"eq\",[\"f64:13842132293034192152\",\"f64:9218868437227405312\"]],[\"invoke\",249,0,\"eq\",[\"f64:4618760256179416344\",\"f64:18444492273895866368\"]],[\"invoke\",256,0,\"eq\",[\"f64:9218868437227405311\",\"f64:0\"]],[\"invoke\",261,0,\"eq\",[\"f64:18442240474082181119\",\"f64:9227875636482146304\"]],[\"invoke\",266,0,\"eq\",[\"f64:18442240474082181119\",\"f64:4602678819172646912\"]],[\"invoke\",271,0,\"eq\",[\"f64:9218868437227405311\",\"f64:13830554455654793216\"]],[\"invoke\",276,0,\"eq\",[\"f64:9218868437227405311\",\"f64:4618760256179416344\"]],[\"invoke\",281,0,\"eq\",[\"f64:18442240474082181119\",\"f64:18442240474082181120\"]],[\"invoke\",285,0,\"eq\",[\"f64:18442240474082181119\",\"f64:18444492273895866368\"]],[\"invoke\",294,0,\"eq\",[\"f64:18442240474082181120\",\"f64:0\"]],[\"invoke\",299,0,\"eq\",[\"f64:9218868437227405312\",\"f64:9223372036854775809\"]],[\"invoke\",304,0,\"eq\",[\"f64:9218868437227405312\",\"f64:4503599627370496\"]],[\"invoke\",309,0,\"eq\",[\"f64:18442240474082181120\",\"f64:13830554455654793216\"]],[\"invoke\",314,0,\"eq\",[\"f64:18442240474082181120\",\"f64:4618760256179416344\"]],[\"invoke\",319,0,\"eq\",[\"f64:9218868437227405312\",\"f64:18442240474082181119\"]],[\"invoke\",323,0,\"eq\",[\"f64:9218868437227405312\",\"f64:18442240474082181120\"]],[\"invoke\",331,0,\"eq\",[\"f64:9218868437227405312\",\"f64:9221120237041090560\"]],[\"invoke\",341,0,\"eq\",[\"f64:18444492273895866368\",\"f64:9223372036854775809\"]],[\"invoke\",351,0,\"eq\",[\"f64:18444492273895866368\",\"f64:4503599627370496\"]],[\"invoke\",361,0,\"eq\",[\"f64:9221120237041090560\",\"f64:13826050856027422720\"]],[\"invoke\",371,0,\"eq\",[\"f64:9221120237041090560\",\"f64:4607182418800017408\"]],[\"invoke\",381,0,\"eq\",[\"f64:18444492273895866368\",\"f64:18442240474082181119\"]],[\"invoke\",389,0,\"eq\",[\"f64:18444492273895866368\",\"f64:18442240474082181120\"]],[\"invoke\",401,0,\"eq\",[\"f64:18444492273895866368\",\"f64:9221120237041090560\"]],[\"invoke\",415,0,\"ne\",[\"f64:0\",\"f64:9223372036854775808\"]],[\"invoke\",420,0,\"ne\",[\"f64:0\",\"f64:1\"]],[\"invoke\",425,0,\"ne\",[\"f64:9223372036854775808\",\"f64:13826050856027422720\"]],[\"invoke\",430,0,\"ne\",[\"f64:9223372036854775808\",\"f64:4607182418800017408\"]],[\"invoke\",435,0,\"ne\",[\"f64:0\",\"f64:13842132293034192152\"]],[\"invoke\",439,0,\"ne\",[\"f64:0\",\"f64:18442240474082181119\"]],[\"invoke\",444,0,\"ne\",[\"f64:0\",\"f64:9218868437227405312\"]],[\"invoke\",453,0,\"ne\",[\"f64:9223372036854775809\",\"f64:9223372036854775808\"]],[\"invoke\",458,0,\"ne\",[\"f64:9223372036854775809\",\"f64:1\"]],[\"invoke\",463,0,\"ne\",[\"f64:1\",\"f64:9227875636482146304\"]],[\"invoke\",468,0,\"ne\",[\"f64:1\",\"f64:4602678819172646912\"]],[\"invoke\",473,0,\"ne\",[\"f64:9223372036854775809\",\"f64:13842132293034192152\"]],[\"invoke\",477,0,\"ne\",[\"f64:9223372036854775809\",\"f64:18442240474082181119\"]],[\"invoke\",482,0,\"ne\",[\"f64:9223372036854775809\",\"f64:9218868437227405312\"]],[\"invoke\",489,0,\"ne\",[\"f64:1\",\"f64:18444492273895866368\"]],[\"invoke\",496,0,\"ne\",[\"f64:4503599627370496\",\"f64:0\"]],[\"invoke\",501,0,\"ne\",[\"f64:9227875636482146304\",\"f64:9227875636482146304\"]],[\"invoke\",506,0,\"ne\",[\"f64:9227875636482146304\",\"f64:4602678819172646912\"]],[\"invoke\",511,0,\"ne\",[\"f64:4503599627370496\",\"f64:13830554455654793216\"]],[\"invoke\",515,0,\"ne\",[\"f64:4503599627370496\",\"f64:13842132293034192152\"]],[\"invoke\",520,0,\"ne\",[\"f64:4503599627370496\",\"f64:9218868437227405311\"]],[\"invoke\",525,0,\"ne\",[\"f64:9227875636482146304\",\"f64:18444492273895866368\"]],[\"invoke\",534,0,\"ne\",[\"f64:13826050856027422720\",\"f64:0\"]],[\"invoke\",539,0,\"ne\",[\"f64:4602678819172646912\",\"f64:9223372036854775809\"]],[\"invoke\",544,0,\"ne\",[\"f64:4602678819172646912\",\"f64:4503599627370496\"]],[\"invoke\",549,0,\"ne\",[\"f64:13826050856027422720\",\"f64:13830554455654793216\"]],[\"invoke\",553,0,\"ne\",[\"f64:13826050856027422720\",\"f64:13842132293034192152\"]],[\"invoke\",558,0,\"ne\",[\"f64:13826050856027422720\",\"f64:9218868437227405311\"]],[\"invoke\",563,0,\"ne\",[\"f64:4602678819172646912\",\"f64:18442240474082181120\"]],[\"invoke\",571,0,\"ne\",[\"f64:4602678819172646912\",\"f64:9221120237041090560\"]],[\"invoke\",577,0,\"ne\",[\"f64:13830554455654793216\",\"f64:9223372036854775809\"]],[\"invoke\",582,0,\"ne\",[\"f64:13830554455654793216\",\"f64:4503599627370496\"]],[\"invoke\",587,0,\"ne\",[\"f64:4607182418800017408\",\"f64:13826050856027422720\"]],[\"invoke\",592,0,\"ne\",[\"f64:4607182418800017408\",\"f64:4607182418800017408\"]],[\"invoke\",596,0,\"ne\",[\"f64:4607182418800017408\",\"f64:4618760256179416344\"]],[\"invoke\",601,0,\"ne\",[\"f64:13830554455654793216\",\"f64:18442240474082181120\"]],[\"invoke\",607,0,\"ne\",[\"f64:13830554455654793216\",\"f64:9221120237041090560\"]],[\"invoke\",615,0,\"ne\",[\"f64:4618760256179416344\",\"f64:9223372036854775808\"]],[\"invoke\",620,0,\"ne\",[\"f64:4618760256179416344\",\"f64:1\"]],[\"invoke\",625,0,\"ne\",[\"f64:13842132293034192152\",\"f64:13826050856027422720\"]],[\"invoke\",630,0,\"ne\",[\"f64:13842132293034192152\",\"f64:4607182418800017408\"]],[\"invoke\",634,0,\"ne\",[\"f64:13842132293034192152\",\"f64:4618760256179416344\"]],[\"invoke\",639,0,\"ne\",[\"f64:4618760256179416344\",\"f64:18442240474082181119\"]],[\"invoke\",644,0,\"ne\",[\"f64:4618760256179416344\",\"f64:9218868437227405312\"]],[\"invoke\",653,0,\"ne\",[\"f64:18442240474082181119\",\"f64:9223372036854775808\"]],[\"invoke\",658,0,\"ne\",[\"f64:18442240474082181119\",\"f64:1\"]],[\"invoke\",663,0,\"ne\",[\"f64:9218868437227405311\",\"f64:9227875636482146304\"]],[\"invoke\",668,0,\"ne\",[\"f64:9218868437227405311\",\"f64:4602678819172646912\"]],[\"invoke\",672,0,\"ne\",[\"f64:9218868437227405311\",\"f64:4607182418800017408\"]],[\"invoke\",677,0,\"ne\",[\"f64:18442240474082181119\",\"f64:18442240474082181119\"]],[\"invoke\",682,0,\"ne\",[\"f64:18442240474082181119\",\"f64:9218868437227405312\"]],[\"invoke\",689,0,\"ne\",[\"f64:9218868437227405311\",\"f64:18444492273895866368\"]],[\"invoke\",696,0,\"ne\",[\"f64:9218868437227405312\",\"f64:0\"]],[\"invoke\",701,0,\"ne\",[\"f64:18442240474082181120\",\"f64:9227875636482146304\"]],[\"invoke\",706,0,\"ne\",[\"f64:18442240474082181120\",\"f64:4602678819172646912\"]],[\"invoke\",710,0,\"ne\",[\"f64:18442240474082181120\",\"f64:4607182418800017408\"]],[\"invoke\",715,0,\"ne\",[\"f64:9218868437227405312\",\"f64:13842132293034192152\"]],[\"invoke\",720,0,\"ne\",[\"f64:9218868437227405312\",\"f64:9218868437227405311\"]],[\"invoke\",725,0,\"ne\",[\"f64:18442240474082181120\",\"f64:18444492273895866368\"]],[\"invoke\",735,0,\"ne\",[\"f64:18444492273895866368\",\"f64:0\"]],[\"invoke\",745,0,\"ne\",[\"f64:9221120237041090560\",\"f64:9223372036854775809\"]],[\"invoke\",755,0,\"ne\",[\"f64:9221120237041090560\",\"f64:4503599627370496\"]],[\"invoke\",763,0,\"ne\",[\"f64:9221120237041090560\",\"f64:4602678819172646912\"]],[\"invoke\",773,0,\"ne\",[\"f64:18444492273895866368\",\"f64:13842132293034192152\"]],[\"invoke\",783,0,\"ne\",[\"f64:18444492273895866368\",\"f64:9218868437227405311\"]],[\"invoke\",793,0,\"ne\",[\"f64:9221120237041090560\",\"f64:18442240474082181120\"]],[\"invoke\",809,0,\"ne\",[\"f64:9221120237041090560\",\"f64:9221120237041090560\"]],[\"invoke\",817,0,\"lt\",[\"f64:9223372036854775808\",\"f64:9223372036854775809\"]],[\"invoke\",822,0,\"lt\",[\"f64:9223372036854775808\",\"f64:4503599627370496\"]],[\"invoke\",826,0,\"lt\",[\"f64:9223372036854775808\",\"f64:4602678819172646912\"]],[\"invoke\",831,0,\"lt\",[\"f64:0\",\"f64:13830554455654793216\"]],[\"invoke\",836,0,\"lt\",[\"f64:0\",\"f64:4618760256179416344\"]],[\"invoke\",841,0,\"lt\",[\"f64:9223372036854775808\",\"f64:18442240474082181120\"]],[\"invoke\",847,0,\"lt\",[\"f64:9223372036854775808\",\"f64:9221120237041090560\"]],[\"invoke\",855,0,\"lt\",[\"f64:1\",\"f64:9223372036854775808\"]],[\"invoke\",860,0,\"lt\",[\"f64:1\",\"f64:1\"]],[\"invoke\",864,0,\"lt\",[\"f64:1\",\"f64:4503599627370496\"]],[\"invoke\",869,0,\"lt\",[\"f64:9223372036854775809\",\"f64:13830554455654793216\"]],[\"invoke\",874,0,\"lt\",[\"f64:9223372036854775809\",\"f64:4618760256179416344\"]],[\"invoke\",879,0,\"lt\",[\"f64:1\",\"f64:18442240474082181119\"]],[\"invoke\",884,0,\"lt\",[\"f64:1\",\"f64:9218868437227405312\"]],[\"invoke\",893,0,\"lt\",[\"f64:9227875636482146304\",\"f64:9223372036854775808\"]],[\"invoke\",898,0,\"lt\",[\"f64:9227875636482146304\",\"f64:1\"]],[\"invoke\",903,0,\"lt\",[\"f64:4503599627370496\",\"f64:9227875636482146304\"]],[\"invoke\",907,0,\"lt\",[\"f64:4503599627370496\",\"f64:13826050856027422720\"]],[\"invoke\",912,0,\"lt\",[\"f64:4503599627370496\",\"f64:4607182418800017408\"]],[\"invoke\",917,0,\"lt\",[\"f64:9227875636482146304\",\"f64:18442240474082181119\"]],[\"invoke\",922,0,\"lt\",[\"f64:9227875636482146304\",\"f64:9218868437227405312\"]],[\"invoke\",929,0,\"lt\",[\"f64:4503599627370496\",\"f64:18444492273895866368\"]],[\"invoke\",936,0,\"lt\",[\"f64:4602678819172646912\",\"f64:0\"]],[\"invoke\",941,0,\"lt\",[\"f64:13826050856027422720\",\"f64:9227875636482146304\"]],[\"invoke\",945,0,\"lt\",[\"f64:13826050856027422720\",\"f64:13826050856027422720\"]],[\"invoke\",950,0,\"lt\",[\"f64:13826050856027422720\",\"f64:4607182418800017408\"]],[\"invoke\",955,0,\"lt\",[\"f64:4602678819172646912\",\"f64:13842132293034192152\"]],[\"invoke\",960,0,\"lt\",[\"f64:4602678819172646912\",\"f64:9218868437227405311\"]],[\"invoke\",965,0,\"lt\",[\"f64:13826050856027422720\",\"f64:18444492273895866368\"]],[\"invoke\",974,0,\"lt\",[\"f64:13830554455654793216\",\"f64:0\"]],[\"invoke\",979,0,\"lt\",[\"f64:4607182418800017408\",\"f64:9223372036854775809\"]],[\"invoke\",983,0,\"lt\",[\"f64:4607182418800017408\",\"f64:9227875636482146304\"]],[\"invoke\",988,0,\"lt\",[\"f64:4607182418800017408\",\"f64:4602678819172646912\"]],[\"invoke\",993,0,\"lt\",[\"f64:13830554455654793216\",\"f64:13842132293034192152\"]],[\"invoke\",998,0,\"lt\",[\"f64:13830554455654793216\",\"f64:9218868437227405311\"]],[\"invoke\",1003,0,\"lt\",[\"f64:4607182418800017408\",\"f64:18442240474082181120\"]],[\"invoke\",1011,0,\"lt\",[\"f64:4607182418800017408\",\"f64:9221120237041090560\"]],[\"invoke\",1017,0,\"lt\",[\"f64:13842132293034192152\",\"f64:9223372036854775809\"]],[\"invoke\",1021,0,\"lt\",[\"f64:13842132293034192152\",\"f64:9227875636482146304\"]],[\"invoke\",1026,0,\"lt\",[\"f64:13842132293034192152\",\"f64:4602678819172646912\"]],[\"invoke\",1031,0,\"lt\",[\"f64:4618760256179416344\",\"f64:13830554455654793216\"]],[\"invoke\",1036,0,\"lt\",[\"f64:4618760256179416344\",\"f64:4618760256179416344\"]],[\"invoke\",1041,0,\"lt\",[\"f64:13842132293034192152\",\"f64:18442240474082181120\"]],[\"invoke\",1047,0,\"lt\",[\"f64:13842132293034192152\",\"f64:9221120237041090560\"]],[\"invoke\",1055,0,\"lt\",[\"f64:9218868437227405311\",\"f64:9223372036854775808\"]],[\"invoke\",1059,0,\"lt\",[\"f64:9218868437227405311\",\"f64:9223372036854775809\"]],[\"invoke\",1064,0,\"lt\",[\"f64:9218868437227405311\",\"f64:4503599627370496\"]],[\"invoke\",1069,0,\"lt\",[\"f64:18442240474082181119\",\"f64:13830554455654793216\"]],[\"invoke\",1074,0,\"lt\",[\"f64:18442240474082181119\",\"f64:4618760256179416344\"]],[\"invoke\",1079,0,\"lt\",[\"f64:9218868437227405311\",\"f64:18442240474082181119\"]],[\"invoke\",1084,0,\"lt\",[\"f64:9218868437227405311\",\"f64:9218868437227405312\"]],[\"invoke\",1093,0,\"lt\",[\"f64:18442240474082181120\",\"f64:9223372036854775808\"]],[\"invoke\",1097,0,\"lt\",[\"f64:18442240474082181120\",\"f64:9223372036854775809\"]],[\"invoke\",1102,0,\"lt\",[\"f64:18442240474082181120\",\"f64:4503599627370496\"]],[\"invoke\",1107,0,\"lt\",[\"f64:9218868437227405312\",\"f64:13826050856027422720\"]],[\"invoke\",1112,0,\"lt\",[\"f64:9218868437227405312\",\"f64:4607182418800017408\"]],[\"invoke\",1117,0,\"lt\",[\"f64:18442240474082181120\",\"f64:18442240474082181119\"]],[\"invoke\",1122,0,\"lt\",[\"f64:18442240474082181120\",\"f64:9218868437227405312\"]],[\"invoke\",1129,0,\"lt\",[\"f64:9218868437227405312\",\"f64:18444492273895866368\"]],[\"invoke\",1137,0,\"lt\",[\"f64:9221120237041090560\",\"f64:9223372036854775808\"]],[\"invoke\",1147,0,\"lt\",[\"f64:9221120237041090560\",\"f64:1\"]],[\"invoke\",1157,0,\"lt\",[\"f64:18444492273895866368\",\"f64:13826050856027422720\"]],[\"invoke\",1167,0,\"lt\",[\"f64:18444492273895866368\",\"f64:4607182418800017408\"]],[\"invoke\",1177,0,\"lt\",[\"f64:9221120237041090560\",\"f64:13842132293034192152\"]],[\"invoke\",1187,0,\"lt\",[\"f64:9221120237041090560\",\"f64:9218868437227405311\"]],[\"invoke\",1197,0,\"lt\",[\"f64:18444492273895866368\",\"f64:18444492273895866368\"]],[\"invoke\",1214,0,\"le\",[\"f64:9223372036854775808\",\"f64:0\"]],[\"invoke\",1218,0,\"le\",[\"f64:9223372036854775808\",\"f64:1\"]],[\"invoke\",1223,0,\"le\",[\"f64:0\",\"f64:9227875636482146304\"]],[\"invoke\",1228,0,\"le\",[\"f64:0\",\"f64:4602678819172646912\"]],[\"invoke\",1233,0,\"le\",[\"f64:9223372036854775808\",\"f64:13842132293034192152\"]],[\"invoke\",1238,0,\"le\",[\"f64:9223372036854775808\",\"f64:9218868437227405311\"]],[\"invoke\",1243,0,\"le\",[\"f64:0\",\"f64:18442240474082181120\"]],[\"invoke\",1251,0,\"le\",[\"f64:0\",\"f64:9221120237041090560\"]],[\"invoke\",1256,0,\"le\",[\"f64:1\",\"f64:0\"]],[\"invoke\",1261,0,\"le\",[\"f64:9223372036854775809\",\"f64:9227875636482146304\"]],[\"invoke\",1266,0,\"le\",[\"f64:9223372036854775809\",\"f64:4602678819172646912\"]],[\"invoke\",1271,0,\"le\",[\"f64:1\",\"f64:13830554455654793216\"]],[\"invoke\",1276,0,\"le\",[\"f64:1\",\"f64:4618760256179416344\"]],[\"invoke\",1281,0,\"le\",[\"f64:9223372036854775809\",\"f64:18442240474082181120\"]],[\"invoke\",1287,0,\"le\",[\"f64:9223372036854775809\",\"f64:9221120237041090560\"]],[\"invoke\",1294,0,\"le\",[\"f64:9227875636482146304\",\"f64:0\"]],[\"invoke\",1299,0,\"le\",[\"f64:4503599627370496\",\"f64:9223372036854775809\"]],[\"invoke\",1304,0,\"le\",[\"f64:4503599627370496\",\"f64:4503599627370496\"]],[\"invoke\",1309,0,\"le\",[\"f64:9227875636482146304\",\"f64:13830554455654793216\"]],[\"invoke\",1314,0,\"le\",[\"f64:9227875636482146304\",\"f64:4618760256179416344\"]],[\"invoke\",1319,0,\"le\",[\"f64:4503599627370496\",\"f64:18442240474082181119\"]],[\"invoke\",1324,0,\"le\",[\"f64:4503599627370496\",\"f64:9218868437227405312\"]],[\"invoke\",1331,0,\"le\",[\"f64:4503599627370496\",\"f64:9221120237041090560\"]],[\"invoke\",1337,0,\"le\",[\"f64:13826050856027422720\",\"f64:9223372036854775809\"]],[\"invoke\",1342,0,\"le\",[\"f64:13826050856027422720\",\"f64:4503599627370496\"]],[\"invoke\",1347,0,\"le\",[\"f64:4602678819172646912\",\"f64:13826050856027422720\"]],[\"invoke\",1352,0,\"le\",[\"f64:4602678819172646912\",\"f64:4607182418800017408\"]],[\"invoke\",1357,0,\"le\",[\"f64:13826050856027422720\",\"f64:18442240474082181119\"]],[\"invoke\",1362,0,\"le\",[\"f64:13826050856027422720\",\"f64:9218868437227405312\"]],[\"invoke\",1367,0,\"le\",[\"f64:13826050856027422720\",\"f64:9221120237041090560\"]],[\"invoke\",1375,0,\"le\",[\"f64:4607182418800017408\",\"f64:9223372036854775808\"]],[\"invoke\",1380,0,\"le\",[\"f64:4607182418800017408\",\"f64:1\"]],[\"invoke\",1385,0,\"le\",[\"f64:13830554455654793216\",\"f64:13826050856027422720\"]],[\"invoke\",1390,0,\"le\",[\"f64:13830554455654793216\",\"f64:4607182418800017408\"]],[\"invoke\",1395,0,\"le\",[\"f64:4607182418800017408\",\"f64:13842132293034192152\"]],[\"invoke\",1400,0,\"le\",[\"f64:4607182418800017408\",\"f64:9218868437227405311\"]],[\"invoke\",1404,0,\"le\",[\"f64:4607182418800017408\",\"f64:9218868437227405312\"]],[\"invoke\",1413,0,\"le\",[\"f64:13842132293034192152\",\"f64:9223372036854775808\"]],[\"invoke\",1418,0,\"le\",[\"f64:13842132293034192152\",\"f64:1\"]],[\"invoke\",1423,0,\"le\",[\"f64:4618760256179416344\",\"f64:9227875636482146304\"]],[\"invoke\",1428,0,\"le\",[\"f64:4618760256179416344\",\"f64:4602678819172646912\"]],[\"invoke\",1433,0,\"le\",[\"f64:13842132293034192152\",\"f64:13842132293034192152\"]],[\"invoke\",1438,0,\"le\",[\"f64:13842132293034192152\",\"f64:9218868437227405311\"]],[\"invoke\",1442,0,\"le\",[\"f64:13842132293034192152\",\"f64:9218868437227405312\"]],[\"invoke\",1449,0,\"le\",[\"f64:4618760256179416344\",\"f64:18444492273895866368\"]],[\"invoke\",1456,0,\"le\",[\"f64:9218868437227405311\",\"f64:0\"]],[\"invoke\",1461,0,\"le\",[\"f64:18442240474082181119\",\"f64:9227875636482146304\"]],[\"invoke\",1466,0,\"le\",[\"f64:18442240474082181119\",\"f64:4602678819172646912\"]],[\"invoke\",1471,0,\"le\",[\"f64:9218868437227405311\",\"f64:13830554455654793216\"]],[\"invoke\",1476,0,\"le\",[\"f64:9218868437227405311\",\"f64:4618760256179416344\"]],[\"invoke\",1480,0,\"le\",[\"f64:9218868437227405311\",\"f64:9218868437227405311\"]],[\"invoke\",1485,0,\"le\",[\"f64:18442240474082181119\",\"f64:18444492273895866368\"]],[\"invoke\",1494,0,\"le\",[\"f64:18442240474082181120\",\"f64:0\"]],[\"invoke\",1499,0,\"le\",[\"f64:9218868437227405312\",\"f64:9223372036854775809\"]],[\"invoke\",1504,0,\"le\",[\"f64:9218868437227405312\",\"f64:4503599627370496\"]],[\"invoke\",1509,0,\"le\",[\"f64:18442240474082181120\",\"f64:13830554455654793216\"]],[\"invoke\",1514,0,\"le\",[\"f64:18442240474082181120\",\"f64:4618760256179416344\"]],[\"invoke\",1519,0,\"le\",[\"f64:9218868437227405312\",\"f64:18442240474082181119\"]],[\"invoke\",1523,0,\"le\",[\"f64:9218868437227405312\",\"f64:18442240474082181120\"]],[\"invoke\",1531,0,\"le\",[\"f64:9218868437227405312\",\"f64:9221120237041090560\"]],[\"invoke\",1541,0,\"le\",[\"f64:18444492273895866368\",\"f64:9223372036854775809\"]],[\"invoke\",1551,0,\"le\",[\"f64:18444492273895866368\",\"f64:4503599627370496\"]],[\"invoke\",1561,0,\"le\",[\"f64:9221120237041090560\",\"f64:13826050856027422720\"]],[\"invoke\",1571,0,\"le\",[\"f64:9221120237041090560\",\"f64:4607182418800017408\"]],[\"invoke\",1581,0,\"le\",[\"f64:18444492273895866368\",\"f64:18442240474082181119\"]],[\"invoke\",1589,0,\"le\",[\"f64:18444492273895866368\",\"f64:18442240474082181120\"]],[\"invoke\",1601,0,\"le\",[\"f64:18444492273895866368\",\"f64:9221120237041090560\"]],[\"invoke\",1615,0,\"gt\",[\"f64:0\",\"f64:9223372036854775808\"]],[\"invoke\",1620,0,\"gt\",[\"f64:0\",\"f64:1\"]],[\"invoke\",1625,0,\"gt\",[\"f64:9223372036854775808\",\"f64:13826050856027422720\"]],[\"invoke\",1630,0,\"gt\",[\"f64:9223372036854775808\",\"f64:4607182418800017408\"]],[\"invoke\",1635,0,\"gt\",[\"f64:0\",\"f64:13842132293034192152\"]],[\"invoke\",1639,0,\"gt\",[\"f64:0\",\"f64:18442240474082181119\"]],[\"invoke\",1644,0,\"gt\",[\"f64:0\",\"f64:9218868437227405312\"]],[\"invoke\",1653,0,\"gt\",[\"f64:9223372036854775809\",\"f64:9223372036854775808\"]],[\"invoke\",1658,0,\"gt\",[\"f64:9223372036854775809\",\"f64:1\"]],[\"invoke\",1663,0,\"gt\",[\"f64:1\",\"f64:9227875636482146304\"]],[\"invoke\",1668,0,\"gt\",[\"f64:1\",\"f64:4602678819172646912\"]],[\"invoke\",1673,0,\"gt\",[\"f64:9223372036854775809\",\"f64:13842132293034192152\"]],[\"invoke\",1677,0,\"gt\",[\"f64:9223372036854775809\",\"f64:18442240474082181119\"]],[\"invoke\",1682,0,\"gt\",[\"f64:9223372036854775809\",\"f64:9218868437227405312\"]],[\"invoke\",1689,0,\"gt\",[\"f64:1\",\"f64:18444492273895866368\"]],[\"invoke\",1696,0,\"gt\",[\"f64:4503599627370496\",\"f64:0\"]],[\"invoke\",1701,0,\"gt\",[\"f64:9227875636482146304\",\"f64:9227875636482146304\"]],[\"invoke\",1706,0,\"gt\",[\"f64:9227875636482146304\",\"f64:4602678819172646912\"]],[\"invoke\",1711,0,\"gt\",[\"f64:4503599627370496\",\"f64:13830554455654793216\"]],[\"invoke\",1715,0,\"gt\",[\"f64:4503599627370496\",\"f64:13842132293034192152\"]],[\"invoke\",1720,0,\"gt\",[\"f64:4503599627370496\",\"f64:9218868437227405311\"]],[\"invoke\",1725,0,\"gt\",[\"f64:9227875636482146304\",\"f64:18444492273895866368\"]],[\"invoke\",1734,0,\"gt\",[\"f64:13826050856027422720\",\"f64:0\"]],[\"invoke\",1739,0,\"gt\",[\"f64:4602678819172646912\",\"f64:9223372036854775809\"]],[\"invoke\",1744,0,\"gt\",[\"f64:4602678819172646912\",\"f64:4503599627370496\"]],[\"invoke\",1749,0,\"gt\",[\"f64:13826050856027422720\",\"f64:13830554455654793216\"]],[\"invoke\",1753,0,\"gt\",[\"f64:13826050856027422720\",\"f64:13842132293034192152\"]],[\"invoke\",1758,0,\"gt\",[\"f64:13826050856027422720\",\"f64:9218868437227405311\"]],[\"invoke\",1763,0,\"gt\",[\"f64:4602678819172646912\",\"f64:18442240474082181120\"]],[\"invoke\",1771,0,\"gt\",[\"f64:4602678819172646912\",\"f64:9221120237041090560\"]],[\"invoke\",1777,0,\"gt\",[\"f64:13830554455654793216\",\"f64:9223372036854775809\"]],[\"invoke\",1782,0,\"gt\",[\"f64:13830554455654793216\",\"f64:4503599627370496\"]],[\"invoke\",1787,0,\"gt\",[\"f64:4607182418800017408\",\"f64:13826050856027422720\"]],[\"invoke\",1791,0,\"gt\",[\"f64:4607182418800017408\",\"f64:13830554455654793216\"]],[\"invoke\",1796,0,\"gt\",[\"f64:4607182418800017408\",\"f64:4618760256179416344\"]],[\"invoke\",1801,0,\"gt\",[\"f64:13830554455654793216\",\"f64:18442240474082181120\"]],[\"invoke\",1807,0,\"gt\",[\"f64:13830554455654793216\",\"f64:9221120237041090560\"]],[\"invoke\",1815,0,\"gt\",[\"f64:4618760256179416344\",\"f64:9223372036854775808\"]],[\"invoke\",1820,0,\"gt\",[\"f64:4618760256179416344\",\"f64:1\"]],[\"invoke\",1825,0,\"gt\",[\"f64:13842132293034192152\",\"f64:13826050856027422720\"]],[\"invoke\",1830,0,\"gt\",[\"f64:13842132293034192152\",\"f64:4607182418800017408\"]],[\"invoke\",1834,0,\"gt\",[\"f64:13842132293034192152\",\"f64:4618760256179416344\"]],[\"invoke\",1839,0,\"gt\",[\"f64:4618760256179416344\",\"f64:18442240474082181119\"]],[\"invoke\",1844,0,\"gt\",[\"f64:4618760256179416344\",\"f64:9218868437227405312\"]],[\"invoke\",1853,0,\"gt\",[\"f64:18442240474082181119\",\"f64:9223372036854775808\"]],[\"invoke\",1858,0,\"gt\",[\"f64:18442240474082181119\",\"f64:1\"]],[\"invoke\",1863,0,\"gt\",[\"f64:9218868437227405311\",\"f64:9227875636482146304\"]],[\"invoke\",1868,0,\"gt\",[\"f64:9218868437227405311\",\"f64:4602678819172646912\"]],[\"invoke\",1872,0,\"gt\",[\"f64:9218868437227405311\",\"f64:4607182418800017408\"]],[\"invoke\",1877,0,\"gt\",[\"f64:18442240474082181119\",\"f64:18442240474082181119\"]],[\"invoke\",1882,0,\"gt\",[\"f64:18442240474082181119\",\"f64:9218868437227405312\"]],[\"invoke\",1889,0,\"gt\",[\"f64:9218868437227405311\",\"f64:18444492273895866368\"]],[\"invoke\",1896,0,\"gt\",[\"f64:9218868437227405312\",\"f64:0\"]],[\"invoke\",1901,0,\"gt\",[\"f64:18442240474082181120\",\"f64:9227875636482146304\"]],[\"invoke\",1906,0,\"gt\",[\"f64:18442240474082181120\",\"f64:4602678819172646912\"]],[\"invoke\",1910,0,\"gt\",[\"f64:18442240474082181120\",\"f64:4607182418800017408\"]],[\"invoke\",1915,0,\"gt\",[\"f64:9218868437227405312\",\"f64:13842132293034192152\"]],[\"invoke\",1920,0,\"gt\",[\"f64:9218868437227405312\",\"f64:9218868437227405311\"]],[\"invoke\",1925,0,\"gt\",[\"f64:18442240474082181120\",\"f64:18444492273895866368\"]],[\"invoke\",1935,0,\"gt\",[\"f64:18444492273895866368\",\"f64:0\"]],[\"invoke\",1945,0,\"gt\",[\"f64:9221120237041090560\",\"f64:9223372036854775809\"]],[\"invoke\",1955,0,\"gt\",[\"f64:9221120237041090560\",\"f64:4503599627370496\"]],[\"invoke\",1963,0,\"gt\",[\"f64:9221120237041090560\",\"f64:4602678819172646912\"]],[\"invoke\",1973,0,\"gt\",[\"f64:18444492273895866368\",\"f64:13842132293034192152\"]],[\"invoke\",1983,0,\"gt\",[\"f64:18444492273895866368\",\"f64:9218868437227405311\"]],[\"invoke\",1993,0,\"gt\",[\"f64:9221120237041090560\",\"f64:18442240474082181120\"]],[\"invoke\",2009,0,\"gt\",[\"f64:9221120237041090560\",\"f64:9221120237041090560\"]],[\"invoke\",2017,0,\"ge\",[\"f64:9223372036854775808\",\"f64:9223372036854775809\"]],[\"invoke\",2022,0,\"ge\",[\"f64:9223372036854775808\",\"f64:4503599627370496\"]],[\"invoke\",2026,0,\"ge\",[\"f64:9223372036854775808\",\"f64:4602678819172646912\"]],[\"invoke\",2031,0,\"ge\",[\"f64:0\",\"f64:13830554455654793216\"]],[\"invoke\",2036,0,\"ge\",[\"f64:0\",\"f64:4618760256179416344\"]],[\"invoke\",2041,0,\"ge\",[\"f64:9223372036854775808\",\"f64:18442240474082181120\"]],[\"invoke\",2047,0,\"ge\",[\"f64:9223372036854775808\",\"f64:9221120237041090560\"]],[\"invoke\",2055,0,\"ge\",[\"f64:1\",\"f64:9223372036854775808\"]],[\"invoke\",2060,0,\"ge\",[\"f64:1\",\"f64:1\"]],[\"invoke\",2064,0,\"ge\",[\"f64:1\",\"f64:4503599627370496\"]],[\"invoke\",2069,0,\"ge\",[\"f64:9223372036854775809\",\"f64:13830554455654793216\"]],[\"invoke\",2074,0,\"ge\",[\"f64:9223372036854775809\",\"f64:4618760256179416344\"]],[\"invoke\",2079,0,\"ge\",[\"f64:1\",\"f64:18442240474082181119\"]],[\"invoke\",2084,0,\"ge\",[\"f64:1\",\"f64:9218868437227405312\"]],[\"invoke\",2093,0,\"ge\",[\"f64:9227875636482146304\",\"f64:9223372036854775808\"]],[\"invoke\",2098,0,\"ge\",[\"f64:9227875636482146304\",\"f64:1\"]],[\"invoke\",2102,0,\"ge\",[\"f64:9227875636482146304\",\"f64:4503599627370496\"]],[\"invoke\",2107,0,\"ge\",[\"f64:4503599627370496\",\"f64:13826050856027422720\"]],[\"invoke\",2112,0,\"ge\",[\"f64:4503599627370496\",\"f64:4607182418800017408\"]],[\"invoke\",2117,0,\"ge\",[\"f64:9227875636482146304\",\"f64:18442240474082181119\"]],[\"invoke\",2122,0,\"ge\",[\"f64:9227875636482146304\",\"f64:9218868437227405312\"]],[\"invoke\",2129,0,\"ge\",[\"f64:4503599627370496\",\"f64:18444492273895866368\"]],[\"invoke\",2136,0,\"ge\",[\"f64:4602678819172646912\",\"f64:0\"]],[\"invoke\",2141,0,\"ge\",[\"f64:13826050856027422720\",\"f64:9227875636482146304\"]],[\"invoke\",2145,0,\"ge\",[\"f64:13826050856027422720\",\"f64:13826050856027422720\"]],[\"invoke\",2150,0,\"ge\",[\"f64:13826050856027422720\",\"f64:4607182418800017408\"]],[\"invoke\",2155,0,\"ge\",[\"f64:4602678819172646912\",\"f64:13842132293034192152\"]],[\"invoke\",2160,0,\"ge\",[\"f64:4602678819172646912\",\"f64:9218868437227405311\"]],[\"invoke\",2165,0,\"ge\",[\"f64:13826050856027422720\",\"f64:18444492273895866368\"]],[\"invoke\",2174,0,\"ge\",[\"f64:13830554455654793216\",\"f64:0\"]],[\"invoke\",2179,0,\"ge\",[\"f64:4607182418800017408\",\"f64:9223372036854775809\"]],[\"invoke\",2183,0,\"ge\",[\"f64:4607182418800017408\",\"f64:9227875636482146304\"]],[\"invoke\",2188,0,\"ge\",[\"f64:4607182418800017408\",\"f64:4602678819172646912\"]],[\"invoke\",2193,0,\"ge\",[\"f64:13830554455654793216\",\"f64:13842132293034192152\"]],[\"invoke\",2198,0,\"ge\",[\"f64:13830554455654793216\",\"f64:9218868437227405311\"]],[\"invoke\",2203,0,\"ge\",[\"f64:4607182418800017408\",\"f64:18442240474082181120\"]],[\"invoke\",2211,0,\"ge\",[\"f64:4607182418800017408\",\"f64:9221120237041090560\"]],[\"invoke\",2217,0,\"ge\",[\"f64:13842132293034192152\",\"f64:9223372036854775809\"]],[\"invoke\",2221,0,\"ge\",[\"f64:13842132293034192152\",\"f64:9227875636482146304\"]],[\"invoke\",2226,0,\"ge\",[\"f64:13842132293034192152\",\"f64:4602678819172646912\"]],[\"invoke\",2231,0,\"ge\",[\"f64:4618760256179416344\",\"f64:13830554455654793216\"]],[\"invoke\",2236,0,\"ge\",[\"f64:4618760256179416344\",\"f64:4618760256179416344\"]],[\"invoke\",2241,0,\"ge\",[\"f64:13842132293034192152\",\"f64:18442240474082181120\"]],[\"invoke\",2247,0,\"ge\",[\"f64:13842132293034192152\",\"f64:9221120237041090560\"]],[\"invoke\",2255,0,\"ge\",[\"f64:9218868437227405311\",\"f64:9223372036854775808\"]],[\"invoke\",2259,0,\"ge\",[\"f64:9218868437227405311\",\"f64:9223372036854775809\"]],[\"invoke\",2264,0,\"ge\",[\"f64:9218868437227405311\",\"f64:4503599627370496\"]],[\"invoke\",2269,0,\"ge\",[\"f64:18442240474082181119\",\"f64:13830554455654793216\"]],[\"invoke\",2274,0,\"ge\",[\"f64:18442240474082181119\",\"f64:4618760256179416344\"]],[\"invoke\",2279,0,\"ge\",[\"f64:9218868437227405311\",\"f64:18442240474082181119\"]],[\"invoke\",2284,0,\"ge\",[\"f64:9218868437227405311\",\"f64:9218868437227405312\"]],[\"invoke\",2293,0,\"ge\",[\"f64:18442240474082181120\",\"f64:9223372036854775808\"]],[\"invoke\",2297,0,\"ge\",[\"f64:18442240474082181120\",\"f64:9223372036854775809\"]],[\"invoke\",2302,0,\"ge\",[\"f64:18442240474082181120\",\"f64:4503599627370496\"]],[\"invoke\",2307,0,\"ge\",[\"f64:9218868437227405312\",\"f64:13826050856027422720\"]],[\"invoke\",2312,0,\"ge\",[\"f64:9218868437227405312\",\"f64:4607182418800017408\"]],[\"invoke\",2317,0,\"ge\",[\"f64:18442240474082181120\",\"f64:18442240474082181119\"]],[\"invoke\",2322,0,\"ge\",[\"f64:18442240474082181120\",\"f64:9218868437227405312\"]],[\"invoke\",2329,0,\"ge\",[\"f64:9218868437227405312\",\"f64:18444492273895866368\"]],[\"invoke\",2337,0,\"ge\",[\"f64:9221120237041090560\",\"f64:9223372036854775808\"]],[\"invoke\",2347,0,\"ge\",[\"f64:9221120237041090560\",\"f64:1\"]],[\"invoke\",2357,0,\"ge\",[\"f64:18444492273895866368\",\"f64:13826050856027422720\"]],[\"invoke\",2367,0,\"ge\",[\"f64:18444492273895866368\",\"f64:4607182418800017408\"]],[\"invoke\",2377,0,\"ge\",[\"f64:9221120237041090560\",\"f64:13842132293034192152\"]],[\"invoke\",2387,0,\"ge\",[\"f64:9221120237041090560\",\"f64:9218868437227405311\"]],[\"invoke\",2397,0,\"ge\",[\"f64:18444492273895866368\",\"f64:18444492273895866368\"]],[\"file\",\"f32_bitwise.wast\"],[\"module\",4,4],[\"invoke\",10,0,\"copysign\",[\"f32:2147483648\",\"f32:2147483648\"]],[\"invoke\",11,0,\"copysign\",[\"f32:2147483648\",\"f32:0\"]],[\"invoke\",12,0,\"copysign\",[\"f32:0\",\"f32:2147483648\"]],[\"invoke\",13,0,\"copysign\",[\"f32:0\",\"f32:0\"]],[\"invoke\",14,0,\"copysign\",[\"f32:2147483648\",\"f32:2147483649\"]],[\"invoke\",15,0,\"copysign\",[\"f32:2147483648\",\"f32:1\"]],[\"invoke\",16,0,\"copysign\",[\"f32:0\",\"f32:2147483649\"]],[\"invoke\",17,0,\"copysign\",[\"f32:0\",\"f32:1\"]],[\"invoke\",18,0,\"copysign\",[\"f32:2147483648\",\"f32:2155872256\"]],[\"invoke\",19,0,\"copysign\",[\"f32:2147483648\",\"f32:8388608\"]],[\"invoke\",20,0,\"copysign\",[\"f32:0\",\"f32:2155872256\"]],[\"invoke\",21,0,\"copysign\",[\"f32:0\",\"f32:8388608\"]],[\"invoke\",22,0,\"copysign\",[\"f32:2147483648\",\"f32:3204448256\"]],[\"invoke\",23,0,\"copysign\",[\"f32:2147483648\",\"f32:1056964608\"]],[\"invoke\",24,0,\"copysign\",[\"f32:0\",\"f32:3204448256\"]],[\"invoke\",25,0,\"copysign\",[\"f32:0\",\"f32:1056964608\"]],[\"invoke\",26,0,\"copysign\",[\"f32:2147483648\",\"f32:3212836864\"]],[\"invoke\",27,0,\"copysign\",[\"f32:2147483648\",\"f32:1065353216\"]],[\"invoke\",28,0,\"copysign\",[\"f32:0\",\"f32:3212836864\"]],[\"invoke\",29,0,\"copysign\",[\"f32:0\",\"f32:1065353216\"]],[\"invoke\",30,0,\"copysign\",[\"f32:2147483648\",\"f32:3234402267\"]],[\"invoke\",31,0,\"copysign\",[\"f32:2147483648\",\"f32:1086918619\"]],[\"invoke\",32,0,\"copysign\",[\"f32:0\",\"f32:3234402267\"]],[\"invoke\",33,0,\"copysign\",[\"f32:0\",\"f32:1086918619\"]],[\"invoke\",34,0,\"copysign\",[\"f32:2147483648\",\"f32:4286578687\"]],[\"invoke\",35,0,\"copysign\",[\"f32:2147483648\",\"f32:2139095039\"]],[\"invoke\",36,0,\"copysign\",[\"f32:0\",\"f32:4286578687\"]],[\"invoke\",37,0,\"copysign\",[\"f32:0\",\"f32:2139095039\"]],[\"invoke\",38,0,\"copysign\",[\"f32:2147483648\",\"f32:4286578688\"]],[\"invoke\",39,0,\"copysign\",[\"f32:2147483648\",\"f32:2139095040\"]],[\"invoke\",40,0,\"copysign\",[\"f32:0\",\"f32:4286578688\"]],[\"invoke\",41,0,\"copysign\",[\"f32:0\",\"f32:2139095040\"]],[\"invoke\",42,0,\"copysign\",[\"f32:2147483648\",\"f32:4290772992\"]],[\"invoke\",43,0,\"copysign\",[\"f32:2147483648\",\"f32:2143289344\"]],[\"invoke\",44,0,\"copysign\",[\"f32:0\",\"f32:4290772992\"]],[\"invoke\",45,0,\"copysign\",[\"f32:0\",\"f32:2143289344\"]],[\"invoke\",46,0,\"copysign\",[\"f32:2147483649\",\"f32:2147483648\"]],[\"invoke\",47,0,\"copysign\",[\"f32:2147483649\",\"f32:0\"]],[\"invoke\",48,0,\"copysign\",[\"f32:1\",\"f32:2147483648\"]],[\"invoke\",49,0,\"copysign\",[\"f32:1\",\"f32:0\"]],[\"invoke\",50,0,\"copysign\",[\"f32:2147483649\",\"f32:2147483649\"]],[\"invoke\",51,0,\"copysign\",[\"f32:2147483649\",\"f32:1\"]],[\"invoke\",52,0,\"copysign\",[\"f32:1\",\"f32:2147483649\"]],[\"invoke\",53,0,\"copysign\",[\"f32:1\",\"f32:1\"]],[\"invoke\",54,0,\"copysign\",[\"f32:2147483649\",\"f32:2155872256\"]],[\"invoke\",55,0,\"copysign\",[\"f32:2147483649\",\"f32:8388608\"]],[\"invoke\",56,0,\"copysign\",[\"f32:1\",\"f32:2155872256\"]],[\"invoke\",57,0,\"copysign\",[\"f32:1\",\"f32:8388608\"]],[\"invoke\",58,0,\"copysign\",[\"f32:2147483649\",\"f32:3204448256\"]],[\"invoke\",59,0,\"copysign\",[\"f32:2147483649\",\"f32:1056964608\"]],[\"invoke\",60,0,\"copysign\",[\"f32:1\",\"f32:3204448256\"]],[\"invoke\",61,0,\"copysign\",[\"f32:1\",\"f32:1056964608\"]],[\"invoke\",62,0,\"copysign\",[\"f32:2147483649\",\"f32:3212836864\"]],[\"invoke\",63,0,\"copysign\",[\"f32:2147483649\",\"f32:1065353216\"]],[\"invoke\",64,0,\"copysign\",[\"f32:1\",\"f32:3212836864\"]],[\"invoke\",65,0,\"copysign\",[\"f32:1\",\"f32:1065353216\"]],[\"invoke\",66,0,\"copysign\",[\"f32:2147483649\",\"f32:3234402267\"]],[\"invoke\",67,0,\"copysign\",[\"f32:2147483649\",\"f32:1086918619\"]],[\"invoke\",68,0,\"copysign\",[\"f32:1\",\"f32:3234402267\"]],[\"invoke\",69,0,\"copysign\",[\"f32:1\",\"f32:1086918619\"]],[\"invoke\",70,0,\"copysign\",[\"f32:2147483649\",\"f32:4286578687\"]],[\"invoke\",71,0,\"copysign\",[\"f32:2147483649\",\"f32:2139095039\"]],[\"invoke\",72,0,\"copysign\",[\"f32:1\",\"f32:4286578687\"]],[\"invoke\",73,0,\"copysign\",[\"f32:1\",\"f32:2139095039\"]],[\"invoke\",74,0,\"copysign\",[\"f32:2147483649\",\"f32:4286578688\"]],[\"invoke\",75,0,\"copysign\",[\"f32:2147483649\",\"f32:2139095040\"]],[\"invoke\",76,0,\"copysign\",[\"f32:1\",\"f32:4286578688\"]],[\"invoke\",77,0,\"copysign\",[\"f32:1\",\"f32:2139095040\"]],[\"invoke\",78,0,\"copysign\",[\"f32:2147483649\",\"f32:4290772992\"]],[\"invoke\",79,0,\"copysign\",[\"f32:2147483649\",\"f32:2143289344\"]],[\"invoke\",80,0,\"copysign\",[\"f32:1\",\"f32:4290772992\"]],[\"invoke\",81,0,\"copysign\",[\"f32:1\",\"f32:2143289344\"]],[\"invoke\",82,0,\"copysign\",[\"f32:2155872256\",\"f32:2147483648\"]],[\"invoke\",83,0,\"copysign\",[\"f32:2155872256\",\"f32:0\"]],[\"invoke\",84,0,\"copysign\",[\"f32:8388608\",\"f32:2147483648\"]],[\"invoke\",85,0,\"copysign\",[\"f32:8388608\",\"f32:0\"]],[\"invoke\",86,0,\"copysign\",[\"f32:2155872256\",\"f32:2147483649\"]],[\"invoke\",87,0,\"copysign\",[\"f32:2155872256\",\"f32:1\"]],[\"invoke\",88,0,\"copysign\",[\"f32:8388608\",\"f32:2147483649\"]],[\"invoke\",89,0,\"copysign\",[\"f32:8388608\",\"f32:1\"]],[\"invoke\",90,0,\"copysign\",[\"f32:2155872256\",\"f32:2155872256\"]],[\"invoke\",91,0,\"copysign\",[\"f32:2155872256\",\"f32:8388608\"]],[\"invoke\",92,0,\"copysign\",[\"f32:8388608\",\"f32:2155872256\"]],[\"invoke\",93,0,\"copysign\",[\"f32:8388608\",\"f32:8388608\"]],[\"invoke\",94,0,\"copysign\",[\"f32:2155872256\",\"f32:3204448256\"]],[\"invoke\",95,0,\"copysign\",[\"f32:2155872256\",\"f32:1056964608\"]],[\"invoke\",96,0,\"copysign\",[\"f32:8388608\",\"f32:3204448256\"]],[\"invoke\",97,0,\"copysign\",[\"f32:8388608\",\"f32:1056964608\"]],[\"invoke\",98,0,\"copysign\",[\"f32:2155872256\",\"f32:3212836864\"]],[\"invoke\",99,0,\"copysign\",[\"f32:2155872256\",\"f32:1065353216\"]],[\"invoke\",100,0,\"copysign\",[\"f32:8388608\",\"f32:3212836864\"]],[\"invoke\",101,0,\"copysign\",[\"f32:8388608\",\"f32:1065353216\"]],[\"invoke\",102,0,\"copysign\",[\"f32:2155872256\",\"f32:3234402267\"]],[\"invoke\",103,0,\"copysign\",[\"f32:2155872256\",\"f32:1086918619\"]],[\"invoke\",104,0,\"copysign\",[\"f32:8388608\",\"f32:3234402267\"]],[\"invoke\",105,0,\"copysign\",[\"f32:8388608\",\"f32:1086918619\"]],[\"invoke\",106,0,\"copysign\",[\"f32:2155872256\",\"f32:4286578687\"]],[\"invoke\",107,0,\"copysign\",[\"f32:2155872256\",\"f32:2139095039\"]],[\"invoke\",108,0,\"copysign\",[\"f32:8388608\",\"f32:4286578687\"]],[\"invoke\",109,0,\"copysign\",[\"f32:8388608\",\"f32:2139095039\"]],[\"invoke\",110,0,\"copysign\",[\"f32:2155872256\",\"f32:4286578688\"]],[\"invoke\",111,0,\"copysign\",[\"f32:2155872256\",\"f32:2139095040\"]],[\"invoke\",112,0,\"copysign\",[\"f32:8388608\",\"f32:4286578688\"]],[\"invoke\",113,0,\"copysign\",[\"f32:8388608\",\"f32:2139095040\"]],[\"invoke\",114,0,\"copysign\",[\"f32:2155872256\",\"f32:4290772992\"]],[\"invoke\",115,0,\"copysign\",[\"f32:2155872256\",\"f32:2143289344\"]],[\"invoke\",116,0,\"copysign\",[\"f32:8388608\",\"f32:4290772992\"]],[\"invoke\",117,0,\"copysign\",[\"f32:8388608\",\"f32:2143289344\"]],[\"invoke\",118,0,\"copysign\",[\"f32:3204448256\",\"f32:2147483648\"]],[\"invoke\",119,0,\"copysign\",[\"f32:3204448256\",\"f32:0\"]],[\"invoke\",120,0,\"copysign\",[\"f32:1056964608\",\"f32:2147483648\"]],[\"invoke\",121,0,\"copysign\",[\"f32:1056964608\",\"f32:0\"]],[\"invoke\",122,0,\"copysign\",[\"f32:3204448256\",\"f32:2147483649\"]],[\"invoke\",123,0,\"copysign\",[\"f32:3204448256\",\"f32:1\"]],[\"invoke\",124,0,\"copysign\",[\"f32:1056964608\",\"f32:2147483649\"]],[\"invoke\",125,0,\"copysign\",[\"f32:1056964608\",\"f32:1\"]],[\"invoke\",126,0,\"copysign\",[\"f32:3204448256\",\"f32:2155872256\"]],[\"invoke\",127,0,\"copysign\",[\"f32:3204448256\",\"f32:8388608\"]],[\"invoke\",128,0,\"copysign\",[\"f32:1056964608\",\"f32:2155872256\"]],[\"invoke\",129,0,\"copysign\",[\"f32:1056964608\",\"f32:8388608\"]],[\"invoke\",130,0,\"copysign\",[\"f32:3204448256\",\"f32:3204448256\"]],[\"invoke\",131,0,\"copysign\",[\"f32:3204448256\",\"f32:1056964608\"]],[\"invoke\",132,0,\"copysign\",[\"f32:1056964608\",\"f32:3204448256\"]],[\"invoke\",133,0,\"copysign\",[\"f32:1056964608\",\"f32:1056964608\"]],[\"invoke\",134,0,\"copysign\",[\"f32:3204448256\",\"f32:3212836864\"]],[\"invoke\",135,0,\"copysign\",[\"f32:3204448256\",\"f32:1065353216\"]],[\"invoke\",136,0,\"copysign\",[\"f32:1056964608\",\"f32:3212836864\"]],[\"invoke\",137,0,\"copysign\",[\"f32:1056964608\",\"f32:1065353216\"]],[\"invoke\",138,0,\"copysign\",[\"f32:3204448256\",\"f32:3234402267\"]],[\"invoke\",139,0,\"copysign\",[\"f32:3204448256\",\"f32:1086918619\"]],[\"invoke\",140,0,\"copysign\",[\"f32:1056964608\",\"f32:3234402267\"]],[\"invoke\",141,0,\"copysign\",[\"f32:1056964608\",\"f32:1086918619\"]],[\"invoke\",142,0,\"copysign\",[\"f32:3204448256\",\"f32:4286578687\"]],[\"invoke\",143,0,\"copysign\",[\"f32:3204448256\",\"f32:2139095039\"]],[\"invoke\",144,0,\"copysign\",[\"f32:1056964608\",\"f32:4286578687\"]],[\"invoke\",145,0,\"copysign\",[\"f32:1056964608\",\"f32:2139095039\"]],[\"invoke\",146,0,\"copysign\",[\"f32:3204448256\",\"f32:4286578688\"]],[\"invoke\",147,0,\"copysign\",[\"f32:3204448256\",\"f32:2139095040\"]],[\"invoke\",148,0,\"copysign\",[\"f32:1056964608\",\"f32:4286578688\"]],[\"invoke\",149,0,\"copysign\",[\"f32:1056964608\",\"f32:2139095040\"]],[\"invoke\",150,0,\"copysign\",[\"f32:3204448256\",\"f32:4290772992\"]],[\"invoke\",151,0,\"copysign\",[\"f32:3204448256\",\"f32:2143289344\"]],[\"invoke\",152,0,\"copysign\",[\"f32:1056964608\",\"f32:4290772992\"]],[\"invoke\",153,0,\"copysign\",[\"f32:1056964608\",\"f32:2143289344\"]],[\"invoke\",154,0,\"copysign\",[\"f32:3212836864\",\"f32:2147483648\"]],[\"invoke\",155,0,\"copysign\",[\"f32:3212836864\",\"f32:0\"]],[\"invoke\",156,0,\"copysign\",[\"f32:1065353216\",\"f32:2147483648\"]],[\"invoke\",157,0,\"copysign\",[\"f32:1065353216\",\"f32:0\"]],[\"invoke\",158,0,\"copysign\",[\"f32:3212836864\",\"f32:2147483649\"]],[\"invoke\",159,0,\"copysign\",[\"f32:3212836864\",\"f32:1\"]],[\"invoke\",160,0,\"copysign\",[\"f32:1065353216\",\"f32:2147483649\"]],[\"invoke\",161,0,\"copysign\",[\"f32:1065353216\",\"f32:1\"]],[\"invoke\",162,0,\"copysign\",[\"f32:3212836864\",\"f32:2155872256\"]],[\"invoke\",163,0,\"copysign\",[\"f32:3212836864\",\"f32:8388608\"]],[\"invoke\",164,0,\"copysign\",[\"f32:1065353216\",\"f32:2155872256\"]],[\"invoke\",165,0,\"copysign\",[\"f32:1065353216\",\"f32:8388608\"]],[\"invoke\",166,0,\"copysign\",[\"f32:3212836864\",\"f32:3204448256\"]],[\"invoke\",167,0,\"copysign\",[\"f32:3212836864\",\"f32:1056964608\"]],[\"invoke\",168,0,\"copysign\",[\"f32:1065353216\",\"f32:3204448256\"]],[\"invoke\",169,0,\"copysign\",[\"f32:1065353216\",\"f32:1056964608\"]],[\"invoke\",170,0,\"copysign\",[\"f32:3212836864\",\"f32:3212836864\"]],[\"invoke\",171,0,\"copysign\",[\"f32:3212836864\",\"f32:1065353216\"]],[\"invoke\",172,0,\"copysign\",[\"f32:1065353216\",\"f32:3212836864\"]],[\"invoke\",173,0,\"copysign\",[\"f32:1065353216\",\"f32:1065353216\"]],[\"invoke\",174,0,\"copysign\",[\"f32:3212836864\",\"f32:3234402267\"]],[\"invoke\",175,0,\"copysign\",[\"f32:3212836864\",\"f32:1086918619\"]],[\"invoke\",176,0,\"copysign\",[\"f32:1065353216\",\"f32:3234402267\"]],[\"invoke\",177,0,\"copysign\",[\"f32:1065353216\",\"f32:1086918619\"]],[\"invoke\",178,0,\"copysign\",[\"f32:3212836864\",\"f32:4286578687\"]],[\"invoke\",179,0,\"copysign\",[\"f32:3212836864\",\"f32:2139095039\"]],[\"invoke\",180,0,\"copysign\",[\"f32:1065353216\",\"f32:4286578687\"]],[\"invoke\",181,0,\"copysign\",[\"f32:1065353216\",\"f32:2139095039\"]],[\"invoke\",182,0,\"copysign\",[\"f32:3212836864\",\"f32:4286578688\"]],[\"invoke\",183,0,\"copysign\",[\"f32:3212836864\",\"f32:2139095040\"]],[\"invoke\",184,0,\"copysign\",[\"f32:1065353216\",\"f32:4286578688\"]],[\"invoke\",185,0,\"copysign\",[\"f32:1065353216\",\"f32:2139095040\"]],[\"invoke\",186,0,\"copysign\",[\"f32:3212836864\",\"f32:4290772992\"]],[\"invoke\",187,0,\"copysign\",[\"f32:3212836864\",\"f32:2143289344\"]],[\"invoke\",188,0,\"copysign\",[\"f32:1065353216\",\"f32:4290772992\"]],[\"invoke\",189,0,\"copysign\",[\"f32:1065353216\",\"f32:2143289344\"]],[\"invoke\",190,0,\"copysign\",[\"f32:3234402267\",\"f32:2147483648\"]],[\"invoke\",191,0,\"copysign\",[\"f32:3234402267\",\"f32:0\"]],[\"invoke\",192,0,\"copysign\",[\"f32:1086918619\",\"f32:2147483648\"]],[\"invoke\",193,0,\"copysign\",[\"f32:1086918619\",\"f32:0\"]],[\"invoke\",194,0,\"copysign\",[\"f32:3234402267\",\"f32:2147483649\"]],[\"invoke\",195,0,\"copysign\",[\"f32:3234402267\",\"f32:1\"]],[\"invoke\",196,0,\"copysign\",[\"f32:1086918619\",\"f32:2147483649\"]],[\"invoke\",197,0,\"copysign\",[\"f32:1086918619\",\"f32:1\"]],[\"invoke\",198,0,\"copysign\",[\"f32:3234402267\",\"f32:2155872256\"]],[\"invoke\",199,0,\"copysign\",[\"f32:3234402267\",\"f32:8388608\"]],[\"invoke\",200,0,\"copysign\",[\"f32:1086918619\",\"f32:2155872256\"]],[\"invoke\",201,0,\"copysign\",[\"f32:1086918619\",\"f32:8388608\"]],[\"invoke\",202,0,\"copysign\",[\"f32:3234402267\",\"f32:3204448256\"]],[\"invoke\",203,0,\"copysign\",[\"f32:3234402267\",\"f32:1056964608\"]],[\"invoke\",204,0,\"copysign\",[\"f32:1086918619\",\"f32:3204448256\"]],[\"invoke\",205,0,\"copysign\",[\"f32:1086918619\",\"f32:1056964608\"]],[\"invoke\",206,0,\"copysign\",[\"f32:3234402267\",\"f32:3212836864\"]],[\"invoke\",207,0,\"copysign\",[\"f32:3234402267\",\"f32:1065353216\"]],[\"invoke\",208,0,\"copysign\",[\"f32:1086918619\",\"f32:3212836864\"]],[\"invoke\",209,0,\"copysign\",[\"f32:1086918619\",\"f32:1065353216\"]],[\"invoke\",210,0,\"copysign\",[\"f32:3234402267\",\"f32:3234402267\"]],[\"invoke\",211,0,\"copysign\",[\"f32:3234402267\",\"f32:1086918619\"]],[\"invoke\",212,0,\"copysign\",[\"f32:1086918619\",\"f32:3234402267\"]],[\"invoke\",213,0,\"copysign\",[\"f32:1086918619\",\"f32:1086918619\"]],[\"invoke\",214,0,\"copysign\",[\"f32:3234402267\",\"f32:4286578687\"]],[\"invoke\",215,0,\"copysign\",[\"f32:3234402267\",\"f32:2139095039\"]],[\"invoke\",216,0,\"copysign\",[\"f32:1086918619\",\"f32:4286578687\"]],[\"invoke\",217,0,\"copysign\",[\"f32:1086918619\",\"f32:2139095039\"]],[\"invoke\",218,0,\"copysign\",[\"f32:3234402267\",\"f32:4286578688\"]],[\"invoke\",219,0,\"copysign\",[\"f32:3234402267\",\"f32:2139095040\"]],[\"invoke\",220,0,\"copysign\",[\"f32:1086918619\",\"f32:4286578688\"]],[\"invoke\",221,0,\"copysign\",[\"f32:1086918619\",\"f32:2139095040\"]],[\"invoke\",222,0,\"copysign\",[\"f32:3234402267\",\"f32:4290772992\"]],[\"invoke\",223,0,\"copysign\",[\"f32:3234402267\",\"f32:2143289344\"]],[\"invoke\",224,0,\"copysign\",[\"f32:1086918619\",\"f32:4290772992\"]],[\"invoke\",225,0,\"copysign\",[\"f32:1086918619\",\"f32:2143289344\"]],[\"invoke\",226,0,\"copysign\",[\"f32:4286578687\",\"f32:2147483648\"]],[\"invoke\",227,0,\"copysign\",[\"f32:4286578687\",\"f32:0\"]],[\"invoke\",228,0,\"copysign\",[\"f32:2139095039\",\"f32:2147483648\"]],[\"invoke\",229,0,\"copysign\",[\"f32:2139095039\",\"f32:0\"]],[\"invoke\",230,0,\"copysign\",[\"f32:4286578687\",\"f32:2147483649\"]],[\"invoke\",231,0,\"copysign\",[\"f32:4286578687\",\"f32:1\"]],[\"invoke\",232,0,\"copysign\",[\"f32:2139095039\",\"f32:2147483649\"]],[\"invoke\",233,0,\"copysign\",[\"f32:2139095039\",\"f32:1\"]],[\"invoke\",234,0,\"copysign\",[\"f32:4286578687\",\"f32:2155872256\"]],[\"invoke\",235,0,\"copysign\",[\"f32:4286578687\",\"f32:8388608\"]],[\"invoke\",236,0,\"copysign\",[\"f32:2139095039\",\"f32:2155872256\"]],[\"invoke\",237,0,\"copysign\",[\"f32:2139095039\",\"f32:8388608\"]],[\"invoke\",238,0,\"copysign\",[\"f32:4286578687\",\"f32:3204448256\"]],[\"invoke\",239,0,\"copysign\",[\"f32:4286578687\",\"f32:1056964608\"]],[\"invoke\",240,0,\"copysign\",[\"f32:2139095039\",\"f32:3204448256\"]],[\"invoke\",241,0,\"copysign\",[\"f32:2139095039\",\"f32:1056964608\"]],[\"invoke\",242,0,\"copysign\",[\"f32:4286578687\",\"f32:3212836864\"]],[\"invoke\",243,0,\"copysign\",[\"f32:4286578687\",\"f32:1065353216\"]],[\"invoke\",244,0,\"copysign\",[\"f32:2139095039\",\"f32:3212836864\"]],[\"invoke\",245,0,\"copysign\",[\"f32:2139095039\",\"f32:1065353216\"]],[\"invoke\",246,0,\"copysign\",[\"f32:4286578687\",\"f32:3234402267\"]],[\"invoke\",247,0,\"copysign\",[\"f32:4286578687\",\"f32:1086918619\"]],[\"invoke\",248,0,\"copysign\",[\"f32:2139095039\",\"f32:3234402267\"]],[\"invoke\",249,0,\"copysign\",[\"f32:2139095039\",\"f32:1086918619\"]],[\"invoke\",250,0,\"copysign\",[\"f32:4286578687\",\"f32:4286578687\"]],[\"invoke\",251,0,\"copysign\",[\"f32:4286578687\",\"f32:2139095039\"]],[\"invoke\",252,0,\"copysign\",[\"f32:2139095039\",\"f32:4286578687\"]],[\"invoke\",253,0,\"copysign\",[\"f32:2139095039\",\"f32:2139095039\"]],[\"invoke\",254,0,\"copysign\",[\"f32:4286578687\",\"f32:4286578688\"]],[\"invoke\",255,0,\"copysign\",[\"f32:4286578687\",\"f32:2139095040\"]],[\"invoke\",256,0,\"copysign\",[\"f32:2139095039\",\"f32:4286578688\"]],[\"invoke\",257,0,\"copysign\",[\"f32:2139095039\",\"f32:2139095040\"]],[\"invoke\",258,0,\"copysign\",[\"f32:4286578687\",\"f32:4290772992\"]],[\"invoke\",259,0,\"copysign\",[\"f32:4286578687\",\"f32:2143289344\"]],[\"invoke\",260,0,\"copysign\",[\"f32:2139095039\",\"f32:4290772992\"]],[\"invoke\",261,0,\"copysign\",[\"f32:2139095039\",\"f32:2143289344\"]],[\"invoke\",262,0,\"copysign\",[\"f32:4286578688\",\"f32:2147483648\"]],[\"invoke\",263,0,\"copysign\",[\"f32:4286578688\",\"f32:0\"]],[\"invoke\",264,0,\"copysign\",[\"f32:2139095040\",\"f32:2147483648\"]],[\"invoke\",265,0,\"copysign\",[\"f32:2139095040\",\"f32:0\"]],[\"invoke\",266,0,\"copysign\",[\"f32:4286578688\",\"f32:2147483649\"]],[\"invoke\",267,0,\"copysign\",[\"f32:4286578688\",\"f32:1\"]],[\"invoke\",268,0,\"copysign\",[\"f32:2139095040\",\"f32:2147483649\"]],[\"invoke\",269,0,\"copysign\",[\"f32:2139095040\",\"f32:1\"]],[\"invoke\",270,0,\"copysign\",[\"f32:4286578688\",\"f32:2155872256\"]],[\"invoke\",271,0,\"copysign\",[\"f32:4286578688\",\"f32:8388608\"]],[\"invoke\",272,0,\"copysign\",[\"f32:2139095040\",\"f32:2155872256\"]],[\"invoke\",273,0,\"copysign\",[\"f32:2139095040\",\"f32:8388608\"]],[\"invoke\",274,0,\"copysign\",[\"f32:4286578688\",\"f32:3204448256\"]],[\"invoke\",275,0,\"copysign\",[\"f32:4286578688\",\"f32:1056964608\"]],[\"invoke\",276,0,\"copysign\",[\"f32:2139095040\",\"f32:3204448256\"]],[\"invoke\",277,0,\"copysign\",[\"f32:2139095040\",\"f32:1056964608\"]],[\"invoke\",278,0,\"copysign\",[\"f32:4286578688\",\"f32:3212836864\"]],[\"invoke\",279,0,\"copysign\",[\"f32:4286578688\",\"f32:1065353216\"]],[\"invoke\",280,0,\"copysign\",[\"f32:2139095040\",\"f32:3212836864\"]],[\"invoke\",281,0,\"copysign\",[\"f32:2139095040\",\"f32:1065353216\"]],[\"invoke\",282,0,\"copysign\",[\"f32:4286578688\",\"f32:3234402267\"]],[\"invoke\",283,0,\"copysign\",[\"f32:4286578688\",\"f32:1086918619\"]],[\"invoke\",284,0,\"copysign\",[\"f32:2139095040\",\"f32:3234402267\"]],[\"invoke\",285,0,\"copysign\",[\"f32:2139095040\",\"f32:1086918619\"]],[\"invoke\",286,0,\"copysign\",[\"f32:4286578688\",\"f32:4286578687\"]],[\"invoke\",287,0,\"copysign\",[\"f32:4286578688\",\"f32:2139095039\"]],[\"invoke\",288,0,\"copysign\",[\"f32:2139095040\",\"f32:4286578687\"]],[\"invoke\",289,0,\"copysign\",[\"f32:2139095040\",\"f32:2139095039\"]],[\"invoke\",290,0,\"copysign\",[\"f32:4286578688\",\"f32:4286578688\"]],[\"invoke\",291,0,\"copysign\",[\"f32:4286578688\",\"f32:2139095040\"]],[\"invoke\",292,0,\"copysign\",[\"f32:2139095040\",\"f32:4286578688\"]],[\"invoke\",293,0,\"copysign\",[\"f32:2139095040\",\"f32:2139095040\"]],[\"invoke\",294,0,\"copysign\",[\"f32:4286578688\",\"f32:4290772992\"]],[\"invoke\",295,0,\"copysign\",[\"f32:4286578688\",\"f32:2143289344\"]],[\"invoke\",296,0,\"copysign\",[\"f32:2139095040\",\"f32:4290772992\"]],[\"invoke\",297,0,\"copysign\",[\"f32:2139095040\",\"f32:2143289344\"]],[\"invoke\",298,0,\"copysign\",[\"f32:4290772992\",\"f32:2147483648\"]],[\"invoke\",299,0,\"copysign\",[\"f32:4290772992\",\"f32:0\"]],[\"invoke\",300,0,\"copysign\",[\"f32:2143289344\",\"f32:2147483648\"]],[\"invoke\",301,0,\"copysign\",[\"f32:2143289344\",\"f32:0\"]],[\"invoke\",302,0,\"copysign\",[\"f32:4290772992\",\"f32:2147483649\"]],[\"invoke\",303,0,\"copysign\",[\"f32:4290772992\",\"f32:1\"]],[\"invoke\",304,0,\"copysign\",[\"f32:2143289344\",\"f32:2147483649\"]],[\"invoke\",305,0,\"copysign\",[\"f32:2143289344\",\"f32:1\"]],[\"invoke\",306,0,\"copysign\",[\"f32:4290772992\",\"f32:2155872256\"]],[\"invoke\",307,0,\"copysign\",[\"f32:4290772992\",\"f32:8388608\"]],[\"invoke\",308,0,\"copysign\",[\"f32:2143289344\",\"f32:2155872256\"]],[\"invoke\",309,0,\"copysign\",[\"f32:2143289344\",\"f32:8388608\"]],[\"invoke\",310,0,\"copysign\",[\"f32:4290772992\",\"f32:3204448256\"]],[\"invoke\",311,0,\"copysign\",[\"f32:4290772992\",\"f32:1056964608\"]],[\"invoke\",312,0,\"copysign\",[\"f32:2143289344\",\"f32:3204448256\"]],[\"invoke\",313,0,\"copysign\",[\"f32:2143289344\",\"f32:1056964608\"]],[\"invoke\",314,0,\"copysign\",[\"f32:4290772992\",\"f32:3212836864\"]],[\"invoke\",315,0,\"copysign\",[\"f32:4290772992\",\"f32:1065353216\"]],[\"invoke\",316,0,\"copysign\",[\"f32:2143289344\",\"f32:3212836864\"]],[\"invoke\",317,0,\"copysign\",[\"f32:2143289344\",\"f32:1065353216\"]],[\"invoke\",318,0,\"copysign\",[\"f32:4290772992\",\"f32:3234402267\"]],[\"invoke\",319,0,\"copysign\",[\"f32:4290772992\",\"f32:1086918619\"]],[\"invoke\",320,0,\"copysign\",[\"f32:2143289344\",\"f32:3234402267\"]],[\"invoke\",321,0,\"copysign\",[\"f32:2143289344\",\"f32:1086918619\"]],[\"invoke\",322,0,\"copysign\",[\"f32:4290772992\",\"f32:4286578687\"]],[\"invoke\",323,0,\"copysign\",[\"f32:4290772992\",\"f32:2139095039\"]],[\"invoke\",324,0,\"copysign\",[\"f32:2143289344\",\"f32:4286578687\"]],[\"invoke\",325,0,\"copysign\",[\"f32:2143289344\",\"f32:2139095039\"]],[\"invoke\",326,0,\"copysign\",[\"f32:4290772992\",\"f32:4286578688\"]],[\"invoke\",327,0,\"copysign\",[\"f32:4290772992\",\"f32:2139095040\"]],[\"invoke\",328,0,\"copysign\",[\"f32:2143289344\",\"f32:4286578688\"]],[\"invoke\",329,0,\"copysign\",[\"f32:2143289344\",\"f32:2139095040\"]],[\"invoke\",330,0,\"copysign\",[\"f32:4290772992\",\"f32:4290772992\"]],[\"invoke\",331,0,\"copysign\",[\"f32:4290772992\",\"f32:2143289344\"]],[\"invoke\",332,0,\"copysign\",[\"f32:2143289344\",\"f32:4290772992\"]],[\"invoke\",333,0,\"copysign\",[\"f32:2143289344\",\"f32:2143289344\"]],[\"invoke\",334,0,\"abs\",[\"f32:2147483648\"]],[\"invoke\",335,0,\"abs\",[\"f32:0\"]],[\"invoke\",336,0,\"abs\",[\"f32:2147483649\"]],[\"invoke\",337,0,\"abs\",[\"f32:1\"]],[\"invoke\",338,0,\"abs\",[\"f32:2155872256\"]],[\"invoke\",339,0,\"abs\",[\"f32:8388608\"]],[\"invoke\",340,0,\"abs\",[\"f32:3204448256\"]],[\"invoke\",341,0,\"abs\",[\"f32:1056964608\"]],[\"invoke\",342,0,\"abs\",[\"f32:3212836864\"]],[\"invoke\",343,0,\"abs\",[\"f32:1065353216\"]],[\"invoke\",344,0,\"abs\",[\"f32:3234402267\"]],[\"invoke\",345,0,\"abs\",[\"f32:1086918619\"]],[\"invoke\",346,0,\"abs\",[\"f32:4286578687\"]],[\"invoke\",347,0,\"abs\",[\"f32:2139095039\"]],[\"invoke\",348,0,\"abs\",[\"f32:4286578688\"]],[\"invoke\",349,0,\"abs\",[\"f32:2139095040\"]],[\"invoke\",350,0,\"abs\",[\"f32:4290772992\"]],[\"invoke\",351,0,\"abs\",[\"f32:2143289344\"]],[\"invoke\",352,0,\"neg\",[\"f32:2147483648\"]],[\"invoke\",353,0,\"neg\",[\"f32:0\"]],[\"invoke\",354,0,\"neg\",[\"f32:2147483649\"]],[\"invoke\",355,0,\"neg\",[\"f32:1\"]],[\"invoke\",356,0,\"neg\",[\"f32:2155872256\"]],[\"invoke\",357,0,\"neg\",[\"f32:8388608\"]],[\"invoke\",358,0,\"neg\",[\"f32:3204448256\"]],[\"invoke\",359,0,\"neg\",[\"f32:1056964608\"]],[\"invoke\",360,0,\"neg\",[\"f32:3212836864\"]],[\"invoke\",361,0,\"neg\",[\"f32:1065353216\"]],[\"invoke\",362,0,\"neg\",[\"f32:3234402267\"]],[\"invoke\",363,0,\"neg\",[\"f32:1086918619\"]],[\"invoke\",364,0,\"neg\",[\"f32:4286578687\"]],[\"invoke\",365,0,\"neg\",[\"f32:2139095039\"]],[\"invoke\",366,0,\"neg\",[\"f32:4286578688\"]],[\"invoke\",367,0,\"neg\",[\"f32:2139095040\"]],[\"invoke\",368,0,\"neg\",[\"f32:4290772992\"]],[\"invoke\",369,0,\"neg\",[\"f32:2143289344\"]],[\"file\",\"f64_bitwise.wast\"],[\"module\",4,5],[\"invoke\",10,0,\"copysign\",[\"f64:9223372036854775808\",\"f64:9223372036854775808\"]],[\"invoke\",11,0,\"copysign\",[\"f64:9223372036854775808\",\"f64:0\"]],[\"invoke\",12,0,\"copysign\",[\"f64:0\",\"f64:9223372036854775808\"]],[\"invoke\",13,0,\"copysign\",[\"f64:0\",\"f64:0\"]],[\"invoke\",14,0,\"copysign\",[\"f64:9223372036854775808\",\"f64:9223372036854775809\"]],[\"invoke\",15,0,\"copysign\",[\"f64:9223372036854775808\",\"f64:1\"]],[\"invoke\",16,0,\"copysign\",[\"f64:0\",\"f64:9223372036854775809\"]],[\"invoke\",17,0,\"copysign\",[\"f64:0\",\"f64:1\"]],[\"invoke\",18,0,\"copysign\",[\"f64:9223372036854775808\",\"f64:9227875636482146304\"]],[\"invoke\",19,0,\"copysign\",[\"f64:9223372036854775808\",\"f64:4503599627370496\"]],[\"invoke\",20,0,\"copysign\",[\"f64:0\",\"f64:9227875636482146304\"]],[\"invoke\",21,0,\"copysign\",[\"f64:0\",\"f64:4503599627370496\"]],[\"invoke\",22,0,\"copysign\",[\"f64:9223372036854775808\",\"f64:13826050856027422720\"]],[\"invoke\",23,0,\"copysign\",[\"f64:9223372036854775808\",\"f64:4602678819172646912\"]],[\"invoke\",24,0,\"copysign\",[\"f64:0\",\"f64:13826050856027422720\"]],[\"invoke\",25,0,\"copysign\",[\"f64:0\",\"f64:4602678819172646912\"]],[\"invoke\",26,0,\"copysign\",[\"f64:9223372036854775808\",\"f64:13830554455654793216\"]],[\"invoke\",27,0,\"copysign\",[\"f64:9223372036854775808\",\"f64:4607182418800017408\"]],[\"invoke\",28,0,\"copysign\",[\"f64:0\",\"f64:13830554455654793216\"]],[\"invoke\",29,0,\"copysign\",[\"f64:0\",\"f64:4607182418800017408\"]],[\"invoke\",30,0,\"copysign\",[\"f64:9223372036854775808\",\"f64:13842132293034192152\"]],[\"invoke\",31,0,\"copysign\",[\"f64:9223372036854775808\",\"f64:4618760256179416344\"]],[\"invoke\",32,0,\"copysign\",[\"f64:0\",\"f64:13842132293034192152\"]],[\"invoke\",33,0,\"copysign\",[\"f64:0\",\"f64:4618760256179416344\"]],[\"invoke\",34,0,\"copysign\",[\"f64:9223372036854775808\",\"f64:18442240474082181119\"]],[\"invoke\",35,0,\"copysign\",[\"f64:9223372036854775808\",\"f64:9218868437227405311\"]],[\"invoke\",36,0,\"copysign\",[\"f64:0\",\"f64:18442240474082181119\"]],[\"invoke\",37,0,\"copysign\",[\"f64:0\",\"f64:9218868437227405311\"]],[\"invoke\",38,0,\"copysign\",[\"f64:9223372036854775808\",\"f64:18442240474082181120\"]],[\"invoke\",39,0,\"copysign\",[\"f64:9223372036854775808\",\"f64:9218868437227405312\"]],[\"invoke\",40,0,\"copysign\",[\"f64:0\",\"f64:18442240474082181120\"]],[\"invoke\",41,0,\"copysign\",[\"f64:0\",\"f64:9218868437227405312\"]],[\"invoke\",42,0,\"copysign\",[\"f64:9223372036854775808\",\"f64:18444492273895866368\"]],[\"invoke\",43,0,\"copysign\",[\"f64:9223372036854775808\",\"f64:9221120237041090560\"]],[\"invoke\",44,0,\"copysign\",[\"f64:0\",\"f64:18444492273895866368\"]],[\"invoke\",45,0,\"copysign\",[\"f64:0\",\"f64:9221120237041090560\"]],[\"invoke\",46,0,\"copysign\",[\"f64:9223372036854775809\",\"f64:9223372036854775808\"]],[\"invoke\",47,0,\"copysign\",[\"f64:9223372036854775809\",\"f64:0\"]],[\"invoke\",48,0,\"copysign\",[\"f64:1\",\"f64:9223372036854775808\"]],[\"invoke\",49,0,\"copysign\",[\"f64:1\",\"f64:0\"]],[\"invoke\",50,0,\"copysign\",[\"f64:9223372036854775809\",\"f64:9223372036854775809\"]],[\"invoke\",51,0,\"copysign\",[\"f64:9223372036854775809\",\"f64:1\"]],[\"invoke\",52,0,\"copysign\",[\"f64:1\",\"f64:9223372036854775809\"]],[\"invoke\",53,0,\"copysign\",[\"f64:1\",\"f64:1\"]],[\"invoke\",54,0,\"copysign\",[\"f64:9223372036854775809\",\"f64:9227875636482146304\"]],[\"invoke\",55,0,\"copysign\",[\"f64:9223372036854775809\",\"f64:4503599627370496\"]],[\"invoke\",56,0,\"copysign\",[\"f64:1\",\"f64:9227875636482146304\"]],[\"invoke\",57,0,\"copysign\",[\"f64:1\",\"f64:4503599627370496\"]],[\"invoke\",58,0,\"copysign\",[\"f64:9223372036854775809\",\"f64:13826050856027422720\"]],[\"invoke\",59,0,\"copysign\",[\"f64:9223372036854775809\",\"f64:4602678819172646912\"]],[\"invoke\",60,0,\"copysign\",[\"f64:1\",\"f64:13826050856027422720\"]],[\"invoke\",61,0,\"copysign\",[\"f64:1\",\"f64:4602678819172646912\"]],[\"invoke\",62,0,\"copysign\",[\"f64:9223372036854775809\",\"f64:13830554455654793216\"]],[\"invoke\",63,0,\"copysign\",[\"f64:9223372036854775809\",\"f64:4607182418800017408\"]],[\"invoke\",64,0,\"copysign\",[\"f64:1\",\"f64:13830554455654793216\"]],[\"invoke\",65,0,\"copysign\",[\"f64:1\",\"f64:4607182418800017408\"]],[\"invoke\",66,0,\"copysign\",[\"f64:9223372036854775809\",\"f64:13842132293034192152\"]],[\"invoke\",67,0,\"copysign\",[\"f64:9223372036854775809\",\"f64:4618760256179416344\"]],[\"invoke\",68,0,\"copysign\",[\"f64:1\",\"f64:13842132293034192152\"]],[\"invoke\",69,0,\"copysign\",[\"f64:1\",\"f64:4618760256179416344\"]],[\"invoke\",70,0,\"copysign\",[\"f64:9223372036854775809\",\"f64:18442240474082181119\"]],[\"invoke\",71,0,\"copysign\",[\"f64:9223372036854775809\",\"f64:9218868437227405311\"]],[\"invoke\",72,0,\"copysign\",[\"f64:1\",\"f64:18442240474082181119\"]],[\"invoke\",73,0,\"copysign\",[\"f64:1\",\"f64:9218868437227405311\"]],[\"invoke\",74,0,\"copysign\",[\"f64:9223372036854775809\",\"f64:18442240474082181120\"]],[\"invoke\",75,0,\"copysign\",[\"f64:9223372036854775809\",\"f64:9218868437227405312\"]],[\"invoke\",76,0,\"copysign\",[\"f64:1\",\"f64:18442240474082181120\"]],[\"invoke\",77,0,\"copysign\",[\"f64:1\",\"f64:9218868437227405312\"]],[\"invoke\",78,0,\"copysign\",[\"f64:9223372036854775809\",\"f64:18444492273895866368\"]],[\"invoke\",79,0,\"copysign\",[\"f64:9223372036854775809\",\"f64:9221120237041090560\"]],[\"invoke\",80,0,\"copysign\",[\"f64:1\",\"f64:18444492273895866368\"]],[\"invoke\",81,0,\"copysign\",[\"f64:1\",\"f64:9221120237041090560\"]],[\"invoke\",82,0,\"copysign\",[\"f64:9227875636482146304\",\"f64:9223372036854775808\"]],[\"invoke\",83,0,\"copysign\",[\"f64:9227875636482146304\",\"f64:0\"]],[\"invoke\",84,0,\"copysign\",[\"f64:4503599627370496\",\"f64:9223372036854775808\"]],[\"invoke\",85,0,\"copysign\",[\"f64:4503599627370496\",\"f64:0\"]],[\"invoke\",86,0,\"copysign\",[\"f64:9227875636482146304\",\"f64:9223372036854775809\"]],[\"invoke\",87,0,\"copysign\",[\"f64:9227875636482146304\",\"f64:1\"]],[\"invoke\",88,0,\"copysign\",[\"f64:4503599627370496\",\"f64:9223372036854775809\"]],[\"invoke\",89,0,\"copysign\",[\"f64:4503599627370496\",\"f64:1\"]],[\"invoke\",90,0,\"copysign\",[\"f64:9227875636482146304\",\"f64:9227875636482146304\"]],[\"invoke\",91,0,\"copysign\",[\"f64:9227875636482146304\",\"f64:4503599627370496\"]],[\"invoke\",92,0,\"copysign\",[\"f64:4503599627370496\",\"f64:9227875636482146304\"]],[\"invoke\",93,0,\"copysign\",[\"f64:4503599627370496\",\"f64:4503599627370496\"]],[\"invoke\",94,0,\"copysign\",[\"f64:9227875636482146304\",\"f64:13826050856027422720\"]],[\"invoke\",95,0,\"copysign\",[\"f64:9227875636482146304\",\"f64:4602678819172646912\"]],[\"invoke\",96,0,\"copysign\",[\"f64:4503599627370496\",\"f64:13826050856027422720\"]],[\"invoke\",97,0,\"copysign\",[\"f64:4503599627370496\",\"f64:4602678819172646912\"]],[\"invoke\",98,0,\"copysign\",[\"f64:9227875636482146304\",\"f64:13830554455654793216\"]],[\"invoke\",99,0,\"copysign\",[\"f64:9227875636482146304\",\"f64:4607182418800017408\"]],[\"invoke\",100,0,\"copysign\",[\"f64:4503599627370496\",\"f64:13830554455654793216\"]],[\"invoke\",101,0,\"copysign\",[\"f64:4503599627370496\",\"f64:4607182418800017408\"]],[\"invoke\",102,0,\"copysign\",[\"f64:9227875636482146304\",\"f64:13842132293034192152\"]],[\"invoke\",103,0,\"copysign\",[\"f64:9227875636482146304\",\"f64:4618760256179416344\"]],[\"invoke\",104,0,\"copysign\",[\"f64:4503599627370496\",\"f64:13842132293034192152\"]],[\"invoke\",105,0,\"copysign\",[\"f64:4503599627370496\",\"f64:4618760256179416344\"]],[\"invoke\",106,0,\"copysign\",[\"f64:9227875636482146304\",\"f64:18442240474082181119\"]],[\"invoke\",107,0,\"copysign\",[\"f64:9227875636482146304\",\"f64:9218868437227405311\"]],[\"invoke\",108,0,\"copysign\",[\"f64:4503599627370496\",\"f64:18442240474082181119\"]],[\"invoke\",109,0,\"copysign\",[\"f64:4503599627370496\",\"f64:9218868437227405311\"]],[\"invoke\",110,0,\"copysign\",[\"f64:9227875636482146304\",\"f64:18442240474082181120\"]],[\"invoke\",111,0,\"copysign\",[\"f64:9227875636482146304\",\"f64:9218868437227405312\"]],[\"invoke\",112,0,\"copysign\",[\"f64:4503599627370496\",\"f64:18442240474082181120\"]],[\"invoke\",113,0,\"copysign\",[\"f64:4503599627370496\",\"f64:9218868437227405312\"]],[\"invoke\",114,0,\"copysign\",[\"f64:9227875636482146304\",\"f64:18444492273895866368\"]],[\"invoke\",115,0,\"copysign\",[\"f64:9227875636482146304\",\"f64:9221120237041090560\"]],[\"invoke\",116,0,\"copysign\",[\"f64:4503599627370496\",\"f64:18444492273895866368\"]],[\"invoke\",117,0,\"copysign\",[\"f64:4503599627370496\",\"f64:9221120237041090560\"]],[\"invoke\",118,0,\"copysign\",[\"f64:13826050856027422720\",\"f64:9223372036854775808\"]],[\"invoke\",119,0,\"copysign\",[\"f64:13826050856027422720\",\"f64:0\"]],[\"invoke\",120,0,\"copysign\",[\"f64:4602678819172646912\",\"f64:9223372036854775808\"]],[\"invoke\",121,0,\"copysign\",[\"f64:4602678819172646912\",\"f64:0\"]],[\"invoke\",122,0,\"copysign\",[\"f64:13826050856027422720\",\"f64:9223372036854775809\"]],[\"invoke\",123,0,\"copysign\",[\"f64:13826050856027422720\",\"f64:1\"]],[\"invoke\",124,0,\"copysign\",[\"f64:4602678819172646912\",\"f64:9223372036854775809\"]],[\"invoke\",125,0,\"copysign\",[\"f64:4602678819172646912\",\"f64:1\"]],[\"invoke\",126,0,\"copysign\",[\"f64:13826050856027422720\",\"f64:9227875636482146304\"]],[\"invoke\",127,0,\"copysign\",[\"f64:13826050856027422720\",\"f64:4503599627370496\"]],[\"invoke\",128,0,\"copysign\",[\"f64:4602678819172646912\",\"f64:9227875636482146304\"]],[\"invoke\",129,0,\"copysign\",[\"f64:4602678819172646912\",\"f64:4503599627370496\"]],[\"invoke\",130,0,\"copysign\",[\"f64:13826050856027422720\",\"f64:13826050856027422720\"]],[\"invoke\",131,0,\"copysign\",[\"f64:13826050856027422720\",\"f64:4602678819172646912\"]],[\"invoke\",132,0,\"copysign\",[\"f64:4602678819172646912\",\"f64:13826050856027422720\"]],[\"invoke\",133,0,\"copysign\",[\"f64:4602678819172646912\",\"f64:4602678819172646912\"]],[\"invoke\",134,0,\"copysign\",[\"f64:13826050856027422720\",\"f64:13830554455654793216\"]],[\"invoke\",135,0,\"copysign\",[\"f64:13826050856027422720\",\"f64:4607182418800017408\"]],[\"invoke\",136,0,\"copysign\",[\"f64:4602678819172646912\",\"f64:13830554455654793216\"]],[\"invoke\",137,0,\"copysign\",[\"f64:4602678819172646912\",\"f64:4607182418800017408\"]],[\"invoke\",138,0,\"copysign\",[\"f64:13826050856027422720\",\"f64:13842132293034192152\"]],[\"invoke\",139,0,\"copysign\",[\"f64:13826050856027422720\",\"f64:4618760256179416344\"]],[\"invoke\",140,0,\"copysign\",[\"f64:4602678819172646912\",\"f64:13842132293034192152\"]],[\"invoke\",141,0,\"copysign\",[\"f64:4602678819172646912\",\"f64:4618760256179416344\"]],[\"invoke\",142,0,\"copysign\",[\"f64:13826050856027422720\",\"f64:18442240474082181119\"]],[\"invoke\",143,0,\"copysign\",[\"f64:13826050856027422720\",\"f64:9218868437227405311\"]],[\"invoke\",144,0,\"copysign\",[\"f64:4602678819172646912\",\"f64:18442240474082181119\"]],[\"invoke\",145,0,\"copysign\",[\"f64:4602678819172646912\",\"f64:9218868437227405311\"]],[\"invoke\",146,0,\"copysign\",[\"f64:13826050856027422720\",\"f64:18442240474082181120\"]],[\"invoke\",147,0,\"copysign\",[\"f64:13826050856027422720\",\"f64:9218868437227405312\"]],[\"invoke\",148,0,\"copysign\",[\"f64:4602678819172646912\",\"f64:18442240474082181120\"]],[\"invoke\",149,0,\"copysign\",[\"f64:4602678819172646912\",\"f64:9218868437227405312\"]],[\"invoke\",150,0,\"copysign\",[\"f64:13826050856027422720\",\"f64:18444492273895866368\"]],[\"invoke\",151,0,\"copysign\",[\"f64:13826050856027422720\",\"f64:9221120237041090560\"]],[\"invoke\",152,0,\"copysign\",[\"f64:4602678819172646912\",\"f64:18444492273895866368\"]],[\"invoke\",153,0,\"copysign\",[\"f64:4602678819172646912\",\"f64:9221120237041090560\"]],[\"invoke\",154,0,\"copysign\",[\"f64:13830554455654793216\",\"f64:9223372036854775808\"]],[\"invoke\",155,0,\"copysign\",[\"f64:13830554455654793216\",\"f64:0\"]],[\"invoke\",156,0,\"copysign\",[\"f64:4607182418800017408\",\"f64:9223372036854775808\"]],[\"invoke\",157,0,\"copysign\",[\"f64:4607182418800017408\",\"f64:0\"]],[\"invoke\",158,0,\"copysign\",[\"f64:13830554455654793216\",\"f64:9223372036854775809\"]],[\"invoke\",159,0,\"copysign\",[\"f64:13830554455654793216\",\"f64:1\"]],[\"invoke\",160,0,\"copysign\",[\"f64:4607182418800017408\",\"f64:9223372036854775809\"]],[\"invoke\",161,0,\"copysign\",[\"f64:4607182418800017408\",\"f64:1\"]],[\"invoke\",162,0,\"copysign\",[\"f64:13830554455654793216\",\"f64:9227875636482146304\"]],[\"invoke\",163,0,\"copysign\",[\"f64:13830554455654793216\",\"f64:4503599627370496\"]],[\"invoke\",164,0,\"copysign\",[\"f64:4607182418800017408\",\"f64:9227875636482146304\"]],[\"invoke\",165,0,\"copysign\",[\"f64:4607182418800017408\",\"f64:4503599627370496\"]],[\"invoke\",166,0,\"copysign\",[\"f64:13830554455654793216\",\"f64:13826050856027422720\"]],[\"invoke\",167,0,\"copysign\",[\"f64:13830554455654793216\",\"f64:4602678819172646912\"]],[\"invoke\",168,0,\"copysign\",[\"f64:4607182418800017408\",\"f64:13826050856027422720\"]],[\"invoke\",169,0,\"copysign\",[\"f64:4607182418800017408\",\"f64:4602678819172646912\"]],[\"invoke\",170,0,\"copysign\",[\"f64:13830554455654793216\",\"f64:13830554455654793216\"]],[\"invoke\",171,0,\"copysign\",[\"f64:13830554455654793216\",\"f64:4607182418800017408\"]],[\"invoke\",172,0,\"copysign\",[\"f64:4607182418800017408\",\"f64:13830554455654793216\"]],[\"invoke\",173,0,\"copysign\",[\"f64:4607182418800017408\",\"f64:4607182418800017408\"]],[\"invoke\",174,0,\"copysign\",[\"f64:13830554455654793216\",\"f64:13842132293034192152\"]],[\"invoke\",175,0,\"copysign\",[\"f64:13830554455654793216\",\"f64:4618760256179416344\"]],[\"invoke\",176,0,\"copysign\",[\"f64:4607182418800017408\",\"f64:13842132293034192152\"]],[\"invoke\",177,0,\"copysign\",[\"f64:4607182418800017408\",\"f64:4618760256179416344\"]],[\"invoke\",178,0,\"copysign\",[\"f64:13830554455654793216\",\"f64:18442240474082181119\"]],[\"invoke\",179,0,\"copysign\",[\"f64:13830554455654793216\",\"f64:9218868437227405311\"]],[\"invoke\",180,0,\"copysign\",[\"f64:4607182418800017408\",\"f64:18442240474082181119\"]],[\"invoke\",181,0,\"copysign\",[\"f64:4607182418800017408\",\"f64:9218868437227405311\"]],[\"invoke\",182,0,\"copysign\",[\"f64:13830554455654793216\",\"f64:18442240474082181120\"]],[\"invoke\",183,0,\"copysign\",[\"f64:13830554455654793216\",\"f64:9218868437227405312\"]],[\"invoke\",184,0,\"copysign\",[\"f64:4607182418800017408\",\"f64:18442240474082181120\"]],[\"invoke\",185,0,\"copysign\",[\"f64:4607182418800017408\",\"f64:9218868437227405312\"]],[\"invoke\",186,0,\"copysign\",[\"f64:13830554455654793216\",\"f64:18444492273895866368\"]],[\"invoke\",187,0,\"copysign\",[\"f64:13830554455654793216\",\"f64:9221120237041090560\"]],[\"invoke\",188,0,\"copysign\",[\"f64:4607182418800017408\",\"f64:18444492273895866368\"]],[\"invoke\",189,0,\"copysign\",[\"f64:4607182418800017408\",\"f64:9221120237041090560\"]],[\"invoke\",190,0,\"copysign\",[\"f64:13842132293034192152\",\"f64:9223372036854775808\"]],[\"invoke\",191,0,\"copysign\",[\"f64:13842132293034192152\",\"f64:0\"]],[\"invoke\",192,0,\"copysign\",[\"f64:4618760256179416344\",\"f64:9223372036854775808\"]],[\"invoke\",193,0,\"copysign\",[\"f64:4618760256179416344\",\"f64:0\"]],[\"invoke\",194,0,\"copysign\",[\"f64:13842132293034192152\",\"f64:9223372036854775809\"]],[\"invoke\",195,0,\"copysign\",[\"f64:13842132293034192152\",\"f64:1\"]],[\"invoke\",196,0,\"copysign\",[\"f64:4618760256179416344\",\"f64:9223372036854775809\"]],[\"invoke\",197,0,\"copysign\",[\"f64:4618760256179416344\",\"f64:1\"]],[\"invoke\",198,0,\"copysign\",[\"f64:13842132293034192152\",\"f64:9227875636482146304\"]],[\"invoke\",199,0,\"copysign\",[\"f64:13842132293034192152\",\"f64:4503599627370496\"]],[\"invoke\",200,0,\"copysign\",[\"f64:4618760256179416344\",\"f64:9227875636482146304\"]],[\"invoke\",201,0,\"copysign\",[\"f64:4618760256179416344\",\"f64:4503599627370496\"]],[\"invoke\",202,0,\"copysign\",[\"f64:13842132293034192152\",\"f64:13826050856027422720\"]],[\"invoke\",203,0,\"copysign\",[\"f64:13842132293034192152\",\"f64:4602678819172646912\"]],[\"invoke\",204,0,\"copysign\",[\"f64:4618760256179416344\",\"f64:13826050856027422720\"]],[\"invoke\",205,0,\"copysign\",[\"f64:4618760256179416344\",\"f64:4602678819172646912\"]],[\"invoke\",206,0,\"copysign\",[\"f64:13842132293034192152\",\"f64:13830554455654793216\"]],[\"invoke\",207,0,\"copysign\",[\"f64:13842132293034192152\",\"f64:4607182418800017408\"]],[\"invoke\",208,0,\"copysign\",[\"f64:4618760256179416344\",\"f64:13830554455654793216\"]],[\"invoke\",209,0,\"copysign\",[\"f64:4618760256179416344\",\"f64:4607182418800017408\"]],[\"invoke\",210,0,\"copysign\",[\"f64:13842132293034192152\",\"f64:13842132293034192152\"]],[\"invoke\",211,0,\"copysign\",[\"f64:13842132293034192152\",\"f64:4618760256179416344\"]],[\"invoke\",212,0,\"copysign\",[\"f64:4618760256179416344\",\"f64:13842132293034192152\"]],[\"invoke\",213,0,\"copysign\",[\"f64:4618760256179416344\",\"f64:4618760256179416344\"]],[\"invoke\",214,0,\"copysign\",[\"f64:13842132293034192152\",\"f64:18442240474082181119\"]],[\"invoke\",215,0,\"copysign\",[\"f64:13842132293034192152\",\"f64:9218868437227405311\"]],[\"invoke\",216,0,\"copysign\",[\"f64:4618760256179416344\",\"f64:18442240474082181119\"]],[\"invoke\",217,0,\"copysign\",[\"f64:4618760256179416344\",\"f64:9218868437227405311\"]],[\"invoke\",218,0,\"copysign\",[\"f64:13842132293034192152\",\"f64:18442240474082181120\"]],[\"invoke\",219,0,\"copysign\",[\"f64:13842132293034192152\",\"f64:9218868437227405312\"]],[\"invoke\",220,0,\"copysign\",[\"f64:4618760256179416344\",\"f64:18442240474082181120\"]],[\"invoke\",221,0,\"copysign\",[\"f64:4618760256179416344\",\"f64:9218868437227405312\"]],[\"invoke\",222,0,\"copysign\",[\"f64:13842132293034192152\",\"f64:18444492273895866368\"]],[\"invoke\",223,0,\"copysign\",[\"f64:13842132293034192152\",\"f64:9221120237041090560\"]],[\"invoke\",224,0,\"copysign\",[\"f64:4618760256179416344\",\"f64:18444492273895866368\"]],[\"invoke\",225,0,\"copysign\",[\"f64:4618760256179416344\",\"f64:9221120237041090560\"]],[\"invoke\",226,0,\"copysign\",[\"f64:18442240474082181119\",\"f64:9223372036854775808\"]],[\"invoke\",227,0,\"copysign\",[\"f64:18442240474082181119\",\"f64:0\"]],[\"invoke\",228,0,\"copysign\",[\"f64:9218868437227405311\",\"f64:9223372036854775808\"]],[\"invoke\",229,0,\"copysign\",[\"f64:9218868437227405311\",\"f64:0\"]],[\"invoke\",230,0,\"copysign\",[\"f64:18442240474082181119\",\"f64:9223372036854775809\"]],[\"invoke\",231,0,\"copysign\",[\"f64:18442240474082181119\",\"f64:1\"]],[\"invoke\",232,0,\"copysign\",[\"f64:9218868437227405311\",\"f64:9223372036854775809\"]],[\"invoke\",233,0,\"copysign\",[\"f64:9218868437227405311\",\"f64:1\"]],[\"invoke\",234,0,\"copysign\",[\"f64:18442240474082181119\",\"f64:9227875636482146304\"]],[\"invoke\",235,0,\"copysign\",[\"f64:18442240474082181119\",\"f64:4503599627370496\"]],[\"invoke\",236,0,\"copysign\",[\"f64:9218868437227405311\",\"f64:9227875636482146304\"]],[\"invoke\",237,0,\"copysign\",[\"f64:9218868437227405311\",\"f64:4503599627370496\"]],[\"invoke\",238,0,\"copysign\",[\"f64:18442240474082181119\",\"f64:13826050856027422720\"]],[\"invoke\",239,0,\"copysign\",[\"f64:18442240474082181119\",\"f64:4602678819172646912\"]],[\"invoke\",240,0,\"copysign\",[\"f64:9218868437227405311\",\"f64:13826050856027422720\"]],[\"invoke\",241,0,\"copysign\",[\"f64:9218868437227405311\",\"f64:4602678819172646912\"]],[\"invoke\",242,0,\"copysign\",[\"f64:18442240474082181119\",\"f64:13830554455654793216\"]],[\"invoke\",243,0,\"copysign\",[\"f64:18442240474082181119\",\"f64:4607182418800017408\"]],[\"invoke\",244,0,\"copysign\",[\"f64:9218868437227405311\",\"f64:13830554455654793216\"]],[\"invoke\",245,0,\"copysign\",[\"f64:9218868437227405311\",\"f64:4607182418800017408\"]],[\"invoke\",246,0,\"copysign\",[\"f64:18442240474082181119\",\"f64:13842132293034192152\"]],[\"invoke\",247,0,\"copysign\",[\"f64:18442240474082181119\",\"f64:4618760256179416344\"]],[\"invoke\",248,0,\"copysign\",[\"f64:9218868437227405311\",\"f64:13842132293034192152\"]],[\"invoke\",249,0,\"copysign\",[\"f64:9218868437227405311\",\"f64:4618760256179416344\"]],[\"invoke\",250,0,\"copysign\",[\"f64:18442240474082181119\",\"f64:18442240474082181119\"]],[\"invoke\",251,0,\"copysign\",[\"f64:18442240474082181119\",\"f64:9218868437227405311\"]],[\"invoke\",252,0,\"copysign\",[\"f64:9218868437227405311\",\"f64:18442240474082181119\"]],[\"invoke\",253,0,\"copysign\",[\"f64:9218868437227405311\",\"f64:9218868437227405311\"]],[\"invoke\",254,0,\"copysign\",[\"f64:18442240474082181119\",\"f64:18442240474082181120\"]],[\"invoke\",255,0,\"copysign\",[\"f64:18442240474082181119\",\"f64:9218868437227405312\"]],[\"invoke\",256,0,\"copysign\",[\"f64:9218868437227405311\",\"f64:18442240474082181120\"]],[\"invoke\",257,0,\"copysign\",[\"f64:9218868437227405311\",\"f64:9218868437227405312\"]],[\"invoke\",258,0,\"copysign\",[\"f64:18442240474082181119\",\"f64:18444492273895866368\"]],[\"invoke\",259,0,\"copysign\",[\"f64:18442240474082181119\",\"f64:9221120237041090560\"]],[\"invoke\",260,0,\"copysign\",[\"f64:9218868437227405311\",\"f64:18444492273895866368\"]],[\"invoke\",261,0,\"copysign\",[\"f64:9218868437227405311\",\"f64:9221120237041090560\"]],[\"invoke\",262,0,\"copysign\",[\"f64:18442240474082181120\",\"f64:9223372036854775808\"]],[\"invoke\",263,0,\"copysign\",[\"f64:18442240474082181120\",\"f64:0\"]],[\"invoke\",264,0,\"copysign\",[\"f64:9218868437227405312\",\"f64:9223372036854775808\"]],[\"invoke\",265,0,\"copysign\",[\"f64:9218868437227405312\",\"f64:0\"]],[\"invoke\",266,0,\"copysign\",[\"f64:18442240474082181120\",\"f64:9223372036854775809\"]],[\"invoke\",267,0,\"copysign\",[\"f64:18442240474082181120\",\"f64:1\"]],[\"invoke\",268,0,\"copysign\",[\"f64:9218868437227405312\",\"f64:9223372036854775809\"]],[\"invoke\",269,0,\"copysign\",[\"f64:9218868437227405312\",\"f64:1\"]],[\"invoke\",270,0,\"copysign\",[\"f64:18442240474082181120\",\"f64:9227875636482146304\"]],[\"invoke\",271,0,\"copysign\",[\"f64:18442240474082181120\",\"f64:4503599627370496\"]],[\"invoke\",272,0,\"copysign\",[\"f64:9218868437227405312\",\"f64:9227875636482146304\"]],[\"invoke\",273,0,\"copysign\",[\"f64:9218868437227405312\",\"f64:4503599627370496\"]],[\"invoke\",274,0,\"copysign\",[\"f64:18442240474082181120\",\"f64:13826050856027422720\"]],[\"invoke\",275,0,\"copysign\",[\"f64:18442240474082181120\",\"f64:4602678819172646912\"]],[\"invoke\",276,0,\"copysign\",[\"f64:9218868437227405312\",\"f64:13826050856027422720\"]],[\"invoke\",277,0,\"copysign\",[\"f64:9218868437227405312\",\"f64:4602678819172646912\"]],[\"invoke\",278,0,\"copysign\",[\"f64:18442240474082181120\",\"f64:13830554455654793216\"]],[\"invoke\",279,0,\"copysign\",[\"f64:18442240474082181120\",\"f64:4607182418800017408\"]],[\"invoke\",280,0,\"copysign\",[\"f64:9218868437227405312\",\"f64:13830554455654793216\"]],[\"invoke\",281,0,\"copysign\",[\"f64:9218868437227405312\",\"f64:4607182418800017408\"]],[\"invoke\",282,0,\"copysign\",[\"f64:18442240474082181120\",\"f64:13842132293034192152\"]],[\"invoke\",283,0,\"copysign\",[\"f64:18442240474082181120\",\"f64:4618760256179416344\"]],[\"invoke\",284,0,\"copysign\",[\"f64:9218868437227405312\",\"f64:13842132293034192152\"]],[\"invoke\",285,0,\"copysign\",[\"f64:9218868437227405312\",\"f64:4618760256179416344\"]],[\"invoke\",286,0,\"copysign\",[\"f64:18442240474082181120\",\"f64:18442240474082181119\"]],[\"invoke\",287,0,\"copysign\",[\"f64:18442240474082181120\",\"f64:9218868437227405311\"]],[\"invoke\",288,0,\"copysign\",[\"f64:9218868437227405312\",\"f64:18442240474082181119\"]],[\"invoke\",289,0,\"copysign\",[\"f64:9218868437227405312\",\"f64:9218868437227405311\"]],[\"invoke\",290,0,\"copysign\",[\"f64:18442240474082181120\",\"f64:18442240474082181120\"]],[\"invoke\",291,0,\"copysign\",[\"f64:18442240474082181120\",\"f64:9218868437227405312\"]],[\"invoke\",292,0,\"copysign\",[\"f64:9218868437227405312\",\"f64:18442240474082181120\"]],[\"invoke\",293,0,\"copysign\",[\"f64:9218868437227405312\",\"f64:9218868437227405312\"]],[\"invoke\",294,0,\"copysign\",[\"f64:18442240474082181120\",\"f64:18444492273895866368\"]],[\"invoke\",295,0,\"copysign\",[\"f64:18442240474082181120\",\"f64:9221120237041090560\"]],[\"invoke\",296,0,\"copysign\",[\"f64:9218868437227405312\",\"f64:18444492273895866368\"]],[\"invoke\",297,0,\"copysign\",[\"f64:9218868437227405312\",\"f64:9221120237041090560\"]],[\"invoke\",298,0,\"copysign\",[\"f64:18444492273895866368\",\"f64:9223372036854775808\"]],[\"invoke\",299,0,\"copysign\",[\"f64:18444492273895866368\",\"f64:0\"]],[\"invoke\",300,0,\"copysign\",[\"f64:9221120237041090560\",\"f64:9223372036854775808\"]],[\"invoke\",301,0,\"copysign\",[\"f64:9221120237041090560\",\"f64:0\"]],[\"invoke\",302,0,\"copysign\",[\"f64:18444492273895866368\",\"f64:9223372036854775809\"]],[\"invoke\",303,0,\"copysign\",[\"f64:18444492273895866368\",\"f64:1\"]],[\"invoke\",304,0,\"copysign\",[\"f64:9221120237041090560\",\"f64:9223372036854775809\"]],[\"invoke\",305,0,\"copysign\",[\"f64:9221120237041090560\",\"f64:1\"]],[\"invoke\",306,0,\"copysign\",[\"f64:18444492273895866368\",\"f64:9227875636482146304\"]],[\"invoke\",307,0,\"copysign\",[\"f64:18444492273895866368\",\"f64:4503599627370496\"]],[\"invoke\",308,0,\"copysign\",[\"f64:9221120237041090560\",\"f64:9227875636482146304\"]],[\"invoke\",309,0,\"copysign\",[\"f64:9221120237041090560\",\"f64:4503599627370496\"]],[\"invoke\",310,0,\"copysign\",[\"f64:18444492273895866368\",\"f64:13826050856027422720\"]],[\"invoke\",311,0,\"copysign\",[\"f64:18444492273895866368\",\"f64:4602678819172646912\"]],[\"invoke\",312,0,\"copysign\",[\"f64:9221120237041090560\",\"f64:13826050856027422720\"]],[\"invoke\",313,0,\"copysign\",[\"f64:9221120237041090560\",\"f64:4602678819172646912\"]],[\"invoke\",314,0,\"copysign\",[\"f64:18444492273895866368\",\"f64:13830554455654793216\"]],[\"invoke\",315,0,\"copysign\",[\"f64:18444492273895866368\",\"f64:4607182418800017408\"]],[\"invoke\",316,0,\"copysign\",[\"f64:9221120237041090560\",\"f64:13830554455654793216\"]],[\"invoke\",317,0,\"copysign\",[\"f64:9221120237041090560\",\"f64:4607182418800017408\"]],[\"invoke\",318,0,\"copysign\",[\"f64:18444492273895866368\",\"f64:13842132293034192152\"]],[\"invoke\",319,0,\"copysign\",[\"f64:18444492273895866368\",\"f64:4618760256179416344\"]],[\"invoke\",320,0,\"copysign\",[\"f64:9221120237041090560\",\"f64:13842132293034192152\"]],[\"invoke\",321,0,\"copysign\",[\"f64:9221120237041090560\",\"f64:4618760256179416344\"]],[\"invoke\",322,0,\"copysign\",[\"f64:18444492273895866368\",\"f64:18442240474082181119\"]],[\"invoke\",323,0,\"copysign\",[\"f64:18444492273895866368\",\"f64:9218868437227405311\"]],[\"invoke\",324,0,\"copysign\",[\"f64:9221120237041090560\",\"f64:18442240474082181119\"]],[\"invoke\",325,0,\"copysign\",[\"f64:9221120237041090560\",\"f64:9218868437227405311\"]],[\"invoke\",326,0,\"copysign\",[\"f64:18444492273895866368\",\"f64:18442240474082181120\"]],[\"invoke\",327,0,\"copysign\",[\"f64:18444492273895866368\",\"f64:9218868437227405312\"]],[\"invoke\",328,0,\"copysign\",[\"f64:9221120237041090560\",\"f64:18442240474082181120\"]],[\"invoke\",329,0,\"copysign\",[\"f64:9221120237041090560\",\"f64:9218868437227405312\"]],[\"invoke\",330,0,\"copysign\",[\"f64:18444492273895866368\",\"f64:18444492273895866368\"]],[\"invoke\",331,0,\"copysign\",[\"f64:18444492273895866368\",\"f64:9221120237041090560\"]],[\"invoke\",332,0,\"copysign\",[\"f64:9221120237041090560\",\"f64:18444492273895866368\"]],[\"invoke\",333,0,\"copysign\",[\"f64:9221120237041090560\",\"f64:9221120237041090560\"]],[\"invoke\",334,0,\"abs\",[\"f64:9223372036854775808\"]],[\"invoke\",335,0,\"abs\",[\"f64:0\"]],[\"invoke\",336,0,\"abs\",[\"f64:9223372036854775809\"]],[\"invoke\",337,0,\"abs\",[\"f64:1\"]],[\"invoke\",338,0,\"abs\",[\"f64:9227875636482146304\"]],[\"invoke\",339,0,\"abs\",[\"f64:4503599627370496\"]],[\"invoke\",340,0,\"abs\",[\"f64:13826050856027422720\"]],[\"invoke\",341,0,\"abs\",[\"f64:4602678819172646912\"]],[\"invoke\",342,0,\"abs\",[\"f64:13830554455654793216\"]],[\"invoke\",343,0,\"abs\",[\"f64:4607182418800017408\"]],[\"invoke\",344,0,\"abs\",[\"f64:13842132293034192152\"]],[\"invoke\",345,0,\"abs\",[\"f64:4618760256179416344\"]],[\"invoke\",346,0,\"abs\",[\"f64:18442240474082181119\"]],[\"invoke\",347,0,\"abs\",[\"f64:9218868437227405311\"]],[\"invoke\",348,0,\"abs\",[\"f64:18442240474082181120\"]],[\"invoke\",349,0,\"abs\",[\"f64:9218868437227405312\"]],[\"invoke\",350,0,\"abs\",[\"f64:18444492273895866368\"]],[\"invoke\",351,0,\"abs\",[\"f64:9221120237041090560\"]],[\"invoke\",352,0,\"neg\",[\"f64:9223372036854775808\"]],[\"invoke\",353,0,\"neg\",[\"f64:0\"]],[\"invoke\",354,0,\"neg\",[\"f64:9223372036854775809\"]],[\"invoke\",355,0,\"neg\",[\"f64:1\"]],[\"invoke\",356,0,\"neg\",[\"f64:9227875636482146304\"]],[\"invoke\",357,0,\"neg\",[\"f64:4503599627370496\"]],[\"invoke\",358,0,\"neg\",[\"f64:13826050856027422720\"]],[\"invoke\",359,0,\"neg\",[\"f64:4602678819172646912\"]],[\"invoke\",360,0,\"neg\",[\"f64:13830554455654793216\"]],[\"invoke\",361,0,\"neg\",[\"f64:4607182418800017408\"]],[\"invoke\",362,0,\"neg\",[\"f64:13842132293034192152\"]],[\"invoke\",363,0,\"neg\",[\"f64:4618760256179416344\"]],[\"invoke\",364,0,\"neg\",[\"f64:18442240474082181119\"]],[\"invoke\",365,0,\"neg\",[\"f64:9218868437227405311\"]],[\"invoke\",366,0,\"neg\",[\"f64:18442240474082181120\"]],[\"invoke\",367,0,\"neg\",[\"f64:9218868437227405312\"]],[\"invoke\",368,0,\"neg\",[\"f64:18444492273895866368\"]],[\"invoke\",369,0,\"neg\",[\"f64:9221120237041090560\"]],[\"file\",\"float_exprs.wast\"],[\"module\",6,6],[\"invoke\",11,0,\"f64.no_contraction\",[\"f64:13369472591878845359\",\"f64:7598224971858294334\",\"f64:7009968021366006149\"]],[\"invoke\",13,0,\"f64.no_contraction\",[\"f64:11159707324127586240\",\"f64:7011538096610110295\",\"f64:4140382893275160737\"]],[\"invoke\",15,0,\"f64.no_contraction\",[\"f64:9554523352352050493\",\"f64:18042841594766434431\",\"f64:4368037109959396445\"]],[\"module\",19,7],[\"invoke\",27,0,\"f32.no_fma\",[\"f32:838240978\",\"f32:2796592697\",\"f32:329493464\"]],[\"invoke\",29,0,\"f32.no_fma\",[\"f32:999635965\",\"f32:3403528619\",\"f32:3222888213\"]],[\"invoke\",31,0,\"f64.no_fma\",[\"f64:7118716943724900052\",\"f64:6546073043412611735\",\"f64:18275705786238687882\"]],[\"invoke\",33,0,\"f64.no_fma\",[\"f64:1362668175782178275\",\"f64:18385570095786966502\",\"f64:5677031731722859914\"]],[\"invoke\",35,0,\"f64.no_fma\",[\"f64:4843589256781277081\",\"f64:7695653093478086834\",\"f64:16938438850771988744\"]],[\"module\",40,8],[\"invoke\",48,0,\"f64.no_fold_add_zero\",[\"f64:9223372036854775808\"]],[\"module\",54,9],[\"invoke\",62,0,\"f64.no_fold_zero_sub\",[\"f64:0\"]],[\"module\",68,10],[\"module\",80,11],[\"invoke\",88,0,\"f32.no_fold_mul_zero\",[\"f32:3212836864\"]],[\"invoke\",91,0,\"f64.no_fold_mul_zero\",[\"f64:9223372036854775808\"]],[\"invoke\",93,0,\"f64.no_fold_mul_zero\",[\"f64:13835058055282163712\"]],[\"module\",99,12],[\"module\",111,13],[\"invoke\",118,0,\"f32.no_fold_zero_div\",[\"f32:0\"]],[\"invoke\",120,0,\"f32.no_fold_zero_div\",[\"f32:2143289344\"]],[\"invoke\",123,0,\"f64.no_fold_zero_div\",[\"f64:9223372036854775808\"]],[\"module\",129,14],[\"module\",141,15],[\"module\",153,16],[\"module\",165,17],[\"module\",177,18],[\"invoke\",184,0,\"f32.no_fold_eq_self\",[\"f32:2143289344\"]],[\"module\",189,19],[\"invoke\",196,0,\"f32.no_fold_ne_self\",[\"f32:2143289344\"]],[\"module\",201,20],[\"invoke\",208,0,\"f32.no_fold_sub_self\",[\"f32:2139095040\"]],[\"invoke\",210,0,\"f64.no_fold_sub_self\",[\"f64:9218868437227405312\"]],[\"module\",215,21],[\"invoke\",222,0,\"f32.no_fold_div_self\",[\"f32:2139095040\"]],[\"invoke\",224,0,\"f32.no_fold_div_self\",[\"f32:0\"]],[\"invoke\",226,0,\"f64.no_fold_div_self\",[\"f64:9218868437227405312\"]],[\"invoke\",228,0,\"f64.no_fold_div_self\",[\"f64:0\"]],[\"module\",233,22],[\"invoke\",240,0,\"f32.no_fold_div_3\",[\"f32:3634023955\"]],[\"invoke\",242,0,\"f32.no_fold_div_3\",[\"f32:2517965963\"]],[\"invoke\",243,0,\"f32.no_fold_div_3\",[\"f32:2173683100\"]],[\"invoke\",245,0,\"f64.no_fold_div_3\",[\"f64:16679796490173820099\"]],[\"invoke\",247,0,\"f64.no_fold_div_3\",[\"f64:674365394458900388\"]],[\"invoke\",249,0,\"f64.no_fold_div_3\",[\"f64:6476267216527259981\"]],[\"module\",253,23],[\"invoke\",261,0,\"f32.no_factor\",[\"f32:3168433147\",\"f32:1028017286\",\"f32:3141035521\"]],[\"invoke\",263,0,\"f32.no_factor\",[\"f32:2684117842\",\"f32:369386499\",\"f32:2061166438\"]],[\"invoke\",265,0,\"f64.no_factor\",[\"f64:2698691837980592503\",\"f64:2529920934327896545\",\"f64:12819783413251458936\"]],[\"invoke\",267,0,\"f64.no_factor\",[\"f64:12326480769054961745\",\"f64:12563546453737163926\",\"f64:15990519985875741037\"]],[\"invoke\",269,0,\"f64.no_factor\",[\"f64:1871759566187673434\",\"f64:2002968319587025494\",\"f64:16033202089880281080\"]],[\"module\",273,24],[\"invoke\",281,0,\"f32.no_distribute\",[\"f32:3168433147\",\"f32:1028017286\",\"f32:3141035521\"]],[\"invoke\",283,0,\"f32.no_distribute\",[\"f32:2684117842\",\"f32:369386499\",\"f32:2061166438\"]],[\"invoke\",285,0,\"f64.no_distribute\",[\"f64:2698691837980592503\",\"f64:2529920934327896545\",\"f64:12819783413251458936\"]],[\"invoke\",287,0,\"f64.no_distribute\",[\"f64:12326480769054961745\",\"f64:12563546453737163926\",\"f64:15990519985875741037\"]],[\"invoke\",288,0,\"f64.no_distribute\",[\"f64:12532477544855171977\",\"f64:3439526350000314825\",\"f64:12694541248380731909\"]],[\"module\",293,25],[\"invoke\",300,0,\"f32.no_regroup_div_mul\",[\"f32:2249624147\",\"f32:2678828342\",\"f32:95319815\"]],[\"invoke\",302,0,\"f32.no_regroup_div_mul\",[\"f32:3350590135\",\"f32:3042588643\",\"f32:2186448635\"]],[\"invoke\",304,0,\"f32.no_regroup_div_mul\",[\"f32:2011387707\",\"f32:1274956446\",\"f32:3811596788\"]],[\"invoke\",306,0,\"f64.no_regroup_div_mul\",[\"f64:6126139291059848917\",\"f64:2596039250849921421\",\"f64:17423258659719899654\"]],[\"invoke\",308,0,\"f64.no_regroup_div_mul\",[\"f64:15994259208199847538\",\"f64:16584156163346075677\",\"f64:17596923907238870430\"]],[\"module\",313,26],[\"invoke\",320,0,\"f32.no_regroup_mul_div\",[\"f32:2249624147\",\"f32:2678828342\",\"f32:95319815\"]],[\"invoke\",322,0,\"f32.no_regroup_mul_div\",[\"f32:3350590135\",\"f32:3042588643\",\"f32:2186448635\"]],[\"invoke\",324,0,\"f32.no_regroup_mul_div\",[\"f32:2011387707\",\"f32:1274956446\",\"f32:3811596788\"]],[\"invoke\",326,0,\"f64.no_regroup_mul_div\",[\"f64:6126139291059848917\",\"f64:2596039250849921421\",\"f64:17423258659719899654\"]],[\"invoke\",328,0,\"f64.no_regroup_mul_div\",[\"f64:15994259208199847538\",\"f64:16584156163346075677\",\"f64:17596923907238870430\"]],[\"module\",333,27],[\"invoke\",340,0,\"f32.no_reassociate_add\",[\"f32:3585064686\",\"f32:1354934024\",\"f32:3612934982\",\"f32:3557837641\"]],[\"invoke\",342,0,\"f32.no_reassociate_add\",[\"f32:3506363549\",\"f32:3562765939\",\"f32:1440782572\",\"f32:1388583643\"]],[\"invoke\",343,0,\"f32.no_reassociate_add\",[\"f32:1460378878\",\"f32:1481791683\",\"f32:3506843934\",\"f32:1493913729\"]],[\"invoke\",345,0,\"f64.no_reassociate_add\",[\"f64:17619937326421449126\",\"f64:8424880666975634327\",\"f64:8461713040394112626\",\"f64:17692076622886930107\"]],[\"invoke\",347,0,\"f64.no_reassociate_add\",[\"f64:15816220208145029204\",\"f64:6443786499090728432\",\"f64:15798639273395365185\",\"f64:6395820899158300605\"]],[\"invoke\",349,0,\"f64.no_reassociate_add\",[\"f64:16720963389015391005\",\"f64:16597092572968550980\",\"f64:7518944085377596897\",\"f64:16733407756820198530\"]],[\"module\",353,28],[\"invoke\",361,0,\"f32.no_reassociate_mul\",[\"f32:598526039\",\"f32:4072603010\",\"f32:2166864805\",\"f32:3802968051\"]],[\"invoke\",363,0,\"f32.no_reassociate_mul\",[\"f32:191948150\",\"f32:1717012201\",\"f32:3682645872\",\"f32:3713382507\"]],[\"invoke\",365,0,\"f64.no_reassociate_mul\",[\"f64:10830726381612138752\",\"f64:18293529276079591087\",\"f64:12137662286027993114\",\"f64:16821646709291069775\"]],[\"invoke\",367,0,\"f64.no_reassociate_mul\",[\"f64:2352911459797566465\",\"f64:17379873157362463143\",\"f64:1179129869275935356\",\"f64:14228398113747850351\"]],[\"invoke\",369,0,\"f64.no_reassociate_mul\",[\"f64:3553622953022765407\",\"f64:1044040287824900408\",\"f64:17112762794520509437\",\"f64:11134095486440145773\"]],[\"module\",373,29],[\"invoke\",381,0,\"f32.no_fold_div_0\",[\"f32:3212836864\"]],[\"invoke\",383,0,\"f32.no_fold_div_0\",[\"f32:4286578688\"]],[\"invoke\",385,0,\"f32.no_fold_div_0\",[\"f32:2147483648\"]],[\"invoke\",388,0,\"f64.no_fold_div_0\",[\"f64:4607182418800017408\"]],[\"invoke\",389,0,\"f64.no_fold_div_0\",[\"f64:13830554455654793216\"]],[\"invoke\",391,0,\"f64.no_fold_div_0\",[\"f64:18442240474082181120\"]],[\"invoke\",393,0,\"f64.no_fold_div_0\",[\"f64:9223372036854775808\"]],[\"module\",399,30],[\"invoke\",406,0,\"f32.no_fold_div_neg0\",[\"f32:1065353216\"]],[\"invoke\",408,0,\"f32.no_fold_div_neg0\",[\"f32:2139095040\"]],[\"invoke\",410,0,\"f32.no_fold_div_neg0\",[\"f32:0\"]],[\"invoke\",413,0,\"f32.no_fold_div_neg0\",[\"f32:2143289344\"]],[\"invoke\",415,0,\"f64.no_fold_div_neg0\",[\"f64:13830554455654793216\"]],[\"invoke\",417,0,\"f64.no_fold_div_neg0\",[\"f64:18442240474082181120\"]],[\"invoke\",419,0,\"f64.no_fold_div_neg0\",[\"f64:9223372036854775808\"]],[\"module\",425,31],[\"invoke\",434,0,\"f32.no_fold_to_hypot\",[\"f32:392264092\",\"f32:497028527\"]],[\"invoke\",436,0,\"f32.no_fold_to_hypot\",[\"f32:2261577829\",\"f32:2641790518\"]],[\"invoke\",438,0,\"f32.no_fold_to_hypot\",[\"f32:432505039\",\"f32:2618036612\"]],[\"invoke\",439,0,\"f64.no_fold_to_hypot\",[\"f64:1743351192697472785\",\"f64:2202602366606243153\"]],[\"invoke\",441,0,\"f64.no_fold_to_hypot\",[\"f64:2195337108264055819\",\"f64:10384237061545402288\"]],[\"invoke\",443,0,\"f64.no_fold_to_hypot\",[\"f64:1591440107418864392\",\"f64:11515806374387309036\"]],[\"module\",447,32],[\"invoke\",453,0,\"f32.no_approximate_reciprocal\",[\"f32:2138280080\"]],[\"invoke\",455,0,\"f32.no_approximate_reciprocal\",[\"f32:1705936409\"]],[\"module\",460,33],[\"invoke\",467,0,\"f32.no_approximate_reciprocal_sqrt\",[\"f32:708147349\"]],[\"invoke\",469,0,\"f32.no_approximate_reciprocal_sqrt\",[\"f32:517799246\"]],[\"invoke\",471,0,\"f32.no_approximate_reciprocal_sqrt\",[\"f32:347001813\"]],[\"invoke\",474,0,\"f64.no_fuse_reciprocal_sqrt\",[\"f64:6008428610859539631\"]],[\"invoke\",476,0,\"f64.no_fuse_reciprocal_sqrt\",[\"f64:7616219057857077123\"]],[\"module\",481,34],[\"invoke\",486,0,\"f32.no_approximate_sqrt_reciprocal\",[\"f32:1574069443\"]],[\"invoke\",488,0,\"f32.no_approximate_sqrt_reciprocal\",[\"f32:1644769121\"]],[\"invoke\",490,0,\"f32.no_approximate_sqrt_reciprocal\",[\"f32:1940205041\"]],[\"module\",494,35],[\"invoke\",505,0,\"i32.no_fold_f32_s\",[\"i32:16777216\"]],[\"invoke\",507,0,\"i32.no_fold_f32_s\",[\"i32:4026531856\"]],[\"invoke\",510,0,\"i32.no_fold_f32_u\",[\"i32:16777217\"]],[\"invoke\",513,0,\"i64.no_fold_f64_s\",[\"i64:9007199254740992\"]],[\"invoke\",515,0,\"i64.no_fold_f64_s\",[\"i64:17293822569102705664\"]],[\"invoke\",518,0,\"i64.no_fold_f64_u\",[\"i64:9007199254740993\"]],[\"module\",523,36],[\"invoke\",530,0,\"f32.no_fold_add_sub\",[\"f32:677030386\",\"f32:2998136214\"]],[\"invoke\",532,0,\"f32.no_fold_add_sub\",[\"f32:3908960888\",\"f32:4063404061\"]],[\"invoke\",534,0,\"f32.no_fold_add_sub\",[\"f32:2307650739\",\"f32:2511328013\"]],[\"invoke\",537,0,\"f64.no_fold_add_sub\",[\"f64:2152218683357821298\",\"f64:2238360073507307376\"]],[\"invoke\",539,0,\"f64.no_fold_add_sub\",[\"f64:12298280617237492384\",\"f64:3233965342858558382\"]],[\"module\",544,37],[\"invoke\",551,0,\"f32.no_fold_sub_add\",[\"f32:3291029084\",\"f32:1137280182\"]],[\"invoke\",553,0,\"f32.no_fold_sub_add\",[\"f32:1285466516\",\"f32:1361849144\"]],[\"invoke\",554,0,\"f32.no_fold_sub_add\",[\"f32:740009747\",\"f32:2989707904\"]],[\"invoke\",557,0,\"f64.no_fold_sub_add\",[\"f64:5758126085282503565\",\"f64:14997141603873875659\"]],[\"invoke\",559,0,\"f64.no_fold_sub_add\",[\"f64:5738179408840599949\",\"f64:15186085143903012996\"]],[\"invoke\",561,0,\"f64.no_fold_sub_add\",[\"f64:2955729038738127538\",\"f64:12208627806665035010\"]],[\"module\",565,38],[\"invoke\",573,0,\"f32.no_fold_mul_div\",[\"f32:2995104604\",\"f32:178524966\"]],[\"invoke\",575,0,\"f32.no_fold_mul_div\",[\"f32:1507152519\",\"f32:3723483599\"]],[\"invoke\",578,0,\"f64.no_fold_mul_div\",[\"f64:10062123074470382106\",\"f64:12910565991996555404\"]],[\"invoke\",580,0,\"f64.no_fold_mul_div\",[\"f64:14905228263410157971\",\"f64:11346251643264732732\"]],[\"invoke\",582,0,\"f64.no_fold_mul_div\",[\"f64:16807035693954817236\",\"f64:12360222454864961326\"]],[\"module\",586,39],[\"invoke\",594,0,\"f32.no_fold_div_mul\",[\"f32:2777664539\",\"f32:3062588018\"]],[\"invoke\",596,0,\"f32.no_fold_div_mul\",[\"f32:2593594703\",\"f32:3709508810\"]],[\"invoke\",599,0,\"f64.no_fold_div_mul\",[\"f64:665690489208775809\",\"f64:14660005164454413124\"]],[\"invoke\",601,0,\"f64.no_fold_div_mul\",[\"f64:13179336828827425934\",\"f64:6536345148565138764\"]],[\"invoke\",602,0,\"f64.no_fold_div_mul\",[\"f64:12582623625647949669\",\"f64:15106746174896642041\"]],[\"module\",607,40],[\"invoke\",614,0,\"f32.no_fold_div2_mul2\",[\"f32:16777215\"]],[\"module\",619,41],[\"invoke\",624,0,\"no_fold_demote_promote\",[\"f64:13235495337234861917\"]],[\"invoke\",626,0,\"no_fold_demote_promote\",[\"f64:5090364081358261697\"]],[\"invoke\",628,0,\"no_fold_demote_promote\",[\"f64:5076240020598306430\"]],[\"module\",633,42],[\"invoke\",640,0,\"no_fold_promote_demote\",[\"f32:2147483648\"]],[\"invoke\",642,0,\"no_fold_promote_demote\",[\"f32:2147483649\"]],[\"invoke\",644,0,\"no_fold_promote_demote\",[\"f32:2155872255\"]],[\"invoke\",646,0,\"no_fold_promote_demote\",[\"f32:2155872256\"]],[\"invoke\",648,0,\"no_fold_promote_demote\",[\"f32:4286578687\"]],[\"invoke\",650,0,\"no_fold_promote_demote\",[\"f32:4286578688\"]],[\"module\",655,43],[\"invoke\",663,0,\"no_demote_mixed_add\",[\"f64:4773927428111915216\",\"f32:1387972204\"]],[\"invoke\",665,0,\"no_demote_mixed_add\",[\"f64:13740716732336801211\",\"f32:822392741\"]],[\"invoke\",666,0,\"no_demote_mixed_add\",[\"f64:13742514716462174325\",\"f32:2870112826\"]],[\"invoke\",669,0,\"no_demote_mixed_add_commuted\",[\"f32:1387972204\",\"f64:4773927428111915216\"]],[\"invoke\",671,0,\"no_demote_mixed_add_commuted\",[\"f32:822392741\",\"f64:13740716732336801211\"]],[\"module\",677,44],[\"invoke\",684,0,\"no_demote_mixed_sub\",[\"f64:4979303437048015281\",\"f32:1583535740\"]],[\"invoke\",686,0,\"no_demote_mixed_sub\",[\"f64:13860263758943608426\",\"f32:969848030\"]],[\"invoke\",688,0,\"no_demote_mixed_sub\",[\"f64:4673175763235896759\",\"f32:1198952676\"]],[\"invoke\",691,0,\"no_demote_mixed_sub_commuted\",[\"f32:1214924370\",\"f64:13967600632962086462\"]],[\"invoke\",693,0,\"no_demote_mixed_sub_commuted\",[\"f32:472962692\",\"f64:4364064588997139903\"]],[\"module\",699,45],[\"invoke\",706,0,\"no_demote_mixed_mul\",[\"f64:3700298049681794165\",\"f32:2017642079\"]],[\"invoke\",708,0,\"no_demote_mixed_mul\",[\"f64:5404042108537284972\",\"f32:592745128\"]],[\"invoke\",710,0,\"no_demote_mixed_mul\",[\"f64:3845043516226476604\",\"f32:1468885281\"]],[\"invoke\",713,0,\"no_demote_mixed_mul_commuted\",[\"f32:169311251\",\"f64:5240203039857786645\"]],[\"invoke\",714,0,\"no_demote_mixed_mul_commuted\",[\"f32:592745128\",\"f64:5404042108537284972\"]],[\"invoke\",716,0,\"no_demote_mixed_mul_commuted\",[\"f32:1468885281\",\"f64:3845043516226476604\"]],[\"module\",721,46],[\"invoke\",729,0,\"no_demote_mixed_div\",[\"f64:3919260277508722072\",\"f32:379509872\"]],[\"invoke\",731,0,\"no_demote_mixed_div\",[\"f64:3987118416164120709\",\"f32:327692725\"]],[\"invoke\",734,0,\"no_demote_mixed_div_commuted\",[\"f32:451659502\",\"f64:4418811329667688930\"]],[\"invoke\",736,0,\"no_demote_mixed_div_commuted\",[\"f32:737399826\",\"f64:3994494799108309244\"]],[\"invoke\",738,0,\"no_demote_mixed_div_commuted\",[\"f32:456887038\",\"f64:3717438582804640027\"]],[\"module\",742,47],[\"invoke\",778,0,\"f32.i32.no_fold_trunc_s_convert_s\",[\"f32:3217031168\"]],[\"invoke\",780,0,\"f32.i32.no_fold_trunc_u_convert_s\",[\"f32:3204448256\"]],[\"invoke\",782,0,\"f32.i32.no_fold_trunc_s_convert_u\",[\"f32:3217031168\"]],[\"invoke\",784,0,\"f32.i32.no_fold_trunc_u_convert_u\",[\"f32:3204448256\"]],[\"invoke\",787,0,\"f64.i32.no_fold_trunc_s_convert_s\",[\"f64:13832806255468478464\"]],[\"invoke\",789,0,\"f64.i32.no_fold_trunc_u_convert_s\",[\"f64:13826050856027422720\"]],[\"invoke\",790,0,\"f64.i32.no_fold_trunc_s_convert_u\",[\"f64:4609434218613702656\"]],[\"invoke\",792,0,\"f64.i32.no_fold_trunc_u_convert_u\",[\"f64:4609434218613702656\"]],[\"invoke\",795,0,\"f32.i64.no_fold_trunc_s_convert_s\",[\"f32:1069547520\"]],[\"invoke\",797,0,\"f32.i64.no_fold_trunc_u_convert_s\",[\"f32:1069547520\"]],[\"invoke\",799,0,\"f32.i64.no_fold_trunc_s_convert_u\",[\"f32:1069547520\"]],[\"invoke\",801,0,\"f32.i64.no_fold_trunc_u_convert_u\",[\"f32:1069547520\"]],[\"invoke\",804,0,\"f64.i64.no_fold_trunc_s_convert_s\",[\"f64:4609434218613702656\"]],[\"invoke\",806,0,\"f64.i64.no_fold_trunc_u_convert_s\",[\"f64:4609434218613702656\"]],[\"invoke\",808,0,\"f64.i64.no_fold_trunc_s_convert_u\",[\"f64:4609434218613702656\"]],[\"invoke\",810,0,\"f64.i64.no_fold_trunc_u_convert_u\",[\"f64:4609434218613702656\"]],[\"module\",817,48],[\"invoke\",838,0,\"init\",[\"i32:0\",\"f32:1097963930\"]],[\"invoke\",840,0,\"init\",[\"i32:8\",\"f32:1098173645\"]],[\"invoke\",842,0,\"check\",[\"i32:0\"]],[\"invoke\",843,0,\"check\",[\"i32:4\"]],[\"invoke\",845,0,\"check\",[\"i32:12\"]],[\"invoke\",847,0,\"check\",[\"i32:0\"]],[\"invoke\",849,0,\"check\",[\"i32:8\"]],[\"module\",852,49],[\"invoke\",873,0,\"init\",[\"i32:0\",\"f64:4624690162351420211\"]],[\"invoke\",875,0,\"init\",[\"i32:16\",\"f64:4624802752342104474\"]],[\"invoke\",877,0,\"check\",[\"i32:0\"]],[\"invoke\",879,0,\"check\",[\"i32:16\"]],[\"invoke\",881,0,\"run\",[\"i32:32\",\"f64:4613937818241073152\"]],[\"invoke\",883,0,\"check\",[\"i32:8\"]],[\"invoke\",885,0,\"check\",[\"i32:24\"]],[\"module\",889,50],[\"invoke\",902,0,\"f32.ult\",[\"f32:1073741824\",\"f32:1073741824\"]],[\"invoke\",904,0,\"f32.ult\",[\"f32:1073741824\",\"f32:2143289344\"]],[\"invoke\",905,0,\"f32.ule\",[\"f32:1077936128\",\"f32:1073741824\"]],[\"invoke\",907,0,\"f32.ule\",[\"f32:1073741824\",\"f32:1077936128\"]],[\"invoke\",909,0,\"f32.ugt\",[\"f32:1077936128\",\"f32:1073741824\"]],[\"invoke\",911,0,\"f32.ugt\",[\"f32:1073741824\",\"f32:1077936128\"]],[\"invoke\",913,0,\"f32.uge\",[\"f32:1077936128\",\"f32:1073741824\"]],[\"invoke\",915,0,\"f32.uge\",[\"f32:1073741824\",\"f32:1077936128\"]],[\"invoke\",917,0,\"f64.ult\",[\"f64:4613937818241073152\",\"f64:4611686018427387904\"]],[\"invoke\",919,0,\"f64.ult\",[\"f64:4611686018427387904\",\"f64:4613937818241073152\"]],[\"invoke\",921,0,\"f64.ule\",[\"f64:4613937818241073152\",\"f64:4611686018427387904\"]],[\"invoke\",923,0,\"f64.ule\",[\"f64:4611686018427387904\",\"f64:4613937818241073152\"]],[\"invoke\",925,0,\"f64.ugt\",[\"f64:4613937818241073152\",\"f64:4611686018427387904\"]],[\"invoke\",927,0,\"f64.ugt\",[\"f64:4611686018427387904\",\"f64:4613937818241073152\"]],[\"invoke\",929,0,\"f64.uge\",[\"f64:4613937818241073152\",\"f64:4611686018427387904\"]],[\"invoke\",930,0,\"f64.uge\",[\"f64:4611686018427387904\",\"f64:4611686018427387904\"]],[\"invoke\",932,0,\"f64.uge\",[\"f64:4611686018427387904\",\"f64:9221120237041090560\"]],[\"module\",936,51],[\"invoke\",949,0,\"f32.no_fold_lt_select\",[\"f32:2143289344\",\"f32:0\"]],[\"invoke\",951,0,\"f32.no_fold_lt_select\",[\"f32:2147483648\",\"f32:0\"]],[\"invoke\",953,0,\"f32.no_fold_le_select\",[\"f32:2143289344\",\"f32:0\"]],[\"invoke\",955,0,\"f32.no_fold_le_select\",[\"f32:2147483648\",\"f32:0\"]],[\"invoke\",957,0,\"f32.no_fold_gt_select\",[\"f32:2143289344\",\"f32:0\"]],[\"invoke\",959,0,\"f32.no_fold_gt_select\",[\"f32:2147483648\",\"f32:0\"]],[\"invoke\",961,0,\"f32.no_fold_ge_select\",[\"f32:2143289344\",\"f32:0\"]],[\"invoke\",963,0,\"f32.no_fold_ge_select\",[\"f32:2147483648\",\"f32:0\"]],[\"invoke\",965,0,\"f64.no_fold_lt_select\",[\"f64:9221120237041090560\",\"f64:0\"]],[\"invoke\",967,0,\"f64.no_fold_lt_select\",[\"f64:9223372036854775808\",\"f64:0\"]],[\"invoke\",969,0,\"f64.no_fold_le_select\",[\"f64:9221120237041090560\",\"f64:0\"]],[\"invoke\",970,0,\"f64.no_fold_le_select\",[\"f64:0\",\"f64:9223372036854775808\"]],[\"invoke\",972,0,\"f64.no_fold_gt_select\",[\"f64:0\",\"f64:9221120237041090560\"]],[\"invoke\",974,0,\"f64.no_fold_gt_select\",[\"f64:0\",\"f64:9223372036854775808\"]],[\"invoke\",976,0,\"f64.no_fold_ge_select\",[\"f64:0\",\"f64:9221120237041090560\"]],[\"invoke\",978,0,\"f64.no_fold_ge_select\",[\"f64:0\",\"f64:9223372036854775808\"]],[\"module\",983,52],[\"invoke\",1027,0,\"f32.no_fold_lt_if\",[\"f32:0\",\"f32:2143289344\"]],[\"invoke\",1029,0,\"f32.no_fold_lt_if\",[\"f32:0\",\"f32:2147483648\"]],[\"invoke\",1031,0,\"f32.no_fold_le_if\",[\"f32:0\",\"f32:2143289344\"]],[\"invoke\",1033,0,\"f32.no_fold_le_if\",[\"f32:0\",\"f32:2147483648\"]],[\"invoke\",1035,0,\"f32.no_fold_gt_if\",[\"f32:0\",\"f32:2143289344\"]],[\"invoke\",1037,0,\"f32.no_fold_gt_if\",[\"f32:0\",\"f32:2147483648\"]],[\"invoke\",1039,0,\"f32.no_fold_ge_if\",[\"f32:0\",\"f32:2143289344\"]],[\"invoke\",1041,0,\"f32.no_fold_ge_if\",[\"f32:0\",\"f32:2147483648\"]],[\"invoke\",1042,0,\"f32.no_fold_ge_if\",[\"f32:2147483648\",\"f32:0\"]],[\"invoke\",1044,0,\"f64.no_fold_lt_if\",[\"f64:9221120237041090560\",\"f64:0\"]],[\"invoke\",1046,0,\"f64.no_fold_lt_if\",[\"f64:9223372036854775808\",\"f64:0\"]],[\"invoke\",1048,0,\"f64.no_fold_le_if\",[\"f64:9221120237041090560\",\"f64:0\"]],[\"invoke\",1050,0,\"f64.no_fold_le_if\",[\"f64:9223372036854775808\",\"f64:0\"]],[\"invoke\",1052,0,\"f64.no_fold_gt_if\",[\"f64:9221120237041090560\",\"f64:0\"]],[\"invoke\",1054,0,\"f64.no_fold_gt_if\",[\"f64:9223372036854775808\",\"f64:0\"]],[\"invoke\",1056,0,\"f64.no_fold_ge_if\",[\"f64:9221120237041090560\",\"f64:0\"]],[\"invoke\",1058,0,\"f64.no_fold_ge_if\",[\"f64:9223372036854775808\",\"f64:0\"]],[\"module\",1062,53],[\"invoke\",1076,0,\"f32.no_fold_lt_select_to_abs\",[\"f32:0\"]],[\"invoke\",1079,0,\"f32.no_fold_le_select_to_abs\",[\"f32:4290772992\"]],[\"invoke\",1081,0,\"f32.no_fold_le_select_to_abs\",[\"f32:2147483648\"]],[\"invoke\",1084,0,\"f32.no_fold_gt_select_to_abs\",[\"f32:0\"]],[\"invoke\",1085,0,\"f32.no_fold_gt_select_to_abs\",[\"f32:2147483648\"]],[\"invoke\",1088,0,\"f32.no_fold_ge_select_to_abs\",[\"f32:0\"]],[\"invoke\",1091,0,\"f64.no_fold_lt_select_to_abs\",[\"f64:18444492273895866368\"]],[\"invoke\",1093,0,\"f64.no_fold_lt_select_to_abs\",[\"f64:9223372036854775808\"]],[\"invoke\",1096,0,\"f64.no_fold_le_select_to_abs\",[\"f64:0\"]],[\"invoke\",1099,0,\"f64.no_fold_gt_select_to_abs\",[\"f64:18444492273895866368\"]],[\"invoke\",1101,0,\"f64.no_fold_gt_select_to_abs\",[\"f64:9223372036854775808\"]],[\"invoke\",1104,0,\"f64.no_fold_ge_select_to_abs\",[\"f64:0\"]],[\"module\",1109,54],[\"invoke\",1154,0,\"f32.no_fold_lt_if_to_abs\",[\"f32:4290772992\"]],[\"invoke\",1156,0,\"f32.no_fold_lt_if_to_abs\",[\"f32:2147483648\"]],[\"invoke\",1159,0,\"f32.no_fold_le_if_to_abs\",[\"f32:0\"]],[\"invoke\",1162,0,\"f32.no_fold_gt_if_to_abs\",[\"f32:4290772992\"]],[\"invoke\",1164,0,\"f32.no_fold_gt_if_to_abs\",[\"f32:2147483648\"]],[\"invoke\",1166,0,\"f32.no_fold_ge_if_to_abs\",[\"f32:4290772992\"]],[\"invoke\",1168,0,\"f32.no_fold_ge_if_to_abs\",[\"f32:2147483648\"]],[\"invoke\",1171,0,\"f64.no_fold_lt_if_to_abs\",[\"f64:0\"]],[\"invoke\",1174,0,\"f64.no_fold_le_if_to_abs\",[\"f64:18444492273895866368\"]],[\"invoke\",1176,0,\"f64.no_fold_le_if_to_abs\",[\"f64:9223372036854775808\"]],[\"invoke\",1179,0,\"f64.no_fold_gt_if_to_abs\",[\"f64:0\"]],[\"invoke\",1182,0,\"f64.no_fold_ge_if_to_abs\",[\"f64:18444492273895866368\"]],[\"invoke\",1184,0,\"f64.no_fold_ge_if_to_abs\",[\"f64:9223372036854775808\"]],[\"module\",1189,55],[\"invoke\",1199,0,\"f64.incorrect_correction\",[]],[\"module\",1204,56],[\"module\",1223,57],[\"invoke\",1240,0,\"calculate\",[]],[\"module\",1245,58],[\"module\",1256,59],[\"invoke\",1262,0,\"llvm_pr27153\",[\"i32:33554434\"]],[\"module\",1267,60],[\"module\",1284,61],[\"invoke\",1298,0,\"thepast0\",[\"f64:9007199254740992\",\"f64:4607182418800017407\",\"f64:4611686018427387904\",\"f64:4602678819172646912\"]],[\"invoke\",1300,0,\"thepast2\",[\"f32:16777216\",\"f32:1056964608\",\"f32:1065353216\"]],[\"module\",1305,62],[\"invoke\",1311,0,\"inverse\",[\"f32:1119879168\"]],[\"module\",1316,63],[\"invoke\",1327,0,\"f64_sqrt_minus_2\",[\"f64:4616189618054758400\"]],[\"module\",1331,64],[\"invoke\",1340,0,\"f32.no_fold_recip_recip\",[\"f32:426844452\"]],[\"invoke\",1342,0,\"f32.no_fold_recip_recip\",[\"f32:3253941441\"]],[\"invoke\",1345,0,\"f32.no_fold_recip_recip\",[\"f32:2147483648\"]],[\"invoke\",1347,0,\"f32.no_fold_recip_recip\",[\"f32:4286578688\"]],[\"invoke\",1350,0,\"f64.no_fold_recip_recip\",[\"f64:14500888369201570768\"]],[\"invoke\",1352,0,\"f64.no_fold_recip_recip\",[\"f64:5359183527603521526\"]],[\"invoke\",1354,0,\"f64.no_fold_recip_recip\",[\"f64:8671785631545870379\"]],[\"invoke\",1357,0,\"f64.no_fold_recip_recip\",[\"f64:0\"]],[\"invoke\",1359,0,\"f64.no_fold_recip_recip\",[\"f64:9218868437227405312\"]],[\"module\",1363,65],[\"invoke\",1374,0,\"f32.no_algebraic_factoring\",[\"f32:3415653214\",\"f32:1274676302\"]],[\"invoke\",1375,0,\"f32.no_algebraic_factoring\",[\"f32:1446924633\",\"f32:3607373982\"]],[\"invoke\",1377,0,\"f32.no_algebraic_factoring\",[\"f32:3278732464\",\"f32:3379389272\"]],[\"invoke\",1380,0,\"f64.no_algebraic_factoring\",[\"f64:2911983657790464931\",\"f64:2814431682419759911\"]],[\"invoke\",1382,0,\"f64.no_algebraic_factoring\",[\"f64:14639789466354372633\",\"f64:5456963169336729236\"]],[\"module\",1387,66],[\"invoke\",1397,0,\"f32.no_algebraic_factoring\",[\"f32:684135946\",\"f32:744319693\"]],[\"invoke\",1399,0,\"f32.no_algebraic_factoring\",[\"f32:968841772\",\"f32:3106497100\"]],[\"invoke\",1401,0,\"f32.no_algebraic_factoring\",[\"f32:1498230729\",\"f32:3650453580\"]],[\"invoke\",1404,0,\"f64.no_algebraic_factoring\",[\"f64:15222970140370015722\",\"f64:15325207139996136125\"]],[\"invoke\",1406,0,\"f64.no_algebraic_factoring\",[\"f64:14653559129294038194\",\"f64:14581996260169223461\"]],[\"module\",1412,67],[\"invoke\",1445,0,\"f32.simple_x4_sum\",[\"i32:0\",\"i32:16\",\"i32:32\"]],[\"invoke\",1447,0,\"f32.load\",[\"i32:36\"]],[\"invoke\",1449,0,\"f32.load\",[\"i32:44\"]],[\"module\",1451,68],[\"invoke\",1485,0,\"f64.load\",[\"i32:64\"]],[\"invoke\",1486,0,\"f64.load\",[\"i32:72\"]],[\"invoke\",1488,0,\"f64.load\",[\"i32:88\"]],[\"module\",1493,69],[\"invoke\",1585,0,\"f32.plain_sum\",[\"i32:0\",\"i32:256\"]],[\"module\",1587,70],[\"invoke\",1636,0,\"f64.plain_sum\",[\"i32:0\",\"i32:256\"]],[\"module\",1640,71],[\"invoke\",1649,0,\"f32.no_fold_neg_sub\",[\"f32:0\",\"f32:2147483648\"]],[\"invoke\",1651,0,\"f32.no_fold_neg_sub\",[\"f32:0\",\"f32:0\"]],[\"invoke\",1654,0,\"f64.no_fold_neg_sub\",[\"f64:0\",\"f64:9223372036854775808\"]],[\"invoke\",1656,0,\"f64.no_fold_neg_sub\",[\"f64:0\",\"f64:0\"]],[\"module\",1660,72],[\"invoke\",1669,0,\"f32.no_fold_neg_add\",[\"f32:0\",\"f32:2147483648\"]],[\"invoke\",1671,0,\"f32.no_fold_neg_add\",[\"f32:0\",\"f32:0\"]],[\"invoke\",1674,0,\"f64.no_fold_neg_add\",[\"f64:0\",\"f64:9223372036854775808\"]],[\"invoke\",1676,0,\"f64.no_fold_neg_add\",[\"f64:0\",\"f64:0\"]],[\"module\",1680,73],[\"invoke\",1689,0,\"f32.no_fold_add_neg_neg\",[\"f32:0\",\"f32:2147483648\"]],[\"invoke\",1690,0,\"f32.no_fold_add_neg_neg\",[\"f32:2147483648\",\"f32:0\"]],[\"invoke\",1693,0,\"f64.no_fold_add_neg_neg\",[\"f64:9223372036854775808\",\"f64:9223372036854775808\"]],[\"invoke\",1695,0,\"f64.no_fold_add_neg_neg\",[\"f64:9223372036854775808\",\"f64:0\"]],[\"module\",1700,74],[\"invoke\",1708,0,\"f32.no_fold_add_neg\",[\"f32:0\"]],[\"invoke\",1710,0,\"f32.no_fold_add_neg\",[\"f32:2139095040\"]],[\"invoke\",1713,0,\"f64.no_fold_add_neg\",[\"f64:0\"]],[\"invoke\",1715,0,\"f64.no_fold_add_neg\",[\"f64:9218868437227405312\"]],[\"module\",1720,75],[\"invoke\",1734,0,\"f32.no_fold_6x_via_add\",[\"f32:4046243078\"]],[\"invoke\",1736,0,\"f32.no_fold_6x_via_add\",[\"f32:419462401\"]],[\"invoke\",1738,0,\"f32.no_fold_6x_via_add\",[\"f32:3883931973\"]],[\"invoke\",1741,0,\"f64.no_fold_6x_via_add\",[\"f64:11424134044545165748\"]],[\"invoke\",1743,0,\"f64.no_fold_6x_via_add\",[\"f64:7991451501228919438\"]],[\"module\",1749,76],[\"invoke\",1757,0,\"f32.no_fold_div_div\",[\"f32:3875242260\",\"f32:3086869257\",\"f32:3301317576\"]],[\"invoke\",1758,0,\"f32.no_fold_div_div\",[\"f32:485052055\",\"f32:1996083391\",\"f32:2276616712\"]],[\"invoke\",1760,0,\"f32.no_fold_div_div\",[\"f32:3101818893\",\"f32:4258133430\",\"f32:2855958950\"]],[\"invoke\",1763,0,\"f64.no_fold_div_div\",[\"f64:6128077243319875447\",\"f64:7240092044185667120\",\"f64:10312472494987686942\"]],[\"invoke\",1765,0,\"f64.no_fold_div_div\",[\"f64:13843263185226986279\",\"f64:17796742619038211051\",\"f64:5375701731263473827\"]],[\"invoke\",1767,0,\"f64.no_fold_div_div\",[\"f64:9835707468114203513\",\"f64:1924400690116523912\",\"f64:13208934041167870811\"]],[\"module\",1773,77],[\"invoke\",1782,0,\"f32.no_fold_mul_divs\",[\"f32:4068974897\",\"f32:1276265036\",\"f32:930821438\",\"f32:1044692964\"]],[\"invoke\",1784,0,\"f32.no_fold_mul_divs\",[\"f32:4135236702\",\"f32:787270424\",\"f32:932959293\",\"f32:1724950821\"]],[\"invoke\",1787,0,\"f64.no_fold_mul_divs\",[\"f64:10143060558527560466\",\"f64:11745059379675007839\",\"f64:16295837305232663584\",\"f64:5444961058358534642\"]],[\"invoke\",1789,0,\"f64.no_fold_mul_divs\",[\"f64:15220380342429201729\",\"f64:14697937818549468616\",\"f64:13203624158275174657\",\"f64:17131104131485469546\"]],[\"invoke\",1791,0,\"f64.no_fold_mul_divs\",[\"f64:4529089342618677929\",\"f64:3361245300043094097\",\"f64:1815899012046749567\",\"f64:15418396504351552390\"]],[\"module\",1795,78],[\"invoke\",1804,0,\"f32.no_fold_add_divs\",[\"f32:2690073844\",\"f32:2809448479\",\"f32:3608905030\"]],[\"invoke\",1806,0,\"f32.no_fold_add_divs\",[\"f32:2365787800\",\"f32:245111369\",\"f32:3952003433\"]],[\"invoke\",1809,0,\"f64.no_fold_add_divs\",[\"f64:15770585325769044278\",\"f64:6564157675451289455\",\"f64:8712254759989822359\"]],[\"invoke\",1810,0,\"f64.no_fold_add_divs\",[\"f64:14069844870254671283\",\"f64:4634122757084803708\",\"f64:9524897388132352235\"]],[\"invoke\",1812,0,\"f64.no_fold_add_divs\",[\"f64:6019502660029506228\",\"f64:15316513033818836241\",\"f64:4039967192182502935\"]],[\"module\",1817,79],[\"invoke\",1825,0,\"f32.no_fold_sqrt_square\",[\"f32:2662226315\"]],[\"invoke\",1827,0,\"f32.no_fold_sqrt_square\",[\"f32:2624528574\"]],[\"invoke\",1829,0,\"f32.no_fold_sqrt_square\",[\"f32:1978715378\"]],[\"invoke\",1832,0,\"f64.no_fold_sqrt_square\",[\"f64:11517048459773840771\"]],[\"invoke\",1834,0,\"f64.no_fold_sqrt_square\",[\"f64:11056484744549647728\"]],[\"module\",1839,80],[\"invoke\",1847,0,\"f32.no_fold_mul_sqrts\",[\"f32:24047316\",\"f32:2517821717\"]],[\"invoke\",1849,0,\"f32.no_fold_mul_sqrts\",[\"f32:329708528\",\"f32:1120042892\"]],[\"invoke\",1851,0,\"f32.no_fold_mul_sqrts\",[\"f32:598482176\",\"f32:990534933\"]],[\"invoke\",1854,0,\"f64.no_fold_mul_sqrts\",[\"f64:1712959863583927241\",\"f64:2792003944717853898\"]],[\"invoke\",1856,0,\"f64.no_fold_mul_sqrts\",[\"f64:2976792199849816182\",\"f64:2030444188042608984\"]],[\"module\",1861,81],[\"invoke\",1869,0,\"f32.no_fold_div_sqrts\",[\"f32:3428799709\",\"f32:2733489079\"]],[\"invoke\",1870,0,\"f32.no_fold_div_sqrts\",[\"f32:1339867611\",\"f32:1296568207\"]],[\"invoke\",1872,0,\"f32.no_fold_div_sqrts\",[\"f32:1566143010\",\"f32:816694667\"]],[\"invoke\",1875,0,\"f64.no_fold_div_sqrts\",[\"f64:10629913473787695463\",\"f64:12991130264919696663\"]],[\"invoke\",1877,0,\"f64.no_fold_div_sqrts\",[\"f64:6965599900716272009\",\"f64:4118781927977980600\"]],[\"invoke\",1879,0,\"f64.no_fold_div_sqrts\",[\"f64:1056821405580891413\",\"f64:8865548665903786673\"]],[\"module\",1883,82],[\"invoke\",1892,0,\"f32.no_fold_mul_sqrt_div\",[\"f32:2210946958\",\"f32:256302916\"]],[\"invoke\",1894,0,\"f32.no_fold_mul_sqrt_div\",[\"f32:3576537897\",\"f32:2010442638\"]],[\"invoke\",1897,0,\"f64.no_fold_mul_sqrt_div\",[\"f64:7751219282814906463\",\"f64:8023732701704228537\"]],[\"invoke\",1899,0,\"f64.no_fold_mul_sqrt_div\",[\"f64:2695209648295623224\",\"f64:7133480874314061811\"]],[\"invoke\",1901,0,\"f64.no_fold_mul_sqrt_div\",[\"f64:5076901024782455083\",\"f64:8399438310541178654\"]],[\"module\",1906,83],[\"invoke\",1915,0,\"f64.no_flush_intermediate_subnormal\",[\"f64:4503599627370496\",\"f64:4372995238176751616\",\"f64:4841369599423283200\"]],[\"module\",1920,84],[\"invoke\",1944,0,\"f32.recoding_le\",[\"f32:4286578688\",\"f32:1077936128\"]],[\"invoke\",1947,0,\"f32.recoding_eq\",[\"f32:0\",\"f32:1065353216\"]],[\"invoke\",1948,0,\"f32.recoding_le\",[\"f32:0\",\"f32:1065353216\"]],[\"invoke\",1951,0,\"f64.recoding_eq\",[\"f64:18442240474082181120\",\"f64:4613937818241073152\"]],[\"invoke\",1953,0,\"f64.recoding_lt\",[\"f64:18442240474082181120\",\"f64:4613937818241073152\"]],[\"invoke\",1956,0,\"f64.recoding_le\",[\"f64:0\",\"f64:4607182418800017408\"]],[\"invoke\",1959,0,\"recoding_demote\",[\"f64:4014054135371399168\",\"f32:1150853120\"]],[\"module\",1964,85],[\"invoke\",1973,0,\"f64.no_extended_precision_div\",[\"f64:4613937818241073152\",\"f64:4619567317775286272\",\"f64:4601392076421969627\"]],[\"module\",1980,86],[\"invoke\",1989,0,\"f64.no_distribute_exact\",[\"f64:9223372036854775808\"]],[\"module\",1994,87],[\"invoke\",2027,0,\"f32.xkcd_sqrt_2\",[\"f32:1077936128\",\"f32:1084227584\",\"f32:1078530011\",\"f32:1088421888\"]],[\"invoke\",2029,0,\"f32.xkcd_sqrt_3\",[\"f32:1073741824\",\"f32:1076754516\",\"f32:1078530011\"]],[\"invoke\",2031,0,\"f32.xkcd_sqrt_5\",[\"f32:1073741824\",\"f32:1076754516\",\"f32:1077936128\"]],[\"invoke\",2034,0,\"f64.sqrt\",[\"f64:4611686018427387904\"]],[\"invoke\",2036,0,\"f64.sqrt\",[\"f64:4613937818241073152\"]],[\"invoke\",2038,0,\"f64.sqrt\",[\"f64:4617315517961601024\"]],[\"invoke\",2039,0,\"f64.xkcd_sqrt_5\",[\"f64:4611686018427387904\",\"f64:4613303445314885481\",\"f64:4613937818241073152\"]],[\"module\",2045,88],[\"invoke\",2123,0,\"f32.compute_radix\",[\"f32:1065353216\",\"f32:1065353216\"]],[\"module\",2129,89],[\"invoke\",2137,0,\"f32.no_fold_sub1_mul_add\",[\"f32:796917760\",\"f32:1065353216\"]],[\"module\",2143,90],[\"invoke\",2157,0,\"f32.no_fold_add_le_monotonicity\",[\"f32:0\",\"f32:0\",\"f32:2143289344\"]],[\"invoke\",2159,0,\"f64.no_fold_add_le_monotonicity\",[\"f64:0\",\"f64:0\",\"f64:9221120237041090560\"]],[\"module\",2164,91],[\"invoke\",2190,0,\"f32.not_lt\",[\"f32:2143289344\",\"f32:0\"]],[\"invoke\",2192,0,\"f32.not_gt\",[\"f32:2143289344\",\"f32:0\"]],[\"invoke\",2194,0,\"f64.not_lt\",[\"f64:9221120237041090560\",\"f64:0\"]],[\"invoke\",2196,0,\"f64.not_gt\",[\"f64:9221120237041090560\",\"f64:0\"]],[\"module\",2203,92],[\"invoke\",2211,0,\"f32.epsilon\",[]],[\"module\",2218,93],[\"invoke\",2266,0,\"f32.epsilon\",[]],[\"module\",2272,94],[\"invoke\",2292,0,\"f32.no_trichotomy_lt\",[\"f32:0\",\"f32:2143289344\"]],[\"invoke\",2294,0,\"f32.no_trichotomy_gt\",[\"f32:0\",\"f32:2143289344\"]],[\"invoke\",2295,0,\"f32.no_trichotomy_ge\",[\"f32:0\",\"f32:2143289344\"]],[\"invoke\",2297,0,\"f64.no_trichotomy_le\",[\"f64:0\",\"f64:9221120237041090560\"]],[\"invoke\",2299,0,\"f64.no_trichotomy_ge\",[\"f64:0\",\"f64:9221120237041090560\"]],[\"module\",2304,95],[\"invoke\",2385,0,\"f32.canonical_nan_bitpattern\",[\"i32:2143289344\",\"i32:2143289344\"]],[\"invoke\",2387,0,\"f32.canonical_nan_bitpattern\",[\"i32:2143289344\",\"i32:4290772992\"]],[\"invoke\",2389,0,\"f32.nonarithmetic_nan_bitpattern\",[\"i32:2143302160\"]],[\"invoke\",2394,0,\"f64.canonical_nan_bitpattern\",[\"i64:0\",\"i64:0\"]],[\"invoke\",2396,0,\"f64.canonical_nan_bitpattern\",[\"i64:18444492273895866368\",\"i64:9221120237041090560\"]],[\"invoke\",2398,0,\"f64.canonical_nan_bitpattern\",[\"i64:18444492273895866368\",\"i64:18444492273895866368\"]],[\"invoke\",2400,0,\"f64.nonarithmetic_nan_bitpattern\",[\"i64:18444492273895879184\"]],[\"module\",2420,96],[\"invoke\",2447,0,\"with_binary_sum_collapse\",[\"f64:4719355144821538816\",\"f64:4607182418800017408\",\"f64:13830554455654793216\",\"f64:4725141118604279808\",\"f64:4720637518976909312\",\"f64:4607182418800017408\",\"f64:13830554455654793216\",\"f64:13938223582048944128\"]],[\"module\",2454,97],[\"invoke\",2466,0,\"f32.contract2fma\",[\"f32:1066192077\",\"f32:1066192077\"]],[\"invoke\",2468,0,\"f64.contract2fma\",[\"f64:4607182418800017408\",\"f64:4607182418800017408\"]],[\"invoke\",2469,0,\"f64.contract2fma\",[\"f64:4607632778762754458\",\"f64:4607632778762754458\"]],[\"module\",2475,98],[\"invoke\",2484,0,\"f32.division_by_small_number\",[\"f32:1289068416\",\"f32:1203982336\",\"f32:980151802\"]],[\"module\",2490,99],[\"invoke\",2497,0,\"f32.golden_ratio\",[\"f32:1056964608\",\"f32:1065353216\",\"f32:1084227584\"]],[\"module\",2503,100],[\"invoke\",2516,0,\"f32.silver_means\",[\"f32:0\"]],[\"invoke\",2518,0,\"f32.silver_means\",[\"f32:1073741824\"]],[\"invoke\",2520,0,\"f32.silver_means\",[\"f32:1082130432\"]],[\"invoke\",2522,0,\"f64.silver_means\",[\"f64:0\"]],[\"invoke\",2524,0,\"f64.silver_means\",[\"f64:4611686018427387904\"]],[\"invoke\",2526,0,\"f64.silver_means\",[\"f64:4616189618054758400\"]],[\"module\",2532,101],[\"invoke\",2537,0,\"point_four\",[\"f64:4616189618054758400\",\"f64:4621819117588971520\"]],[\"module\",2542,102],[\"invoke\",2608,0,\"tau\",[\"i32:11\"]],[\"module\",2612,103],[\"invoke\",2624,0,\"f64.no_fold_conditional_inc\",[\"f64:9223372036854775808\",\"f64:13830554455654793216\"]],[\"file\",\"float_literals.wast\"],[\"module\",3,104],[\"invoke\",121,0,\"f32.nan\",[]],[\"invoke\",122,0,\"f32.positive_nan\",[]],[\"invoke\",123,0,\"f32.negative_nan\",[]],[\"invoke\",124,0,\"f32.plain_nan\",[]],[\"invoke\",125,0,\"f32.informally_known_as_plain_snan\",[]],[\"invoke\",126,0,\"f32.all_ones_nan\",[]],[\"invoke\",127,0,\"f32.misc_nan\",[]],[\"invoke\",128,0,\"f32.misc_positive_nan\",[]],[\"invoke\",129,0,\"f32.misc_negative_nan\",[]],[\"invoke\",130,0,\"f32.infinity\",[]],[\"invoke\",131,0,\"f32.positive_infinity\",[]],[\"invoke\",132,0,\"f32.negative_infinity\",[]],[\"invoke\",133,0,\"f32.zero\",[]],[\"invoke\",134,0,\"f32.positive_zero\",[]],[\"invoke\",135,0,\"f32.negative_zero\",[]],[\"invoke\",136,0,\"f32.misc\",[]],[\"invoke\",137,0,\"f32.min_positive\",[]],[\"invoke\",138,0,\"f32.min_normal\",[]],[\"invoke\",139,0,\"f32.max_subnormal\",[]],[\"invoke\",140,0,\"f32.max_finite\",[]],[\"invoke\",141,0,\"f32.trailing_dot\",[]],[\"invoke\",142,0,\"f32.misc_int\",[]],[\"invoke\",143,0,\"f32.large_int\",[]],[\"invoke\",144,0,\"f32.min_int32\",[]],[\"invoke\",145,0,\"f32.min_int64\",[]],[\"invoke\",146,0,\"f32_dec.zero\",[]],[\"invoke\",147,0,\"f32_dec.positive_zero\",[]],[\"invoke\",148,0,\"f32_dec.negative_zero\",[]],[\"invoke\",149,0,\"f32_dec.misc\",[]],[\"invoke\",150,0,\"f32_dec.min_positive\",[]],[\"invoke\",151,0,\"f32_dec.min_normal\",[]],[\"invoke\",152,0,\"f32_dec.max_subnormal\",[]],[\"invoke\",153,0,\"f32_dec.max_finite\",[]],[\"invoke\",154,0,\"f32_dec.trailing_dot\",[]],[\"invoke\",155,0,\"f32_dec.root_beer_float\",[]],[\"invoke\",156,0,\"f32_dec.misc_int\",[]],[\"invoke\",157,0,\"f32_dec.large_int\",[]],[\"invoke\",158,0,\"f32_dec.min_int32\",[]],[\"invoke\",159,0,\"f32_dec.min_int64\",[]],[\"invoke\",161,0,\"f64.nan\",[]],[\"invoke\",162,0,\"f64.positive_nan\",[]],[\"invoke\",163,0,\"f64.negative_nan\",[]],[\"invoke\",164,0,\"f64.plain_nan\",[]],[\"invoke\",165,0,\"f64.informally_known_as_plain_snan\",[]],[\"invoke\",166,0,\"f64.all_ones_nan\",[]],[\"invoke\",167,0,\"f64.misc_nan\",[]],[\"invoke\",168,0,\"f64.misc_positive_nan\",[]],[\"invoke\",169,0,\"f64.misc_negative_nan\",[]],[\"invoke\",170,0,\"f64.infinity\",[]],[\"invoke\",171,0,\"f64.positive_infinity\",[]],[\"invoke\",172,0,\"f64.negative_infinity\",[]],[\"invoke\",173,0,\"f64.zero\",[]],[\"invoke\",174,0,\"f64.positive_zero\",[]],[\"invoke\",175,0,\"f64.negative_zero\",[]],[\"invoke\",176,0,\"f64.misc\",[]],[\"invoke\",177,0,\"f64.min_positive\",[]],[\"invoke\",178,0,\"f64.min_normal\",[]],[\"invoke\",179,0,\"f64.max_subnormal\",[]],[\"invoke\",180,0,\"f64.max_finite\",[]],[\"invoke\",181,0,\"f64.trailing_dot\",[]],[\"invoke\",182,0,\"f64.misc_int\",[]],[\"invoke\",183,0,\"f64.large_int\",[]],[\"invoke\",184,0,\"f64.min_int32\",[]],[\"invoke\",185,0,\"f64.min_int64\",[]],[\"invoke\",186,0,\"f64_dec.zero\",[]],[\"invoke\",187,0,\"f64_dec.positive_zero\",[]],[\"invoke\",188,0,\"f64_dec.negative_zero\",[]],[\"invoke\",189,0,\"f64_dec.misc\",[]],[\"invoke\",190,0,\"f64_dec.min_positive\",[]],[\"invoke\",191,0,\"f64_dec.min_normal\",[]],[\"invoke\",192,0,\"f64_dec.max_subnormal\",[]],[\"invoke\",193,0,\"f64_dec.max_finite\",[]],[\"invoke\",194,0,\"f64_dec.trailing_dot\",[]],[\"invoke\",195,0,\"f64_dec.root_beer_float\",[]],[\"invoke\",196,0,\"f64_dec.misc_int\",[]],[\"invoke\",197,0,\"f64_dec.large_int\",[]],[\"invoke\",198,0,\"f64_dec.min_int32\",[]],[\"invoke\",199,0,\"f64_dec.min_int64\",[]],[\"invoke\",201,0,\"f32-dec-sep1\",[]],[\"invoke\",202,0,\"f32-dec-sep2\",[]],[\"invoke\",203,0,\"f32-dec-sep3\",[]],[\"invoke\",204,0,\"f32-dec-sep4\",[]],[\"invoke\",205,0,\"f32-dec-sep5\",[]],[\"invoke\",206,0,\"f32-hex-sep1\",[]],[\"invoke\",207,0,\"f32-hex-sep2\",[]],[\"invoke\",208,0,\"f32-hex-sep3\",[]],[\"invoke\",209,0,\"f32-hex-sep4\",[]],[\"invoke\",210,0,\"f32-hex-sep5\",[]],[\"invoke\",212,0,\"f64-dec-sep1\",[]],[\"invoke\",213,0,\"f64-dec-sep2\",[]],[\"invoke\",214,0,\"f64-dec-sep3\",[]],[\"invoke\",215,0,\"f64-dec-sep4\",[]],[\"invoke\",216,0,\"f64-dec-sep5\",[]],[\"invoke\",217,0,\"f64-hex-sep1\",[]],[\"invoke\",218,0,\"f64-hex-sep2\",[]],[\"invoke\",219,0,\"f64-hex-sep3\",[]],[\"invoke\",220,0,\"f64-hex-sep4\",[]],[\"invoke\",221,0,\"f64-hex-sep5\",[]],[\"module\",224,105],[\"invoke\",233,0,\"4294967249\",[]],[\"file\",\"float_misc.wast\"],[\"module\",17,106],[\"invoke\",50,0,\"f32.add\",[\"f32:1066388847\",\"f32:789036054\"]],[\"invoke\",55,0,\"f32.add\",[\"f32:1065353216\",\"f32:864026624\"]],[\"invoke\",56,0,\"f32.add\",[\"f32:1065353216\",\"f32:864026625\"]],[\"invoke\",57,0,\"f64.add\",[\"f64:4607182418800017408\",\"f64:4368491638549381120\"]],[\"invoke\",58,0,\"f64.add\",[\"f64:4607182418800017408\",\"f64:4368491638549381121\"]],[\"invoke\",61,0,\"f32.add\",[\"f32:1\",\"f32:8388607\"]],[\"invoke\",62,0,\"f64.add\",[\"f64:1\",\"f64:4503599627370495\"]],[\"invoke\",67,0,\"f32.add\",[\"f32:1325400064\",\"f32:1149241344\"]],[\"invoke\",72,0,\"f64.add\",[\"f64:9290926031265333248\",\"f64:5910\"]],[\"invoke\",75,0,\"f64.add\",[\"f64:4845873199050653696\",\"f64:4607182463836013682\"]],[\"invoke\",78,0,\"f64.add\",[\"f64:4845873199050653697\",\"f64:4607182281361063936\"]],[\"invoke\",81,0,\"f32.add\",[\"f32:1258291200\",\"f32:1056964608\"]],[\"invoke\",82,0,\"f32.add\",[\"f32:1258291201\",\"f32:1056964608\"]],[\"invoke\",83,0,\"f64.add\",[\"f64:4841369599423283200\",\"f64:4602678819172646912\"]],[\"invoke\",84,0,\"f64.add\",[\"f64:4841369599423283201\",\"f64:4602678819172646912\"]],[\"invoke\",88,0,\"f32.add\",[\"f32:1630930834\",\"f32:3650472296\"]],[\"invoke\",89,0,\"f32.add\",[\"f32:376639884\",\"f32:24880479\"]],[\"invoke\",90,0,\"f32.add\",[\"f32:1393899754\",\"f32:3680827526\"]],[\"invoke\",91,0,\"f32.add\",[\"f32:947682203\",\"f32:1958603311\"]],[\"invoke\",92,0,\"f64.add\",[\"f64:2751474943031650218\",\"f64:14953834855654151696\"]],[\"invoke\",93,0,\"f64.add\",[\"f64:14174076479053295281\",\"f64:3779173703388472492\"]],[\"invoke\",94,0,\"f64.add\",[\"f64:3940735352412940081\",\"f64:13776826739676942972\"]],[\"invoke\",95,0,\"f64.add\",[\"f64:6845567588107709194\",\"f64:4904758653169279867\"]],[\"invoke\",99,0,\"f32.add\",[\"f32:1670249659\",\"f32:3384781876\"]],[\"invoke\",100,0,\"f32.add\",[\"f32:692047414\",\"f32:2564611463\"]],[\"invoke\",101,0,\"f32.add\",[\"f32:2862180574\",\"f32:2122049802\"]],[\"invoke\",102,0,\"f32.add\",[\"f32:2848402951\",\"f32:2325576998\"]],[\"invoke\",103,0,\"f32.add\",[\"f32:982116028\",\"f32:2317187467\"]],[\"invoke\",104,0,\"f64.add\",[\"f64:5417704807092288842\",\"f64:11458115339210975423\"]],[\"invoke\",105,0,\"f64.add\",[\"f64:5306888566943064716\",\"f64:13560253914302152139\"]],[\"invoke\",107,0,\"f64.add\",[\"f64:14493512931660601393\",\"f64:14459238760182946131\"]],[\"invoke\",108,0,\"f64.add\",[\"f64:13847269089137787654\",\"f64:5913400236268010570\"]],[\"invoke\",111,0,\"f32.add\",[\"f32:2231979500\",\"f32:711174153\"]],[\"invoke\",112,0,\"f32.add\",[\"f32:3084976721\",\"f32:843077785\"]],[\"invoke\",113,0,\"f32.add\",[\"f32:3522127374\",\"f32:157500525\"]],[\"invoke\",114,0,\"f32.add\",[\"f32:78702389\",\"f32:3468399689\"]],[\"invoke\",115,0,\"f32.add\",[\"f32:2208228371\",\"f32:1870536627\"]],[\"invoke\",116,0,\"f64.add\",[\"f64:1111252809067936271\",\"f64:18353849863141451174\"]],[\"invoke\",118,0,\"f64.add\",[\"f64:9478779231447112314\",\"f64:3336259491488022866\"]],[\"invoke\",119,0,\"f64.add\",[\"f64:11541137186853127278\",\"f64:735421354967021004\"]],[\"invoke\",120,0,\"f64.add\",[\"f64:18181384676953296798\",\"f64:8211214354507491487\"]],[\"invoke\",123,0,\"f64.add\",[\"f64:17455847474217352892\",\"f64:8328504330151758329\"]],[\"invoke\",124,0,\"f64.add\",[\"f64:8138029755241725240\",\"f64:7967841770626914860\"]],[\"invoke\",125,0,\"f64.add\",[\"f64:16644741111433920314\",\"f64:7647005019700459394\"]],[\"invoke\",126,0,\"f64.add\",[\"f64:6696851869641768443\",\"f64:6935821972358342665\"]],[\"invoke\",130,0,\"f64.add\",[\"f64:17221080860858566255\",\"f64:18119355194379769652\"]],[\"invoke\",131,0,\"f64.add\",[\"f64:5479469458864399218\",\"f64:15063363222154738502\"]],[\"invoke\",132,0,\"f64.add\",[\"f64:15191393426281101490\",\"f64:2705322087145917275\"]],[\"invoke\",133,0,\"f64.add\",[\"f64:12404517211283155052\",\"f64:15933096090325362723\"]],[\"invoke\",134,0,\"f64.add\",[\"f64:12941874353187635000\",\"f64:149235811938438489\"]],[\"invoke\",137,0,\"f32.add\",[\"f32:2039859408\",\"f32:2137384617\"]],[\"invoke\",138,0,\"f32.add\",[\"f32:2135652809\",\"f32:4113932278\"]],[\"invoke\",140,0,\"f32.add\",[\"f32:4220448050\",\"f32:4286022000\"]],[\"invoke\",141,0,\"f32.add\",[\"f32:4280642669\",\"f32:4269109313\"]],[\"invoke\",142,0,\"f64.add\",[\"f64:9214390167692528523\",\"f64:9025765862177526868\"]],[\"invoke\",143,0,\"f64.add\",[\"f64:9218174330906182895\",\"f64:9066655639269665468\"]],[\"invoke\",144,0,\"f64.add\",[\"f64:18440065048487386960\",\"f64:9166530475417861020\"]],[\"invoke\",145,0,\"f64.add\",[\"f64:18341378616977760125\",\"f64:9218376305633483958\"]],[\"invoke\",146,0,\"f64.add\",[\"f64:9214484525044672441\",\"f64:18297568705700624757\"]],[\"invoke\",149,0,\"f32.add\",[\"f32:5932680\",\"f32:623\"]],[\"invoke\",151,0,\"f32.add\",[\"f32:2147483656\",\"f32:3701056\"]],[\"invoke\",152,0,\"f32.add\",[\"f32:2147483668\",\"f32:44113961\"]],[\"invoke\",153,0,\"f32.add\",[\"f32:453\",\"f32:2147507390\"]],[\"invoke\",154,0,\"f64.add\",[\"f64:5760669582171681\",\"f64:9233067416817195210\"]],[\"invoke\",155,0,\"f64.add\",[\"f64:9301019945277718\",\"f64:9236877899492518055\"]],[\"invoke\",156,0,\"f64.add\",[\"f64:9225260466475370890\",\"f64:9229416573822848190\"]],[\"invoke\",157,0,\"f64.add\",[\"f64:16303139584665809\",\"f64:9240639734747285230\"]],[\"invoke\",162,0,\"f32.add\",[\"f32:2139095038\",\"f32:1937768448\"]],[\"invoke\",163,0,\"f64.add\",[\"f64:9218868437227405310\",\"f64:8980177656976769024\"]],[\"invoke\",166,0,\"f32.add\",[\"f32:1073741824\",\"f32:1073741824\"]],[\"invoke\",167,0,\"f64.add\",[\"f64:4611686018427387904\",\"f64:4611686018427387904\"]],[\"invoke\",170,0,\"f32.add\",[\"f32:2139095039\",\"f32:1929379839\"]],[\"invoke\",171,0,\"f32.add\",[\"f32:2139095039\",\"f32:1929379840\"]],[\"invoke\",172,0,\"f64.add\",[\"f64:9218868437227405311\",\"f64:8975674057349398527\"]],[\"invoke\",173,0,\"f64.add\",[\"f64:9218868437227405311\",\"f64:8975674057349398528\"]],[\"invoke\",178,0,\"f64.sub\",[\"f64:4679240012837945344\",\"f64:4440549232587309056\"]],[\"invoke\",182,0,\"f32.sub\",[\"f32:1065353216\",\"f32:855638016\"]],[\"invoke\",183,0,\"f32.sub\",[\"f32:1065353216\",\"f32:855638017\"]],[\"invoke\",184,0,\"f64.sub\",[\"f64:4607182418800017408\",\"f64:4363988038922010624\"]],[\"invoke\",185,0,\"f64.sub\",[\"f64:4607182418800017408\",\"f64:4363988038922010625\"]],[\"invoke\",188,0,\"f32.sub\",[\"f32:183964211\",\"f32:4211807167\"]],[\"invoke\",189,0,\"f32.sub\",[\"f32:4213323727\",\"f32:3575761746\"]],[\"invoke\",191,0,\"f32.sub\",[\"f32:88711906\",\"f32:431212897\"]],[\"invoke\",192,0,\"f32.sub\",[\"f32:919562194\",\"f32:2319656354\"]],[\"invoke\",193,0,\"f64.sub\",[\"f64:12332465020357998661\",\"f64:9766989582560416510\"]],[\"invoke\",194,0,\"f64.sub\",[\"f64:16318450733361321773\",\"f64:13120762196173477233\"]],[\"invoke\",195,0,\"f64.sub\",[\"f64:2534186520144737456\",\"f64:10065159679028096147\"]],[\"invoke\",196,0,\"f64.sub\",[\"f64:81253721542311597\",\"f64:14524181566355681001\"]],[\"invoke\",197,0,\"f64.sub\",[\"f64:13189545483472981053\",\"f64:11407195172005604952\"]],[\"invoke\",201,0,\"f32.sub\",[\"f32:4130431355\",\"f32:1119299749\"]],[\"invoke\",202,0,\"f32.sub\",[\"f32:2517909066\",\"f32:864524238\"]],[\"invoke\",203,0,\"f32.sub\",[\"f32:2573484334\",\"f32:3114628459\"]],[\"invoke\",204,0,\"f32.sub\",[\"f32:4044667730\",\"f32:576942556\"]],[\"invoke\",205,0,\"f64.sub\",[\"f64:14742371419310964805\",\"f64:17888404506408184249\"]],[\"invoke\",206,0,\"f64.sub\",[\"f64:2953146626865245854\",\"f64:2577681730975527916\"]],[\"invoke\",207,0,\"f64.sub\",[\"f64:12932364735331397407\",\"f64:2320782934320318207\"]],[\"invoke\",208,0,\"f64.sub\",[\"f64:9365060414983047910\",\"f64:12353777816259046974\"]],[\"invoke\",212,0,\"f32.sub\",[\"f32:1869943590\",\"f32:943887556\"]],[\"invoke\",213,0,\"f32.sub\",[\"f32:3372906046\",\"f32:2919590399\"]],[\"invoke\",214,0,\"f32.sub\",[\"f32:2912495853\",\"f32:2745492671\"]],[\"invoke\",215,0,\"f32.sub\",[\"f32:3096029999\",\"f32:957859028\"]],[\"invoke\",216,0,\"f32.sub\",[\"f32:2939165019\",\"f32:2346559691\"]],[\"invoke\",217,0,\"f64.sub\",[\"f64:13126685627069188368\",\"f64:17084005755352353256\"]],[\"invoke\",218,0,\"f64.sub\",[\"f64:17250804263238954355\",\"f64:17897322818375888829\"]],[\"invoke\",220,0,\"f64.sub\",[\"f64:11250563508203208480\",\"f64:2160870138446053709\"]],[\"invoke\",221,0,\"f64.sub\",[\"f64:11315355526970152728\",\"f64:13767999508795332779\"]],[\"invoke\",224,0,\"f64.sub\",[\"f64:4259315722107263852\",\"f64:4089445689175118070\"]],[\"invoke\",225,0,\"f64.sub\",[\"f64:188121565440651108\",\"f64:62416789530785743\"]],[\"invoke\",226,0,\"f64.sub\",[\"f64:3467770562990504390\",\"f64:3710644584044210353\"]],[\"invoke\",227,0,\"f64.sub\",[\"f64:13655868372788825570\",\"f64:13522732182992332383\"]],[\"invoke\",228,0,\"f64.sub\",[\"f64:4496158230847685281\",\"f64:4595312328816348364\"]],[\"invoke\",231,0,\"f64.sub\",[\"f64:10165048318361601487\",\"f64:10384658282813060399\"]],[\"invoke\",233,0,\"f64.sub\",[\"f64:5124509539958121241\",\"f64:1953255029772502995\"]],[\"invoke\",234,0,\"f64.sub\",[\"f64:2408125051325635052\",\"f64:10707017964771367822\"]],[\"invoke\",235,0,\"f64.sub\",[\"f64:4352135458168599028\",\"f64:1826599214642193119\"]],[\"invoke\",239,0,\"f32.sub\",[\"f32:1102651427\",\"f32:1078530011\"]],[\"invoke\",240,0,\"f64.sub\",[\"f64:4627206743602008890\",\"f64:4614256656552045848\"]],[\"invoke\",243,0,\"f32.sub\",[\"f32:1245125372\",\"f32:1245125368\"]],[\"invoke\",244,0,\"f32.sub\",[\"f32:1240736760\",\"f32:1240736728\"]],[\"invoke\",246,0,\"f32.sub\",[\"f32:1220759616\",\"f32:1220759584\"]],[\"invoke\",247,0,\"f32.sub\",[\"f32:1220759616\",\"f32:1220759552\"]],[\"invoke\",248,0,\"f64.sub\",[\"f64:4838362400168542206\",\"f64:4838362400168542204\"]],[\"invoke\",249,0,\"f64.sub\",[\"f64:4835858800541171708\",\"f64:4835858800541171692\"]],[\"invoke\",250,0,\"f64.sub\",[\"f64:4835858800541171708\",\"f64:4835858800541171684\"]],[\"invoke\",251,0,\"f64.sub\",[\"f64:4825251601286430752\",\"f64:4825251601286430736\"]],[\"invoke\",252,0,\"f64.sub\",[\"f64:4825251601286430752\",\"f64:4825251601286430720\"]],[\"invoke\",256,0,\"f64.sub\",[\"f64:4503599627370496\",\"f64:4503599627370495\"]],[\"invoke\",259,0,\"f32.sub\",[\"f32:1065353217\",\"f32:1065353215\"]],[\"invoke\",260,0,\"f32.sub\",[\"f32:1065353217\",\"f32:1065353216\"]],[\"invoke\",261,0,\"f32.sub\",[\"f32:1065353216\",\"f32:1065353215\"]],[\"invoke\",262,0,\"f64.sub\",[\"f64:4607182418800017409\",\"f64:4607182418800017407\"]],[\"invoke\",263,0,\"f64.sub\",[\"f64:4607182418800017409\",\"f64:4607182418800017408\"]],[\"invoke\",264,0,\"f64.sub\",[\"f64:4607182418800017408\",\"f64:4607182418800017407\"]],[\"invoke\",268,0,\"f32.sub\",[\"f32:2139095039\",\"f32:1929379839\"]],[\"invoke\",270,0,\"f64.sub\",[\"f64:9218868437227405311\",\"f64:8975674057349398527\"]],[\"invoke\",271,0,\"f64.sub\",[\"f64:9218868437227405311\",\"f64:8975674057349398528\"]],[\"invoke\",274,0,\"f32.mul\",[\"f32:1482907561\",\"f32:1482907561\"]],[\"invoke\",275,0,\"f32.mul\",[\"f32:1621981420\",\"f32:1621981420\"]],[\"invoke\",276,0,\"f32.mul\",[\"f32:1761892689\",\"f32:1761892689\"]],[\"invoke\",277,0,\"f64.mul\",[\"f64:4831355200913801216\",\"f64:4831355200913801216\"]],[\"invoke\",278,0,\"f64.mul\",[\"f64:4906019910204099648\",\"f64:4906019910204099648\"]],[\"invoke\",284,0,\"f32.mul\",[\"f32:1323067183\",\"f32:1351920719\"]],[\"invoke\",285,0,\"f64.mul\",[\"f64:4745541551160033280\",\"f64:4761032175258435584\"]],[\"invoke\",289,0,\"f32.mul\",[\"f32:1117401907\",\"f32:1146388480\"]],[\"invoke\",290,0,\"f64.mul\",[\"f64:4635125847112967782\",\"f64:4650687894887858176\"]],[\"invoke\",293,0,\"f32.mul\",[\"f32:3725225879\",\"f32:767181884\"]],[\"invoke\",294,0,\"f32.mul\",[\"f32:4071298289\",\"f32:2363594761\"]],[\"invoke\",295,0,\"f32.mul\",[\"f32:3990403914\",\"f32:2406636213\"]],[\"invoke\",296,0,\"f32.mul\",[\"f32:3876262739\",\"f32:2590162616\"]],[\"invoke\",298,0,\"f64.mul\",[\"f64:15745248579468343268\",\"f64:8867411559652116184\"]],[\"invoke\",299,0,\"f64.mul\",[\"f64:9393595877903301723\",\"f64:12847704090045979814\"]],[\"invoke\",300,0,\"f64.mul\",[\"f64:7339743909496900438\",\"f64:14312273837817528234\"]],[\"invoke\",301,0,\"f64.mul\",[\"f64:12422633289194844785\",\"f64:7233496047441461849\"]],[\"invoke\",302,0,\"f64.mul\",[\"f64:8188526721198436258\",\"f64:745378815681991665\"]],[\"invoke\",305,0,\"f32.mul\",[\"f32:2468326353\",\"f32:3984723636\"]],[\"invoke\",306,0,\"f32.mul\",[\"f32:1680245441\",\"f32:1228341789\"]],[\"invoke\",308,0,\"f32.mul\",[\"f32:1631446143\",\"f32:3340593865\"]],[\"invoke\",309,0,\"f32.mul\",[\"f32:1321841569\",\"f32:3771977928\"]],[\"invoke\",310,0,\"f64.mul\",[\"f64:10956034376207055635\",\"f64:3195464683520095288\"]],[\"invoke\",311,0,\"f64.mul\",[\"f64:3366372084365197849\",\"f64:10971437597834234015\"]],[\"invoke\",312,0,\"f64.mul\",[\"f64:15316253934569525376\",\"f64:17593766689751523793\"]],[\"invoke\",313,0,\"f64.mul\",[\"f64:8873430008942894499\",\"f64:2630310872370005737\"]],[\"invoke\",314,0,\"f64.mul\",[\"f64:6138390736122864607\",\"f64:14591737303103877464\"]],[\"invoke\",318,0,\"f32.mul\",[\"f32:3192278970\",\"f32:242309673\"]],[\"invoke\",319,0,\"f32.mul\",[\"f32:3060150317\",\"f32:1953896717\"]],[\"invoke\",320,0,\"f32.mul\",[\"f32:3685735869\",\"f32:265571119\"]],[\"invoke\",321,0,\"f32.mul\",[\"f32:3355295625\",\"f32:69023757\"]],[\"invoke\",322,0,\"f64.mul\",[\"f64:11598370094301102699\",\"f64:15783697741936198553\"]],[\"invoke\",323,0,\"f64.mul\",[\"f64:6760137556672200005\",\"f64:10390971429121674926\"]],[\"invoke\",324,0,\"f64.mul\",[\"f64:14608361260346079328\",\"f64:6227029768418337647\"]],[\"invoke\",325,0,\"f64.mul\",[\"f64:8069908676175581646\",\"f64:15517844864458283108\"]],[\"invoke\",329,0,\"f64.mul\",[\"f64:3075846375446710711\",\"f64:2019524319895558702\"]],[\"invoke\",330,0,\"f64.mul\",[\"f64:13792389967993824121\",\"f64:7865224186729676301\"]],[\"invoke\",331,0,\"f64.mul\",[\"f64:15483491341737357200\",\"f64:10898188329492193507\"]],[\"invoke\",332,0,\"f64.mul\",[\"f64:8100886621587996114\",\"f64:4881220253517731398\"]],[\"invoke\",333,0,\"f64.mul\",[\"f64:12589892576446671178\",\"f64:8646729507413822745\"]],[\"invoke\",336,0,\"f64.mul\",[\"f64:1913383041739811140\",\"f64:2696786534754768330\"]],[\"invoke\",337,0,\"f64.mul\",[\"f64:13576886274997223712\",\"f64:9480836654241272360\"]],[\"invoke\",339,0,\"f64.mul\",[\"f64:10357270161024523488\",\"f64:3460198093814186274\"]],[\"invoke\",340,0,\"f64.mul\",[\"f64:11506848031881565957\",\"f64:11547514064126512393\"]],[\"invoke\",343,0,\"f64.mul\",[\"f64:8446730755803745435\",\"f64:640105622936255012\"]],[\"invoke\",344,0,\"f64.mul\",[\"f64:4578331547514318947\",\"f64:3937894457345907544\"]],[\"invoke\",345,0,\"f64.mul\",[\"f64:13839181621982597923\",\"f64:4426983905639375259\"]],[\"invoke\",346,0,\"f64.mul\",[\"f64:2931365221683549507\",\"f64:3687257801017498184\"]],[\"invoke\",347,0,\"f64.mul\",[\"f64:14180592859625794472\",\"f64:3043918530412552819\"]],[\"invoke\",350,0,\"f32.mul\",[\"f32:436207616\",\"f32:436207616\"]],[\"invoke\",352,0,\"f64.mul\",[\"f64:2186111271319845836\",\"f64:2186111271319845836\"]],[\"invoke\",353,0,\"f64.mul\",[\"f64:2186111271319845837\",\"f64:2186111271319845837\"]],[\"invoke\",356,0,\"f32.mul\",[\"f32:1602224127\",\"f32:1602224127\"]],[\"invoke\",357,0,\"f32.mul\",[\"f32:1602224128\",\"f32:1602224128\"]],[\"invoke\",358,0,\"f64.mul\",[\"f64:6913025428013711359\",\"f64:6913025428013711359\"]],[\"invoke\",359,0,\"f64.mul\",[\"f64:6913025428013711360\",\"f64:6913025428013711360\"]],[\"invoke\",362,0,\"f32.mul\",[\"f32:1065353217\",\"f32:1065353217\"]],[\"invoke\",364,0,\"f64.mul\",[\"f64:4607182418800017409\",\"f64:4607182418800017409\"]],[\"invoke\",365,0,\"f64.mul\",[\"f64:4607182418800017407\",\"f64:4607182418800017407\"]],[\"invoke\",368,0,\"f32.mul\",[\"f32:1065353217\",\"f32:1065353215\"]],[\"invoke\",369,0,\"f32.mul\",[\"f32:1065353218\",\"f32:1065353214\"]],[\"invoke\",370,0,\"f64.mul\",[\"f64:4607182418800017409\",\"f64:4607182418800017407\"]],[\"invoke\",371,0,\"f64.mul\",[\"f64:4607182418800017410\",\"f64:4607182418800017406\"]],[\"invoke\",375,0,\"f32.mul\",[\"f32:8388608\",\"f32:872415232\"]],[\"invoke\",376,0,\"f64.mul\",[\"f64:4503599627370496\",\"f64:4372995238176751616\"]],[\"invoke\",382,0,\"f32.div\",[\"f32:1066388847\",\"f32:1120403456\"]],[\"invoke\",383,0,\"f32.div\",[\"f32:1258294259\",\"f32:1262485497\"]],[\"invoke\",384,0,\"f32.div\",[\"f32:1199570944\",\"f32:754974720\"]],[\"invoke\",385,0,\"f32.div\",[\"f32:1072586677\",\"f32:2139095039\"]],[\"invoke\",386,0,\"f32.div\",[\"f32:1082130432\",\"f32:1077936128\"]],[\"invoke\",387,0,\"f64.div\",[\"f64:4607738418748954166\",\"f64:4636737291354636288\"]],[\"invoke\",388,0,\"f64.div\",[\"f64:4710766852517658624\",\"f64:4713017006285127680\"]],[\"invoke\",390,0,\"f64.div\",[\"f64:4611065853604003840\",\"f64:9218868437227405311\"]],[\"invoke\",391,0,\"f64.div\",[\"f64:4616189618054758400\",\"f64:4613937818241073152\"]],[\"invoke\",395,0,\"f32.div\",[\"f32:1249905654\",\"f32:1245708284\"]],[\"invoke\",396,0,\"f64.div\",[\"f64:4706263254500900864\",\"f64:4704009808640999424\"]],[\"invoke\",399,0,\"f32.div\",[\"f32:666187309\",\"f32:2138899896\"]],[\"invoke\",400,0,\"f32.div\",[\"f32:338737113\",\"f32:1637454009\"]],[\"invoke\",401,0,\"f32.div\",[\"f32:3351999420\",\"f32:401966635\"]],[\"invoke\",403,0,\"f32.div\",[\"f32:3564182439\",\"f32:397999726\"]],[\"invoke\",404,0,\"f64.div\",[\"f64:5269602717919885505\",\"f64:5615431061534361830\"]],[\"invoke\",405,0,\"f64.div\",[\"f64:1791703022234099881\",\"f64:230587289790163684\"]],[\"invoke\",406,0,\"f64.div\",[\"f64:15176390632865983384\",\"f64:7489729865403831125\"]],[\"invoke\",407,0,\"f64.div\",[\"f64:16945752639368638170\",\"f64:5718505335991307775\"]],[\"invoke\",408,0,\"f64.div\",[\"f64:16263708435887736913\",\"f64:6648866667391375614\"]],[\"invoke\",411,0,\"f32.div\",[\"f32:3798033061\",\"f32:2491443361\"]],[\"invoke\",412,0,\"f32.div\",[\"f32:686927199\",\"f32:1440960248\"]],[\"invoke\",414,0,\"f32.div\",[\"f32:642177221\",\"f32:2685135\"]],[\"invoke\",415,0,\"f32.div\",[\"f32:1824271560\",\"f32:1797890210\"]],[\"invoke\",416,0,\"f64.div\",[\"f64:4278321332874065\",\"f64:15464375907975098426\"]],[\"invoke\",417,0,\"f64.div\",[\"f64:9385779280320516508\",\"f64:2195352144435258723\"]],[\"invoke\",418,0,\"f64.div\",[\"f64:12019785500442997560\",\"f64:14033717547286793792\"]],[\"invoke\",419,0,\"f64.div\",[\"f64:14192622858179995755\",\"f64:1048270709368415171\"]],[\"invoke\",420,0,\"f64.div\",[\"f64:14895971708944847415\",\"f64:14082502105592202184\"]],[\"invoke\",424,0,\"f32.div\",[\"f32:3861505243\",\"f32:1298875600\"]],[\"invoke\",425,0,\"f32.div\",[\"f32:1387472197\",\"f32:4021087697\"]],[\"invoke\",426,0,\"f32.div\",[\"f32:4149014653\",\"f32:3294714580\"]],[\"invoke\",427,0,\"f32.div\",[\"f32:3987554477\",\"f32:3327836421\"]],[\"invoke\",428,0,\"f64.div\",[\"f64:10882983485852580843\",\"f64:1499380901194126188\"]],[\"invoke\",429,0,\"f64.div\",[\"f64:17353632673902705731\",\"f64:11343375610449883809\"]],[\"invoke\",430,0,\"f64.div\",[\"f64:10471640091267549911\",\"f64:4317563730789156718\"]],[\"invoke\",431,0,\"f64.div\",[\"f64:15262578458239868670\",\"f64:11184694400998603321\"]],[\"invoke\",435,0,\"f64.div\",[\"f64:16467910284400317286\",\"f64:14710955777971283471\"]],[\"invoke\",436,0,\"f64.div\",[\"f64:16290965053652735591\",\"f64:15317706837775392625\"]],[\"invoke\",437,0,\"f64.div\",[\"f64:5440366292977110369\",\"f64:18177315027663270891\"]],[\"invoke\",438,0,\"f64.div\",[\"f64:5586144947709395659\",\"f64:6532436425888624877\"]],[\"invoke\",439,0,\"f64.div\",[\"f64:17550005606375580620\",\"f64:6180045224129992598\"]],[\"invoke\",442,0,\"f64.div\",[\"f64:4611065853588275309\",\"f64:9218868437227405311\"]],[\"invoke\",443,0,\"f64.div\",[\"f64:58005932411648970\",\"f64:4662638783615926141\"]],[\"invoke\",445,0,\"f64.div\",[\"f64:12062544014359965409\",\"f64:16674780236028867992\"]],[\"invoke\",446,0,\"f64.div\",[\"f64:752499975442349235\",\"f64:5356690663768240614\"]],[\"invoke\",447,0,\"f64.div\",[\"f64:83041154348962762\",\"f64:13934889252531101931\"]],[\"invoke\",450,0,\"f32.div\",[\"f32:1817629909\",\"f32:1421132838\"]],[\"invoke\",451,0,\"f32.div\",[\"f32:1824604388\",\"f32:4105258014\"]],[\"invoke\",452,0,\"f32.div\",[\"f32:1747858929\",\"f32:1063784191\"]],[\"invoke\",453,0,\"f32.div\",[\"f32:3378374953\",\"f32:3497907368\"]],[\"invoke\",455,0,\"f64.div\",[\"f64:4885037015672297625\",\"f64:9954263091431620051\"]],[\"invoke\",456,0,\"f64.div\",[\"f64:4022347395424227722\",\"f64:11486191764643320419\"]],[\"invoke\",457,0,\"f64.div\",[\"f64:7623973190248181230\",\"f64:7481900282945497881\"]],[\"invoke\",458,0,\"f64.div\",[\"f64:14650287530633645126\",\"f64:8091500841285620267\"]],[\"invoke\",459,0,\"f64.div\",[\"f64:9910334119372230161\",\"f64:10921408834055405722\"]],[\"invoke\",462,0,\"f64.div\",[\"f64:4352764235752579571\",\"f64:4610853254537913145\"]],[\"invoke\",463,0,\"f64.div\",[\"f64:4148344307949876337\",\"f64:13642564096968604240\"]],[\"invoke\",464,0,\"f64.div\",[\"f64:4425554483259831683\",\"f64:4103199251532205583\"]],[\"invoke\",466,0,\"f64.div\",[\"f64:4558988817252519457\",\"f64:14066629987516208597\"]],[\"invoke\",469,0,\"f32.div\",[\"f32:8388608\",\"f32:8388607\"]],[\"invoke\",470,0,\"f32.div\",[\"f32:8388607\",\"f32:8388608\"]],[\"invoke\",471,0,\"f64.div\",[\"f64:4503599627370496\",\"f64:4503599627370495\"]],[\"invoke\",472,0,\"f64.div\",[\"f64:4503599627370495\",\"f64:4503599627370496\"]],[\"invoke\",475,0,\"f32.div\",[\"f32:880803839\",\"f32:2139095039\"]],[\"invoke\",476,0,\"f32.div\",[\"f32:880803840\",\"f32:2139095039\"]],[\"invoke\",478,0,\"f64.div\",[\"f64:4377498837804122112\",\"f64:9218868437227405311\"]],[\"invoke\",481,0,\"f32.div\",[\"f32:1065353216\",\"f32:2097152\"]],[\"invoke\",482,0,\"f32.div\",[\"f32:1065353216\",\"f32:2097153\"]],[\"invoke\",483,0,\"f64.div\",[\"f64:4607182418800017408\",\"f64:1125899906842624\"]],[\"invoke\",484,0,\"f64.div\",[\"f64:4607182418800017408\",\"f64:1125899906842625\"]],[\"invoke\",487,0,\"f32.div\",[\"f32:1065353216\",\"f32:2122317825\"]],[\"invoke\",488,0,\"f32.div\",[\"f32:1065353216\",\"f32:2122317824\"]],[\"invoke\",489,0,\"f64.div\",[\"f64:4607182418800017408\",\"f64:9209861237972664321\"]],[\"invoke\",500,0,\"f32.div\",[\"f32:1065353216\",\"f32:1077936128\"]],[\"invoke\",501,0,\"f32.div\",[\"f32:1077936128\",\"f32:1091567616\"]],[\"invoke\",502,0,\"f32.div\",[\"f32:1091567616\",\"f32:1104674816\"]],[\"invoke\",503,0,\"f64.div\",[\"f64:4607182418800017408\",\"f64:4613937818241073152\"]],[\"invoke\",504,0,\"f64.div\",[\"f64:4613937818241073152\",\"f64:4621256167635550208\"]],[\"invoke\",505,0,\"f64.div\",[\"f64:4621256167635550208\",\"f64:4628293042053316608\"]],[\"invoke\",508,0,\"f32.div\",[\"f32:1065353217\",\"f32:1065353215\"]],[\"invoke\",510,0,\"f32.div\",[\"f32:1065353216\",\"f32:1065353215\"]],[\"invoke\",511,0,\"f32.div\",[\"f32:1065353216\",\"f32:1065353217\"]],[\"invoke\",512,0,\"f64.div\",[\"f64:4607182418800017409\",\"f64:4607182418800017407\"]],[\"invoke\",513,0,\"f64.div\",[\"f64:4607182418800017407\",\"f64:4607182418800017409\"]],[\"invoke\",514,0,\"f64.div\",[\"f64:4607182418800017408\",\"f64:4607182418800017407\"]],[\"invoke\",515,0,\"f64.div\",[\"f64:4607182418800017408\",\"f64:4607182418800017409\"]],[\"invoke\",519,0,\"f32.sqrt\",[\"f32:1126891520\"]],[\"invoke\",521,0,\"f64.sqrt\",[\"f64:4640220544191430656\"]],[\"invoke\",522,0,\"f64.sqrt\",[\"f64:4505170691092029701\"]],[\"invoke\",525,0,\"f64.sqrt\",[\"f64:3868634135368364633\"]],[\"invoke\",526,0,\"f64.sqrt\",[\"f64:6130334638238385213\"]],[\"invoke\",527,0,\"f64.sqrt\",[\"f64:6420749722515481850\"]],[\"invoke\",528,0,\"f64.sqrt\",[\"f64:4441983858611369811\"]],[\"invoke\",529,0,\"f64.sqrt\",[\"f64:2943222801950574478\"]],[\"invoke\",533,0,\"f64.sqrt\",[\"f64:4607182418800017407\"]],[\"invoke\",537,0,\"f32.sqrt\",[\"f32:1911349112\"]],[\"invoke\",538,0,\"f32.sqrt\",[\"f32:1033984731\"]],[\"invoke\",539,0,\"f32.sqrt\",[\"f32:471548520\"]],[\"invoke\",540,0,\"f32.sqrt\",[\"f32:980180519\"]],[\"invoke\",541,0,\"f64.sqrt\",[\"f64:447478826231748356\"]],[\"invoke\",542,0,\"f64.sqrt\",[\"f64:7882765141420635228\"]],[\"invoke\",543,0,\"f64.sqrt\",[\"f64:6345006020483303032\"]],[\"invoke\",545,0,\"f64.sqrt\",[\"f64:9108179429173493573\"]],[\"invoke\",548,0,\"f32.sqrt\",[\"f32:1293112087\"]],[\"invoke\",549,0,\"f32.sqrt\",[\"f32:118173697\"]],[\"invoke\",550,0,\"f32.sqrt\",[\"f32:941555112\"]],[\"invoke\",551,0,\"f32.sqrt\",[\"f32:60323222\"]],[\"invoke\",552,0,\"f32.sqrt\",[\"f32:40220718\"]],[\"invoke\",553,0,\"f64.sqrt\",[\"f64:7741365502393472048\"]],[\"invoke\",554,0,\"f64.sqrt\",[\"f64:1175297548666390979\"]],[\"invoke\",556,0,\"f64.sqrt\",[\"f64:3521792205891300764\"]],[\"invoke\",557,0,\"f64.sqrt\",[\"f64:8798159938601760820\"]],[\"invoke\",560,0,\"f32.sqrt\",[\"f32:2058533950\"]],[\"invoke\",561,0,\"f32.sqrt\",[\"f32:1194962978\"]],[\"invoke\",562,0,\"f32.sqrt\",[\"f32:1062209384\"]],[\"invoke\",563,0,\"f32.sqrt\",[\"f32:339551808\"]],[\"invoke\",564,0,\"f32.sqrt\",[\"f32:279876202\"]],[\"invoke\",566,0,\"f64.sqrt\",[\"f64:2893298197477532149\"]],[\"invoke\",567,0,\"f64.sqrt\",[\"f64:8954101765007123282\"]],[\"invoke\",568,0,\"f64.sqrt\",[\"f64:4405641787023763989\"]],[\"invoke\",569,0,\"f64.sqrt\",[\"f64:2394824404681191749\"]],[\"invoke\",572,0,\"f64.sqrt\",[\"f64:9494538992465685200\"]],[\"invoke\",573,0,\"f64.sqrt\",[\"f64:5074882158241187075\"]],[\"invoke\",574,0,\"f64.sqrt\",[\"f64:1187437724299488295\"]],[\"invoke\",576,0,\"f64.sqrt\",[\"f64:7161993531047854177\"]],[\"invoke\",579,0,\"f32.sqrt\",[\"f32:1065353217\"]],[\"invoke\",580,0,\"f32.sqrt\",[\"f32:1065353218\"]],[\"invoke\",581,0,\"f64.sqrt\",[\"f64:4607182418800017409\"]],[\"invoke\",582,0,\"f64.sqrt\",[\"f64:4607182418800017410\"]],[\"invoke\",585,0,\"f32.sqrt\",[\"f32:1065353214\"]],[\"invoke\",586,0,\"f32.sqrt\",[\"f32:1065353213\"]],[\"invoke\",587,0,\"f64.sqrt\",[\"f64:4607182418800017406\"]],[\"invoke\",591,0,\"f32.sqrt\",[\"f32:1048575999\"]],[\"invoke\",592,0,\"f32.sqrt\",[\"f32:1048576000\"]],[\"invoke\",593,0,\"f32.sqrt\",[\"f32:1048576001\"]],[\"invoke\",594,0,\"f32.sqrt\",[\"f32:1048576002\"]],[\"invoke\",595,0,\"f64.sqrt\",[\"f64:4598175219545276415\"]],[\"invoke\",596,0,\"f64.sqrt\",[\"f64:4598175219545276416\"]],[\"invoke\",597,0,\"f64.sqrt\",[\"f64:4598175219545276417\"]],[\"invoke\",602,0,\"f32.sqrt\",[\"f32:1383964906\"]],[\"invoke\",603,0,\"f64.sqrt\",[\"f64:4778235767454362395\"]],[\"invoke\",607,0,\"f32.sqrt\",[\"f32:1344155865\"]],[\"invoke\",608,0,\"f32.sqrt\",[\"f32:1496014503\"]],[\"invoke\",609,0,\"f32.sqrt\",[\"f32:1437792278\"]],[\"invoke\",610,0,\"f32.sqrt\",[\"f32:1318267910\"]],[\"invoke\",611,0,\"f64.sqrt\",[\"f64:4756863450987102208\"]],[\"invoke\",612,0,\"f64.sqrt\",[\"f64:4838391936821527880\"]],[\"invoke\",614,0,\"f64.sqrt\",[\"f64:4742964961037254656\"]],[\"invoke\",618,0,\"f32.sqrt\",[\"f32:1057229822\"]],[\"invoke\",619,0,\"f32.sqrt\",[\"f32:1057013196\"]],[\"invoke\",620,0,\"f64.sqrt\",[\"f64:4602821204854702080\"]],[\"invoke\",621,0,\"f64.sqrt\",[\"f64:4602704904656519168\"]],[\"invoke\",625,0,\"f32.sqrt\",[\"f32:1091567616\"]],[\"invoke\",626,0,\"f32.sqrt\",[\"f32:1124663296\"]],[\"invoke\",628,0,\"f32.sqrt\",[\"f32:1148846080\"]],[\"invoke\",629,0,\"f64.sqrt\",[\"f64:4621256167635550208\"]],[\"invoke\",630,0,\"f64.sqrt\",[\"f64:4639024275540410368\"]],[\"invoke\",631,0,\"f64.sqrt\",[\"f64:4610667174614947795\"]],[\"invoke\",632,0,\"f64.sqrt\",[\"f64:4652007308841189376\"]],[\"invoke\",656,0,\"f32.ceil\",[\"f32:1065353215\"]],[\"invoke\",657,0,\"f32.ceil\",[\"f32:1065353217\"]],[\"invoke\",659,0,\"f64.ceil\",[\"f64:4607182418800017409\"]],[\"invoke\",662,0,\"f32.ceil\",[\"f32:1258291199\"]],[\"invoke\",663,0,\"f32.ceil\",[\"f32:3405774847\"]],[\"invoke\",664,0,\"f64.ceil\",[\"f64:4841369599423283199\"]],[\"invoke\",665,0,\"f64.ceil\",[\"f64:14064741636278059007\"]],[\"invoke\",669,0,\"f32.ceil\",[\"f32:1266679807\"]],[\"invoke\",670,0,\"f32.ceil\",[\"f32:3414163455\"]],[\"invoke\",671,0,\"f64.ceil\",[\"f64:4845873199050653695\"]],[\"invoke\",675,0,\"f32.floor\",[\"f32:3212836863\"]],[\"invoke\",676,0,\"f32.floor\",[\"f32:3212836865\"]],[\"invoke\",677,0,\"f64.floor\",[\"f64:13830554455654793215\"]],[\"invoke\",678,0,\"f64.floor\",[\"f64:13830554455654793217\"]],[\"invoke\",681,0,\"f32.floor\",[\"f32:3405774847\"]],[\"invoke\",682,0,\"f32.floor\",[\"f32:1258291199\"]],[\"invoke\",683,0,\"f64.floor\",[\"f64:14064741636278059007\"]],[\"invoke\",688,0,\"f32.floor\",[\"f32:1202524032\"]],[\"invoke\",689,0,\"f64.floor\",[\"f64:4680825439885721600\"]],[\"invoke\",692,0,\"f32.trunc\",[\"f32:3405774847\"]],[\"invoke\",693,0,\"f32.trunc\",[\"f32:1258291199\"]],[\"invoke\",694,0,\"f64.trunc\",[\"f64:14064741636278059007\"]],[\"invoke\",695,0,\"f64.trunc\",[\"f64:4841369599423283199\"]],[\"invoke\",700,0,\"f32.nearest\",[\"f32:1258291201\"]],[\"invoke\",701,0,\"f32.nearest\",[\"f32:1258291202\"]],[\"invoke\",703,0,\"f32.nearest\",[\"f32:1468006399\"]],[\"invoke\",704,0,\"f64.nearest\",[\"f64:4841369599423283201\"]],[\"invoke\",705,0,\"f64.nearest\",[\"f64:4841369599423283202\"]],[\"invoke\",706,0,\"f64.nearest\",[\"f64:4602678819172646911\"]],[\"invoke\",707,0,\"f64.nearest\",[\"f64:5084563979301289983\"]],[\"invoke\",711,0,\"f32.nearest\",[\"f32:1083179008\"]],[\"invoke\",712,0,\"f32.nearest\",[\"f32:3230662656\"]],[\"invoke\",714,0,\"f64.nearest\",[\"f64:4616752568008179712\"]],[\"invoke\",715,0,\"f64.nearest\",[\"f64:13840124604862955520\"]],[\"invoke\",716,0,\"f64.nearest\",[\"f64:13838435755002691584\"]],[\"invoke\",719,0,\"f32.nearest\",[\"f32:3405774847\"]],[\"invoke\",720,0,\"f32.nearest\",[\"f32:1258291199\"]],[\"invoke\",721,0,\"f64.nearest\",[\"f64:14064741636278059007\"]],[\"invoke\",722,0,\"f64.nearest\",[\"f64:4841369599423283199\"]]]"; diff --git a/tests/corpus/webassembly-spec-float/main.mjs b/tests/corpus/webassembly-spec-float/main.mjs new file mode 100644 index 000000000..289dad212 --- /dev/null +++ b/tests/corpus/webassembly-spec-float/main.mjs @@ -0,0 +1,5 @@ +// Executes WebAssembly spec tests (9 files); see tests/wasm-spec/runner.mjs. +import { run } from "../../wasm-spec/runner.mjs"; +import { modules, script } from "./data.mjs"; + +run(modules, script); diff --git a/tests/corpus/webassembly-spec-int/data.mjs b/tests/corpus/webassembly-spec-int/data.mjs new file mode 100644 index 000000000..08a72be2f --- /dev/null +++ b/tests/corpus/webassembly-spec-int/data.mjs @@ -0,0 +1,33 @@ +/* Generated by tests/wasm-spec/gen.mjs from the WebAssembly 2.0 core test + * suite (WebAssembly/spec wg-2.0, Apache-2.0) with wabt 1.0.37 wast2json: + * i32.wast, i64.wast, int_exprs.wast, int_literals.wast, conversions.wast, traps.wast. Do not edit. */ +export const modules = [ + "AGFzbQEAAAABDAJgAn9/AX9gAX8BfwMgHwAAAAAAAAAAAAAAAAAAAAEBAQEBAQAAAAAAAAAAAAAH3gEfA2FkZAAAA3N1YgABA211bAACBWRpdl9zAAMFZGl2X3UABAVyZW1fcwAFBXJlbV91AAYDYW5kAAcCb3IACAN4b3IACQNzaGwACgVzaHJfcwALBXNocl91AAwEcm90bAANBHJvdHIADgNjbHoADwNjdHoAEAZwb3BjbnQAEQlleHRlbmQ4X3MAEgpleHRlbmQxNl9zABMDZXF6ABQCZXEAFQJuZQAWBGx0X3MAFwRsdF91ABgEbGVfcwAZBGxlX3UAGgRndF9zABsEZ3RfdQAcBGdlX3MAHQRnZV91AB4K7QEfBwAgACABagsHACAAIAFrCwcAIAAgAWwLBwAgACABbQsHACAAIAFuCwcAIAAgAW8LBwAgACABcAsHACAAIAFxCwcAIAAgAXILBwAgACABcwsHACAAIAF0CwcAIAAgAXULBwAgACABdgsHACAAIAF3CwcAIAAgAXgLBQAgAGcLBQAgAGgLBQAgAGkLBQAgAMALBQAgAMELBQAgAEULBwAgACABRgsHACAAIAFHCwcAIAAgAUgLBwAgACABSQsHACAAIAFMCwcAIAAgAU0LBwAgACABSgsHACAAIAFLCwcAIAAgAU4LBwAgACABTws=", + "AGFzbQEAAAABFwRgAn5+AX5gAX4BfmABfgF/YAJ+fgF/AyEgAAAAAAAAAAAAAAAAAAAAAQEBAQEBAgMDAwMDAwMDAwMH6wEgA2FkZAAAA3N1YgABA211bAACBWRpdl9zAAMFZGl2X3UABAVyZW1fcwAFBXJlbV91AAYDYW5kAAcCb3IACAN4b3IACQNzaGwACgVzaHJfcwALBXNocl91AAwEcm90bAANBHJvdHIADgNjbHoADwNjdHoAEAZwb3BjbnQAEQlleHRlbmQ4X3MAEgpleHRlbmQxNl9zABMKZXh0ZW5kMzJfcwAUA2VxegAVAmVxABYCbmUAFwRsdF9zABgEbHRfdQAZBGxlX3MAGgRsZV91ABsEZ3RfcwAcBGd0X3UAHQRnZV9zAB4EZ2VfdQAfCvMBIAcAIAAgAXwLBwAgACABfQsHACAAIAF+CwcAIAAgAX8LBwAgACABgAsHACAAIAGBCwcAIAAgAYILBwAgACABgwsHACAAIAGECwcAIAAgAYULBwAgACABhgsHACAAIAGHCwcAIAAgAYgLBwAgACABiQsHACAAIAGKCwUAIAB5CwUAIAB6CwUAIAB7CwUAIADCCwUAIADDCwUAIADECwUAIABQCwcAIAAgAVELBwAgACABUgsHACAAIAFTCwcAIAAgAVQLBwAgACABVwsHACAAIAFYCwcAIAAgAVULBwAgACABVgsHACAAIAFZCwcAIAAgAVoL", + "AGFzbQEAAAABDQJgAn9/AX9gAn5+AX8DBQQAAAEBB20EGGkzMi5ub19mb2xkX2NtcF9zX29mZnNldAAAGGkzMi5ub19mb2xkX2NtcF91X29mZnNldAABGGk2NC5ub19mb2xkX2NtcF9zX29mZnNldAACGGk2NC5ub19mb2xkX2NtcF91X29mZnNldAADCjkEDQAgAEEBaiABQQFqSAsNACAAQQFqIAFBAWpJCw0AIABCAXwgAUIBfFMLDQAgAEIBfCABQgF8VAs=", + "AGFzbQEAAAABBgFgAX4BfgMCAQAHHQEZaTY0Lm5vX2ZvbGRfd3JhcF9leHRlbmRfcwAACggBBgAgAKesCw==", + "AGFzbQEAAAABBgFgAX4BfgMCAQAHHQEZaTY0Lm5vX2ZvbGRfd3JhcF9leHRlbmRfdQAACggBBgAgAKetCw==", + "AGFzbQEAAAABCwJgAX8Bf2ABfgF+AwUEAAABAQdhBBVpMzIubm9fZm9sZF9zaGxfc2hyX3MAABVpMzIubm9fZm9sZF9zaGxfc2hyX3UAARVpNjQubm9fZm9sZF9zaGxfc2hyX3MAAhVpNjQubm9fZm9sZF9zaGxfc2hyX3UAAwotBAoAIABBAXRBAXULCgAgAEEBdEEBdgsKACAAQgGGQgGHCwoAIABCAYZCAYgL", + "AGFzbQEAAAABCwJgAX8Bf2ABfgF+AwUEAAABAQdhBBVpMzIubm9fZm9sZF9zaHJfc19zaGwAABVpMzIubm9fZm9sZF9zaHJfdV9zaGwAARVpNjQubm9fZm9sZF9zaHJfc19zaGwAAhVpNjQubm9fZm9sZF9zaHJfdV9zaGwAAwotBAoAIABBAXVBAXQLCgAgAEEBdkEBdAsKACAAQgGHQgGGCwoAIABCAYhCAYYL", + "AGFzbQEAAAABCwJgAX8Bf2ABfgF+AwUEAAABAQdhBBVpMzIubm9fZm9sZF9kaXZfc19tdWwAABVpMzIubm9fZm9sZF9kaXZfdV9tdWwAARVpNjQubm9fZm9sZF9kaXZfc19tdWwAAhVpNjQubm9fZm9sZF9kaXZfdV9tdWwAAwotBAoAIABBBm1BBmwLCgAgAEEGbkEGbAsKACAAQgZ/QgZ+CwoAIABCBoBCBn4L", + "AGFzbQEAAAABCwJgAX8Bf2ABfgF+AwUEAAABAQdlBBZpMzIubm9fZm9sZF9kaXZfc19zZWxmAAAWaTMyLm5vX2ZvbGRfZGl2X3Vfc2VsZgABFmk2NC5ub19mb2xkX2Rpdl9zX3NlbGYAAhZpNjQubm9fZm9sZF9kaXZfdV9zZWxmAAMKIQQHACAAIABtCwcAIAAgAG4LBwAgACAAfwsHACAAIACACw==", + "AGFzbQEAAAABCwJgAX8Bf2ABfgF+AwUEAAABAQdlBBZpMzIubm9fZm9sZF9yZW1fc19zZWxmAAAWaTMyLm5vX2ZvbGRfcmVtX3Vfc2VsZgABFmk2NC5ub19mb2xkX3JlbV9zX3NlbGYAAhZpNjQubm9fZm9sZF9yZW1fdV9zZWxmAAMKIQQHACAAIABvCwcAIAAgAHALBwAgACAAgQsHACAAIACCCw==", + "AGFzbQEAAAABCwJgAX8Bf2ABfgF+AwUEAAABAQdhBBVpMzIubm9fZm9sZF9tdWxfZGl2X3MAABVpMzIubm9fZm9sZF9tdWxfZGl2X3UAARVpNjQubm9fZm9sZF9tdWxfZGl2X3MAAhVpNjQubm9fZm9sZF9tdWxfZGl2X3UAAwotBAoAIABBBmxBBm0LCgAgAEEGbEEGbgsKACAAQgZ+QgZ/CwoAIABCBn5CBoAL", + "AGFzbQEAAAABCwJgAX8Bf2ABfgF+AwMCAAEHLQITaTMyLm5vX2ZvbGRfZGl2X3NfMgAAE2k2NC5ub19mb2xkX2Rpdl9zXzIAAQoRAgcAIABBAm0LBwAgAEICfws=", + "AGFzbQEAAAABCwJgAX8Bf2ABfgF+AwMCAAEHLQITaTMyLm5vX2ZvbGRfcmVtX3NfMgAAE2k2NC5ub19mb2xkX3JlbV9zXzIAAQoRAgcAIABBAm8LBwAgAEICgQs=", + "AGFzbQEAAAABCwJgAX8Bf2ABfgF+AwUEAAABAQc5BAtpMzIuZGl2X3NfMAAAC2kzMi5kaXZfdV8wAAELaTY0LmRpdl9zXzAAAgtpNjQuZGl2X3VfMAADCiEEBwAgAEEAbQsHACAAQQBuCwcAIABCAH8LBwAgAEIAgAs=", + "AGFzbQEAAAABCwJgAX8Bf2ABfgF+AwUEAAABAQc5BAtpMzIuZGl2X3NfMwAAC2kzMi5kaXZfdV8zAAELaTY0LmRpdl9zXzMAAgtpNjQuZGl2X3VfMwADCiEEBwAgAEEDbQsHACAAQQNuCwcAIABCA38LBwAgAEIDgAs=", + "AGFzbQEAAAABCwJgAX8Bf2ABfgF+AwUEAAABAQc5BAtpMzIuZGl2X3NfNQAAC2kzMi5kaXZfdV81AAELaTY0LmRpdl9zXzUAAgtpNjQuZGl2X3VfNQADCiEEBwAgAEEFbQsHACAAQQVuCwcAIABCBX8LBwAgAEIFgAs=", + "AGFzbQEAAAABCwJgAX8Bf2ABfgF+AwUEAAABAQc5BAtpMzIuZGl2X3NfNwAAC2kzMi5kaXZfdV83AAELaTY0LmRpdl9zXzcAAgtpNjQuZGl2X3VfNwADCiEEBwAgAEEHbQsHACAAQQduCwcAIABCB38LBwAgAEIHgAs=", + "AGFzbQEAAAABCwJgAX8Bf2ABfgF+AwUEAAABAQc5BAtpMzIucmVtX3NfMwAAC2kzMi5yZW1fdV8zAAELaTY0LnJlbV9zXzMAAgtpNjQucmVtX3VfMwADCiEEBwAgAEEDbwsHACAAQQNwCwcAIABCA4ELBwAgAEIDggs=", + "AGFzbQEAAAABCwJgAX8Bf2ABfgF+AwUEAAABAQc5BAtpMzIucmVtX3NfNQAAC2kzMi5yZW1fdV81AAELaTY0LnJlbV9zXzUAAgtpNjQucmVtX3VfNQADCiEEBwAgAEEFbwsHACAAQQVwCwcAIABCBYELBwAgAEIFggs=", + "AGFzbQEAAAABCwJgAX8Bf2ABfgF+AwUEAAABAQc5BAtpMzIucmVtX3NfNwAAC2kzMi5yZW1fdV83AAELaTY0LnJlbV9zXzcAAgtpNjQucmVtX3VfNwADCiEEBwAgAEEHbwsHACAAQQdwCwcAIABCB4ELBwAgAEIHggs=", + "AGFzbQEAAAABCwJgAX8Bf2ABfgF+AwMCAAEHLwIUaTMyLm5vX2ZvbGRfZGl2X25lZzEAABRpNjQubm9fZm9sZF9kaXZfbmVnMQABChECBwAgAEF/bQsHACAAQn9/Cw==", + "AGFzbQEAAAABCQJgAAF/YAABfgMfHgAAAAAAAAAAAAAAAQEBAQEBAQEBAQEAAAAAAQEBAQe3Ax4IaTMyLnRlc3QAAAhpMzIudW1heAABCGkzMi5zbWF4AAIMaTMyLm5lZ19zbWF4AAMIaTMyLnNtaW4ABAxpMzIuYWx0X3NtaW4ABQxpMzIuaW5jX3NtaW4ABgxpMzIubmVnX3plcm8ABw1pMzIubm90X29jdGFsAAgUaTMyLnVuc2lnbmVkX2RlY2ltYWwACQ1pMzIucGx1c19zaWduAAoIaTY0LnRlc3QACwhpNjQudW1heAAMCGk2NC5zbWF4AA0MaTY0Lm5lZ19zbWF4AA4IaTY0LnNtaW4ADwxpNjQuYWx0X3NtaW4AEAxpNjQuaW5jX3NtaW4AEQxpNjQubmVnX3plcm8AEg1pNjQubm90X29jdGFsABMUaTY0LnVuc2lnbmVkX2RlY2ltYWwAFA1pNjQucGx1c19zaWduABUMaTMyLWRlYy1zZXAxABYMaTMyLWRlYy1zZXAyABcMaTMyLWhleC1zZXAxABgMaTMyLWhleC1zZXAyABkMaTY0LWRlYy1zZXAxABoMaTY0LWRlYy1zZXAyABsMaTY0LWhleC1zZXAxABwMaTY0LWhleC1zZXAyAB0KlQIeCQBBjaC33QAPCwUAQX8PCwkAQf////8HDwsJAEGBgICAeA8LCQBBgICAgHgPCwkAQYCAgIB4DwsMAEGAgICAeEEBag8LBQBBAA8LBQBBCg8LBQBBfw8LBQBBKg8LDQBC7tSZ3eDN7tUMDwsFAEJ/DwsOAEL///////////8ADwsOAEKBgICAgICAgIB/DwsOAEKAgICAgICAgIB/DwsOAEKAgICAgICAgIB/DwsRAEKAgICAgICAgIB/QgF8DwsFAEIADwsFAEIKDwsFAEJ/DwsFAEIqDwsGAEHAhD0LBQBB6AcLCABBmYG80AALBgBBj9QGCwYAQsCEPQsFAELoBwsLAEKZs4KA8IG8BQsGAEKP1AYL", + "AGFzbQEAAAABPQxgAX8BfmABfgF/YAF9AX9gAXwBf2ABfQF+YAF8AX5gAX8BfWABfgF9YAF/AXxgAX4BfGABfQF8YAF8AX0DIiEAAAECAgMDBAQFBQICAwMEBAUFBgcICQYHCAkKCwYJAgUHkQUhEGk2NC5leHRlbmRfaTMyX3MAABBpNjQuZXh0ZW5kX2kzMl91AAEMaTMyLndyYXBfaTY0AAIPaTMyLnRydW5jX2YzMl9zAAMPaTMyLnRydW5jX2YzMl91AAQPaTMyLnRydW5jX2Y2NF9zAAUPaTMyLnRydW5jX2Y2NF91AAYPaTY0LnRydW5jX2YzMl9zAAcPaTY0LnRydW5jX2YzMl91AAgPaTY0LnRydW5jX2Y2NF9zAAkPaTY0LnRydW5jX2Y2NF91AAoTaTMyLnRydW5jX3NhdF9mMzJfcwALE2kzMi50cnVuY19zYXRfZjMyX3UADBNpMzIudHJ1bmNfc2F0X2Y2NF9zAA0TaTMyLnRydW5jX3NhdF9mNjRfdQAOE2k2NC50cnVuY19zYXRfZjMyX3MADxNpNjQudHJ1bmNfc2F0X2YzMl91ABATaTY0LnRydW5jX3NhdF9mNjRfcwARE2k2NC50cnVuY19zYXRfZjY0X3UAEhFmMzIuY29udmVydF9pMzJfcwATEWYzMi5jb252ZXJ0X2k2NF9zABQRZjY0LmNvbnZlcnRfaTMyX3MAFRFmNjQuY29udmVydF9pNjRfcwAWEWYzMi5jb252ZXJ0X2kzMl91ABcRZjMyLmNvbnZlcnRfaTY0X3UAGBFmNjQuY29udmVydF9pMzJfdQAZEWY2NC5jb252ZXJ0X2k2NF91ABoPZjY0LnByb21vdGVfZjMyABsOZjMyLmRlbW90ZV9mNjQAHBNmMzIucmVpbnRlcnByZXRfaTMyAB0TZjY0LnJlaW50ZXJwcmV0X2k2NAAeE2kzMi5yZWludGVycHJldF9mMzIAHxNpNjQucmVpbnRlcnByZXRfZjY0ACAKzwEhBQAgAKwLBQAgAK0LBQAgAKcLBQAgAKgLBQAgAKkLBQAgAKoLBQAgAKsLBQAgAK4LBQAgAK8LBQAgALALBQAgALELBgAgAPwACwYAIAD8AQsGACAA/AILBgAgAPwDCwYAIAD8BAsGACAA/AULBgAgAPwGCwYAIAD8BwsFACAAsgsFACAAtAsFACAAtwsFACAAuQsFACAAswsFACAAtQsFACAAuAsFACAAugsFACAAuwsFACAAtgsFACAAvgsFACAAvwsFACAAvAsFACAAvQs=", + "AGFzbQEAAAABCwJgAn9/AGACfn4AAwUEAAABAQdNBBBub19kY2UuaTMyLmRpdl9zAAAQbm9fZGNlLmkzMi5kaXZfdQABEG5vX2RjZS5pNjQuZGl2X3MAAhBub19kY2UuaTY0LmRpdl91AAMKJQQIACAAIAFtGgsIACAAIAFuGgsIACAAIAF/GgsIACAAIAGAGgs=", + "AGFzbQEAAAABCwJgAn9/AGACfn4AAwUEAAABAQdNBBBub19kY2UuaTMyLnJlbV9zAAAQbm9fZGNlLmkzMi5yZW1fdQABEG5vX2RjZS5pNjQucmVtX3MAAhBub19kY2UuaTY0LnJlbV91AAMKJQQIACAAIAFvGgsIACAAIAFwGgsIACAAIAGBGgsIACAAIAGCGgs=", + "AGFzbQEAAAABCQJgAX0AYAF8AAMJCAAAAQEAAAEBB8kBCBZub19kY2UuaTMyLnRydW5jX2YzMl9zAAAWbm9fZGNlLmkzMi50cnVuY19mMzJfdQABFm5vX2RjZS5pMzIudHJ1bmNfZjY0X3MAAhZub19kY2UuaTMyLnRydW5jX2Y2NF91AAMWbm9fZGNlLmk2NC50cnVuY19mMzJfcwAEFm5vX2RjZS5pNjQudHJ1bmNfZjMyX3UABRZub19kY2UuaTY0LnRydW5jX2Y2NF9zAAYWbm9fZGNlLmk2NC50cnVuY19mNjRfdQAHCjkIBgAgAKgaCwYAIACpGgsGACAAqhoLBgAgAKsaCwYAIACuGgsGACAArxoLBgAgALAaCwYAIACxGgs=", + "AGFzbQEAAAABBQFgAX8AAw8OAAAAAAAAAAAAAAAAAAAFAwEAAQehAg4Pbm9fZGNlLmkzMi5sb2FkAAATbm9fZGNlLmkzMi5sb2FkMTZfcwABE25vX2RjZS5pMzIubG9hZDE2X3UAAhJub19kY2UuaTMyLmxvYWQ4X3MAAxJub19kY2UuaTMyLmxvYWQ4X3UABA9ub19kY2UuaTY0LmxvYWQABRNub19kY2UuaTY0LmxvYWQzMl9zAAYTbm9fZGNlLmk2NC5sb2FkMzJfdQAHE25vX2RjZS5pNjQubG9hZDE2X3MACBNub19kY2UuaTY0LmxvYWQxNl91AAkSbm9fZGNlLmk2NC5sb2FkOF9zAAoSbm9fZGNlLmk2NC5sb2FkOF91AAsPbm9fZGNlLmYzMi5sb2FkAAwPbm9fZGNlLmY2NC5sb2FkAA0Kfw4IACAAKAIAGgsIACAALgEAGgsIACAALwEAGgsIACAALAAAGgsIACAALQAAGgsIACAAKQMAGgsIACAANAIAGgsIACAANQIAGgsIACAAMgEAGgsIACAAMwEAGgsIACAAMAAAGgsIACAAMQAAGgsIACAAKgIAGgsIACAAKwMAGgs=", +]; +export const script = "[[\"file\",\"i32.wast\"],[\"module\",3,0],[\"invoke\",37,0,\"add\",[\"i32:1\",\"i32:1\"]],[\"invoke\",38,0,\"add\",[\"i32:1\",\"i32:0\"]],[\"invoke\",39,0,\"add\",[\"i32:4294967295\",\"i32:4294967295\"]],[\"invoke\",40,0,\"add\",[\"i32:4294967295\",\"i32:1\"]],[\"invoke\",41,0,\"add\",[\"i32:2147483647\",\"i32:1\"]],[\"invoke\",42,0,\"add\",[\"i32:2147483648\",\"i32:4294967295\"]],[\"invoke\",43,0,\"add\",[\"i32:2147483648\",\"i32:2147483648\"]],[\"invoke\",44,0,\"add\",[\"i32:1073741823\",\"i32:1\"]],[\"invoke\",46,0,\"sub\",[\"i32:1\",\"i32:1\"]],[\"invoke\",47,0,\"sub\",[\"i32:1\",\"i32:0\"]],[\"invoke\",48,0,\"sub\",[\"i32:4294967295\",\"i32:4294967295\"]],[\"invoke\",49,0,\"sub\",[\"i32:2147483647\",\"i32:4294967295\"]],[\"invoke\",50,0,\"sub\",[\"i32:2147483648\",\"i32:1\"]],[\"invoke\",51,0,\"sub\",[\"i32:2147483648\",\"i32:2147483648\"]],[\"invoke\",52,0,\"sub\",[\"i32:1073741823\",\"i32:4294967295\"]],[\"invoke\",54,0,\"mul\",[\"i32:1\",\"i32:1\"]],[\"invoke\",55,0,\"mul\",[\"i32:1\",\"i32:0\"]],[\"invoke\",56,0,\"mul\",[\"i32:4294967295\",\"i32:4294967295\"]],[\"invoke\",57,0,\"mul\",[\"i32:268435456\",\"i32:4096\"]],[\"invoke\",58,0,\"mul\",[\"i32:2147483648\",\"i32:0\"]],[\"invoke\",59,0,\"mul\",[\"i32:2147483648\",\"i32:4294967295\"]],[\"invoke\",60,0,\"mul\",[\"i32:2147483647\",\"i32:4294967295\"]],[\"invoke\",61,0,\"mul\",[\"i32:19088743\",\"i32:1985229328\"]],[\"invoke\",62,0,\"mul\",[\"i32:2147483647\",\"i32:2147483647\"]],[\"invoke\",64,0,\"div_s\",[\"i32:1\",\"i32:0\"]],[\"invoke\",65,0,\"div_s\",[\"i32:0\",\"i32:0\"]],[\"invoke\",66,0,\"div_s\",[\"i32:2147483648\",\"i32:4294967295\"]],[\"invoke\",67,0,\"div_s\",[\"i32:2147483648\",\"i32:0\"]],[\"invoke\",68,0,\"div_s\",[\"i32:1\",\"i32:1\"]],[\"invoke\",69,0,\"div_s\",[\"i32:0\",\"i32:1\"]],[\"invoke\",70,0,\"div_s\",[\"i32:0\",\"i32:4294967295\"]],[\"invoke\",71,0,\"div_s\",[\"i32:4294967295\",\"i32:4294967295\"]],[\"invoke\",72,0,\"div_s\",[\"i32:2147483648\",\"i32:2\"]],[\"invoke\",73,0,\"div_s\",[\"i32:2147483649\",\"i32:1000\"]],[\"invoke\",74,0,\"div_s\",[\"i32:5\",\"i32:2\"]],[\"invoke\",75,0,\"div_s\",[\"i32:4294967291\",\"i32:2\"]],[\"invoke\",76,0,\"div_s\",[\"i32:5\",\"i32:4294967294\"]],[\"invoke\",77,0,\"div_s\",[\"i32:4294967291\",\"i32:4294967294\"]],[\"invoke\",78,0,\"div_s\",[\"i32:7\",\"i32:3\"]],[\"invoke\",79,0,\"div_s\",[\"i32:4294967289\",\"i32:3\"]],[\"invoke\",80,0,\"div_s\",[\"i32:7\",\"i32:4294967293\"]],[\"invoke\",81,0,\"div_s\",[\"i32:4294967289\",\"i32:4294967293\"]],[\"invoke\",82,0,\"div_s\",[\"i32:11\",\"i32:5\"]],[\"invoke\",83,0,\"div_s\",[\"i32:17\",\"i32:7\"]],[\"invoke\",85,0,\"div_u\",[\"i32:1\",\"i32:0\"]],[\"invoke\",86,0,\"div_u\",[\"i32:0\",\"i32:0\"]],[\"invoke\",87,0,\"div_u\",[\"i32:1\",\"i32:1\"]],[\"invoke\",88,0,\"div_u\",[\"i32:0\",\"i32:1\"]],[\"invoke\",89,0,\"div_u\",[\"i32:4294967295\",\"i32:4294967295\"]],[\"invoke\",90,0,\"div_u\",[\"i32:2147483648\",\"i32:4294967295\"]],[\"invoke\",91,0,\"div_u\",[\"i32:2147483648\",\"i32:2\"]],[\"invoke\",92,0,\"div_u\",[\"i32:2414874608\",\"i32:65537\"]],[\"invoke\",93,0,\"div_u\",[\"i32:2147483649\",\"i32:1000\"]],[\"invoke\",94,0,\"div_u\",[\"i32:5\",\"i32:2\"]],[\"invoke\",95,0,\"div_u\",[\"i32:4294967291\",\"i32:2\"]],[\"invoke\",96,0,\"div_u\",[\"i32:5\",\"i32:4294967294\"]],[\"invoke\",97,0,\"div_u\",[\"i32:4294967291\",\"i32:4294967294\"]],[\"invoke\",98,0,\"div_u\",[\"i32:7\",\"i32:3\"]],[\"invoke\",99,0,\"div_u\",[\"i32:11\",\"i32:5\"]],[\"invoke\",100,0,\"div_u\",[\"i32:17\",\"i32:7\"]],[\"invoke\",102,0,\"rem_s\",[\"i32:1\",\"i32:0\"]],[\"invoke\",103,0,\"rem_s\",[\"i32:0\",\"i32:0\"]],[\"invoke\",104,0,\"rem_s\",[\"i32:2147483647\",\"i32:4294967295\"]],[\"invoke\",105,0,\"rem_s\",[\"i32:1\",\"i32:1\"]],[\"invoke\",106,0,\"rem_s\",[\"i32:0\",\"i32:1\"]],[\"invoke\",107,0,\"rem_s\",[\"i32:0\",\"i32:4294967295\"]],[\"invoke\",108,0,\"rem_s\",[\"i32:4294967295\",\"i32:4294967295\"]],[\"invoke\",109,0,\"rem_s\",[\"i32:2147483648\",\"i32:4294967295\"]],[\"invoke\",110,0,\"rem_s\",[\"i32:2147483648\",\"i32:2\"]],[\"invoke\",111,0,\"rem_s\",[\"i32:2147483649\",\"i32:1000\"]],[\"invoke\",112,0,\"rem_s\",[\"i32:5\",\"i32:2\"]],[\"invoke\",113,0,\"rem_s\",[\"i32:4294967291\",\"i32:2\"]],[\"invoke\",114,0,\"rem_s\",[\"i32:5\",\"i32:4294967294\"]],[\"invoke\",115,0,\"rem_s\",[\"i32:4294967291\",\"i32:4294967294\"]],[\"invoke\",116,0,\"rem_s\",[\"i32:7\",\"i32:3\"]],[\"invoke\",117,0,\"rem_s\",[\"i32:4294967289\",\"i32:3\"]],[\"invoke\",118,0,\"rem_s\",[\"i32:7\",\"i32:4294967293\"]],[\"invoke\",119,0,\"rem_s\",[\"i32:4294967289\",\"i32:4294967293\"]],[\"invoke\",120,0,\"rem_s\",[\"i32:11\",\"i32:5\"]],[\"invoke\",121,0,\"rem_s\",[\"i32:17\",\"i32:7\"]],[\"invoke\",123,0,\"rem_u\",[\"i32:1\",\"i32:0\"]],[\"invoke\",124,0,\"rem_u\",[\"i32:0\",\"i32:0\"]],[\"invoke\",125,0,\"rem_u\",[\"i32:1\",\"i32:1\"]],[\"invoke\",126,0,\"rem_u\",[\"i32:0\",\"i32:1\"]],[\"invoke\",127,0,\"rem_u\",[\"i32:4294967295\",\"i32:4294967295\"]],[\"invoke\",128,0,\"rem_u\",[\"i32:2147483648\",\"i32:4294967295\"]],[\"invoke\",129,0,\"rem_u\",[\"i32:2147483648\",\"i32:2\"]],[\"invoke\",130,0,\"rem_u\",[\"i32:2414874608\",\"i32:65537\"]],[\"invoke\",131,0,\"rem_u\",[\"i32:2147483649\",\"i32:1000\"]],[\"invoke\",132,0,\"rem_u\",[\"i32:5\",\"i32:2\"]],[\"invoke\",133,0,\"rem_u\",[\"i32:4294967291\",\"i32:2\"]],[\"invoke\",134,0,\"rem_u\",[\"i32:5\",\"i32:4294967294\"]],[\"invoke\",135,0,\"rem_u\",[\"i32:4294967291\",\"i32:4294967294\"]],[\"invoke\",136,0,\"rem_u\",[\"i32:7\",\"i32:3\"]],[\"invoke\",137,0,\"rem_u\",[\"i32:11\",\"i32:5\"]],[\"invoke\",138,0,\"rem_u\",[\"i32:17\",\"i32:7\"]],[\"invoke\",140,0,\"and\",[\"i32:1\",\"i32:0\"]],[\"invoke\",141,0,\"and\",[\"i32:0\",\"i32:1\"]],[\"invoke\",142,0,\"and\",[\"i32:1\",\"i32:1\"]],[\"invoke\",143,0,\"and\",[\"i32:0\",\"i32:0\"]],[\"invoke\",144,0,\"and\",[\"i32:2147483647\",\"i32:2147483648\"]],[\"invoke\",145,0,\"and\",[\"i32:2147483647\",\"i32:4294967295\"]],[\"invoke\",146,0,\"and\",[\"i32:4042326015\",\"i32:4294963440\"]],[\"invoke\",147,0,\"and\",[\"i32:4294967295\",\"i32:4294967295\"]],[\"invoke\",149,0,\"or\",[\"i32:1\",\"i32:0\"]],[\"invoke\",150,0,\"or\",[\"i32:0\",\"i32:1\"]],[\"invoke\",151,0,\"or\",[\"i32:1\",\"i32:1\"]],[\"invoke\",152,0,\"or\",[\"i32:0\",\"i32:0\"]],[\"invoke\",153,0,\"or\",[\"i32:2147483647\",\"i32:2147483648\"]],[\"invoke\",154,0,\"or\",[\"i32:2147483648\",\"i32:0\"]],[\"invoke\",155,0,\"or\",[\"i32:4042326015\",\"i32:4294963440\"]],[\"invoke\",156,0,\"or\",[\"i32:4294967295\",\"i32:4294967295\"]],[\"invoke\",158,0,\"xor\",[\"i32:1\",\"i32:0\"]],[\"invoke\",159,0,\"xor\",[\"i32:0\",\"i32:1\"]],[\"invoke\",160,0,\"xor\",[\"i32:1\",\"i32:1\"]],[\"invoke\",161,0,\"xor\",[\"i32:0\",\"i32:0\"]],[\"invoke\",162,0,\"xor\",[\"i32:2147483647\",\"i32:2147483648\"]],[\"invoke\",163,0,\"xor\",[\"i32:2147483648\",\"i32:0\"]],[\"invoke\",164,0,\"xor\",[\"i32:4294967295\",\"i32:2147483648\"]],[\"invoke\",165,0,\"xor\",[\"i32:4294967295\",\"i32:2147483647\"]],[\"invoke\",166,0,\"xor\",[\"i32:4042326015\",\"i32:4294963440\"]],[\"invoke\",167,0,\"xor\",[\"i32:4294967295\",\"i32:4294967295\"]],[\"invoke\",169,0,\"shl\",[\"i32:1\",\"i32:1\"]],[\"invoke\",170,0,\"shl\",[\"i32:1\",\"i32:0\"]],[\"invoke\",171,0,\"shl\",[\"i32:2147483647\",\"i32:1\"]],[\"invoke\",172,0,\"shl\",[\"i32:4294967295\",\"i32:1\"]],[\"invoke\",173,0,\"shl\",[\"i32:2147483648\",\"i32:1\"]],[\"invoke\",174,0,\"shl\",[\"i32:1073741824\",\"i32:1\"]],[\"invoke\",175,0,\"shl\",[\"i32:1\",\"i32:31\"]],[\"invoke\",176,0,\"shl\",[\"i32:1\",\"i32:32\"]],[\"invoke\",177,0,\"shl\",[\"i32:1\",\"i32:33\"]],[\"invoke\",178,0,\"shl\",[\"i32:1\",\"i32:4294967295\"]],[\"invoke\",179,0,\"shl\",[\"i32:1\",\"i32:2147483647\"]],[\"invoke\",181,0,\"shr_s\",[\"i32:1\",\"i32:1\"]],[\"invoke\",182,0,\"shr_s\",[\"i32:1\",\"i32:0\"]],[\"invoke\",183,0,\"shr_s\",[\"i32:4294967295\",\"i32:1\"]],[\"invoke\",184,0,\"shr_s\",[\"i32:2147483647\",\"i32:1\"]],[\"invoke\",185,0,\"shr_s\",[\"i32:2147483648\",\"i32:1\"]],[\"invoke\",186,0,\"shr_s\",[\"i32:1073741824\",\"i32:1\"]],[\"invoke\",187,0,\"shr_s\",[\"i32:1\",\"i32:32\"]],[\"invoke\",188,0,\"shr_s\",[\"i32:1\",\"i32:33\"]],[\"invoke\",189,0,\"shr_s\",[\"i32:1\",\"i32:4294967295\"]],[\"invoke\",190,0,\"shr_s\",[\"i32:1\",\"i32:2147483647\"]],[\"invoke\",191,0,\"shr_s\",[\"i32:1\",\"i32:2147483648\"]],[\"invoke\",192,0,\"shr_s\",[\"i32:2147483648\",\"i32:31\"]],[\"invoke\",193,0,\"shr_s\",[\"i32:4294967295\",\"i32:32\"]],[\"invoke\",194,0,\"shr_s\",[\"i32:4294967295\",\"i32:33\"]],[\"invoke\",195,0,\"shr_s\",[\"i32:4294967295\",\"i32:4294967295\"]],[\"invoke\",196,0,\"shr_s\",[\"i32:4294967295\",\"i32:2147483647\"]],[\"invoke\",197,0,\"shr_s\",[\"i32:4294967295\",\"i32:2147483648\"]],[\"invoke\",199,0,\"shr_u\",[\"i32:1\",\"i32:1\"]],[\"invoke\",200,0,\"shr_u\",[\"i32:1\",\"i32:0\"]],[\"invoke\",201,0,\"shr_u\",[\"i32:4294967295\",\"i32:1\"]],[\"invoke\",202,0,\"shr_u\",[\"i32:2147483647\",\"i32:1\"]],[\"invoke\",203,0,\"shr_u\",[\"i32:2147483648\",\"i32:1\"]],[\"invoke\",204,0,\"shr_u\",[\"i32:1073741824\",\"i32:1\"]],[\"invoke\",205,0,\"shr_u\",[\"i32:1\",\"i32:32\"]],[\"invoke\",206,0,\"shr_u\",[\"i32:1\",\"i32:33\"]],[\"invoke\",207,0,\"shr_u\",[\"i32:1\",\"i32:4294967295\"]],[\"invoke\",208,0,\"shr_u\",[\"i32:1\",\"i32:2147483647\"]],[\"invoke\",209,0,\"shr_u\",[\"i32:1\",\"i32:2147483648\"]],[\"invoke\",210,0,\"shr_u\",[\"i32:2147483648\",\"i32:31\"]],[\"invoke\",211,0,\"shr_u\",[\"i32:4294967295\",\"i32:32\"]],[\"invoke\",212,0,\"shr_u\",[\"i32:4294967295\",\"i32:33\"]],[\"invoke\",213,0,\"shr_u\",[\"i32:4294967295\",\"i32:4294967295\"]],[\"invoke\",214,0,\"shr_u\",[\"i32:4294967295\",\"i32:2147483647\"]],[\"invoke\",215,0,\"shr_u\",[\"i32:4294967295\",\"i32:2147483648\"]],[\"invoke\",217,0,\"rotl\",[\"i32:1\",\"i32:1\"]],[\"invoke\",218,0,\"rotl\",[\"i32:1\",\"i32:0\"]],[\"invoke\",219,0,\"rotl\",[\"i32:4294967295\",\"i32:1\"]],[\"invoke\",220,0,\"rotl\",[\"i32:1\",\"i32:32\"]],[\"invoke\",221,0,\"rotl\",[\"i32:2882377846\",\"i32:1\"]],[\"invoke\",222,0,\"rotl\",[\"i32:4261469184\",\"i32:4\"]],[\"invoke\",223,0,\"rotl\",[\"i32:2965492451\",\"i32:5\"]],[\"invoke\",224,0,\"rotl\",[\"i32:32768\",\"i32:37\"]],[\"invoke\",225,0,\"rotl\",[\"i32:2965492451\",\"i32:65285\"]],[\"invoke\",226,0,\"rotl\",[\"i32:1989852383\",\"i32:4294967277\"]],[\"invoke\",227,0,\"rotl\",[\"i32:1989852383\",\"i32:2147483661\"]],[\"invoke\",228,0,\"rotl\",[\"i32:1\",\"i32:31\"]],[\"invoke\",229,0,\"rotl\",[\"i32:2147483648\",\"i32:1\"]],[\"invoke\",231,0,\"rotr\",[\"i32:1\",\"i32:1\"]],[\"invoke\",232,0,\"rotr\",[\"i32:1\",\"i32:0\"]],[\"invoke\",233,0,\"rotr\",[\"i32:4294967295\",\"i32:1\"]],[\"invoke\",234,0,\"rotr\",[\"i32:1\",\"i32:32\"]],[\"invoke\",235,0,\"rotr\",[\"i32:4278242304\",\"i32:1\"]],[\"invoke\",236,0,\"rotr\",[\"i32:524288\",\"i32:4\"]],[\"invoke\",237,0,\"rotr\",[\"i32:2965492451\",\"i32:5\"]],[\"invoke\",238,0,\"rotr\",[\"i32:32768\",\"i32:37\"]],[\"invoke\",239,0,\"rotr\",[\"i32:2965492451\",\"i32:65285\"]],[\"invoke\",240,0,\"rotr\",[\"i32:1989852383\",\"i32:4294967277\"]],[\"invoke\",241,0,\"rotr\",[\"i32:1989852383\",\"i32:2147483661\"]],[\"invoke\",242,0,\"rotr\",[\"i32:1\",\"i32:31\"]],[\"invoke\",243,0,\"rotr\",[\"i32:2147483648\",\"i32:31\"]],[\"invoke\",245,0,\"clz\",[\"i32:4294967295\"]],[\"invoke\",246,0,\"clz\",[\"i32:0\"]],[\"invoke\",247,0,\"clz\",[\"i32:32768\"]],[\"invoke\",248,0,\"clz\",[\"i32:255\"]],[\"invoke\",249,0,\"clz\",[\"i32:2147483648\"]],[\"invoke\",250,0,\"clz\",[\"i32:1\"]],[\"invoke\",251,0,\"clz\",[\"i32:2\"]],[\"invoke\",252,0,\"clz\",[\"i32:2147483647\"]],[\"invoke\",254,0,\"ctz\",[\"i32:4294967295\"]],[\"invoke\",255,0,\"ctz\",[\"i32:0\"]],[\"invoke\",256,0,\"ctz\",[\"i32:32768\"]],[\"invoke\",257,0,\"ctz\",[\"i32:65536\"]],[\"invoke\",258,0,\"ctz\",[\"i32:2147483648\"]],[\"invoke\",259,0,\"ctz\",[\"i32:2147483647\"]],[\"invoke\",261,0,\"popcnt\",[\"i32:4294967295\"]],[\"invoke\",262,0,\"popcnt\",[\"i32:0\"]],[\"invoke\",263,0,\"popcnt\",[\"i32:32768\"]],[\"invoke\",264,0,\"popcnt\",[\"i32:2147516416\"]],[\"invoke\",265,0,\"popcnt\",[\"i32:2147483647\"]],[\"invoke\",266,0,\"popcnt\",[\"i32:2863311530\"]],[\"invoke\",267,0,\"popcnt\",[\"i32:1431655765\"]],[\"invoke\",268,0,\"popcnt\",[\"i32:3735928559\"]],[\"invoke\",270,0,\"extend8_s\",[\"i32:0\"]],[\"invoke\",271,0,\"extend8_s\",[\"i32:127\"]],[\"invoke\",272,0,\"extend8_s\",[\"i32:128\"]],[\"invoke\",273,0,\"extend8_s\",[\"i32:255\"]],[\"invoke\",274,0,\"extend8_s\",[\"i32:19088640\"]],[\"invoke\",275,0,\"extend8_s\",[\"i32:4275878528\"]],[\"invoke\",276,0,\"extend8_s\",[\"i32:4294967295\"]],[\"invoke\",278,0,\"extend16_s\",[\"i32:0\"]],[\"invoke\",279,0,\"extend16_s\",[\"i32:32767\"]],[\"invoke\",280,0,\"extend16_s\",[\"i32:32768\"]],[\"invoke\",281,0,\"extend16_s\",[\"i32:65535\"]],[\"invoke\",282,0,\"extend16_s\",[\"i32:19070976\"]],[\"invoke\",283,0,\"extend16_s\",[\"i32:4275863552\"]],[\"invoke\",284,0,\"extend16_s\",[\"i32:4294967295\"]],[\"invoke\",286,0,\"eqz\",[\"i32:0\"]],[\"invoke\",287,0,\"eqz\",[\"i32:1\"]],[\"invoke\",288,0,\"eqz\",[\"i32:2147483648\"]],[\"invoke\",289,0,\"eqz\",[\"i32:2147483647\"]],[\"invoke\",290,0,\"eqz\",[\"i32:4294967295\"]],[\"invoke\",292,0,\"eq\",[\"i32:0\",\"i32:0\"]],[\"invoke\",293,0,\"eq\",[\"i32:1\",\"i32:1\"]],[\"invoke\",294,0,\"eq\",[\"i32:4294967295\",\"i32:1\"]],[\"invoke\",295,0,\"eq\",[\"i32:2147483648\",\"i32:2147483648\"]],[\"invoke\",296,0,\"eq\",[\"i32:2147483647\",\"i32:2147483647\"]],[\"invoke\",297,0,\"eq\",[\"i32:4294967295\",\"i32:4294967295\"]],[\"invoke\",298,0,\"eq\",[\"i32:1\",\"i32:0\"]],[\"invoke\",299,0,\"eq\",[\"i32:0\",\"i32:1\"]],[\"invoke\",300,0,\"eq\",[\"i32:2147483648\",\"i32:0\"]],[\"invoke\",301,0,\"eq\",[\"i32:0\",\"i32:2147483648\"]],[\"invoke\",302,0,\"eq\",[\"i32:2147483648\",\"i32:4294967295\"]],[\"invoke\",303,0,\"eq\",[\"i32:4294967295\",\"i32:2147483648\"]],[\"invoke\",304,0,\"eq\",[\"i32:2147483648\",\"i32:2147483647\"]],[\"invoke\",305,0,\"eq\",[\"i32:2147483647\",\"i32:2147483648\"]],[\"invoke\",307,0,\"ne\",[\"i32:0\",\"i32:0\"]],[\"invoke\",308,0,\"ne\",[\"i32:1\",\"i32:1\"]],[\"invoke\",309,0,\"ne\",[\"i32:4294967295\",\"i32:1\"]],[\"invoke\",310,0,\"ne\",[\"i32:2147483648\",\"i32:2147483648\"]],[\"invoke\",311,0,\"ne\",[\"i32:2147483647\",\"i32:2147483647\"]],[\"invoke\",312,0,\"ne\",[\"i32:4294967295\",\"i32:4294967295\"]],[\"invoke\",313,0,\"ne\",[\"i32:1\",\"i32:0\"]],[\"invoke\",314,0,\"ne\",[\"i32:0\",\"i32:1\"]],[\"invoke\",315,0,\"ne\",[\"i32:2147483648\",\"i32:0\"]],[\"invoke\",316,0,\"ne\",[\"i32:0\",\"i32:2147483648\"]],[\"invoke\",317,0,\"ne\",[\"i32:2147483648\",\"i32:4294967295\"]],[\"invoke\",318,0,\"ne\",[\"i32:4294967295\",\"i32:2147483648\"]],[\"invoke\",319,0,\"ne\",[\"i32:2147483648\",\"i32:2147483647\"]],[\"invoke\",320,0,\"ne\",[\"i32:2147483647\",\"i32:2147483648\"]],[\"invoke\",322,0,\"lt_s\",[\"i32:0\",\"i32:0\"]],[\"invoke\",323,0,\"lt_s\",[\"i32:1\",\"i32:1\"]],[\"invoke\",324,0,\"lt_s\",[\"i32:4294967295\",\"i32:1\"]],[\"invoke\",325,0,\"lt_s\",[\"i32:2147483648\",\"i32:2147483648\"]],[\"invoke\",326,0,\"lt_s\",[\"i32:2147483647\",\"i32:2147483647\"]],[\"invoke\",327,0,\"lt_s\",[\"i32:4294967295\",\"i32:4294967295\"]],[\"invoke\",328,0,\"lt_s\",[\"i32:1\",\"i32:0\"]],[\"invoke\",329,0,\"lt_s\",[\"i32:0\",\"i32:1\"]],[\"invoke\",330,0,\"lt_s\",[\"i32:2147483648\",\"i32:0\"]],[\"invoke\",331,0,\"lt_s\",[\"i32:0\",\"i32:2147483648\"]],[\"invoke\",332,0,\"lt_s\",[\"i32:2147483648\",\"i32:4294967295\"]],[\"invoke\",333,0,\"lt_s\",[\"i32:4294967295\",\"i32:2147483648\"]],[\"invoke\",334,0,\"lt_s\",[\"i32:2147483648\",\"i32:2147483647\"]],[\"invoke\",335,0,\"lt_s\",[\"i32:2147483647\",\"i32:2147483648\"]],[\"invoke\",337,0,\"lt_u\",[\"i32:0\",\"i32:0\"]],[\"invoke\",338,0,\"lt_u\",[\"i32:1\",\"i32:1\"]],[\"invoke\",339,0,\"lt_u\",[\"i32:4294967295\",\"i32:1\"]],[\"invoke\",340,0,\"lt_u\",[\"i32:2147483648\",\"i32:2147483648\"]],[\"invoke\",341,0,\"lt_u\",[\"i32:2147483647\",\"i32:2147483647\"]],[\"invoke\",342,0,\"lt_u\",[\"i32:4294967295\",\"i32:4294967295\"]],[\"invoke\",343,0,\"lt_u\",[\"i32:1\",\"i32:0\"]],[\"invoke\",344,0,\"lt_u\",[\"i32:0\",\"i32:1\"]],[\"invoke\",345,0,\"lt_u\",[\"i32:2147483648\",\"i32:0\"]],[\"invoke\",346,0,\"lt_u\",[\"i32:0\",\"i32:2147483648\"]],[\"invoke\",347,0,\"lt_u\",[\"i32:2147483648\",\"i32:4294967295\"]],[\"invoke\",348,0,\"lt_u\",[\"i32:4294967295\",\"i32:2147483648\"]],[\"invoke\",349,0,\"lt_u\",[\"i32:2147483648\",\"i32:2147483647\"]],[\"invoke\",350,0,\"lt_u\",[\"i32:2147483647\",\"i32:2147483648\"]],[\"invoke\",352,0,\"le_s\",[\"i32:0\",\"i32:0\"]],[\"invoke\",353,0,\"le_s\",[\"i32:1\",\"i32:1\"]],[\"invoke\",354,0,\"le_s\",[\"i32:4294967295\",\"i32:1\"]],[\"invoke\",355,0,\"le_s\",[\"i32:2147483648\",\"i32:2147483648\"]],[\"invoke\",356,0,\"le_s\",[\"i32:2147483647\",\"i32:2147483647\"]],[\"invoke\",357,0,\"le_s\",[\"i32:4294967295\",\"i32:4294967295\"]],[\"invoke\",358,0,\"le_s\",[\"i32:1\",\"i32:0\"]],[\"invoke\",359,0,\"le_s\",[\"i32:0\",\"i32:1\"]],[\"invoke\",360,0,\"le_s\",[\"i32:2147483648\",\"i32:0\"]],[\"invoke\",361,0,\"le_s\",[\"i32:0\",\"i32:2147483648\"]],[\"invoke\",362,0,\"le_s\",[\"i32:2147483648\",\"i32:4294967295\"]],[\"invoke\",363,0,\"le_s\",[\"i32:4294967295\",\"i32:2147483648\"]],[\"invoke\",364,0,\"le_s\",[\"i32:2147483648\",\"i32:2147483647\"]],[\"invoke\",365,0,\"le_s\",[\"i32:2147483647\",\"i32:2147483648\"]],[\"invoke\",367,0,\"le_u\",[\"i32:0\",\"i32:0\"]],[\"invoke\",368,0,\"le_u\",[\"i32:1\",\"i32:1\"]],[\"invoke\",369,0,\"le_u\",[\"i32:4294967295\",\"i32:1\"]],[\"invoke\",370,0,\"le_u\",[\"i32:2147483648\",\"i32:2147483648\"]],[\"invoke\",371,0,\"le_u\",[\"i32:2147483647\",\"i32:2147483647\"]],[\"invoke\",372,0,\"le_u\",[\"i32:4294967295\",\"i32:4294967295\"]],[\"invoke\",373,0,\"le_u\",[\"i32:1\",\"i32:0\"]],[\"invoke\",374,0,\"le_u\",[\"i32:0\",\"i32:1\"]],[\"invoke\",375,0,\"le_u\",[\"i32:2147483648\",\"i32:0\"]],[\"invoke\",376,0,\"le_u\",[\"i32:0\",\"i32:2147483648\"]],[\"invoke\",377,0,\"le_u\",[\"i32:2147483648\",\"i32:4294967295\"]],[\"invoke\",378,0,\"le_u\",[\"i32:4294967295\",\"i32:2147483648\"]],[\"invoke\",379,0,\"le_u\",[\"i32:2147483648\",\"i32:2147483647\"]],[\"invoke\",380,0,\"le_u\",[\"i32:2147483647\",\"i32:2147483648\"]],[\"invoke\",382,0,\"gt_s\",[\"i32:0\",\"i32:0\"]],[\"invoke\",383,0,\"gt_s\",[\"i32:1\",\"i32:1\"]],[\"invoke\",384,0,\"gt_s\",[\"i32:4294967295\",\"i32:1\"]],[\"invoke\",385,0,\"gt_s\",[\"i32:2147483648\",\"i32:2147483648\"]],[\"invoke\",386,0,\"gt_s\",[\"i32:2147483647\",\"i32:2147483647\"]],[\"invoke\",387,0,\"gt_s\",[\"i32:4294967295\",\"i32:4294967295\"]],[\"invoke\",388,0,\"gt_s\",[\"i32:1\",\"i32:0\"]],[\"invoke\",389,0,\"gt_s\",[\"i32:0\",\"i32:1\"]],[\"invoke\",390,0,\"gt_s\",[\"i32:2147483648\",\"i32:0\"]],[\"invoke\",391,0,\"gt_s\",[\"i32:0\",\"i32:2147483648\"]],[\"invoke\",392,0,\"gt_s\",[\"i32:2147483648\",\"i32:4294967295\"]],[\"invoke\",393,0,\"gt_s\",[\"i32:4294967295\",\"i32:2147483648\"]],[\"invoke\",394,0,\"gt_s\",[\"i32:2147483648\",\"i32:2147483647\"]],[\"invoke\",395,0,\"gt_s\",[\"i32:2147483647\",\"i32:2147483648\"]],[\"invoke\",397,0,\"gt_u\",[\"i32:0\",\"i32:0\"]],[\"invoke\",398,0,\"gt_u\",[\"i32:1\",\"i32:1\"]],[\"invoke\",399,0,\"gt_u\",[\"i32:4294967295\",\"i32:1\"]],[\"invoke\",400,0,\"gt_u\",[\"i32:2147483648\",\"i32:2147483648\"]],[\"invoke\",401,0,\"gt_u\",[\"i32:2147483647\",\"i32:2147483647\"]],[\"invoke\",402,0,\"gt_u\",[\"i32:4294967295\",\"i32:4294967295\"]],[\"invoke\",403,0,\"gt_u\",[\"i32:1\",\"i32:0\"]],[\"invoke\",404,0,\"gt_u\",[\"i32:0\",\"i32:1\"]],[\"invoke\",405,0,\"gt_u\",[\"i32:2147483648\",\"i32:0\"]],[\"invoke\",406,0,\"gt_u\",[\"i32:0\",\"i32:2147483648\"]],[\"invoke\",407,0,\"gt_u\",[\"i32:2147483648\",\"i32:4294967295\"]],[\"invoke\",408,0,\"gt_u\",[\"i32:4294967295\",\"i32:2147483648\"]],[\"invoke\",409,0,\"gt_u\",[\"i32:2147483648\",\"i32:2147483647\"]],[\"invoke\",410,0,\"gt_u\",[\"i32:2147483647\",\"i32:2147483648\"]],[\"invoke\",412,0,\"ge_s\",[\"i32:0\",\"i32:0\"]],[\"invoke\",413,0,\"ge_s\",[\"i32:1\",\"i32:1\"]],[\"invoke\",414,0,\"ge_s\",[\"i32:4294967295\",\"i32:1\"]],[\"invoke\",415,0,\"ge_s\",[\"i32:2147483648\",\"i32:2147483648\"]],[\"invoke\",416,0,\"ge_s\",[\"i32:2147483647\",\"i32:2147483647\"]],[\"invoke\",417,0,\"ge_s\",[\"i32:4294967295\",\"i32:4294967295\"]],[\"invoke\",418,0,\"ge_s\",[\"i32:1\",\"i32:0\"]],[\"invoke\",419,0,\"ge_s\",[\"i32:0\",\"i32:1\"]],[\"invoke\",420,0,\"ge_s\",[\"i32:2147483648\",\"i32:0\"]],[\"invoke\",421,0,\"ge_s\",[\"i32:0\",\"i32:2147483648\"]],[\"invoke\",422,0,\"ge_s\",[\"i32:2147483648\",\"i32:4294967295\"]],[\"invoke\",423,0,\"ge_s\",[\"i32:4294967295\",\"i32:2147483648\"]],[\"invoke\",424,0,\"ge_s\",[\"i32:2147483648\",\"i32:2147483647\"]],[\"invoke\",425,0,\"ge_s\",[\"i32:2147483647\",\"i32:2147483648\"]],[\"invoke\",427,0,\"ge_u\",[\"i32:0\",\"i32:0\"]],[\"invoke\",428,0,\"ge_u\",[\"i32:1\",\"i32:1\"]],[\"invoke\",429,0,\"ge_u\",[\"i32:4294967295\",\"i32:1\"]],[\"invoke\",430,0,\"ge_u\",[\"i32:2147483648\",\"i32:2147483648\"]],[\"invoke\",431,0,\"ge_u\",[\"i32:2147483647\",\"i32:2147483647\"]],[\"invoke\",432,0,\"ge_u\",[\"i32:4294967295\",\"i32:4294967295\"]],[\"invoke\",433,0,\"ge_u\",[\"i32:1\",\"i32:0\"]],[\"invoke\",434,0,\"ge_u\",[\"i32:0\",\"i32:1\"]],[\"invoke\",435,0,\"ge_u\",[\"i32:2147483648\",\"i32:0\"]],[\"invoke\",436,0,\"ge_u\",[\"i32:0\",\"i32:2147483648\"]],[\"invoke\",437,0,\"ge_u\",[\"i32:2147483648\",\"i32:4294967295\"]],[\"invoke\",438,0,\"ge_u\",[\"i32:4294967295\",\"i32:2147483648\"]],[\"invoke\",439,0,\"ge_u\",[\"i32:2147483648\",\"i32:2147483647\"]],[\"invoke\",440,0,\"ge_u\",[\"i32:2147483647\",\"i32:2147483648\"]],[\"file\",\"i64.wast\"],[\"module\",3,1],[\"invoke\",38,0,\"add\",[\"i64:1\",\"i64:1\"]],[\"invoke\",39,0,\"add\",[\"i64:1\",\"i64:0\"]],[\"invoke\",40,0,\"add\",[\"i64:18446744073709551615\",\"i64:18446744073709551615\"]],[\"invoke\",41,0,\"add\",[\"i64:18446744073709551615\",\"i64:1\"]],[\"invoke\",42,0,\"add\",[\"i64:9223372036854775807\",\"i64:1\"]],[\"invoke\",43,0,\"add\",[\"i64:9223372036854775808\",\"i64:18446744073709551615\"]],[\"invoke\",44,0,\"add\",[\"i64:9223372036854775808\",\"i64:9223372036854775808\"]],[\"invoke\",45,0,\"add\",[\"i64:1073741823\",\"i64:1\"]],[\"invoke\",47,0,\"sub\",[\"i64:1\",\"i64:1\"]],[\"invoke\",48,0,\"sub\",[\"i64:1\",\"i64:0\"]],[\"invoke\",49,0,\"sub\",[\"i64:18446744073709551615\",\"i64:18446744073709551615\"]],[\"invoke\",50,0,\"sub\",[\"i64:9223372036854775807\",\"i64:18446744073709551615\"]],[\"invoke\",51,0,\"sub\",[\"i64:9223372036854775808\",\"i64:1\"]],[\"invoke\",52,0,\"sub\",[\"i64:9223372036854775808\",\"i64:9223372036854775808\"]],[\"invoke\",53,0,\"sub\",[\"i64:1073741823\",\"i64:18446744073709551615\"]],[\"invoke\",55,0,\"mul\",[\"i64:1\",\"i64:1\"]],[\"invoke\",56,0,\"mul\",[\"i64:1\",\"i64:0\"]],[\"invoke\",57,0,\"mul\",[\"i64:18446744073709551615\",\"i64:18446744073709551615\"]],[\"invoke\",58,0,\"mul\",[\"i64:1152921504606846976\",\"i64:4096\"]],[\"invoke\",59,0,\"mul\",[\"i64:9223372036854775808\",\"i64:0\"]],[\"invoke\",60,0,\"mul\",[\"i64:9223372036854775808\",\"i64:18446744073709551615\"]],[\"invoke\",61,0,\"mul\",[\"i64:9223372036854775807\",\"i64:18446744073709551615\"]],[\"invoke\",62,0,\"mul\",[\"i64:81985529216486895\",\"i64:18364758544493064720\"]],[\"invoke\",63,0,\"mul\",[\"i64:9223372036854775807\",\"i64:9223372036854775807\"]],[\"invoke\",65,0,\"div_s\",[\"i64:1\",\"i64:0\"]],[\"invoke\",66,0,\"div_s\",[\"i64:0\",\"i64:0\"]],[\"invoke\",67,0,\"div_s\",[\"i64:9223372036854775808\",\"i64:18446744073709551615\"]],[\"invoke\",68,0,\"div_s\",[\"i64:9223372036854775808\",\"i64:0\"]],[\"invoke\",69,0,\"div_s\",[\"i64:1\",\"i64:1\"]],[\"invoke\",70,0,\"div_s\",[\"i64:0\",\"i64:1\"]],[\"invoke\",71,0,\"div_s\",[\"i64:0\",\"i64:18446744073709551615\"]],[\"invoke\",72,0,\"div_s\",[\"i64:18446744073709551615\",\"i64:18446744073709551615\"]],[\"invoke\",73,0,\"div_s\",[\"i64:9223372036854775808\",\"i64:2\"]],[\"invoke\",74,0,\"div_s\",[\"i64:9223372036854775809\",\"i64:1000\"]],[\"invoke\",75,0,\"div_s\",[\"i64:5\",\"i64:2\"]],[\"invoke\",76,0,\"div_s\",[\"i64:18446744073709551611\",\"i64:2\"]],[\"invoke\",77,0,\"div_s\",[\"i64:5\",\"i64:18446744073709551614\"]],[\"invoke\",78,0,\"div_s\",[\"i64:18446744073709551611\",\"i64:18446744073709551614\"]],[\"invoke\",79,0,\"div_s\",[\"i64:7\",\"i64:3\"]],[\"invoke\",80,0,\"div_s\",[\"i64:18446744073709551609\",\"i64:3\"]],[\"invoke\",81,0,\"div_s\",[\"i64:7\",\"i64:18446744073709551613\"]],[\"invoke\",82,0,\"div_s\",[\"i64:18446744073709551609\",\"i64:18446744073709551613\"]],[\"invoke\",83,0,\"div_s\",[\"i64:11\",\"i64:5\"]],[\"invoke\",84,0,\"div_s\",[\"i64:17\",\"i64:7\"]],[\"invoke\",86,0,\"div_u\",[\"i64:1\",\"i64:0\"]],[\"invoke\",87,0,\"div_u\",[\"i64:0\",\"i64:0\"]],[\"invoke\",88,0,\"div_u\",[\"i64:1\",\"i64:1\"]],[\"invoke\",89,0,\"div_u\",[\"i64:0\",\"i64:1\"]],[\"invoke\",90,0,\"div_u\",[\"i64:18446744073709551615\",\"i64:18446744073709551615\"]],[\"invoke\",91,0,\"div_u\",[\"i64:9223372036854775808\",\"i64:18446744073709551615\"]],[\"invoke\",92,0,\"div_u\",[\"i64:9223372036854775808\",\"i64:2\"]],[\"invoke\",93,0,\"div_u\",[\"i64:10371807465568210928\",\"i64:4294967297\"]],[\"invoke\",94,0,\"div_u\",[\"i64:9223372036854775809\",\"i64:1000\"]],[\"invoke\",95,0,\"div_u\",[\"i64:5\",\"i64:2\"]],[\"invoke\",96,0,\"div_u\",[\"i64:18446744073709551611\",\"i64:2\"]],[\"invoke\",97,0,\"div_u\",[\"i64:5\",\"i64:18446744073709551614\"]],[\"invoke\",98,0,\"div_u\",[\"i64:18446744073709551611\",\"i64:18446744073709551614\"]],[\"invoke\",99,0,\"div_u\",[\"i64:7\",\"i64:3\"]],[\"invoke\",100,0,\"div_u\",[\"i64:11\",\"i64:5\"]],[\"invoke\",101,0,\"div_u\",[\"i64:17\",\"i64:7\"]],[\"invoke\",103,0,\"rem_s\",[\"i64:1\",\"i64:0\"]],[\"invoke\",104,0,\"rem_s\",[\"i64:0\",\"i64:0\"]],[\"invoke\",105,0,\"rem_s\",[\"i64:9223372036854775807\",\"i64:18446744073709551615\"]],[\"invoke\",106,0,\"rem_s\",[\"i64:1\",\"i64:1\"]],[\"invoke\",107,0,\"rem_s\",[\"i64:0\",\"i64:1\"]],[\"invoke\",108,0,\"rem_s\",[\"i64:0\",\"i64:18446744073709551615\"]],[\"invoke\",109,0,\"rem_s\",[\"i64:18446744073709551615\",\"i64:18446744073709551615\"]],[\"invoke\",110,0,\"rem_s\",[\"i64:9223372036854775808\",\"i64:18446744073709551615\"]],[\"invoke\",111,0,\"rem_s\",[\"i64:9223372036854775808\",\"i64:2\"]],[\"invoke\",112,0,\"rem_s\",[\"i64:9223372036854775809\",\"i64:1000\"]],[\"invoke\",113,0,\"rem_s\",[\"i64:5\",\"i64:2\"]],[\"invoke\",114,0,\"rem_s\",[\"i64:18446744073709551611\",\"i64:2\"]],[\"invoke\",115,0,\"rem_s\",[\"i64:5\",\"i64:18446744073709551614\"]],[\"invoke\",116,0,\"rem_s\",[\"i64:18446744073709551611\",\"i64:18446744073709551614\"]],[\"invoke\",117,0,\"rem_s\",[\"i64:7\",\"i64:3\"]],[\"invoke\",118,0,\"rem_s\",[\"i64:18446744073709551609\",\"i64:3\"]],[\"invoke\",119,0,\"rem_s\",[\"i64:7\",\"i64:18446744073709551613\"]],[\"invoke\",120,0,\"rem_s\",[\"i64:18446744073709551609\",\"i64:18446744073709551613\"]],[\"invoke\",121,0,\"rem_s\",[\"i64:11\",\"i64:5\"]],[\"invoke\",122,0,\"rem_s\",[\"i64:17\",\"i64:7\"]],[\"invoke\",124,0,\"rem_u\",[\"i64:1\",\"i64:0\"]],[\"invoke\",125,0,\"rem_u\",[\"i64:0\",\"i64:0\"]],[\"invoke\",126,0,\"rem_u\",[\"i64:1\",\"i64:1\"]],[\"invoke\",127,0,\"rem_u\",[\"i64:0\",\"i64:1\"]],[\"invoke\",128,0,\"rem_u\",[\"i64:18446744073709551615\",\"i64:18446744073709551615\"]],[\"invoke\",129,0,\"rem_u\",[\"i64:9223372036854775808\",\"i64:18446744073709551615\"]],[\"invoke\",130,0,\"rem_u\",[\"i64:9223372036854775808\",\"i64:2\"]],[\"invoke\",131,0,\"rem_u\",[\"i64:10371807465568210928\",\"i64:4294967297\"]],[\"invoke\",132,0,\"rem_u\",[\"i64:9223372036854775809\",\"i64:1000\"]],[\"invoke\",133,0,\"rem_u\",[\"i64:5\",\"i64:2\"]],[\"invoke\",134,0,\"rem_u\",[\"i64:18446744073709551611\",\"i64:2\"]],[\"invoke\",135,0,\"rem_u\",[\"i64:5\",\"i64:18446744073709551614\"]],[\"invoke\",136,0,\"rem_u\",[\"i64:18446744073709551611\",\"i64:18446744073709551614\"]],[\"invoke\",137,0,\"rem_u\",[\"i64:7\",\"i64:3\"]],[\"invoke\",138,0,\"rem_u\",[\"i64:11\",\"i64:5\"]],[\"invoke\",139,0,\"rem_u\",[\"i64:17\",\"i64:7\"]],[\"invoke\",141,0,\"and\",[\"i64:1\",\"i64:0\"]],[\"invoke\",142,0,\"and\",[\"i64:0\",\"i64:1\"]],[\"invoke\",143,0,\"and\",[\"i64:1\",\"i64:1\"]],[\"invoke\",144,0,\"and\",[\"i64:0\",\"i64:0\"]],[\"invoke\",145,0,\"and\",[\"i64:9223372036854775807\",\"i64:9223372036854775808\"]],[\"invoke\",146,0,\"and\",[\"i64:9223372036854775807\",\"i64:18446744073709551615\"]],[\"invoke\",147,0,\"and\",[\"i64:4042326015\",\"i64:4294963440\"]],[\"invoke\",148,0,\"and\",[\"i64:18446744073709551615\",\"i64:18446744073709551615\"]],[\"invoke\",150,0,\"or\",[\"i64:1\",\"i64:0\"]],[\"invoke\",151,0,\"or\",[\"i64:0\",\"i64:1\"]],[\"invoke\",152,0,\"or\",[\"i64:1\",\"i64:1\"]],[\"invoke\",153,0,\"or\",[\"i64:0\",\"i64:0\"]],[\"invoke\",154,0,\"or\",[\"i64:9223372036854775807\",\"i64:9223372036854775808\"]],[\"invoke\",155,0,\"or\",[\"i64:9223372036854775808\",\"i64:0\"]],[\"invoke\",156,0,\"or\",[\"i64:4042326015\",\"i64:4294963440\"]],[\"invoke\",157,0,\"or\",[\"i64:18446744073709551615\",\"i64:18446744073709551615\"]],[\"invoke\",159,0,\"xor\",[\"i64:1\",\"i64:0\"]],[\"invoke\",160,0,\"xor\",[\"i64:0\",\"i64:1\"]],[\"invoke\",161,0,\"xor\",[\"i64:1\",\"i64:1\"]],[\"invoke\",162,0,\"xor\",[\"i64:0\",\"i64:0\"]],[\"invoke\",163,0,\"xor\",[\"i64:9223372036854775807\",\"i64:9223372036854775808\"]],[\"invoke\",164,0,\"xor\",[\"i64:9223372036854775808\",\"i64:0\"]],[\"invoke\",165,0,\"xor\",[\"i64:18446744073709551615\",\"i64:9223372036854775808\"]],[\"invoke\",166,0,\"xor\",[\"i64:18446744073709551615\",\"i64:9223372036854775807\"]],[\"invoke\",167,0,\"xor\",[\"i64:4042326015\",\"i64:4294963440\"]],[\"invoke\",168,0,\"xor\",[\"i64:18446744073709551615\",\"i64:18446744073709551615\"]],[\"invoke\",170,0,\"shl\",[\"i64:1\",\"i64:1\"]],[\"invoke\",171,0,\"shl\",[\"i64:1\",\"i64:0\"]],[\"invoke\",172,0,\"shl\",[\"i64:9223372036854775807\",\"i64:1\"]],[\"invoke\",173,0,\"shl\",[\"i64:18446744073709551615\",\"i64:1\"]],[\"invoke\",174,0,\"shl\",[\"i64:9223372036854775808\",\"i64:1\"]],[\"invoke\",175,0,\"shl\",[\"i64:4611686018427387904\",\"i64:1\"]],[\"invoke\",176,0,\"shl\",[\"i64:1\",\"i64:63\"]],[\"invoke\",177,0,\"shl\",[\"i64:1\",\"i64:64\"]],[\"invoke\",178,0,\"shl\",[\"i64:1\",\"i64:65\"]],[\"invoke\",179,0,\"shl\",[\"i64:1\",\"i64:18446744073709551615\"]],[\"invoke\",180,0,\"shl\",[\"i64:1\",\"i64:9223372036854775807\"]],[\"invoke\",182,0,\"shr_s\",[\"i64:1\",\"i64:1\"]],[\"invoke\",183,0,\"shr_s\",[\"i64:1\",\"i64:0\"]],[\"invoke\",184,0,\"shr_s\",[\"i64:18446744073709551615\",\"i64:1\"]],[\"invoke\",185,0,\"shr_s\",[\"i64:9223372036854775807\",\"i64:1\"]],[\"invoke\",186,0,\"shr_s\",[\"i64:9223372036854775808\",\"i64:1\"]],[\"invoke\",187,0,\"shr_s\",[\"i64:4611686018427387904\",\"i64:1\"]],[\"invoke\",188,0,\"shr_s\",[\"i64:1\",\"i64:64\"]],[\"invoke\",189,0,\"shr_s\",[\"i64:1\",\"i64:65\"]],[\"invoke\",190,0,\"shr_s\",[\"i64:1\",\"i64:18446744073709551615\"]],[\"invoke\",191,0,\"shr_s\",[\"i64:1\",\"i64:9223372036854775807\"]],[\"invoke\",192,0,\"shr_s\",[\"i64:1\",\"i64:9223372036854775808\"]],[\"invoke\",193,0,\"shr_s\",[\"i64:9223372036854775808\",\"i64:63\"]],[\"invoke\",194,0,\"shr_s\",[\"i64:18446744073709551615\",\"i64:64\"]],[\"invoke\",195,0,\"shr_s\",[\"i64:18446744073709551615\",\"i64:65\"]],[\"invoke\",196,0,\"shr_s\",[\"i64:18446744073709551615\",\"i64:18446744073709551615\"]],[\"invoke\",197,0,\"shr_s\",[\"i64:18446744073709551615\",\"i64:9223372036854775807\"]],[\"invoke\",198,0,\"shr_s\",[\"i64:18446744073709551615\",\"i64:9223372036854775808\"]],[\"invoke\",200,0,\"shr_u\",[\"i64:1\",\"i64:1\"]],[\"invoke\",201,0,\"shr_u\",[\"i64:1\",\"i64:0\"]],[\"invoke\",202,0,\"shr_u\",[\"i64:18446744073709551615\",\"i64:1\"]],[\"invoke\",203,0,\"shr_u\",[\"i64:9223372036854775807\",\"i64:1\"]],[\"invoke\",204,0,\"shr_u\",[\"i64:9223372036854775808\",\"i64:1\"]],[\"invoke\",205,0,\"shr_u\",[\"i64:4611686018427387904\",\"i64:1\"]],[\"invoke\",206,0,\"shr_u\",[\"i64:1\",\"i64:64\"]],[\"invoke\",207,0,\"shr_u\",[\"i64:1\",\"i64:65\"]],[\"invoke\",208,0,\"shr_u\",[\"i64:1\",\"i64:18446744073709551615\"]],[\"invoke\",209,0,\"shr_u\",[\"i64:1\",\"i64:9223372036854775807\"]],[\"invoke\",210,0,\"shr_u\",[\"i64:1\",\"i64:9223372036854775808\"]],[\"invoke\",211,0,\"shr_u\",[\"i64:9223372036854775808\",\"i64:63\"]],[\"invoke\",212,0,\"shr_u\",[\"i64:18446744073709551615\",\"i64:64\"]],[\"invoke\",213,0,\"shr_u\",[\"i64:18446744073709551615\",\"i64:65\"]],[\"invoke\",214,0,\"shr_u\",[\"i64:18446744073709551615\",\"i64:18446744073709551615\"]],[\"invoke\",215,0,\"shr_u\",[\"i64:18446744073709551615\",\"i64:9223372036854775807\"]],[\"invoke\",216,0,\"shr_u\",[\"i64:18446744073709551615\",\"i64:9223372036854775808\"]],[\"invoke\",218,0,\"rotl\",[\"i64:1\",\"i64:1\"]],[\"invoke\",219,0,\"rotl\",[\"i64:1\",\"i64:0\"]],[\"invoke\",220,0,\"rotl\",[\"i64:18446744073709551615\",\"i64:1\"]],[\"invoke\",221,0,\"rotl\",[\"i64:1\",\"i64:64\"]],[\"invoke\",222,0,\"rotl\",[\"i64:12379718583323101902\",\"i64:1\"]],[\"invoke\",223,0,\"rotl\",[\"i64:18302628889324683264\",\"i64:4\"]],[\"invoke\",224,0,\"rotl\",[\"i64:12379570969274382345\",\"i64:53\"]],[\"invoke\",225,0,\"rotl\",[\"i64:12380715672649826460\",\"i64:63\"]],[\"invoke\",226,0,\"rotl\",[\"i64:12379570969274382345\",\"i64:245\"]],[\"invoke\",227,0,\"rotl\",[\"i64:12379676934707509257\",\"i64:18446744073709551597\"]],[\"invoke\",228,0,\"rotl\",[\"i64:12380715672649826460\",\"i64:9223372036854775871\"]],[\"invoke\",229,0,\"rotl\",[\"i64:1\",\"i64:63\"]],[\"invoke\",230,0,\"rotl\",[\"i64:9223372036854775808\",\"i64:1\"]],[\"invoke\",232,0,\"rotr\",[\"i64:1\",\"i64:1\"]],[\"invoke\",233,0,\"rotr\",[\"i64:1\",\"i64:0\"]],[\"invoke\",234,0,\"rotr\",[\"i64:18446744073709551615\",\"i64:1\"]],[\"invoke\",235,0,\"rotr\",[\"i64:1\",\"i64:64\"]],[\"invoke\",236,0,\"rotr\",[\"i64:12379718583323101902\",\"i64:1\"]],[\"invoke\",237,0,\"rotr\",[\"i64:18302628889324683264\",\"i64:4\"]],[\"invoke\",238,0,\"rotr\",[\"i64:12379570969274382345\",\"i64:53\"]],[\"invoke\",239,0,\"rotr\",[\"i64:12380715672649826460\",\"i64:63\"]],[\"invoke\",240,0,\"rotr\",[\"i64:12379570969274382345\",\"i64:245\"]],[\"invoke\",241,0,\"rotr\",[\"i64:12379676934707509257\",\"i64:18446744073709551597\"]],[\"invoke\",242,0,\"rotr\",[\"i64:12380715672649826460\",\"i64:9223372036854775871\"]],[\"invoke\",243,0,\"rotr\",[\"i64:1\",\"i64:63\"]],[\"invoke\",244,0,\"rotr\",[\"i64:9223372036854775808\",\"i64:63\"]],[\"invoke\",246,0,\"clz\",[\"i64:18446744073709551615\"]],[\"invoke\",247,0,\"clz\",[\"i64:0\"]],[\"invoke\",248,0,\"clz\",[\"i64:32768\"]],[\"invoke\",249,0,\"clz\",[\"i64:255\"]],[\"invoke\",250,0,\"clz\",[\"i64:9223372036854775808\"]],[\"invoke\",251,0,\"clz\",[\"i64:1\"]],[\"invoke\",252,0,\"clz\",[\"i64:2\"]],[\"invoke\",253,0,\"clz\",[\"i64:9223372036854775807\"]],[\"invoke\",255,0,\"ctz\",[\"i64:18446744073709551615\"]],[\"invoke\",256,0,\"ctz\",[\"i64:0\"]],[\"invoke\",257,0,\"ctz\",[\"i64:32768\"]],[\"invoke\",258,0,\"ctz\",[\"i64:65536\"]],[\"invoke\",259,0,\"ctz\",[\"i64:9223372036854775808\"]],[\"invoke\",260,0,\"ctz\",[\"i64:9223372036854775807\"]],[\"invoke\",262,0,\"popcnt\",[\"i64:18446744073709551615\"]],[\"invoke\",263,0,\"popcnt\",[\"i64:0\"]],[\"invoke\",264,0,\"popcnt\",[\"i64:32768\"]],[\"invoke\",265,0,\"popcnt\",[\"i64:9223512776490647552\"]],[\"invoke\",266,0,\"popcnt\",[\"i64:9223372036854775807\"]],[\"invoke\",267,0,\"popcnt\",[\"i64:12297829381041378645\"]],[\"invoke\",268,0,\"popcnt\",[\"i64:11068046444512062122\"]],[\"invoke\",269,0,\"popcnt\",[\"i64:16045690984833335023\"]],[\"invoke\",271,0,\"extend8_s\",[\"i64:0\"]],[\"invoke\",272,0,\"extend8_s\",[\"i64:127\"]],[\"invoke\",273,0,\"extend8_s\",[\"i64:128\"]],[\"invoke\",274,0,\"extend8_s\",[\"i64:255\"]],[\"invoke\",275,0,\"extend8_s\",[\"i64:81985529216486656\"]],[\"invoke\",276,0,\"extend8_s\",[\"i64:18364758544493064832\"]],[\"invoke\",277,0,\"extend8_s\",[\"i64:18446744073709551615\"]],[\"invoke\",279,0,\"extend16_s\",[\"i64:0\"]],[\"invoke\",280,0,\"extend16_s\",[\"i64:32767\"]],[\"invoke\",281,0,\"extend16_s\",[\"i64:32768\"]],[\"invoke\",282,0,\"extend16_s\",[\"i64:65535\"]],[\"invoke\",283,0,\"extend16_s\",[\"i64:1311768467463733248\"]],[\"invoke\",284,0,\"extend16_s\",[\"i64:18364758544493084672\"]],[\"invoke\",285,0,\"extend16_s\",[\"i64:18446744073709551615\"]],[\"invoke\",287,0,\"extend32_s\",[\"i64:0\"]],[\"invoke\",288,0,\"extend32_s\",[\"i64:32767\"]],[\"invoke\",289,0,\"extend32_s\",[\"i64:32768\"]],[\"invoke\",290,0,\"extend32_s\",[\"i64:65535\"]],[\"invoke\",291,0,\"extend32_s\",[\"i64:2147483647\"]],[\"invoke\",292,0,\"extend32_s\",[\"i64:2147483648\"]],[\"invoke\",293,0,\"extend32_s\",[\"i64:4294967295\"]],[\"invoke\",294,0,\"extend32_s\",[\"i64:81985526906748928\"]],[\"invoke\",295,0,\"extend32_s\",[\"i64:18364758544655319040\"]],[\"invoke\",296,0,\"extend32_s\",[\"i64:18446744073709551615\"]],[\"invoke\",298,0,\"eqz\",[\"i64:0\"]],[\"invoke\",299,0,\"eqz\",[\"i64:1\"]],[\"invoke\",300,0,\"eqz\",[\"i64:9223372036854775808\"]],[\"invoke\",301,0,\"eqz\",[\"i64:9223372036854775807\"]],[\"invoke\",302,0,\"eqz\",[\"i64:18446744073709551615\"]],[\"invoke\",304,0,\"eq\",[\"i64:0\",\"i64:0\"]],[\"invoke\",305,0,\"eq\",[\"i64:1\",\"i64:1\"]],[\"invoke\",306,0,\"eq\",[\"i64:18446744073709551615\",\"i64:1\"]],[\"invoke\",307,0,\"eq\",[\"i64:9223372036854775808\",\"i64:9223372036854775808\"]],[\"invoke\",308,0,\"eq\",[\"i64:9223372036854775807\",\"i64:9223372036854775807\"]],[\"invoke\",309,0,\"eq\",[\"i64:18446744073709551615\",\"i64:18446744073709551615\"]],[\"invoke\",310,0,\"eq\",[\"i64:1\",\"i64:0\"]],[\"invoke\",311,0,\"eq\",[\"i64:0\",\"i64:1\"]],[\"invoke\",312,0,\"eq\",[\"i64:9223372036854775808\",\"i64:0\"]],[\"invoke\",313,0,\"eq\",[\"i64:0\",\"i64:9223372036854775808\"]],[\"invoke\",314,0,\"eq\",[\"i64:9223372036854775808\",\"i64:18446744073709551615\"]],[\"invoke\",315,0,\"eq\",[\"i64:18446744073709551615\",\"i64:9223372036854775808\"]],[\"invoke\",316,0,\"eq\",[\"i64:9223372036854775808\",\"i64:9223372036854775807\"]],[\"invoke\",317,0,\"eq\",[\"i64:9223372036854775807\",\"i64:9223372036854775808\"]],[\"invoke\",319,0,\"ne\",[\"i64:0\",\"i64:0\"]],[\"invoke\",320,0,\"ne\",[\"i64:1\",\"i64:1\"]],[\"invoke\",321,0,\"ne\",[\"i64:18446744073709551615\",\"i64:1\"]],[\"invoke\",322,0,\"ne\",[\"i64:9223372036854775808\",\"i64:9223372036854775808\"]],[\"invoke\",323,0,\"ne\",[\"i64:9223372036854775807\",\"i64:9223372036854775807\"]],[\"invoke\",324,0,\"ne\",[\"i64:18446744073709551615\",\"i64:18446744073709551615\"]],[\"invoke\",325,0,\"ne\",[\"i64:1\",\"i64:0\"]],[\"invoke\",326,0,\"ne\",[\"i64:0\",\"i64:1\"]],[\"invoke\",327,0,\"ne\",[\"i64:9223372036854775808\",\"i64:0\"]],[\"invoke\",328,0,\"ne\",[\"i64:0\",\"i64:9223372036854775808\"]],[\"invoke\",329,0,\"ne\",[\"i64:9223372036854775808\",\"i64:18446744073709551615\"]],[\"invoke\",330,0,\"ne\",[\"i64:18446744073709551615\",\"i64:9223372036854775808\"]],[\"invoke\",331,0,\"ne\",[\"i64:9223372036854775808\",\"i64:9223372036854775807\"]],[\"invoke\",332,0,\"ne\",[\"i64:9223372036854775807\",\"i64:9223372036854775808\"]],[\"invoke\",334,0,\"lt_s\",[\"i64:0\",\"i64:0\"]],[\"invoke\",335,0,\"lt_s\",[\"i64:1\",\"i64:1\"]],[\"invoke\",336,0,\"lt_s\",[\"i64:18446744073709551615\",\"i64:1\"]],[\"invoke\",337,0,\"lt_s\",[\"i64:9223372036854775808\",\"i64:9223372036854775808\"]],[\"invoke\",338,0,\"lt_s\",[\"i64:9223372036854775807\",\"i64:9223372036854775807\"]],[\"invoke\",339,0,\"lt_s\",[\"i64:18446744073709551615\",\"i64:18446744073709551615\"]],[\"invoke\",340,0,\"lt_s\",[\"i64:1\",\"i64:0\"]],[\"invoke\",341,0,\"lt_s\",[\"i64:0\",\"i64:1\"]],[\"invoke\",342,0,\"lt_s\",[\"i64:9223372036854775808\",\"i64:0\"]],[\"invoke\",343,0,\"lt_s\",[\"i64:0\",\"i64:9223372036854775808\"]],[\"invoke\",344,0,\"lt_s\",[\"i64:9223372036854775808\",\"i64:18446744073709551615\"]],[\"invoke\",345,0,\"lt_s\",[\"i64:18446744073709551615\",\"i64:9223372036854775808\"]],[\"invoke\",346,0,\"lt_s\",[\"i64:9223372036854775808\",\"i64:9223372036854775807\"]],[\"invoke\",347,0,\"lt_s\",[\"i64:9223372036854775807\",\"i64:9223372036854775808\"]],[\"invoke\",349,0,\"lt_u\",[\"i64:0\",\"i64:0\"]],[\"invoke\",350,0,\"lt_u\",[\"i64:1\",\"i64:1\"]],[\"invoke\",351,0,\"lt_u\",[\"i64:18446744073709551615\",\"i64:1\"]],[\"invoke\",352,0,\"lt_u\",[\"i64:9223372036854775808\",\"i64:9223372036854775808\"]],[\"invoke\",353,0,\"lt_u\",[\"i64:9223372036854775807\",\"i64:9223372036854775807\"]],[\"invoke\",354,0,\"lt_u\",[\"i64:18446744073709551615\",\"i64:18446744073709551615\"]],[\"invoke\",355,0,\"lt_u\",[\"i64:1\",\"i64:0\"]],[\"invoke\",356,0,\"lt_u\",[\"i64:0\",\"i64:1\"]],[\"invoke\",357,0,\"lt_u\",[\"i64:9223372036854775808\",\"i64:0\"]],[\"invoke\",358,0,\"lt_u\",[\"i64:0\",\"i64:9223372036854775808\"]],[\"invoke\",359,0,\"lt_u\",[\"i64:9223372036854775808\",\"i64:18446744073709551615\"]],[\"invoke\",360,0,\"lt_u\",[\"i64:18446744073709551615\",\"i64:9223372036854775808\"]],[\"invoke\",361,0,\"lt_u\",[\"i64:9223372036854775808\",\"i64:9223372036854775807\"]],[\"invoke\",362,0,\"lt_u\",[\"i64:9223372036854775807\",\"i64:9223372036854775808\"]],[\"invoke\",364,0,\"le_s\",[\"i64:0\",\"i64:0\"]],[\"invoke\",365,0,\"le_s\",[\"i64:1\",\"i64:1\"]],[\"invoke\",366,0,\"le_s\",[\"i64:18446744073709551615\",\"i64:1\"]],[\"invoke\",367,0,\"le_s\",[\"i64:9223372036854775808\",\"i64:9223372036854775808\"]],[\"invoke\",368,0,\"le_s\",[\"i64:9223372036854775807\",\"i64:9223372036854775807\"]],[\"invoke\",369,0,\"le_s\",[\"i64:18446744073709551615\",\"i64:18446744073709551615\"]],[\"invoke\",370,0,\"le_s\",[\"i64:1\",\"i64:0\"]],[\"invoke\",371,0,\"le_s\",[\"i64:0\",\"i64:1\"]],[\"invoke\",372,0,\"le_s\",[\"i64:9223372036854775808\",\"i64:0\"]],[\"invoke\",373,0,\"le_s\",[\"i64:0\",\"i64:9223372036854775808\"]],[\"invoke\",374,0,\"le_s\",[\"i64:9223372036854775808\",\"i64:18446744073709551615\"]],[\"invoke\",375,0,\"le_s\",[\"i64:18446744073709551615\",\"i64:9223372036854775808\"]],[\"invoke\",376,0,\"le_s\",[\"i64:9223372036854775808\",\"i64:9223372036854775807\"]],[\"invoke\",377,0,\"le_s\",[\"i64:9223372036854775807\",\"i64:9223372036854775808\"]],[\"invoke\",379,0,\"le_u\",[\"i64:0\",\"i64:0\"]],[\"invoke\",380,0,\"le_u\",[\"i64:1\",\"i64:1\"]],[\"invoke\",381,0,\"le_u\",[\"i64:18446744073709551615\",\"i64:1\"]],[\"invoke\",382,0,\"le_u\",[\"i64:9223372036854775808\",\"i64:9223372036854775808\"]],[\"invoke\",383,0,\"le_u\",[\"i64:9223372036854775807\",\"i64:9223372036854775807\"]],[\"invoke\",384,0,\"le_u\",[\"i64:18446744073709551615\",\"i64:18446744073709551615\"]],[\"invoke\",385,0,\"le_u\",[\"i64:1\",\"i64:0\"]],[\"invoke\",386,0,\"le_u\",[\"i64:0\",\"i64:1\"]],[\"invoke\",387,0,\"le_u\",[\"i64:9223372036854775808\",\"i64:0\"]],[\"invoke\",388,0,\"le_u\",[\"i64:0\",\"i64:9223372036854775808\"]],[\"invoke\",389,0,\"le_u\",[\"i64:9223372036854775808\",\"i64:18446744073709551615\"]],[\"invoke\",390,0,\"le_u\",[\"i64:18446744073709551615\",\"i64:9223372036854775808\"]],[\"invoke\",391,0,\"le_u\",[\"i64:9223372036854775808\",\"i64:9223372036854775807\"]],[\"invoke\",392,0,\"le_u\",[\"i64:9223372036854775807\",\"i64:9223372036854775808\"]],[\"invoke\",394,0,\"gt_s\",[\"i64:0\",\"i64:0\"]],[\"invoke\",395,0,\"gt_s\",[\"i64:1\",\"i64:1\"]],[\"invoke\",396,0,\"gt_s\",[\"i64:18446744073709551615\",\"i64:1\"]],[\"invoke\",397,0,\"gt_s\",[\"i64:9223372036854775808\",\"i64:9223372036854775808\"]],[\"invoke\",398,0,\"gt_s\",[\"i64:9223372036854775807\",\"i64:9223372036854775807\"]],[\"invoke\",399,0,\"gt_s\",[\"i64:18446744073709551615\",\"i64:18446744073709551615\"]],[\"invoke\",400,0,\"gt_s\",[\"i64:1\",\"i64:0\"]],[\"invoke\",401,0,\"gt_s\",[\"i64:0\",\"i64:1\"]],[\"invoke\",402,0,\"gt_s\",[\"i64:9223372036854775808\",\"i64:0\"]],[\"invoke\",403,0,\"gt_s\",[\"i64:0\",\"i64:9223372036854775808\"]],[\"invoke\",404,0,\"gt_s\",[\"i64:9223372036854775808\",\"i64:18446744073709551615\"]],[\"invoke\",405,0,\"gt_s\",[\"i64:18446744073709551615\",\"i64:9223372036854775808\"]],[\"invoke\",406,0,\"gt_s\",[\"i64:9223372036854775808\",\"i64:9223372036854775807\"]],[\"invoke\",407,0,\"gt_s\",[\"i64:9223372036854775807\",\"i64:9223372036854775808\"]],[\"invoke\",409,0,\"gt_u\",[\"i64:0\",\"i64:0\"]],[\"invoke\",410,0,\"gt_u\",[\"i64:1\",\"i64:1\"]],[\"invoke\",411,0,\"gt_u\",[\"i64:18446744073709551615\",\"i64:1\"]],[\"invoke\",412,0,\"gt_u\",[\"i64:9223372036854775808\",\"i64:9223372036854775808\"]],[\"invoke\",413,0,\"gt_u\",[\"i64:9223372036854775807\",\"i64:9223372036854775807\"]],[\"invoke\",414,0,\"gt_u\",[\"i64:18446744073709551615\",\"i64:18446744073709551615\"]],[\"invoke\",415,0,\"gt_u\",[\"i64:1\",\"i64:0\"]],[\"invoke\",416,0,\"gt_u\",[\"i64:0\",\"i64:1\"]],[\"invoke\",417,0,\"gt_u\",[\"i64:9223372036854775808\",\"i64:0\"]],[\"invoke\",418,0,\"gt_u\",[\"i64:0\",\"i64:9223372036854775808\"]],[\"invoke\",419,0,\"gt_u\",[\"i64:9223372036854775808\",\"i64:18446744073709551615\"]],[\"invoke\",420,0,\"gt_u\",[\"i64:18446744073709551615\",\"i64:9223372036854775808\"]],[\"invoke\",421,0,\"gt_u\",[\"i64:9223372036854775808\",\"i64:9223372036854775807\"]],[\"invoke\",422,0,\"gt_u\",[\"i64:9223372036854775807\",\"i64:9223372036854775808\"]],[\"invoke\",424,0,\"ge_s\",[\"i64:0\",\"i64:0\"]],[\"invoke\",425,0,\"ge_s\",[\"i64:1\",\"i64:1\"]],[\"invoke\",426,0,\"ge_s\",[\"i64:18446744073709551615\",\"i64:1\"]],[\"invoke\",427,0,\"ge_s\",[\"i64:9223372036854775808\",\"i64:9223372036854775808\"]],[\"invoke\",428,0,\"ge_s\",[\"i64:9223372036854775807\",\"i64:9223372036854775807\"]],[\"invoke\",429,0,\"ge_s\",[\"i64:18446744073709551615\",\"i64:18446744073709551615\"]],[\"invoke\",430,0,\"ge_s\",[\"i64:1\",\"i64:0\"]],[\"invoke\",431,0,\"ge_s\",[\"i64:0\",\"i64:1\"]],[\"invoke\",432,0,\"ge_s\",[\"i64:9223372036854775808\",\"i64:0\"]],[\"invoke\",433,0,\"ge_s\",[\"i64:0\",\"i64:9223372036854775808\"]],[\"invoke\",434,0,\"ge_s\",[\"i64:9223372036854775808\",\"i64:18446744073709551615\"]],[\"invoke\",435,0,\"ge_s\",[\"i64:18446744073709551615\",\"i64:9223372036854775808\"]],[\"invoke\",436,0,\"ge_s\",[\"i64:9223372036854775808\",\"i64:9223372036854775807\"]],[\"invoke\",437,0,\"ge_s\",[\"i64:9223372036854775807\",\"i64:9223372036854775808\"]],[\"invoke\",439,0,\"ge_u\",[\"i64:0\",\"i64:0\"]],[\"invoke\",440,0,\"ge_u\",[\"i64:1\",\"i64:1\"]],[\"invoke\",441,0,\"ge_u\",[\"i64:18446744073709551615\",\"i64:1\"]],[\"invoke\",442,0,\"ge_u\",[\"i64:9223372036854775808\",\"i64:9223372036854775808\"]],[\"invoke\",443,0,\"ge_u\",[\"i64:9223372036854775807\",\"i64:9223372036854775807\"]],[\"invoke\",444,0,\"ge_u\",[\"i64:18446744073709551615\",\"i64:18446744073709551615\"]],[\"invoke\",445,0,\"ge_u\",[\"i64:1\",\"i64:0\"]],[\"invoke\",446,0,\"ge_u\",[\"i64:0\",\"i64:1\"]],[\"invoke\",447,0,\"ge_u\",[\"i64:9223372036854775808\",\"i64:0\"]],[\"invoke\",448,0,\"ge_u\",[\"i64:0\",\"i64:9223372036854775808\"]],[\"invoke\",449,0,\"ge_u\",[\"i64:9223372036854775808\",\"i64:18446744073709551615\"]],[\"invoke\",450,0,\"ge_u\",[\"i64:18446744073709551615\",\"i64:9223372036854775808\"]],[\"invoke\",451,0,\"ge_u\",[\"i64:9223372036854775808\",\"i64:9223372036854775807\"]],[\"invoke\",452,0,\"ge_u\",[\"i64:9223372036854775807\",\"i64:9223372036854775808\"]],[\"file\",\"int_exprs.wast\"],[\"module\",6,2],[\"invoke\",18,0,\"i32.no_fold_cmp_s_offset\",[\"i32:2147483647\",\"i32:0\"]],[\"invoke\",19,0,\"i32.no_fold_cmp_u_offset\",[\"i32:4294967295\",\"i32:0\"]],[\"invoke\",20,0,\"i64.no_fold_cmp_s_offset\",[\"i64:9223372036854775807\",\"i64:0\"]],[\"invoke\",21,0,\"i64.no_fold_cmp_u_offset\",[\"i64:18446744073709551615\",\"i64:0\"]],[\"module\",25,3],[\"invoke\",30,0,\"i64.no_fold_wrap_extend_s\",[\"i64:4538991236898928\"]],[\"invoke\",31,0,\"i64.no_fold_wrap_extend_s\",[\"i64:45230338458316960\"]],[\"module\",35,4],[\"invoke\",40,0,\"i64.no_fold_wrap_extend_u\",[\"i64:4538991236898928\"]],[\"module\",44,5],[\"invoke\",56,0,\"i32.no_fold_shl_shr_s\",[\"i32:2147483648\"]],[\"invoke\",57,0,\"i32.no_fold_shl_shr_u\",[\"i32:2147483648\"]],[\"invoke\",58,0,\"i64.no_fold_shl_shr_s\",[\"i64:9223372036854775808\"]],[\"invoke\",59,0,\"i64.no_fold_shl_shr_u\",[\"i64:9223372036854775808\"]],[\"module\",63,6],[\"invoke\",75,0,\"i32.no_fold_shr_s_shl\",[\"i32:1\"]],[\"invoke\",76,0,\"i32.no_fold_shr_u_shl\",[\"i32:1\"]],[\"invoke\",77,0,\"i64.no_fold_shr_s_shl\",[\"i64:1\"]],[\"invoke\",78,0,\"i64.no_fold_shr_u_shl\",[\"i64:1\"]],[\"module\",82,7],[\"invoke\",94,0,\"i32.no_fold_div_s_mul\",[\"i32:1\"]],[\"invoke\",95,0,\"i32.no_fold_div_u_mul\",[\"i32:1\"]],[\"invoke\",96,0,\"i64.no_fold_div_s_mul\",[\"i64:1\"]],[\"invoke\",97,0,\"i64.no_fold_div_u_mul\",[\"i64:1\"]],[\"module\",101,8],[\"invoke\",113,0,\"i32.no_fold_div_s_self\",[\"i32:0\"]],[\"invoke\",114,0,\"i32.no_fold_div_u_self\",[\"i32:0\"]],[\"invoke\",115,0,\"i64.no_fold_div_s_self\",[\"i64:0\"]],[\"invoke\",116,0,\"i64.no_fold_div_u_self\",[\"i64:0\"]],[\"module\",120,9],[\"invoke\",132,0,\"i32.no_fold_rem_s_self\",[\"i32:0\"]],[\"invoke\",133,0,\"i32.no_fold_rem_u_self\",[\"i32:0\"]],[\"invoke\",134,0,\"i64.no_fold_rem_s_self\",[\"i64:0\"]],[\"invoke\",135,0,\"i64.no_fold_rem_u_self\",[\"i64:0\"]],[\"module\",139,10],[\"invoke\",151,0,\"i32.no_fold_mul_div_s\",[\"i32:2147483648\"]],[\"invoke\",152,0,\"i32.no_fold_mul_div_u\",[\"i32:2147483648\"]],[\"invoke\",153,0,\"i64.no_fold_mul_div_s\",[\"i64:9223372036854775808\"]],[\"invoke\",154,0,\"i64.no_fold_mul_div_u\",[\"i64:9223372036854775808\"]],[\"module\",158,11],[\"invoke\",166,0,\"i32.no_fold_div_s_2\",[\"i32:4294967285\"]],[\"invoke\",167,0,\"i64.no_fold_div_s_2\",[\"i64:18446744073709551605\"]],[\"module\",171,12],[\"invoke\",179,0,\"i32.no_fold_rem_s_2\",[\"i32:4294967285\"]],[\"invoke\",180,0,\"i64.no_fold_rem_s_2\",[\"i64:18446744073709551605\"]],[\"module\",184,13],[\"invoke\",196,0,\"i32.div_s_0\",[\"i32:71\"]],[\"invoke\",197,0,\"i32.div_u_0\",[\"i32:71\"]],[\"invoke\",198,0,\"i64.div_s_0\",[\"i64:71\"]],[\"invoke\",199,0,\"i64.div_u_0\",[\"i64:71\"]],[\"module\",203,14],[\"invoke\",215,0,\"i32.div_s_3\",[\"i32:71\"]],[\"invoke\",216,0,\"i32.div_s_3\",[\"i32:1610612736\"]],[\"invoke\",217,0,\"i32.div_u_3\",[\"i32:71\"]],[\"invoke\",218,0,\"i32.div_u_3\",[\"i32:3221225472\"]],[\"invoke\",219,0,\"i64.div_s_3\",[\"i64:71\"]],[\"invoke\",220,0,\"i64.div_s_3\",[\"i64:3458764513820540928\"]],[\"invoke\",221,0,\"i64.div_u_3\",[\"i64:71\"]],[\"invoke\",222,0,\"i64.div_u_3\",[\"i64:13835058055282163712\"]],[\"module\",226,15],[\"invoke\",238,0,\"i32.div_s_5\",[\"i32:71\"]],[\"invoke\",239,0,\"i32.div_s_5\",[\"i32:1342177280\"]],[\"invoke\",240,0,\"i32.div_u_5\",[\"i32:71\"]],[\"invoke\",241,0,\"i32.div_u_5\",[\"i32:2684354560\"]],[\"invoke\",242,0,\"i64.div_s_5\",[\"i64:71\"]],[\"invoke\",243,0,\"i64.div_s_5\",[\"i64:5764607523034234880\"]],[\"invoke\",244,0,\"i64.div_u_5\",[\"i64:71\"]],[\"invoke\",245,0,\"i64.div_u_5\",[\"i64:11529215046068469760\"]],[\"module\",249,16],[\"invoke\",261,0,\"i32.div_s_7\",[\"i32:71\"]],[\"invoke\",262,0,\"i32.div_s_7\",[\"i32:1879048192\"]],[\"invoke\",263,0,\"i32.div_u_7\",[\"i32:71\"]],[\"invoke\",264,0,\"i32.div_u_7\",[\"i32:3758096384\"]],[\"invoke\",265,0,\"i64.div_s_7\",[\"i64:71\"]],[\"invoke\",266,0,\"i64.div_s_7\",[\"i64:8070450532247928832\"]],[\"invoke\",267,0,\"i64.div_u_7\",[\"i64:71\"]],[\"invoke\",268,0,\"i64.div_u_7\",[\"i64:16140901064495857664\"]],[\"module\",272,17],[\"invoke\",284,0,\"i32.rem_s_3\",[\"i32:71\"]],[\"invoke\",285,0,\"i32.rem_s_3\",[\"i32:1610612736\"]],[\"invoke\",286,0,\"i32.rem_u_3\",[\"i32:71\"]],[\"invoke\",287,0,\"i32.rem_u_3\",[\"i32:3221225472\"]],[\"invoke\",288,0,\"i64.rem_s_3\",[\"i64:71\"]],[\"invoke\",289,0,\"i64.rem_s_3\",[\"i64:3458764513820540928\"]],[\"invoke\",290,0,\"i64.rem_u_3\",[\"i64:71\"]],[\"invoke\",291,0,\"i64.rem_u_3\",[\"i64:13835058055282163712\"]],[\"module\",295,18],[\"invoke\",307,0,\"i32.rem_s_5\",[\"i32:71\"]],[\"invoke\",308,0,\"i32.rem_s_5\",[\"i32:1342177280\"]],[\"invoke\",309,0,\"i32.rem_u_5\",[\"i32:71\"]],[\"invoke\",310,0,\"i32.rem_u_5\",[\"i32:2684354560\"]],[\"invoke\",311,0,\"i64.rem_s_5\",[\"i64:71\"]],[\"invoke\",312,0,\"i64.rem_s_5\",[\"i64:5764607523034234880\"]],[\"invoke\",313,0,\"i64.rem_u_5\",[\"i64:71\"]],[\"invoke\",314,0,\"i64.rem_u_5\",[\"i64:11529215046068469760\"]],[\"module\",318,19],[\"invoke\",330,0,\"i32.rem_s_7\",[\"i32:71\"]],[\"invoke\",331,0,\"i32.rem_s_7\",[\"i32:1879048192\"]],[\"invoke\",332,0,\"i32.rem_u_7\",[\"i32:71\"]],[\"invoke\",333,0,\"i32.rem_u_7\",[\"i32:3758096384\"]],[\"invoke\",334,0,\"i64.rem_s_7\",[\"i64:71\"]],[\"invoke\",335,0,\"i64.rem_s_7\",[\"i64:8070450532247928832\"]],[\"invoke\",336,0,\"i64.rem_u_7\",[\"i64:71\"]],[\"invoke\",337,0,\"i64.rem_u_7\",[\"i64:16140901064495857664\"]],[\"module\",341,20],[\"invoke\",349,0,\"i32.no_fold_div_neg1\",[\"i32:2147483648\"]],[\"invoke\",350,0,\"i64.no_fold_div_neg1\",[\"i64:9223372036854775808\"]],[\"file\",\"int_literals.wast\"],[\"module\",1,21],[\"invoke\",37,0,\"i32.test\",[]],[\"invoke\",38,0,\"i32.umax\",[]],[\"invoke\",39,0,\"i32.smax\",[]],[\"invoke\",40,0,\"i32.neg_smax\",[]],[\"invoke\",41,0,\"i32.smin\",[]],[\"invoke\",42,0,\"i32.alt_smin\",[]],[\"invoke\",43,0,\"i32.inc_smin\",[]],[\"invoke\",44,0,\"i32.neg_zero\",[]],[\"invoke\",45,0,\"i32.not_octal\",[]],[\"invoke\",46,0,\"i32.unsigned_decimal\",[]],[\"invoke\",47,0,\"i32.plus_sign\",[]],[\"invoke\",49,0,\"i64.test\",[]],[\"invoke\",50,0,\"i64.umax\",[]],[\"invoke\",51,0,\"i64.smax\",[]],[\"invoke\",52,0,\"i64.neg_smax\",[]],[\"invoke\",53,0,\"i64.smin\",[]],[\"invoke\",54,0,\"i64.alt_smin\",[]],[\"invoke\",55,0,\"i64.inc_smin\",[]],[\"invoke\",56,0,\"i64.neg_zero\",[]],[\"invoke\",57,0,\"i64.not_octal\",[]],[\"invoke\",58,0,\"i64.unsigned_decimal\",[]],[\"invoke\",59,0,\"i64.plus_sign\",[]],[\"invoke\",61,0,\"i32-dec-sep1\",[]],[\"invoke\",62,0,\"i32-dec-sep2\",[]],[\"invoke\",63,0,\"i32-hex-sep1\",[]],[\"invoke\",64,0,\"i32-hex-sep2\",[]],[\"invoke\",66,0,\"i64-dec-sep1\",[]],[\"invoke\",67,0,\"i64-dec-sep2\",[]],[\"invoke\",68,0,\"i64-hex-sep1\",[]],[\"invoke\",69,0,\"i64-hex-sep2\",[]],[\"file\",\"conversions.wast\"],[\"module\",1,22],[\"invoke\",37,0,\"i64.extend_i32_s\",[\"i32:0\"]],[\"invoke\",39,0,\"i64.extend_i32_s\",[\"i32:4294957296\"]],[\"invoke\",40,0,\"i64.extend_i32_s\",[\"i32:4294967295\"]],[\"invoke\",42,0,\"i64.extend_i32_s\",[\"i32:2147483648\"]],[\"invoke\",44,0,\"i64.extend_i32_u\",[\"i32:0\"]],[\"invoke\",45,0,\"i64.extend_i32_u\",[\"i32:10000\"]],[\"invoke\",47,0,\"i64.extend_i32_u\",[\"i32:4294967295\"]],[\"invoke\",48,0,\"i64.extend_i32_u\",[\"i32:2147483647\"]],[\"invoke\",51,0,\"i32.wrap_i64\",[\"i64:18446744073709551615\"]],[\"invoke\",52,0,\"i32.wrap_i64\",[\"i64:18446744073709451616\"]],[\"invoke\",53,0,\"i32.wrap_i64\",[\"i64:2147483648\"]],[\"invoke\",55,0,\"i32.wrap_i64\",[\"i64:18446744069414584320\"]],[\"invoke\",56,0,\"i32.wrap_i64\",[\"i64:18446744069414584319\"]],[\"invoke\",57,0,\"i32.wrap_i64\",[\"i64:18446744069414584321\"]],[\"invoke\",59,0,\"i32.wrap_i64\",[\"i64:1311768467463790320\"]],[\"invoke\",60,0,\"i32.wrap_i64\",[\"i64:4294967295\"]],[\"invoke\",62,0,\"i32.wrap_i64\",[\"i64:4294967297\"]],[\"invoke\",64,0,\"i32.trunc_f32_s\",[\"f32:0\"]],[\"invoke\",65,0,\"i32.trunc_f32_s\",[\"f32:2147483648\"]],[\"invoke\",67,0,\"i32.trunc_f32_s\",[\"f32:2147483649\"]],[\"invoke\",68,0,\"i32.trunc_f32_s\",[\"f32:1065353216\"]],[\"invoke\",69,0,\"i32.trunc_f32_s\",[\"f32:1066192077\"]],[\"invoke\",71,0,\"i32.trunc_f32_s\",[\"f32:3212836864\"]],[\"invoke\",72,0,\"i32.trunc_f32_s\",[\"f32:3213675725\"]],[\"invoke\",74,0,\"i32.trunc_f32_s\",[\"f32:3220386611\"]],[\"invoke\",75,0,\"i32.trunc_f32_s\",[\"f32:3221225472\"]],[\"invoke\",76,0,\"i32.trunc_f32_s\",[\"f32:1325400063\"]],[\"invoke\",78,0,\"i32.trunc_f32_s\",[\"f32:1325400064\"]],[\"invoke\",79,0,\"i32.trunc_f32_s\",[\"f32:3472883713\"]],[\"invoke\",80,0,\"i32.trunc_f32_s\",[\"f32:2139095040\"]],[\"invoke\",82,0,\"i32.trunc_f32_s\",[\"f32:2143289344\"]],[\"invoke\",84,0,\"i32.trunc_f32_s\",[\"f32:4290772992\"]],[\"invoke\",88,0,\"i32.trunc_f32_u\",[\"f32:2147483648\"]],[\"invoke\",89,0,\"i32.trunc_f32_u\",[\"f32:1\"]],[\"invoke\",90,0,\"i32.trunc_f32_u\",[\"f32:2147483649\"]],[\"invoke\",92,0,\"i32.trunc_f32_u\",[\"f32:1066192077\"]],[\"invoke\",93,0,\"i32.trunc_f32_u\",[\"f32:1069547520\"]],[\"invoke\",94,0,\"i32.trunc_f32_u\",[\"f32:1072902963\"]],[\"invoke\",96,0,\"i32.trunc_f32_u\",[\"f32:1325400064\"]],[\"invoke\",97,0,\"i32.trunc_f32_u\",[\"f32:1333788671\"]],[\"invoke\",99,0,\"i32.trunc_f32_u\",[\"f32:3212836863\"]],[\"invoke\",100,0,\"i32.trunc_f32_u\",[\"f32:1333788672\"]],[\"invoke\",101,0,\"i32.trunc_f32_u\",[\"f32:3212836864\"]],[\"invoke\",103,0,\"i32.trunc_f32_u\",[\"f32:4286578688\"]],[\"invoke\",104,0,\"i32.trunc_f32_u\",[\"f32:2143289344\"]],[\"invoke\",106,0,\"i32.trunc_f32_u\",[\"f32:4290772992\"]],[\"invoke\",110,0,\"i32.trunc_f64_s\",[\"f64:9223372036854775808\"]],[\"invoke\",111,0,\"i32.trunc_f64_s\",[\"f64:1\"]],[\"invoke\",113,0,\"i32.trunc_f64_s\",[\"f64:4607182418800017408\"]],[\"invoke\",114,0,\"i32.trunc_f64_s\",[\"f64:4607632778762754458\"]],[\"invoke\",115,0,\"i32.trunc_f64_s\",[\"f64:4609434218613702656\"]],[\"invoke\",117,0,\"i32.trunc_f64_s\",[\"f64:13831004815617530266\"]],[\"invoke\",118,0,\"i32.trunc_f64_s\",[\"f64:13832806255468478464\"]],[\"invoke\",120,0,\"i32.trunc_f64_s\",[\"f64:13835058055282163712\"]],[\"invoke\",121,0,\"i32.trunc_f64_s\",[\"f64:4746794007244308480\"]],[\"invoke\",122,0,\"i32.trunc_f64_s\",[\"f64:13970166044103278592\"]],[\"invoke\",124,0,\"i32.trunc_f64_s\",[\"f64:4746794007248083354\"]],[\"invoke\",125,0,\"i32.trunc_f64_s\",[\"f64:4746794007248502784\"]],[\"invoke\",126,0,\"i32.trunc_f64_s\",[\"f64:13970166044105375744\"]],[\"invoke\",128,0,\"i32.trunc_f64_s\",[\"f64:18442240474082181120\"]],[\"invoke\",129,0,\"i32.trunc_f64_s\",[\"f64:9221120237041090560\"]],[\"invoke\",134,0,\"i32.trunc_f64_u\",[\"f64:0\"]],[\"invoke\",135,0,\"i32.trunc_f64_u\",[\"f64:9223372036854775808\"]],[\"invoke\",136,0,\"i32.trunc_f64_u\",[\"f64:1\"]],[\"invoke\",138,0,\"i32.trunc_f64_u\",[\"f64:4607182418800017408\"]],[\"invoke\",139,0,\"i32.trunc_f64_u\",[\"f64:4607632778762754458\"]],[\"invoke\",140,0,\"i32.trunc_f64_u\",[\"f64:4609434218613702656\"]],[\"invoke\",142,0,\"i32.trunc_f64_u\",[\"f64:4611686018427387904\"]],[\"invoke\",143,0,\"i32.trunc_f64_u\",[\"f64:4746794007248502784\"]],[\"invoke\",145,0,\"i32.trunc_f64_u\",[\"f64:13829653735729319117\"]],[\"invoke\",146,0,\"i32.trunc_f64_u\",[\"f64:13830554455654793215\"]],[\"invoke\",147,0,\"i32.trunc_f64_u\",[\"f64:4726483295884279808\"]],[\"invoke\",149,0,\"i32.trunc_f64_u\",[\"f64:4751297606875663565\"]],[\"invoke\",150,0,\"i32.trunc_f64_u\",[\"f64:4751297606875873280\"]],[\"invoke\",151,0,\"i32.trunc_f64_u\",[\"f64:13830554455654793216\"]],[\"invoke\",153,0,\"i32.trunc_f64_u\",[\"f64:5055640609639927018\"]],[\"invoke\",154,0,\"i32.trunc_f64_u\",[\"f64:4890909195324358656\"]],[\"invoke\",156,0,\"i32.trunc_f64_u\",[\"f64:18442240474082181120\"]],[\"invoke\",157,0,\"i32.trunc_f64_u\",[\"f64:9221120237041090560\"]],[\"invoke\",159,0,\"i32.trunc_f64_u\",[\"f64:18444492273895866368\"]],[\"invoke\",163,0,\"i64.trunc_f32_s\",[\"f32:2147483648\"]],[\"invoke\",164,0,\"i64.trunc_f32_s\",[\"f32:1\"]],[\"invoke\",165,0,\"i64.trunc_f32_s\",[\"f32:2147483649\"]],[\"invoke\",167,0,\"i64.trunc_f32_s\",[\"f32:1066192077\"]],[\"invoke\",168,0,\"i64.trunc_f32_s\",[\"f32:1069547520\"]],[\"invoke\",170,0,\"i64.trunc_f32_s\",[\"f32:3213675725\"]],[\"invoke\",171,0,\"i64.trunc_f32_s\",[\"f32:3217031168\"]],[\"invoke\",172,0,\"i64.trunc_f32_s\",[\"f32:3220386611\"]],[\"invoke\",174,0,\"i64.trunc_f32_s\",[\"f32:1333788672\"]],[\"invoke\",175,0,\"i64.trunc_f32_s\",[\"f32:3481272320\"]],[\"invoke\",176,0,\"i64.trunc_f32_s\",[\"f32:1593835519\"]],[\"invoke\",178,0,\"i64.trunc_f32_s\",[\"f32:1593835520\"]],[\"invoke\",179,0,\"i64.trunc_f32_s\",[\"f32:3741319169\"]],[\"invoke\",181,0,\"i64.trunc_f32_s\",[\"f32:4286578688\"]],[\"invoke\",182,0,\"i64.trunc_f32_s\",[\"f32:2143289344\"]],[\"invoke\",184,0,\"i64.trunc_f32_s\",[\"f32:4290772992\"]],[\"invoke\",188,0,\"i64.trunc_f32_u\",[\"f32:2147483648\"]],[\"invoke\",189,0,\"i64.trunc_f32_u\",[\"f32:1\"]],[\"invoke\",190,0,\"i64.trunc_f32_u\",[\"f32:2147483649\"]],[\"invoke\",192,0,\"i64.trunc_f32_u\",[\"f32:1066192077\"]],[\"invoke\",193,0,\"i64.trunc_f32_u\",[\"f32:1069547520\"]],[\"invoke\",195,0,\"i64.trunc_f32_u\",[\"f32:1602224127\"]],[\"invoke\",196,0,\"i64.trunc_f32_u\",[\"f32:3211159142\"]],[\"invoke\",197,0,\"i64.trunc_f32_u\",[\"f32:3212836863\"]],[\"invoke\",199,0,\"i64.trunc_f32_u\",[\"f32:3212836864\"]],[\"invoke\",200,0,\"i64.trunc_f32_u\",[\"f32:2139095040\"]],[\"invoke\",202,0,\"i64.trunc_f32_u\",[\"f32:2143289344\"]],[\"invoke\",204,0,\"i64.trunc_f32_u\",[\"f32:4290772992\"]],[\"invoke\",207,0,\"i64.trunc_f64_s\",[\"f64:0\"]],[\"invoke\",209,0,\"i64.trunc_f64_s\",[\"f64:1\"]],[\"invoke\",210,0,\"i64.trunc_f64_s\",[\"f64:9223372036854775809\"]],[\"invoke\",211,0,\"i64.trunc_f64_s\",[\"f64:4607182418800017408\"]],[\"invoke\",213,0,\"i64.trunc_f64_s\",[\"f64:4609434218613702656\"]],[\"invoke\",214,0,\"i64.trunc_f64_s\",[\"f64:13830554455654793216\"]],[\"invoke\",216,0,\"i64.trunc_f64_s\",[\"f64:13832806255468478464\"]],[\"invoke\",217,0,\"i64.trunc_f64_s\",[\"f64:13834607695319426662\"]],[\"invoke\",218,0,\"i64.trunc_f64_s\",[\"f64:13835058055282163712\"]],[\"invoke\",220,0,\"i64.trunc_f64_s\",[\"f64:13974669643730649088\"]],[\"invoke\",221,0,\"i64.trunc_f64_s\",[\"f64:4890909195324358655\"]],[\"invoke\",222,0,\"i64.trunc_f64_s\",[\"f64:14114281232179134464\"]],[\"invoke\",224,0,\"i64.trunc_f64_s\",[\"f64:14114281232179134465\"]],[\"invoke\",225,0,\"i64.trunc_f64_s\",[\"f64:9218868437227405312\"]],[\"invoke\",227,0,\"i64.trunc_f64_s\",[\"f64:9221120237041090560\"]],[\"invoke\",229,0,\"i64.trunc_f64_s\",[\"f64:18444492273895866368\"]],[\"invoke\",232,0,\"i64.trunc_f64_u\",[\"f64:0\"]],[\"invoke\",234,0,\"i64.trunc_f64_u\",[\"f64:1\"]],[\"invoke\",235,0,\"i64.trunc_f64_u\",[\"f64:9223372036854775809\"]],[\"invoke\",236,0,\"i64.trunc_f64_u\",[\"f64:4607182418800017408\"]],[\"invoke\",238,0,\"i64.trunc_f64_u\",[\"f64:4609434218613702656\"]],[\"invoke\",239,0,\"i64.trunc_f64_u\",[\"f64:4751297606873776128\"]],[\"invoke\",241,0,\"i64.trunc_f64_u\",[\"f64:4895412794951729151\"]],[\"invoke\",242,0,\"i64.trunc_f64_u\",[\"f64:13829653735729319117\"]],[\"invoke\",243,0,\"i64.trunc_f64_u\",[\"f64:13830554455654793215\"]],[\"invoke\",245,0,\"i64.trunc_f64_u\",[\"f64:4846369599423283200\"]],[\"invoke\",246,0,\"i64.trunc_f64_u\",[\"f64:4890909195324358656\"]],[\"invoke\",247,0,\"i64.trunc_f64_u\",[\"f64:4895412794951729152\"]],[\"invoke\",249,0,\"i64.trunc_f64_u\",[\"f64:9218868437227405312\"]],[\"invoke\",250,0,\"i64.trunc_f64_u\",[\"f64:18442240474082181120\"]],[\"invoke\",253,0,\"i64.trunc_f64_u\",[\"f64:18444492273895866368\"]],[\"invoke\",256,0,\"f32.convert_i32_s\",[\"i32:1\"]],[\"invoke\",257,0,\"f32.convert_i32_s\",[\"i32:4294967295\"]],[\"invoke\",259,0,\"f32.convert_i32_s\",[\"i32:2147483647\"]],[\"invoke\",260,0,\"f32.convert_i32_s\",[\"i32:2147483648\"]],[\"invoke\",261,0,\"f32.convert_i32_s\",[\"i32:1234567890\"]],[\"invoke\",266,0,\"i32.trunc_sat_f32_s\",[\"f32:2147483648\"]],[\"invoke\",267,0,\"i32.trunc_sat_f32_s\",[\"f32:1\"]],[\"invoke\",269,0,\"i32.trunc_sat_f32_s\",[\"f32:1065353216\"]],[\"invoke\",270,0,\"i32.trunc_sat_f32_s\",[\"f32:1066192077\"]],[\"invoke\",271,0,\"i32.trunc_sat_f32_s\",[\"f32:1069547520\"]],[\"invoke\",273,0,\"i32.trunc_sat_f32_s\",[\"f32:3213675725\"]],[\"invoke\",274,0,\"i32.trunc_sat_f32_s\",[\"f32:3217031168\"]],[\"invoke\",276,0,\"i32.trunc_sat_f32_s\",[\"f32:3221225472\"]],[\"invoke\",277,0,\"i32.trunc_sat_f32_s\",[\"f32:1325400063\"]],[\"invoke\",278,0,\"i32.trunc_sat_f32_s\",[\"f32:3472883712\"]],[\"invoke\",280,0,\"i32.trunc_sat_f32_s\",[\"f32:3472883713\"]],[\"invoke\",281,0,\"i32.trunc_sat_f32_s\",[\"f32:2139095040\"]],[\"invoke\",282,0,\"i32.trunc_sat_f32_s\",[\"f32:4286578688\"]],[\"invoke\",285,0,\"i32.trunc_sat_f32_s\",[\"f32:4290772992\"]],[\"invoke\",288,0,\"i32.trunc_sat_f32_u\",[\"f32:0\"]],[\"invoke\",290,0,\"i32.trunc_sat_f32_u\",[\"f32:1\"]],[\"invoke\",291,0,\"i32.trunc_sat_f32_u\",[\"f32:2147483649\"]],[\"invoke\",292,0,\"i32.trunc_sat_f32_u\",[\"f32:1065353216\"]],[\"invoke\",294,0,\"i32.trunc_sat_f32_u\",[\"f32:1069547520\"]],[\"invoke\",295,0,\"i32.trunc_sat_f32_u\",[\"f32:1072902963\"]],[\"invoke\",296,0,\"i32.trunc_sat_f32_u\",[\"f32:1073741824\"]],[\"invoke\",298,0,\"i32.trunc_sat_f32_u\",[\"f32:1333788671\"]],[\"invoke\",299,0,\"i32.trunc_sat_f32_u\",[\"f32:3211159142\"]],[\"invoke\",301,0,\"i32.trunc_sat_f32_u\",[\"f32:1333788672\"]],[\"invoke\",302,0,\"i32.trunc_sat_f32_u\",[\"f32:3212836864\"]],[\"invoke\",303,0,\"i32.trunc_sat_f32_u\",[\"f32:2139095040\"]],[\"invoke\",305,0,\"i32.trunc_sat_f32_u\",[\"f32:2143289344\"]],[\"invoke\",307,0,\"i32.trunc_sat_f32_u\",[\"f32:4290772992\"]],[\"invoke\",310,0,\"i32.trunc_sat_f64_s\",[\"f64:0\"]],[\"invoke\",312,0,\"i32.trunc_sat_f64_s\",[\"f64:1\"]],[\"invoke\",313,0,\"i32.trunc_sat_f64_s\",[\"f64:9223372036854775809\"]],[\"invoke\",315,0,\"i32.trunc_sat_f64_s\",[\"f64:4607632778762754458\"]],[\"invoke\",316,0,\"i32.trunc_sat_f64_s\",[\"f64:4609434218613702656\"]],[\"invoke\",317,0,\"i32.trunc_sat_f64_s\",[\"f64:13830554455654793216\"]],[\"invoke\",319,0,\"i32.trunc_sat_f64_s\",[\"f64:13832806255468478464\"]],[\"invoke\",320,0,\"i32.trunc_sat_f64_s\",[\"f64:13834607695319426662\"]],[\"invoke\",321,0,\"i32.trunc_sat_f64_s\",[\"f64:13835058055282163712\"]],[\"invoke\",323,0,\"i32.trunc_sat_f64_s\",[\"f64:13970166044103278592\"]],[\"invoke\",324,0,\"i32.trunc_sat_f64_s\",[\"f64:4746794007248502784\"]],[\"invoke\",326,0,\"i32.trunc_sat_f64_s\",[\"f64:9218868437227405312\"]],[\"invoke\",327,0,\"i32.trunc_sat_f64_s\",[\"f64:18442240474082181120\"]],[\"invoke\",328,0,\"i32.trunc_sat_f64_s\",[\"f64:9221120237041090560\"]],[\"invoke\",333,0,\"i32.trunc_sat_f64_u\",[\"f64:0\"]],[\"invoke\",334,0,\"i32.trunc_sat_f64_u\",[\"f64:9223372036854775808\"]],[\"invoke\",335,0,\"i32.trunc_sat_f64_u\",[\"f64:1\"]],[\"invoke\",337,0,\"i32.trunc_sat_f64_u\",[\"f64:4607182418800017408\"]],[\"invoke\",338,0,\"i32.trunc_sat_f64_u\",[\"f64:4607632778762754458\"]],[\"invoke\",340,0,\"i32.trunc_sat_f64_u\",[\"f64:4611235658464650854\"]],[\"invoke\",341,0,\"i32.trunc_sat_f64_u\",[\"f64:4611686018427387904\"]],[\"invoke\",342,0,\"i32.trunc_sat_f64_u\",[\"f64:4746794007248502784\"]],[\"invoke\",344,0,\"i32.trunc_sat_f64_u\",[\"f64:13829653735729319117\"]],[\"invoke\",345,0,\"i32.trunc_sat_f64_u\",[\"f64:13830554455654793215\"]],[\"invoke\",346,0,\"i32.trunc_sat_f64_u\",[\"f64:4726483295884279808\"]],[\"invoke\",348,0,\"i32.trunc_sat_f64_u\",[\"f64:13830554455654793216\"]],[\"invoke\",349,0,\"i32.trunc_sat_f64_u\",[\"f64:4846369599423283200\"]],[\"invoke\",351,0,\"i32.trunc_sat_f64_u\",[\"f64:4890909195324358656\"]],[\"invoke\",352,0,\"i32.trunc_sat_f64_u\",[\"f64:9218868437227405312\"]],[\"invoke\",353,0,\"i32.trunc_sat_f64_u\",[\"f64:18442240474082181120\"]],[\"invoke\",356,0,\"i32.trunc_sat_f64_u\",[\"f64:18444492273895866368\"]],[\"invoke\",359,0,\"i64.trunc_sat_f32_s\",[\"f32:0\"]],[\"invoke\",361,0,\"i64.trunc_sat_f32_s\",[\"f32:1\"]],[\"invoke\",362,0,\"i64.trunc_sat_f32_s\",[\"f32:2147483649\"]],[\"invoke\",363,0,\"i64.trunc_sat_f32_s\",[\"f32:1065353216\"]],[\"invoke\",365,0,\"i64.trunc_sat_f32_s\",[\"f32:1069547520\"]],[\"invoke\",366,0,\"i64.trunc_sat_f32_s\",[\"f32:3212836864\"]],[\"invoke\",367,0,\"i64.trunc_sat_f32_s\",[\"f32:3213675725\"]],[\"invoke\",369,0,\"i64.trunc_sat_f32_s\",[\"f32:3220386611\"]],[\"invoke\",370,0,\"i64.trunc_sat_f32_s\",[\"f32:3221225472\"]],[\"invoke\",372,0,\"i64.trunc_sat_f32_s\",[\"f32:3481272320\"]],[\"invoke\",373,0,\"i64.trunc_sat_f32_s\",[\"f32:1593835519\"]],[\"invoke\",374,0,\"i64.trunc_sat_f32_s\",[\"f32:3741319168\"]],[\"invoke\",376,0,\"i64.trunc_sat_f32_s\",[\"f32:3741319169\"]],[\"invoke\",377,0,\"i64.trunc_sat_f32_s\",[\"f32:2139095040\"]],[\"invoke\",378,0,\"i64.trunc_sat_f32_s\",[\"f32:4286578688\"]],[\"invoke\",381,0,\"i64.trunc_sat_f32_s\",[\"f32:4290772992\"]],[\"invoke\",384,0,\"i64.trunc_sat_f32_u\",[\"f32:0\"]],[\"invoke\",386,0,\"i64.trunc_sat_f32_u\",[\"f32:1\"]],[\"invoke\",387,0,\"i64.trunc_sat_f32_u\",[\"f32:2147483649\"]],[\"invoke\",388,0,\"i64.trunc_sat_f32_u\",[\"f32:1065353216\"]],[\"invoke\",390,0,\"i64.trunc_sat_f32_u\",[\"f32:1069547520\"]],[\"invoke\",391,0,\"i64.trunc_sat_f32_u\",[\"f32:1333788672\"]],[\"invoke\",392,0,\"i64.trunc_sat_f32_u\",[\"f32:1602224127\"]],[\"invoke\",394,0,\"i64.trunc_sat_f32_u\",[\"f32:3212836863\"]],[\"invoke\",395,0,\"i64.trunc_sat_f32_u\",[\"f32:1602224128\"]],[\"invoke\",397,0,\"i64.trunc_sat_f32_u\",[\"f32:2139095040\"]],[\"invoke\",398,0,\"i64.trunc_sat_f32_u\",[\"f32:4286578688\"]],[\"invoke\",399,0,\"i64.trunc_sat_f32_u\",[\"f32:2143289344\"]],[\"invoke\",404,0,\"i64.trunc_sat_f64_s\",[\"f64:0\"]],[\"invoke\",405,0,\"i64.trunc_sat_f64_s\",[\"f64:9223372036854775808\"]],[\"invoke\",406,0,\"i64.trunc_sat_f64_s\",[\"f64:1\"]],[\"invoke\",408,0,\"i64.trunc_sat_f64_s\",[\"f64:4607182418800017408\"]],[\"invoke\",409,0,\"i64.trunc_sat_f64_s\",[\"f64:4607632778762754458\"]],[\"invoke\",411,0,\"i64.trunc_sat_f64_s\",[\"f64:13830554455654793216\"]],[\"invoke\",412,0,\"i64.trunc_sat_f64_s\",[\"f64:13831004815617530266\"]],[\"invoke\",413,0,\"i64.trunc_sat_f64_s\",[\"f64:13832806255468478464\"]],[\"invoke\",415,0,\"i64.trunc_sat_f64_s\",[\"f64:13835058055282163712\"]],[\"invoke\",416,0,\"i64.trunc_sat_f64_s\",[\"f64:4751297606875873280\"]],[\"invoke\",417,0,\"i64.trunc_sat_f64_s\",[\"f64:13974669643730649088\"]],[\"invoke\",419,0,\"i64.trunc_sat_f64_s\",[\"f64:14114281232179134464\"]],[\"invoke\",420,0,\"i64.trunc_sat_f64_s\",[\"f64:4890909195324358656\"]],[\"invoke\",422,0,\"i64.trunc_sat_f64_s\",[\"f64:9218868437227405312\"]],[\"invoke\",423,0,\"i64.trunc_sat_f64_s\",[\"f64:18442240474082181120\"]],[\"invoke\",424,0,\"i64.trunc_sat_f64_s\",[\"f64:9221120237041090560\"]],[\"invoke\",429,0,\"i64.trunc_sat_f64_u\",[\"f64:0\"]],[\"invoke\",430,0,\"i64.trunc_sat_f64_u\",[\"f64:9223372036854775808\"]],[\"invoke\",431,0,\"i64.trunc_sat_f64_u\",[\"f64:1\"]],[\"invoke\",433,0,\"i64.trunc_sat_f64_u\",[\"f64:4607182418800017408\"]],[\"invoke\",434,0,\"i64.trunc_sat_f64_u\",[\"f64:4607632778762754458\"]],[\"invoke\",436,0,\"i64.trunc_sat_f64_u\",[\"f64:4751297606873776128\"]],[\"invoke\",437,0,\"i64.trunc_sat_f64_u\",[\"f64:4751297606875873280\"]],[\"invoke\",438,0,\"i64.trunc_sat_f64_u\",[\"f64:4895412794951729151\"]],[\"invoke\",440,0,\"i64.trunc_sat_f64_u\",[\"f64:13830554455654793215\"]],[\"invoke\",441,0,\"i64.trunc_sat_f64_u\",[\"f64:4726483295884279808\"]],[\"invoke\",443,0,\"i64.trunc_sat_f64_u\",[\"f64:4890909195324358656\"]],[\"invoke\",444,0,\"i64.trunc_sat_f64_u\",[\"f64:4895412794951729152\"]],[\"invoke\",445,0,\"i64.trunc_sat_f64_u\",[\"f64:13830554455654793216\"]],[\"invoke\",447,0,\"i64.trunc_sat_f64_u\",[\"f64:18442240474082181120\"]],[\"invoke\",448,0,\"i64.trunc_sat_f64_u\",[\"f64:9221120237041090560\"]],[\"invoke\",450,0,\"i64.trunc_sat_f64_u\",[\"f64:18444492273895866368\"]],[\"invoke\",455,0,\"f32.convert_i32_s\",[\"i32:4278190079\"]],[\"invoke\",456,0,\"f32.convert_i32_s\",[\"i32:16777219\"]],[\"invoke\",459,0,\"f32.convert_i64_s\",[\"i64:1\"]],[\"invoke\",460,0,\"f32.convert_i64_s\",[\"i64:18446744073709551615\"]],[\"invoke\",461,0,\"f32.convert_i64_s\",[\"i64:0\"]],[\"invoke\",463,0,\"f32.convert_i64_s\",[\"i64:9223372036854775808\"]],[\"invoke\",464,0,\"f32.convert_i64_s\",[\"i64:314159265358979\"]],[\"invoke\",466,0,\"f32.convert_i64_s\",[\"i64:16777217\"]],[\"invoke\",468,0,\"f32.convert_i64_s\",[\"i64:16777219\"]],[\"invoke\",469,0,\"f32.convert_i64_s\",[\"i64:18446744073692774397\"]],[\"invoke\",472,0,\"f32.convert_i64_s\",[\"i64:9223372311732682753\"]],[\"invoke\",473,0,\"f32.convert_i64_s\",[\"i64:9007199791611905\"]],[\"invoke\",474,0,\"f32.convert_i64_s\",[\"i64:18437736873917939711\"]],[\"invoke\",477,0,\"f64.convert_i32_s\",[\"i32:4294967295\"]],[\"invoke\",478,0,\"f64.convert_i32_s\",[\"i32:0\"]],[\"invoke\",479,0,\"f64.convert_i32_s\",[\"i32:2147483647\"]],[\"invoke\",481,0,\"f64.convert_i32_s\",[\"i32:987654321\"]],[\"invoke\",483,0,\"f64.convert_i64_s\",[\"i64:1\"]],[\"invoke\",485,0,\"f64.convert_i64_s\",[\"i64:0\"]],[\"invoke\",486,0,\"f64.convert_i64_s\",[\"i64:9223372036854775807\"]],[\"invoke\",487,0,\"f64.convert_i64_s\",[\"i64:9223372036854775808\"]],[\"invoke\",490,0,\"f64.convert_i64_s\",[\"i64:9007199254740993\"]],[\"invoke\",491,0,\"f64.convert_i64_s\",[\"i64:18437736874454810623\"]],[\"invoke\",492,0,\"f64.convert_i64_s\",[\"i64:9007199254740995\"]],[\"invoke\",495,0,\"f32.convert_i32_u\",[\"i32:1\"]],[\"invoke\",496,0,\"f32.convert_i32_u\",[\"i32:0\"]],[\"invoke\",498,0,\"f32.convert_i32_u\",[\"i32:2147483648\"]],[\"invoke\",499,0,\"f32.convert_i32_u\",[\"i32:305419896\"]],[\"invoke\",500,0,\"f32.convert_i32_u\",[\"i32:4294967295\"]],[\"invoke\",502,0,\"f32.convert_i32_u\",[\"i32:2147483777\"]],[\"invoke\",503,0,\"f32.convert_i32_u\",[\"i32:2147483778\"]],[\"invoke\",504,0,\"f32.convert_i32_u\",[\"i32:4294966912\"]],[\"invoke\",506,0,\"f32.convert_i32_u\",[\"i32:4294966914\"]],[\"invoke\",508,0,\"f32.convert_i32_u\",[\"i32:16777217\"]],[\"invoke\",511,0,\"f32.convert_i64_u\",[\"i64:1\"]],[\"invoke\",512,0,\"f32.convert_i64_u\",[\"i64:0\"]],[\"invoke\",513,0,\"f32.convert_i64_u\",[\"i64:9223372036854775807\"]],[\"invoke\",515,0,\"f32.convert_i64_u\",[\"i64:18446744073709551615\"]],[\"invoke\",517,0,\"f32.convert_i64_u\",[\"i64:16777217\"]],[\"invoke\",520,0,\"f32.convert_i64_u\",[\"i64:9007199791611905\"]],[\"invoke\",521,0,\"f32.convert_i64_u\",[\"i64:9223371761976868863\"]],[\"invoke\",522,0,\"f32.convert_i64_u\",[\"i64:9223372586610589697\"]],[\"invoke\",525,0,\"f64.convert_i32_u\",[\"i32:1\"]],[\"invoke\",526,0,\"f64.convert_i32_u\",[\"i32:0\"]],[\"invoke\",527,0,\"f64.convert_i32_u\",[\"i32:2147483647\"]],[\"invoke\",529,0,\"f64.convert_i32_u\",[\"i32:4294967295\"]],[\"invoke\",531,0,\"f64.convert_i64_u\",[\"i64:1\"]],[\"invoke\",533,0,\"f64.convert_i64_u\",[\"i64:9223372036854775807\"]],[\"invoke\",534,0,\"f64.convert_i64_u\",[\"i64:9223372036854775808\"]],[\"invoke\",535,0,\"f64.convert_i64_u\",[\"i64:18446744073709551615\"]],[\"invoke\",537,0,\"f64.convert_i64_u\",[\"i64:9223372036854776833\"]],[\"invoke\",538,0,\"f64.convert_i64_u\",[\"i64:9223372036854776834\"]],[\"invoke\",539,0,\"f64.convert_i64_u\",[\"i64:18446744073709548544\"]],[\"invoke\",541,0,\"f64.convert_i64_u\",[\"i64:18446744073709548546\"]],[\"invoke\",543,0,\"f64.convert_i64_u\",[\"i64:9007199254740993\"]],[\"invoke\",546,0,\"f64.promote_f32\",[\"f32:0\"]],[\"invoke\",547,0,\"f64.promote_f32\",[\"f32:2147483648\"]],[\"invoke\",548,0,\"f64.promote_f32\",[\"f32:1\"]],[\"invoke\",550,0,\"f64.promote_f32\",[\"f32:1065353216\"]],[\"invoke\",551,0,\"f64.promote_f32\",[\"f32:3212836864\"]],[\"invoke\",552,0,\"f64.promote_f32\",[\"f32:4286578687\"]],[\"invoke\",555,0,\"f64.promote_f32\",[\"f32:67108864\"]],[\"invoke\",557,0,\"f64.promote_f32\",[\"f32:2118632255\"]],[\"invoke\",559,0,\"f64.promote_f32\",[\"f32:4286578688\"]],[\"invoke\",560,0,\"f64.promote_f32\",[\"f32:2143289344\"]],[\"invoke\",562,0,\"f64.promote_f32\",[\"f32:4290772992\"]],[\"invoke\",566,0,\"f32.demote_f64\",[\"f64:9223372036854775808\"]],[\"invoke\",567,0,\"f32.demote_f64\",[\"f64:1\"]],[\"invoke\",568,0,\"f32.demote_f64\",[\"f64:9223372036854775809\"]],[\"invoke\",570,0,\"f32.demote_f64\",[\"f64:13830554455654793216\"]],[\"invoke\",571,0,\"f32.demote_f64\",[\"f64:4039728865214464000\"]],[\"invoke\",573,0,\"f32.demote_f64\",[\"f64:4039728865214463999\"]],[\"invoke\",574,0,\"f32.demote_f64\",[\"f64:13263100902069239807\"]],[\"invoke\",575,0,\"f32.demote_f64\",[\"f64:3936146074321813504\"]],[\"invoke\",577,0,\"f32.demote_f64\",[\"f64:5183643170298134528\"]],[\"invoke\",578,0,\"f32.demote_f64\",[\"f64:14407015207152910336\"]],[\"invoke\",579,0,\"f32.demote_f64\",[\"f64:5183643170298134529\"]],[\"invoke\",581,0,\"f32.demote_f64\",[\"f64:5183643170566569984\"]],[\"invoke\",582,0,\"f32.demote_f64\",[\"f64:14407015207421345792\"]],[\"invoke\",584,0,\"f32.demote_f64\",[\"f64:14407015207689781247\"]],[\"invoke\",585,0,\"f32.demote_f64\",[\"f64:5183643170835005440\"]],[\"invoke\",586,0,\"f32.demote_f64\",[\"f64:14407015207689781248\"]],[\"invoke\",588,0,\"f32.demote_f64\",[\"f64:5172657297058430976\"]],[\"invoke\",589,0,\"f32.demote_f64\",[\"f64:9218868437227405312\"]],[\"invoke\",590,0,\"f32.demote_f64\",[\"f64:18442240474082181120\"]],[\"invoke\",592,0,\"f32.demote_f64\",[\"f64:4607182418800017407\"]],[\"invoke\",593,0,\"f32.demote_f64\",[\"f64:4607182419068452864\"]],[\"invoke\",595,0,\"f32.demote_f64\",[\"f64:4607182419605323775\"]],[\"invoke\",596,0,\"f32.demote_f64\",[\"f64:4607182419605323776\"]],[\"invoke\",597,0,\"f32.demote_f64\",[\"f64:4607182420142194688\"]],[\"invoke\",599,0,\"f32.demote_f64\",[\"f64:4715268810125344769\"]],[\"invoke\",600,0,\"f32.demote_f64\",[\"f64:4715268810662215679\"]],[\"invoke\",602,0,\"f32.demote_f64\",[\"f64:5094955347580439664\"]],[\"invoke\",603,0,\"f32.demote_f64\",[\"f64:4101111194527827589\"]],[\"invoke\",604,0,\"f32.demote_f64\",[\"f64:4038806939559600639\"]],[\"invoke\",606,0,\"f32.demote_f64\",[\"f64:14338315240173327556\"]],[\"invoke\",607,0,\"f32.demote_f64\",[\"f64:9221120237041090560\"]],[\"invoke\",609,0,\"f32.demote_f64\",[\"f64:18444492273895866368\"]],[\"invoke\",612,0,\"f32.demote_f64\",[\"f64:9227875636482146304\"]],[\"invoke\",613,0,\"f32.demote_f64\",[\"f64:3931642474694443008\"]],[\"invoke\",615,0,\"f32.demote_f64\",[\"f64:3931642474694443009\"]],[\"invoke\",616,0,\"f32.demote_f64\",[\"f64:13155014511549218817\"]],[\"invoke\",618,0,\"f32.reinterpret_i32\",[\"i32:0\"]],[\"invoke\",620,0,\"f32.reinterpret_i32\",[\"i32:1\"]],[\"invoke\",621,0,\"f32.reinterpret_i32\",[\"i32:4294967295\"]],[\"invoke\",622,0,\"f32.reinterpret_i32\",[\"i32:123456789\"]],[\"invoke\",624,0,\"f32.reinterpret_i32\",[\"i32:2139095040\"]],[\"invoke\",625,0,\"f32.reinterpret_i32\",[\"i32:4286578688\"]],[\"invoke\",627,0,\"f32.reinterpret_i32\",[\"i32:4290772992\"]],[\"invoke\",628,0,\"f32.reinterpret_i32\",[\"i32:2141192192\"]],[\"invoke\",629,0,\"f32.reinterpret_i32\",[\"i32:4288675840\"]],[\"invoke\",632,0,\"f64.reinterpret_i64\",[\"i64:1\"]],[\"invoke\",633,0,\"f64.reinterpret_i64\",[\"i64:18446744073709551615\"]],[\"invoke\",634,0,\"f64.reinterpret_i64\",[\"i64:9223372036854775808\"]],[\"invoke\",636,0,\"f64.reinterpret_i64\",[\"i64:9223372036854775809\"]],[\"invoke\",637,0,\"f64.reinterpret_i64\",[\"i64:9218868437227405312\"]],[\"invoke\",639,0,\"f64.reinterpret_i64\",[\"i64:9221120237041090560\"]],[\"invoke\",640,0,\"f64.reinterpret_i64\",[\"i64:18444492273895866368\"]],[\"invoke\",641,0,\"f64.reinterpret_i64\",[\"i64:9219994337134247936\"]],[\"invoke\",644,0,\"i32.reinterpret_f32\",[\"f32:0\"]],[\"invoke\",645,0,\"i32.reinterpret_f32\",[\"f32:2147483648\"]],[\"invoke\",646,0,\"i32.reinterpret_f32\",[\"f32:1\"]],[\"invoke\",649,0,\"i32.reinterpret_f32\",[\"f32:1065353216\"]],[\"invoke\",650,0,\"i32.reinterpret_f32\",[\"f32:1078530010\"]],[\"invoke\",652,0,\"i32.reinterpret_f32\",[\"f32:4286578687\"]],[\"invoke\",653,0,\"i32.reinterpret_f32\",[\"f32:2139095040\"]],[\"invoke\",654,0,\"i32.reinterpret_f32\",[\"f32:4286578688\"]],[\"invoke\",656,0,\"i32.reinterpret_f32\",[\"f32:4290772992\"]],[\"invoke\",660,0,\"i64.reinterpret_f64\",[\"f64:0\"]],[\"invoke\",661,0,\"i64.reinterpret_f64\",[\"f64:9223372036854775808\"]],[\"invoke\",664,0,\"i64.reinterpret_f64\",[\"f64:9223372036854775809\"]],[\"invoke\",665,0,\"i64.reinterpret_f64\",[\"f64:4607182418800017408\"]],[\"invoke\",667,0,\"i64.reinterpret_f64\",[\"f64:9218868437227405311\"]],[\"invoke\",668,0,\"i64.reinterpret_f64\",[\"f64:18442240474082181119\"]],[\"invoke\",669,0,\"i64.reinterpret_f64\",[\"f64:9218868437227405312\"]],[\"invoke\",671,0,\"i64.reinterpret_f64\",[\"f64:9221120237041090560\"]],[\"invoke\",672,0,\"i64.reinterpret_f64\",[\"f64:18444492273895866368\"]],[\"file\",\"traps.wast\"],[\"module\",5,23],[\"invoke\",16,0,\"no_dce.i32.div_s\",[\"i32:1\",\"i32:0\"]],[\"invoke\",17,0,\"no_dce.i32.div_u\",[\"i32:1\",\"i32:0\"]],[\"invoke\",18,0,\"no_dce.i64.div_s\",[\"i64:1\",\"i64:0\"]],[\"invoke\",19,0,\"no_dce.i64.div_u\",[\"i64:1\",\"i64:0\"]],[\"invoke\",20,0,\"no_dce.i32.div_s\",[\"i32:2147483648\",\"i32:4294967295\"]],[\"invoke\",21,0,\"no_dce.i64.div_s\",[\"i64:9223372036854775808\",\"i64:18446744073709551615\"]],[\"module\",23,24],[\"invoke\",34,0,\"no_dce.i32.rem_s\",[\"i32:1\",\"i32:0\"]],[\"invoke\",35,0,\"no_dce.i32.rem_u\",[\"i32:1\",\"i32:0\"]],[\"invoke\",36,0,\"no_dce.i64.rem_s\",[\"i64:1\",\"i64:0\"]],[\"invoke\",37,0,\"no_dce.i64.rem_u\",[\"i64:1\",\"i64:0\"]],[\"module\",39,25],[\"invoke\",50,0,\"no_dce.i32.trunc_f32_s\",[\"f32:2143289344\"]],[\"invoke\",51,0,\"no_dce.i32.trunc_f32_u\",[\"f32:2143289344\"]],[\"invoke\",52,0,\"no_dce.i32.trunc_f64_s\",[\"f64:9221120237041090560\"]],[\"invoke\",53,0,\"no_dce.i32.trunc_f64_u\",[\"f64:9221120237041090560\"]],[\"invoke\",54,0,\"no_dce.i64.trunc_f32_s\",[\"f32:2143289344\"]],[\"invoke\",55,0,\"no_dce.i64.trunc_f32_u\",[\"f32:2143289344\"]],[\"invoke\",56,0,\"no_dce.i64.trunc_f64_s\",[\"f64:9221120237041090560\"]],[\"invoke\",57,0,\"no_dce.i64.trunc_f64_u\",[\"f64:9221120237041090560\"]],[\"module\",59,26],[\"invoke\",78,0,\"no_dce.i32.load\",[\"i32:65536\"]],[\"invoke\",79,0,\"no_dce.i32.load16_s\",[\"i32:65536\"]],[\"invoke\",80,0,\"no_dce.i32.load16_u\",[\"i32:65536\"]],[\"invoke\",81,0,\"no_dce.i32.load8_s\",[\"i32:65536\"]],[\"invoke\",82,0,\"no_dce.i32.load8_u\",[\"i32:65536\"]],[\"invoke\",83,0,\"no_dce.i64.load\",[\"i32:65536\"]],[\"invoke\",84,0,\"no_dce.i64.load32_s\",[\"i32:65536\"]],[\"invoke\",85,0,\"no_dce.i64.load32_u\",[\"i32:65536\"]],[\"invoke\",86,0,\"no_dce.i64.load16_s\",[\"i32:65536\"]],[\"invoke\",87,0,\"no_dce.i64.load16_u\",[\"i32:65536\"]],[\"invoke\",88,0,\"no_dce.i64.load8_s\",[\"i32:65536\"]],[\"invoke\",89,0,\"no_dce.i64.load8_u\",[\"i32:65536\"]],[\"invoke\",90,0,\"no_dce.f32.load\",[\"i32:65536\"]],[\"invoke\",91,0,\"no_dce.f64.load\",[\"i32:65536\"]]]"; diff --git a/tests/corpus/webassembly-spec-int/main.mjs b/tests/corpus/webassembly-spec-int/main.mjs new file mode 100644 index 000000000..3f5bef066 --- /dev/null +++ b/tests/corpus/webassembly-spec-int/main.mjs @@ -0,0 +1,5 @@ +// Executes WebAssembly spec tests (6 files); see tests/wasm-spec/runner.mjs. +import { run } from "../../wasm-spec/runner.mjs"; +import { modules, script } from "./data.mjs"; + +run(modules, script); diff --git a/tests/corpus/webassembly-spec-memory/data.mjs b/tests/corpus/webassembly-spec-memory/data.mjs new file mode 100644 index 000000000..9b2ec91d7 --- /dev/null +++ b/tests/corpus/webassembly-spec-memory/data.mjs @@ -0,0 +1,161 @@ +/* Generated by tests/wasm-spec/gen.mjs from the WebAssembly 2.0 core test + * suite (WebAssembly/spec wg-2.0, Apache-2.0) with wabt 1.0.37 wast2json: + * address.wast, align.wast, endianness.wast, float_memory.wast, load.wast, store.wast, memory.wast, memory_grow.wast, memory_redundancy.wast, memory_size.wast, memory_trap.wast, bulk.wast, memory_copy.wast, memory_fill.wast, memory_init.wast, data.wast. Do not edit. */ +export const modules = [ + "AGFzbQEAAAABCgJgAX8Bf2ABfwADHx4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEFAwEAAQfNAh4IOHVfZ29vZDEAAAg4dV9nb29kMgABCDh1X2dvb2QzAAIIOHVfZ29vZDQAAwg4dV9nb29kNQAECDhzX2dvb2QxAAUIOHNfZ29vZDIABgg4c19nb29kMwAHCDhzX2dvb2Q0AAgIOHNfZ29vZDUACQkxNnVfZ29vZDEACgkxNnVfZ29vZDIACwkxNnVfZ29vZDMADAkxNnVfZ29vZDQADQkxNnVfZ29vZDUADgkxNnNfZ29vZDEADwkxNnNfZ29vZDIAEAkxNnNfZ29vZDMAEQkxNnNfZ29vZDQAEgkxNnNfZ29vZDUAEwgzMl9nb29kMQAUCDMyX2dvb2QyABUIMzJfZ29vZDMAFggzMl9nb29kNAAXCDMyX2dvb2Q1ABgGOHVfYmFkABkGOHNfYmFkABoHMTZ1X2JhZAAbBzE2c19iYWQAHAYzMl9iYWQAHQqKAh4HACAALQAACwcAIAAtAAALBwAgAC0AAQsHACAALQACCwcAIAAtABkLBwAgACwAAAsHACAALAAACwcAIAAsAAELBwAgACwAAgsHACAALAAZCwcAIAAvAQALBwAgAC8AAAsHACAALwABCwcAIAAvAQILBwAgAC8BGQsHACAALgEACwcAIAAuAAALBwAgAC4AAQsHACAALgECCwcAIAAuARkLBwAgACgCAAsHACAAKAAACwcAIAAoAAELBwAgACgBAgsHACAAKAIZCwwAIAAtAP////8PGgsMACAALAD/////DxoLDAAgAC8B/////w8aCwwAIAAuAf////8PGgsMACAAKAL/////DxoLCyABAEEACxphYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5eg==", + "AGFzbQEAAAABCgJgAX8BfmABfwADKyoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEFAwEAAQfZAyoIOHVfZ29vZDEAAAg4dV9nb29kMgABCDh1X2dvb2QzAAIIOHVfZ29vZDQAAwg4dV9nb29kNQAECDhzX2dvb2QxAAUIOHNfZ29vZDIABgg4c19nb29kMwAHCDhzX2dvb2Q0AAgIOHNfZ29vZDUACQkxNnVfZ29vZDEACgkxNnVfZ29vZDIACwkxNnVfZ29vZDMADAkxNnVfZ29vZDQADQkxNnVfZ29vZDUADgkxNnNfZ29vZDEADwkxNnNfZ29vZDIAEAkxNnNfZ29vZDMAEQkxNnNfZ29vZDQAEgkxNnNfZ29vZDUAEwkzMnVfZ29vZDEAFAkzMnVfZ29vZDIAFQkzMnVfZ29vZDMAFgkzMnVfZ29vZDQAFwkzMnVfZ29vZDUAGAkzMnNfZ29vZDEAGQkzMnNfZ29vZDIAGgkzMnNfZ29vZDMAGwkzMnNfZ29vZDQAHAkzMnNfZ29vZDUAHQg2NF9nb29kMQAeCDY0X2dvb2QyAB8INjRfZ29vZDMAIAg2NF9nb29kNAAhCDY0X2dvb2Q1ACIGOHVfYmFkACMGOHNfYmFkACQHMTZ1X2JhZAAlBzE2c19iYWQAJgczMnVfYmFkACcHMzJzX2JhZAAoBjY0X2JhZAApCvQCKgcAIAAxAAALBwAgADEAAAsHACAAMQABCwcAIAAxAAILBwAgADEAGQsHACAAMAAACwcAIAAwAAALBwAgADAAAQsHACAAMAACCwcAIAAwABkLBwAgADMBAAsHACAAMwAACwcAIAAzAAELBwAgADMBAgsHACAAMwEZCwcAIAAyAQALBwAgADIAAAsHACAAMgABCwcAIAAyAQILBwAgADIBGQsHACAANQIACwcAIAA1AAALBwAgADUAAQsHACAANQECCwcAIAA1AhkLBwAgADQCAAsHACAANAAACwcAIAA0AAELBwAgADQBAgsHACAANAIZCwcAIAApAwALBwAgACkAAAsHACAAKQABCwcAIAApAQILBwAgACkDGQsMACAAMQD/////DxoLDAAgADAA/////w8aCwwAIAAzAf////8PGgsMACAAMgH/////DxoLDAAgADUC/////w8aCwwAIAA0Av////8PGgsMACAAKQP/////DxoLCyABAEEACxphYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5eg==", + "AGFzbQEAAAABCgJgAX8BfWABfwADBwYAAAAAAAEFAwEAAQdBBggzMl9nb29kMQAACDMyX2dvb2QyAAEIMzJfZ29vZDMAAggzMl9nb29kNAADCDMyX2dvb2Q1AAQGMzJfYmFkAAUKNgYHACAAKgIACwcAIAAqAAALBwAgACoAAQsHACAAKgECCwcAIAAqAggLDAAgACoC/////w8aCwsSAQBBAAsMAAAAAAAAoH8BANB/", + "AGFzbQEAAAABCgJgAX8BfGABfwADBwYAAAAAAAEFAwEAAQdBBgg2NF9nb29kMQAACDY0X2dvb2QyAAEINjRfZ29vZDMAAgg2NF9nb29kNAADCDY0X2dvb2Q1AAQGNjRfYmFkAAUKNgYHACAAKwMACwcAIAArAAALBwAgACsAAQsHACAAKwECCwcAIAArAxILDAAgACsD/////w8aCwsgAQBBAAsaAAAAAAAAAAAAAAAAAAAAAPR/AQAAAAAA/H8=", + "AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCgEIAEEALAAAGgs=", + "AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCgEIAEEALQAAGgs=", + "AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCgEIAEEALgEAGgs=", + "AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCgEIAEEALwEAGgs=", + "AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCgEIAEEAKAIAGgs=", + "AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCgEIAEEAMAAAGgs=", + "AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCgEIAEEAMQAAGgs=", + "AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCgEIAEEAMgEAGgs=", + "AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCgEIAEEAMwEAGgs=", + "AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCgEIAEEANAIAGgs=", + "AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCgEIAEEANQIAGgs=", + "AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCgEIAEEAKQMAGgs=", + "AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCgEIAEEAKgIAGgs=", + "AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCgEIAEEAKwMAGgs=", + "AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCwEJAEEAQQE6AAAL", + "AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCwEJAEEAQQE7AQAL", + "AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCwEJAEEAQQE2AgAL", + "AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCwEJAEEAQgE8AAAL", + "AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCwEJAEEAQgE9AQAL", + "AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCwEJAEEAQgE+AgAL", + "AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCwEJAEEAQgE3AwAL", + "AGFzbQEAAAABBAFgAAADAgEABQMBAAAKDgEMAEEAQwAAgD84AgAL", + "AGFzbQEAAAABBAFgAAADAgEABQMBAAAKEgEQAEEARAAAAAAAAPA/OQMACw==", + "AGFzbQEAAAABFwRgAX8BfWABfwF8YAJ/fwF/YAJ/fwF+AwUEAAECAwUDAQABB00EEGYzMl9hbGlnbl9zd2l0Y2gAABBmNjRfYWxpZ25fc3dpdGNoAAEQaTMyX2FsaWduX3N3aXRjaAACEGk2NF9hbGlnbl9zd2l0Y2gAAwr7CARjAQJ9QwAAIEEhAQJAAkACQAJAAkAgAA4EAAECAwQLQQAgATgCAEEAKgIAIQIMAwtBACABOAAAQQAqAAAhAgwCC0EAIAE4AQBBACoBACECDAELQQAgATgCAEEAKgIAIQILIAILewECfEQAAAAAAAAkQCEBAkACQAJAAkACQAJAIAAOBQABAgMEBQtBACABOQMAQQArAwAhAgwEC0EAIAE5AABBACsAACECDAMLQQAgATkBAEEAKwEAIQIMAgtBACABOQIAQQArAgAhAgwBC0EAIAE5AwBBACsDACECCyACC+ICAQJ/QQohAgJAAkACQAJAAkACQCAADgUAAQIDBAULIAFBAEYEQEEAIAI6AABBACwAACEDCyABQQFGBEBBACACOgAAQQAsAAAhAwsMBAsgAUEARgRAQQAgAjoAAEEALQAAIQMLIAFBAUYEQEEAIAI6AABBAC0AACEDCwwDCyABQQBGBEBBACACOwEAQQAuAQAhAwsgAUEBRgRAQQAgAjsAAEEALgAAIQMLIAFBAkYEQEEAIAI7AQBBAC4BACEDCwwCCyABQQBGBEBBACACOwEAQQAvAQAhAwsgAUEBRgRAQQAgAjsAAEEALwAAIQMLIAFBAkYEQEEAIAI7AQBBAC8BACEDCwwBCyABQQBGBEBBACACNgIAQQAoAgAhAwsgAUEBRgRAQQAgAjYAAEEAKAAAIQMLIAFBAkYEQEEAIAI2AQBBACgBACEDCyABQQRGBEBBACACNgIAQQAoAgAhAwsLIAMLtAQBAn5CCiECAkACQAJAAkACQAJAAkACQCAADgcAAQIDBAUGBwsgAUEARgRAQQAgAjwAAEEAMAAAIQMLIAFBAUYEQEEAIAI8AABBADAAACEDCwwGCyABQQBGBEBBACACPAAAQQAxAAAhAwsgAUEBRgRAQQAgAjwAAEEAMQAAIQMLDAULIAFBAEYEQEEAIAI9AQBBADIBACEDCyABQQFGBEBBACACPQAAQQAyAAAhAwsgAUECRgRAQQAgAj0BAEEAMgEAIQMLDAQLIAFBAEYEQEEAIAI9AQBBADMBACEDCyABQQFGBEBBACACPQAAQQAzAAAhAwsgAUECRgRAQQAgAj0BAEEAMwEAIQMLDAMLIAFBAEYEQEEAIAI+AgBBADQCACEDCyABQQFGBEBBACACPgAAQQA0AAAhAwsgAUECRgRAQQAgAj4BAEEANAEAIQMLIAFBBEYEQEEAIAI+AgBBADQCACEDCwwCCyABQQBGBEBBACACPgIAQQA1AgAhAwsgAUEBRgRAQQAgAj4AAEEANQAAIQMLIAFBAkYEQEEAIAI+AQBBADUBACEDCyABQQRGBEBBACACPgIAQQA1AgAhAwsMAQsgAUEARgRAQQAgAjcDAEEAKQMAIQMLIAFBAUYEQEEAIAI3AABBACkAACEDCyABQQJGBEBBACACNwEAQQApAQAhAwsgAUEERgRAQQAgAjcCAEEAKQIAIQMLIAFBCEYEQEEAIAI3AwBBACkDACEDCwsgAws=", + "AGFzbQEAAAABCwJgAn9+AGABfwF/AwMCAAEFAwEAAQcQAgVzdG9yZQAABGxvYWQAAQoTAgkAIAAgATcCAAsHACAAKAIACw==", + "AGFzbQEAAAABJAdgAn9/AGACf34AYAF/AX9gAX8BfmABfgF+YAF9AX1gAXwBfAMYFwAAAQICAwICAgQEBAQEBQYCAgQEBAUGBQMBAAEH4QERDGkzMl9sb2FkMTZfcwAGDGkzMl9sb2FkMTZfdQAHCGkzMl9sb2FkAAgMaTY0X2xvYWQxNl9zAAkMaTY0X2xvYWQxNl91AAoMaTY0X2xvYWQzMl9zAAsMaTY0X2xvYWQzMl91AAwIaTY0X2xvYWQADQhmMzJfbG9hZAAOCGY2NF9sb2FkAA8LaTMyX3N0b3JlMTYAEAlpMzJfc3RvcmUAEQtpNjRfc3RvcmUxNgASC2k2NF9zdG9yZTMyABMJaTY0X3N0b3JlABQJZjMyX3N0b3JlABUJZjY0X3N0b3JlABYK9gIXFgAgACABOgAAIABBAWogAUEIdjoAAAsUACAAIAEQACAAQQJqIAFBEHYQAAsWACAAIAGnEAEgAEEEaiABQiCIpxABCxMAIAAtAAAgAEEBai0AAEEIdHILEQAgABADIABBAmoQA0EQdHILEwAgABAErSAAQQRqEAStQiCGhAsNAEEAIAAQAEEALgEACw0AQQAgABAAQQAvAQALDQBBACAAEAFBACgCAAsOAEEAIACnEABBADIBAAsOAEEAIACnEABBADMBAAsOAEEAIACnEAFBADQCAAsOAEEAIACnEAFBADUCAAsNAEEAIAAQAkEAKQMACw4AQQAgALwQAUEAKgIACw4AQQAgAL0QAkEAKwMACw0AQQAgADsBAEEAEAMLDQBBACAANgIAQQAQBAsOAEEAIAA9AQBBABADrQsOAEEAIAA+AgBBABAErQsNAEEAIAA3AwBBABAFCw4AQQAgADgCAEEAEAS+Cw4AQQAgADkDAEEAEAW/Cw==", + "AGFzbQEAAAABDANgAAF9YAABf2AAAAMGBQABAgICBQQBAQEBBzcFCGYzMi5sb2FkAAAIaTMyLmxvYWQAAQlmMzIuc3RvcmUAAglpMzIuc3RvcmUAAwVyZXNldAAECjYFBwBBACoCAAsHAEEAKAIACwwAQQBDAACgfzgCAAsNAEEAQYCAgP0HNgIACwkAQQBBADYCAAsLCgEAQQALBAAAoH8=", + "AGFzbQEAAAABDANgAAF8YAABfmAAAAMGBQABAgICBQQBAQEBBzcFCGY2NC5sb2FkAAAIaTY0LmxvYWQAAQlmNjQuc3RvcmUAAglpNjQuc3RvcmUAAwVyZXNldAAECj8FBwBBACsDAAsHAEEAKQMACxAAQQBEAAAAAAAA9H85AwALEgBBAEKAgICAgICA+v8ANwMACwkAQQBCADcDAAsLDgEAQQALCAAAAAAAAPR/", + "AGFzbQEAAAABDANgAAF9YAABf2AAAAMGBQABAgICBQQBAQEBBzcFCGYzMi5sb2FkAAAIaTMyLmxvYWQAAQlmMzIuc3RvcmUAAglpMzIuc3RvcmUAAwVyZXNldAAECjYFBwBBASoCAAsHAEEBKAIACwwAQQFDAACgfzgCAAsNAEEBQYCAgP0HNgIACwkAQQFBADYCAAsLCwEAQQALBQAAAKB/", + "AGFzbQEAAAABDANgAAF8YAABfmAAAAMGBQABAgICBQQBAQEBBzcFCGY2NC5sb2FkAAAIaTY0LmxvYWQAAQlmNjQuc3RvcmUAAglpNjQuc3RvcmUAAwVyZXNldAAECj8FBwBBASsDAAsHAEEBKQMACxAAQQFEAAAAAAAA9H85AwALEgBBAUKAgICAgICA+v8ANwMACwkAQQFCADcDAAsLDwEAQQALCQAAAAAAAAD0fw==", + "AGFzbQEAAAABDANgAAF9YAABf2AAAAMGBQABAgICBQQBAQEBBzcFCGYzMi5sb2FkAAAIaTMyLmxvYWQAAQlmMzIuc3RvcmUAAglpMzIuc3RvcmUAAwVyZXNldAAECjYFBwBBACoCAAsHAEEAKAIACwwAQQBDAQDQfzgCAAsNAEEAQYGAwP4HNgIACwkAQQBBADYCAAsLCgEAQQALBAEA0H8=", + "AGFzbQEAAAABDANgAAF8YAABfmAAAAMGBQABAgICBQQBAQEBBzcFCGY2NC5sb2FkAAAIaTY0LmxvYWQAAQlmNjQuc3RvcmUAAglpNjQuc3RvcmUAAwVyZXNldAAECj8FBwBBACsDAAsHAEEAKQMACxAAQQBEAQAAAAAA/H85AwALEgBBAEKBgICAgICA/v8ANwMACwkAQQBCADcDAAsLDgEAQQALCAEAAAAAAPx/", + "AGFzbQEAAAABFQRgA39/fwF/YAABf2AAAGACf38BfwMnJgECAQECAQEBAQEBAwMBAAEBAQEBAQECAQIBAQICAgIBAQEBAQEBBAUBcAEBAQUDAQABBgYBfwFBAAsHtwUlC2FzLWJyLXZhbHVlAAANYXMtYnJfaWYtY29uZAABDmFzLWJyX2lmLXZhbHVlAAITYXMtYnJfaWYtdmFsdWUtY29uZAADEWFzLWJyX3RhYmxlLWluZGV4AAQRYXMtYnJfdGFibGUtdmFsdWUABRdhcy1icl90YWJsZS12YWx1ZS1pbmRleAAGD2FzLXJldHVybi12YWx1ZQAHCmFzLWlmLWNvbmQACAphcy1pZi10aGVuAAkKYXMtaWYtZWxzZQAKD2FzLXNlbGVjdC1maXJzdAALEGFzLXNlbGVjdC1zZWNvbmQADA5hcy1zZWxlY3QtY29uZAANDWFzLWNhbGwtZmlyc3QADwthcy1jYWxsLW1pZAAQDGFzLWNhbGwtbGFzdAARFmFzLWNhbGxfaW5kaXJlY3QtZmlyc3QAEhRhcy1jYWxsX2luZGlyZWN0LW1pZAATFWFzLWNhbGxfaW5kaXJlY3QtbGFzdAAUFmFzLWNhbGxfaW5kaXJlY3QtaW5kZXgAFRJhcy1sb2NhbC5zZXQtdmFsdWUAFhJhcy1sb2NhbC50ZWUtdmFsdWUAFxNhcy1nbG9iYWwuc2V0LXZhbHVlABgPYXMtbG9hZC1hZGRyZXNzABkQYXMtbG9hZE4tYWRkcmVzcwAaEGFzLXN0b3JlLWFkZHJlc3MAGw5hcy1zdG9yZS12YWx1ZQAcEWFzLXN0b3JlTi1hZGRyZXNzAB0PYXMtc3RvcmVOLXZhbHVlAB4QYXMtdW5hcnktb3BlcmFuZAAfDmFzLWJpbmFyeS1sZWZ0ACAPYXMtYmluYXJ5LXJpZ2h0ACEPYXMtdGVzdC1vcGVyYW5kACIPYXMtY29tcGFyZS1sZWZ0ACMQYXMtY29tcGFyZS1yaWdodAAkE2FzLW1lbW9yeS5ncm93LXNpemUAJQkHAQBBAAsBDgqGBCYMAAJ/QQAoAgAMAAsLDAACQEEAKAIADQALCxEAAn9BACgCAEEBDQAaQQcLCxEAAn9BBkEAKAIADQAaQQcLCw8AAkBBACgCAA4CAAAACwsTAAJ/QQAoAgBBAQ4CAAAAQQcLCxIAAn9BBkEAKAIADgEAAEEHCwsIAEEAKAIADwsPAEEAKAIABH9BAAVBAQsLDwBBAQR/QQAoAgAFQQALCw8AQQAEf0EABUEAKAIACwsMAEEAKAIAIAAgARsLDAAgAEEAKAIAIAEbCwwAQQBBAUEAKAIAGwsEAEF/Cw0AQQAoAgBBAkEDEA4LDQBBAUEAKAIAQQMQDgsNAEEBQQJBACgCABAOCxAAQQAoAgBBAkEDQQARAAALEABBAUEAKAIAQQNBABEAAAsQAEEBQQJBACgCAEEAEQAACxAAQQFBAkEDQQAoAgARAAALCwEBf0EAKAIAIQALCwEBf0EAKAIAIgALCwEBf0EAKAIAJAALCgBBACgCACgCAAsKAEEAKAIALAAACwwAQQAoAgBBBzYCAAsMAEECQQAoAgA2AgALDABBACwAAEEHOgAACwwAQQJBACgCADsBAAsJAEHkACgCAGcLCwBB5AAoAgBBCmoLCwBBCkHkACgCAGsLCQBB5AAoAgBFCwsAQeQAKAIAQQpMCwsAQQpB5AAoAgBHCwoAQeQAKAIAQAAL", + "AGFzbQEAAAABBAFgAAADCgkAAAAAAAAAAAAFAwEAAQeXAQkOYXMtYmxvY2stdmFsdWUAAA1hcy1sb29wLXZhbHVlAAELYXMtYnItdmFsdWUAAg5hcy1icl9pZi12YWx1ZQADE2FzLWJyX2lmLXZhbHVlLWNvbmQABBFhcy1icl90YWJsZS12YWx1ZQAFD2FzLXJldHVybi12YWx1ZQAGCmFzLWlmLXRoZW4ABwphcy1pZi1lbHNlAAgKiAEJDAACQEEAQQE2AgALCwwAA0BBAEEBNgIACwsOAAJAQQBBATYCAAwACwsQAAJAQQBBATYCAEEBDQALCxAAAkBBBkEAQQE2AgANAAsLEQACQEEAQQE2AgBBAQ4AAAsLCgBBAEEBNgIADwsOAEEBBEBBAEEBNgIACwsPAEEABEAFQQBBATYCAAsL", + "AGFzbQEAAAAFAwEAAA==", + "AGFzbQEAAAAFAwEAAQ==", + "AGFzbQEAAAAFBAEBAAA=", + "AGFzbQEAAAAFBAEBAAE=", + "AGFzbQEAAAAFBQEBAYAC", + "AGFzbQEAAAAFBgEBAICABA==", + "AGFzbQEAAAABBQFgAAF/AwIBAAUEAQEAAAcLAQdtZW1zaXplAAAKBgEEAD8ACwsGAQBBAAsA", + "AGFzbQEAAAABBQFgAAF/AwIBAAUEAQEBAQcLAQdtZW1zaXplAAAKBgEEAD8ACwsHAQBBAAsBeA==", + "AGFzbQEAAAABEwRgAAF/YAABfGABfwF/YAF+AX4DDQwAAQICAgIDAwMDAwMFAwEAAQehAQwEZGF0YQAABGNhc3QAAQtpMzJfbG9hZDhfcwACC2kzMl9sb2FkOF91AAMMaTMyX2xvYWQxNl9zAAQMaTMyX2xvYWQxNl91AAULaTY0X2xvYWQ4X3MABgtpNjRfbG9hZDhfdQAHDGk2NF9sb2FkMTZfcwAIDGk2NF9sb2FkMTZfdQAJDGk2NF9sb2FkMzJfcwAKDGk2NF9sb2FkMzJfdQALCp8CDE4AQQAtAABBwQBGQQMtAABBpwFGcUEGLQAAQQBGQRMtAABBAEZxcUEULQAAQdcARkEXLQAAQc0ARnFBGC0AAEEARkH/By0AAEEARnFxcQs4AEEIQseffzcDAEEIKwMAQseff79hBEBEAAAAAAAAAAAPC0EJQgA3AABBD0HFgAE7AABBCSsAAAsOAEEIIAA6AABBCCwAAAsOAEEIIAA6AABBCC0AAAsOAEEIIAA7AQBBCC4BAAsOAEEIIAA7AQBBCC8BAAsOAEEIIAA8AABBCDAAAAsOAEEIIAA8AABBCDEAAAsOAEEIIAA9AQBBCDIBAAsOAEEIIAA9AQBBCDMBAAsOAEEIIAA+AgBBCDQCAAsOAEEIIAA+AgBBCDUCAAsLFAIAQQALBUFCQ6dEAEEUCwRXQVNN", + "AGFzbQEAAAABBgFgAX8BfwMCAQAFBAEBAQEGFgN/AEGQzgALfwBBkM4AC38AQZDOAAsHOgUGbWVtb3J5AgAKX19kYXRhX2VuZAMAC19fc3RhY2tfdG9wAwELX19oZWFwX2Jhc2UDAgRsb2FkAAAKCQEHACAALQAACw==", + "AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8DBwYAAQABAgAFAwEAAAdXBgxsb2FkX2F0X3plcm8AAA1zdG9yZV9hdF96ZXJvAAERbG9hZF9hdF9wYWdlX3NpemUAAhJzdG9yZV9hdF9wYWdlX3NpemUAAwRncm93AAQEc2l6ZQAFCjUGBwBBACgCAAsJAEEAQQI2AgALCQBBgIAEKAIACwsAQYCABEEDNgIACwYAIABAAAsEAD8ACw==", + "AGFzbQEAAAABBgFgAX8BfwMCAQAFAwEAAAcIAQRncm93AAAKCAEGACAAQAAL", + "AGFzbQEAAAABBgFgAX8BfwMCAQAFBAEBAAoHCAEEZ3JvdwAACggBBgAgAEAACw==", + "AGFzbQEAAAABDAJgAX8Bf2ACf38BfwMDAgABBQMBAAEHHAIEZ3JvdwAAEWNoZWNrLW1lbW9yeS16ZXJvAAEKPAIGACAAQAALMwEBf0EBIQICQANAIAAtAAAhAiACQQBHDQEgACABTw0BIABBAWohACAAIAFNDQALCyACCw==", + "AGFzbQEAAAABFQRgA39/fwF/YAABf2AAAGACf38BfwMnJgECAQECAQEBAQEBAwMBAAEBAQEBAQECAQIBAQICAgIBAQEBAQEBBAUBcAEBAQUDAQABBgYBfwFBAAsHtwUlC2FzLWJyLXZhbHVlAAANYXMtYnJfaWYtY29uZAABDmFzLWJyX2lmLXZhbHVlAAITYXMtYnJfaWYtdmFsdWUtY29uZAADEWFzLWJyX3RhYmxlLWluZGV4AAQRYXMtYnJfdGFibGUtdmFsdWUABRdhcy1icl90YWJsZS12YWx1ZS1pbmRleAAGD2FzLXJldHVybi12YWx1ZQAHCmFzLWlmLWNvbmQACAphcy1pZi10aGVuAAkKYXMtaWYtZWxzZQAKD2FzLXNlbGVjdC1maXJzdAALEGFzLXNlbGVjdC1zZWNvbmQADA5hcy1zZWxlY3QtY29uZAANDWFzLWNhbGwtZmlyc3QADwthcy1jYWxsLW1pZAAQDGFzLWNhbGwtbGFzdAARFmFzLWNhbGxfaW5kaXJlY3QtZmlyc3QAEhRhcy1jYWxsX2luZGlyZWN0LW1pZAATFWFzLWNhbGxfaW5kaXJlY3QtbGFzdAAUFmFzLWNhbGxfaW5kaXJlY3QtaW5kZXgAFRJhcy1sb2NhbC5zZXQtdmFsdWUAFhJhcy1sb2NhbC50ZWUtdmFsdWUAFxNhcy1nbG9iYWwuc2V0LXZhbHVlABgPYXMtbG9hZC1hZGRyZXNzABkQYXMtbG9hZE4tYWRkcmVzcwAaEGFzLXN0b3JlLWFkZHJlc3MAGw5hcy1zdG9yZS12YWx1ZQAcEWFzLXN0b3JlTi1hZGRyZXNzAB0PYXMtc3RvcmVOLXZhbHVlAB4QYXMtdW5hcnktb3BlcmFuZAAfDmFzLWJpbmFyeS1sZWZ0ACAPYXMtYmluYXJ5LXJpZ2h0ACEPYXMtdGVzdC1vcGVyYW5kACIPYXMtY29tcGFyZS1sZWZ0ACMQYXMtY29tcGFyZS1yaWdodAAkE2FzLW1lbW9yeS5ncm93LXNpemUAJQkHAQBBAAsBDgraAyYLAAJ/QQBAAAwACwsLAAJAQQBAAA0ACwsQAAJ/QQBAAEEBDQAaQQcLCxAAAn9BBkEAQAANABpBBwsLDgACQEEAQAAOAgAAAAsLEgACf0EAQABBAQ4CAAAAQQcLCxEAAn9BBkEAQAAOAQAAQQcLCwcAQQBAAA8LDgBBAEAABH9BAAVBAQsLDgBBAQR/QQBAAAVBAAsLDgBBAAR/QQAFQQBAAAsLCwBBAEAAIAAgARsLCwAgAEEAQAAgARsLCwBBAEEBQQBAABsLBABBfwsMAEEAQABBAkEDEA4LDABBAUEAQABBAxAOCwwAQQFBAkEAQAAQDgsPAEEAQABBAkEDQQARAAALDwBBAUEAQABBA0EAEQAACw8AQQFBAkEAQABBABEAAAsPAEEBQQJBA0EAQAARAAALCgEBf0EAQAAhAAsKAQF/QQBAACIACwoBAX9BAEAAJAALCQBBAEAAKAIACwkAQQBAACwAAAsLAEEAQABBBzYCAAsLAEECQQBAADYCAAsLAEEAQABBBzoAAAsLAEECQQBAADsBAAsHAEEAQABnCwkAQQBAAEEKagsJAEEKQQBAAGsLBwBBAEAARQsJAEEAQABBCkwLCQBBCkEAQABHCwgAQQBAAEAACw==", + "AGFzbQEAAAABBQFgAAF/AwIBAAUDAQABBxECBm1lbW9yeQIABGdyb3cAAAoIAQYAQQFAAAs=", + "AGFzbQEAAAABBQFgAAF/AhgBDGdyb3duLW1lbW9yeQZtZW1vcnkCAAIDAgEABxECBm1lbW9yeQIABGdyb3cAAAoIAQYAQQFAAAs=", + "AGFzbQEAAAABBQFgAAF/AiEBFWdyb3duLWltcG9ydGVkLW1lbW9yeQZtZW1vcnkCAAMDAgEABwgBBHNpemUAAAoGAQQAPwAL", + "AGFzbQEAAAABEQRgAABgAAF/YAABfWABfwF/AwcGAAEBAgMBBQQBAQEBB2sGD3plcm9fZXZlcnl0aGluZwAAEnRlc3Rfc3RvcmVfdG9fbG9hZAABE3Rlc3RfcmVkdW5kYW50X2xvYWQAAg90ZXN0X2RlYWRfc3RvcmUAAwZtYWxsb2MABA9tYWxsb2NfYWxpYXNpbmcABQqlAQYeAEEAQQA2AgBBBEEANgIAQQhBADYCAEEMQQA2AgALGABBCEEANgIAQQVDAAAAgDgCAEEIKAIACyIBAn9BCCgCACEAQQVBgICAgHg2AgBBCCgCACEBIAAgAWoLHwEBfUEIQaPGjJkCNgIAQQsqAgAhAEEIQQA2AgAgAAsEAEEQCyMBAn9BBBAEIQBBBBAEIQEgAEEqNgIAIAFBKzYCACAAKAIACw==", + "AGFzbQEAAAABCQJgAAF/YAF/AAMDAgABBQMBAAAHDwIEc2l6ZQAABGdyb3cAAQoOAgQAPwALBwAgAEAAGgs=", + "AGFzbQEAAAABCQJgAAF/YAF/AAMDAgABBQMBAAEHDwIEc2l6ZQAABGdyb3cAAQoOAgQAPwALBwAgAEAAGgs=", + "AGFzbQEAAAABCQJgAAF/YAF/AAMDAgABBQQBAQACBw8CBHNpemUAAARncm93AAEKDgIEAD8ACwcAIABAABoL", + "AGFzbQEAAAABCQJgAAF/YAF/AAMDAgABBQQBAQMIBw8CBHNpemUAAARncm93AAEKDgIEAD8ACwcAIABAABoL", + "AGFzbQEAAAABDwNgAAF/YAJ/fwBgAX8BfwMFBAABAgIFAwEAAQceAwVzdG9yZQABBGxvYWQAAgttZW1vcnkuZ3JvdwADCioECQA/AEGAgARsCwwAEAAgAGogATYCAAsKABAAIABqKAIACwYAIABAAAs=", + "AGFzbQEAAAABKQhgAX8Bf2ABfwF+YAF/AX1gAX8BfGACf38AYAJ/fgBgAn99AGACf3wAAxgXAAECAwAAAAABAQEBAQEEBQYHBAQFBQUFAwEAAQezAhcIaTMyLmxvYWQAAAhpNjQubG9hZAABCGYzMi5sb2FkAAIIZjY0LmxvYWQAAwtpMzIubG9hZDhfcwAEC2kzMi5sb2FkOF91AAUMaTMyLmxvYWQxNl9zAAYMaTMyLmxvYWQxNl91AAcLaTY0LmxvYWQ4X3MACAtpNjQubG9hZDhfdQAJDGk2NC5sb2FkMTZfcwAKDGk2NC5sb2FkMTZfdQALDGk2NC5sb2FkMzJfcwAMDGk2NC5sb2FkMzJfdQANCWkzMi5zdG9yZQAOCWk2NC5zdG9yZQAPCWYzMi5zdG9yZQAQCWY2NC5zdG9yZQARCmkzMi5zdG9yZTgAEgtpMzIuc3RvcmUxNgATCmk2NC5zdG9yZTgAFAtpNjQuc3RvcmUxNgAVC2k2NC5zdG9yZTMyABYKywEXBwAgACgCAAsHACAAKQMACwcAIAAqAgALBwAgACsDAAsHACAALAAACwcAIAAtAAALBwAgAC4BAAsHACAALwEACwcAIAAwAAALBwAgADEAAAsHACAAMgEACwcAIAAzAQALBwAgADQCAAsHACAANQIACwkAIAAgATYCAAsJACAAIAE3AwALCQAgACABOAIACwkAIAAgATkDAAsJACAAIAE6AAALCQAgACABOwEACwkAIAAgATwAAAsJACAAIAE9AQALCQAgACABPgIACwsdAgBBAAsIYWJjZGVmZ2gAQfj/AwsIYWJjZGVmZ2g=", + "AGFzbQEAAAAFAwEAAQsGAQEDZm9v", + "AGFzbQEAAAABBAFgAAADAwIAAAQEAXAAAwkNAQVwA9IAC9BwC9IBCwoHAgIACwIACw==", + "AGFzbQEAAAABDAJgA39/fwBgAX8BfwMDAgABBQMBAAEHEgIEZmlsbAAAB2xvYWQ4X3UAAQoVAgsAIAAgASAC/AsACwcAIAAtAAAL", + "AGFzbQEAAAABDAJgA39/fwBgAX8BfwMDAgABBQQBAQEBBxICBGNvcHkAAAdsb2FkOF91AAEKFgIMACAAIAEgAvwKAAALBwAgAC0AAAsLCgEAQQALBKq7zN0=", + "AGFzbQEAAAABDAJgA39/fwBgAX8BfwMDAgABBQMBAAEHEgIEaW5pdAAAB2xvYWQ4X3UAAQwBAQoWAgwAIAAgASAC/AgAAAsHACAALQAACwsHAQEEqrvM3Q==", + "AGFzbQEAAAABCAJgAABgAX8AAwUEAAEAAQUDAQABBzsEDGRyb3BfcGFzc2l2ZQAADGluaXRfcGFzc2l2ZQABC2Ryb3BfYWN0aXZlAAILaW5pdF9hY3RpdmUAAwwBAgonBAUA/AkACwwAQQBBACAA/AgAAAsFAPwJAQsMAEEAQQAgAPwIAQALCwoCAQF4AEEACwF4", + "AGFzbQEAAAABBAFgAAADAgEADAFBCgcBBQD8CUALC4MBQQEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQA=", + "AGFzbQEAAAABBAFgAAADAgEADAEBCgcBBQD8CQALCwoBAQdnb29kYnll", + "AGFzbQEAAAABEANgAAF/YAN/f38AYAF/AX8DBQQAAAECBAQBcAADBw8CBGluaXQAAgRjYWxsAAMJCAEBAAQAAQABCiAEBABBAAsEAEEBCwwAIAAgASAC/AwAAAsHACAAEQAACw==", + "AGFzbQEAAAABCAJgAABgAX8AAwYFAAABAAEEBAFwAAEHOwQMZHJvcF9wYXNzaXZlAAEMaW5pdF9wYXNzaXZlAAILZHJvcF9hY3RpdmUAAwtpbml0X2FjdGl2ZQAECQsCAQABAABBAAsBAAoqBQIACwUA/A0ACwwAQQBBACAA/AwAAAsFAPwNAQsMAEEAQQAgAPwMAQAL", + "AGFzbQEAAAABBAFgAAADAgEACcQBQQEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAoHAQUA/A1ACw==", + "AGFzbQEAAAABBAFgAAADAgEACQUBAQABAAoHAQUA/A0ACw==", + "AGFzbQEAAAABEANgAAF/YAN/f38AYAF/AX8DBgUAAAABAgQEAXAACgcPAgRjb3B5AAMEY2FsbAAECQkBAEEACwMAAQIKJQUEAEEACwQAQQELBABBAgsMACAAIAEgAvwOAAALBwAgABEAAAs=", + "AGFzbQEAAAABCQJgAABgAX8BfwMDAgABBQQBAQEBBxwDB21lbW9yeTACAAR0ZXN0AAAHbG9hZDhfdQABCg0CAwABCwcAIAAtAAALCxQCAEECCwQDAQQBAEEMCwUHBQIDBg==", + "AGFzbQEAAAABCQJgAABgAX8BfwMDAgABBQQBAQEBBxwDB21lbW9yeTACAAR0ZXN0AAAHbG9hZDhfdQABChYCDABBDUECQQP8CgAACwcAIAAtAAALCxQCAEECCwQDAQQBAEEMCwUHBQIDBg==", + "AGFzbQEAAAABCQJgAABgAX8BfwMDAgABBQQBAQEBBxwDB21lbW9yeTACAAR0ZXN0AAAHbG9hZDhfdQABChYCDABBGUEPQQL8CgAACwcAIAAtAAALCxQCAEECCwQDAQQBAEEMCwUHBQIDBg==", + "AGFzbQEAAAABCQJgAABgAX8BfwMDAgABBQQBAQEBBxwDB21lbW9yeTACAAR0ZXN0AAAHbG9hZDhfdQABChYCDABBDUEZQQP8CgAACwcAIAAtAAALCxQCAEECCwQDAQQBAEEMCwUHBQIDBg==", + "AGFzbQEAAAABCQJgAABgAX8BfwMDAgABBQQBAQEBBxwDB21lbW9yeTACAAR0ZXN0AAAHbG9hZDhfdQABChYCDABBFEEWQQT8CgAACwcAIAAtAAALCxQCAEECCwQDAQQBAEEMCwUHBQIDBg==", + "AGFzbQEAAAABCQJgAABgAX8BfwMDAgABBQQBAQEBBxwDB21lbW9yeTACAAR0ZXN0AAAHbG9hZDhfdQABChYCDABBGUEBQQP8CgAACwcAIAAtAAALCxQCAEECCwQDAQQBAEEMCwUHBQIDBg==", + "AGFzbQEAAAABCQJgAABgAX8BfwMDAgABBQQBAQEBBxwDB21lbW9yeTACAAR0ZXN0AAAHbG9hZDhfdQABChYCDABBCkEMQQf8CgAACwcAIAAtAAALCxQCAEECCwQDAQQBAEEMCwUHBQIDBg==", + "AGFzbQEAAAABCQJgAABgAX8BfwMDAgABBQQBAQEBBxwDB21lbW9yeTACAAR0ZXN0AAAHbG9hZDhfdQABChYCDABBDEEKQQf8CgAACwcAIAAtAAALCxQCAEECCwQDAQQBAEEMCwUHBQIDBg==", + "AGFzbQEAAAABDAJgA39/fwBgAX8BfwMDAgABBQQBAQEBBxcDA21lbQIAA3J1bgAAB2xvYWQ4X3UAAQoWAgwAIAAgASAC/AoAAAsHACAALQAACwsaAQBBAAsUAAECAwQFBgcICQoLDA0ODxAREhM=", + "AGFzbQEAAAABDAJgA39/fwBgAX8BfwMDAgABBQQBAQEBBxcDA21lbQIAA3J1bgAAB2xvYWQ4X3UAAQoWAgwAIAAgASAC/AoAAAsHACAALQAACwsbAQBBAAsVAAECAwQFBgcICQoLDA0ODxAREhMU", + "AGFzbQEAAAABDAJgA39/fwBgAX8BfwMDAgABBQQBAQEBBxcDA21lbQIAA3J1bgAAB2xvYWQ4X3UAAQoWAgwAIAAgASAC/AoAAAsHACAALQAACwscAQBB7P8DCxQAAQIDBAUGBwgJCgsMDQ4PEBESEw==", + "AGFzbQEAAAABDAJgA39/fwBgAX8BfwMDAgABBQQBAQEBBxcDA21lbQIAA3J1bgAAB2xvYWQ4X3UAAQoWAgwAIAAgASAC/AoAAAsHACAALQAACwsdAQBB6/8DCxUAAQIDBAUGBwgJCgsMDQ4PEBESExQ=", + "AGFzbQEAAAABDAJgA39/fwBgAX8BfwMDAgABBQQBAQEBBxcDA21lbQIAA3J1bgAAB2xvYWQ4X3UAAQoWAgwAIAAgASAC/AoAAAsHACAALQAACwscAQBBzv8DCxQAAQIDBAUGBwgJCgsMDQ4PEBESEw==", + "AGFzbQEAAAABDAJgA39/fwBgAX8BfwMDAgABBQQBAQEBBxcDA21lbQIAA3J1bgAAB2xvYWQ4X3UAAQoWAgwAIAAgASAC/AoAAAsHACAALQAACwscAQBB4v8DCxQAAQIDBAUGBwgJCgsMDQ4PEBESEw==", + "AGFzbQEAAAABDAJgA39/fwBgAX8BfwMDAgABBQMBAAEHFwMDbWVtAgADcnVuAAAHbG9hZDhfdQABChYCDAAgACABIAL8CgAACwcAIAAtAAALCxwBAEHs/wMLFAABAgMEBQYHCAkKCwwNDg8QERIT", + "AGFzbQEAAAABDAJgA39/fwBgAX8BfwMDAgABBQQBAQEBBxcDA21lbQIAA3J1bgAAB2xvYWQ4X3UAAQoWAgwAIAAgASAC/AoAAAsHACAALQAACwscAQBBgOADCxQAAQIDBAUGBwgJCgsMDQ4PEBESEw==", + "AGFzbQEAAAABCwJgAABgA39/fwF/AwMCAAEFBAEBAQEHFQIEdGVzdAAACmNoZWNrUmFuZ2UAAQpAAhYAQQpB1QBBCvwLAEEJQQpBBfwKAAALJwADQCAAIAFGBEBBfw8LIAAtAAAgAkYEQCAAQQFqIQAMAQsLIAAPCw==", + "AGFzbQEAAAABCwJgAABgA39/fwF/AwMCAAEFBAEBAQEHFQIEdGVzdAAACmNoZWNrUmFuZ2UAAQpAAhYAQQpB1QBBCvwLAEEQQQ9BBfwKAAALJwADQCAAIAFGBEBBfw8LIAAtAAAgAkYEQCAAQQFqIQAMAQsLIAAPCw==", + "AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwgBBHRlc3QAAAoTAREAQYD+A0GAgAJBgQL8CgAACw==", + "AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwgBBHRlc3QAAAoSARAAQYB+QYCAAUGBAvwKAAAL", + "AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwgBBHRlc3QAAAoTAREAQYCAAkGA/gNBgQL8CgAACw==", + "AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwgBBHRlc3QAAAoSARAAQYCAAUGAfkGBAvwKAAAL", + "AGFzbQEAAAABCwJgAABgA39/fwF/AwMCAAEFBAEBAQEHFQIEdGVzdAAACmNoZWNrUmFuZ2UAAQpUAioAQQBB1QBBgIAC/AsAQYCAAkGqAUGAgAL8CwBBgKACQYDgAUEA/AoAAAsnAANAIAAgAUYEQEF/DwsgAC0AACACRgRAIABBAWohAAwBCwsgAA8L", + "AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwgBBHRlc3QAAAoSARAAQYCABEGA4AFBAPwKAAAL", + "AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwgBBHRlc3QAAAoSARAAQYCACEGA4AFBAPwKAAAL", + "AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwgBBHRlc3QAAAoSARAAQYCgAkGAgARBAPwKAAAL", + "AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwgBBHRlc3QAAAoSARAAQYCgAkGAgAhBAPwKAAAL", + "AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwgBBHRlc3QAAAoSARAAQYCABEGAgARBAPwKAAAL", + "AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwgBBHRlc3QAAAoSARAAQYCACEGAgAhBAPwKAAAL", + "AGFzbQEAAAABCwJgAABgA39/fwF/AwMCAAEFBAEBAQEHFQIEdGVzdAAACmNoZWNrUmFuZ2UAAQq3FQKMFQBB54oBQQFBwAr8CwBB6bACQQJBnwj8CwBB0bgDQQNB3Af8CwBByqgCQQRBwgL8CwBBqT5BBUHKD/wLAEG6sQFBBkHcF/wLAEHygwFBB0HEEvwLAEHj0wJBCEHDBvwLAEH8AEEJQfEK/AsAQdQQQQpBxhX8CwBBm8YAQQtBmhj8CwBB55sDQQxB5QX8CwBB9h5BDUGHFvwLAEGzhANBDkGACvwLAEHJiQNBD0G6C/wLAEGNoAFBEEHWGPwLAEGx9AJBEUGgBPwLAEGj4QBBEkHtFPwLAEGlwgFBE0HbFPwLAEGF4gJBFEGiDPwLAEHY0AJBFUGbDfwLAEHeiAJBFkGGBfwLAEGr+wJBF0HCDvwLAEHNoQNBGEHhFPwLAEGb7QFBGUHVB/wLAEHUyABBGkGPDvwLAEGOiANBG0HnA/wLAEGh6gNBHEGSBPwLAEHcmwJBHUGvB/wLAEHwNEEeQf0C/AsAQb6QA0EfQZEY/AsAQcGEA0EgQZIF/AsAQfzbAkEhQaYN/AsAQb6EAkEiQcQI/AsAQf6MA0EjQYIL/AsAQerzAkEkQZwR/AsAQeumA0ElQdoS/AsAQY+vA0EmQfoB/AsAQdywAUEnQbEQ/AsAQeyFAUEoQcAZ/AsAQbuoA0EpQeMZ/AsAQbK0AkEqQewV/AsAQbyaAkErQZYQ/AsAQeyTAkEsQcsV/AsAQdv/AUEtQbgC/AsAQYLyA0EuQcAB/AsAQf7xAUEvQdQE/AsAQfuBAUEwQfUD/AsAQaq9A0ExQa4F/AsAQfuLAkEyQYED/AsAQdHbA0EzQYcH/AsAQYXgA0E0QdYS/AsAQfzuAkE1QaEL/AsAQfXKAUE2QdoY/AsAQb4rQTdB1xD8CwBBiZkCQThBhwT8CwBB3N4CQTlB0Bn8CwBBqO0CQTpBjg38CwBBj+wCQTtB4Bj8CwBBsa8BQTxBoQv8CwBB8ckDQT1BlwX8CwBBhfwBQT5Bhw38CwBB9xdBP0HRBfwLAEHpiQJBwABB1AD8CwBBuoQCQcEAQe0P/AsAQcqfAkHCAEEd/AsAQcuVAUHDAEHaF/wLAEHI4gBBxABBkwj8CwBB5I4BQcUAQfwZ/AsAQZ8kQcYAQcMI/AsAQZ7+AEHHAEHND/wLAEGcjgFByABB0xH8CwBB5IoDQckAQfUY/AsAQZTWAEHKAEGwD/wLAEHa/ABBywBBrwv8CwBB3uICQcwAQZkJ/AsAQfmmA0HNAEGgDPwLAEG7ggJBzgBB6gz8CwBB5NwDQc8AQdQZ/AsAQZGUA0HQAEHfAfwLAEGJIkHRAEH7EPwLAEGqwQNB0gBBqgr8CwBBrLMDQdMAQdgU/AsAQZu8AUHUAEGVCPwLAEGv0QJB1QBBmRj8CwBBs/wBQdYAQewV/AsAQeMdQdcAQdoP/AsAQcisA0HYAEEA/AsAQZWGA0HZAEGVEPwLAEG7nwFB2gBB0Bb8CwBBoogCQdsAQcAB/AsAQbrJAEHcAEGTEfwLAEH94ABB3QBBGPwLAEGL7gBB3gBBwQT8CwBBmtgCQd8AQakQ/AsAQf+eAkHgAEHsGvwLAEH4tQFB4QBBzRX8CwBB+DFB4gBBvgb8CwBBm4QCQeMAQZIP/AsAQbWrAUHkAEG+FfwLAEHOzgNB6KcDQbIQ/AoAAEGy7ANBuLICQeYB/AoAAEH5lANBzbgBQfwR/AoAAEG0NEG8uwFB/wT8CgAAQc42QfeEAkHJCPwKAABBy5cBQezQAEH9GPwKAABBrNUBQYapA0HkAPwKAABB1dQBQaLVAkG1DfwKAABB8NgDQbXDAEH3APwKAABBuy5BhBJBkgX8CgAAQbMlQa+TA0HdEfwKAABByeIAQf2VAUHBBvwKAABBztwAQanrAkHkGfwKAABB8NgAQdTfAkHpEfwKAABBiosCQak0QYwU/AoAAEHIJkGaDUGwCvwKAABBvO0DQdU7QYYN/AoAAEGY3AJBqI8BQSH8CgAAQY7XAkHMrgFBkwv8CgAAQa3sAkGbhQNBmgv8CgAAQcTxA0GzxABBwgb8CgAAQc2FAkGjnQFB9Rn8CgAAQf+8AkGtqANBgRn8CgAAQdTJAUH2zgNBlBP8CgAAQd6ZAUGyvANB2gL8CgAAQez7AEHKmAJB/hL8CgAAQbDcAEH2lQJBrAL8CgAAQaPQA0GF7QBB0Rj8CgAAQfuLAkGy2QNBgQr8CgAAQYTGAEH03wBBrwf8CgAAQYsWQbnRAEHfDvwKAABButECQYbXAkHiBfwKAABBvuwDQYWUAUH6APwKAABB7LsBQdndAkHbDfwKAABB0LABQaPzAEG+BfwKAABBlNgAQdPPAUGmDvwKAABBtLQBQfefAUGoCPwKAABBoL8DQfKrA0HHFPwKAABBlMcBQYEIQakY/AoAAEG0gwNBvNkCQc8H/AoAAEH43AFB+sUCQaAS/AoAAEHp3gNB5gFBuBb8CgAAQdCvAUGamgNBlRH8CgAAQem8AkHqygBBpg/8CgAAQcziAUH+ogFBihH8CgAAQaWeA0Gz1wJBjQj8CgAAQYTHAUHTlgJB8gz8CgAAQZTJA0H75QJBwg/8CgAAQZmrAkGQLUGjD/wKAABB194BQcSwA0HAEvwKAABBm+kDQbyNAUHMCvwKAABB5YcDQaXsAEH+AvwKAABBiIQBQfWbAkHsDvwKAABB4vcCQd7YAEH3FfwKAABB4N4BQaq7AkHDAvwKAABBspUCQdDZAUGGDfwKAABB+usDQdSgA0G9CvwKAABBte4AQejpAkGEBfwKAABB5uIBQYKVAUHwA/wKAABBmN8CQdnzAkHgFfwKAABBh7UCQfXcAkHGCvwKAABB8NAAQdrkAUHDC/wKAABBv+4CQeLoAkG7C/wKAABBqSZBxOABQecO/AoAAEH8qAJBpb8DQdcN/AoAAEHOzgFB19QBQecI/AoAAEHTywNB0cABQacI/AoAAEGs3wNBhq8CQf4F/AoAAEGA2QJB7BFB8Av8CgAAQeT/AUGF8QJBxhf8CgAAQYzXAEGMpgFB8wf8CgAAQfE7Qfz2AUHaF/wKAABB/IwBQbvlAEH4GfwKAABB2r8DQeG0A0G0AvwKAABB48ABQa+DAUGDCfwKAABBvJsBQYPPAEHSBfwKAABB6RZBry5BwhL8CgAAQf/7AUGvhwNB7hb8CgAAQZb2AEGThwFBrxT8CgAAQYfkAkGf3gFB/Q/8CgAAQe2uA0GRmgJBpBT8CgAAQa3eAUGNpwNBkAn8CgAAQc/2AkGJoQNBwRj8CgAAQbbvAUHj4AJB2RT8CgAAQcEnQcchQTT8CgAAQaQ0QYO9AUG5A/wKAABB2IECQe3TAUH1GvwKAABBkv4BQezPA0HhFfwKAABBuYwCQYLGAEHmEvwKAABB5YsBQYqqA0G1GvwKAABBnbEBQffYAkGIAfwKAABB0c0DQaU3QZUI/AoAAEHBzwJB9K0DQdUS/AoAAEGV3QJBqp0BQe0G/AoAAEHKnwJB7MQBQfca/AoAAEGu5QJBkPkBQdYG/AoAAEGsvQFB+vgBQeEK/AoAAEHyhwJBtAVBugz8CgAAQcrZA0GZkQFBqxf8CgAAQcKJA0G3wgJB/gr8CgAACycAA0AgACABRgRAQX8PCyAALQAAIAJGBEAgAEEBaiEADAELCyAADws=", + "AGFzbQEAAAABCwJgA39/fwF/YAAAAwMCAAEFBAEBAQEHFQIKY2hlY2tSYW5nZQAABHRlc3QAAQo5AicAA0AgACABRgRAQX8PCyAALQAAIAJGBEAgAEEBaiEADAELCyAADwsPAEGA/gNB1QBBgAL8CwAL", + "AGFzbQEAAAABCwJgA39/fwF/YAAAAwMCAAEFBAEBAQEHFQIKY2hlY2tSYW5nZQAABHRlc3QAAQo5AicAA0AgACABRgRAQX8PCyAALQAAIAJGBEAgAEEBaiEADAELCyAADwsPAEGA/gNB1QBBgQL8CwAL", + "AGFzbQEAAAABCwJgA39/fwF/YAAAAwMCAAEFBAEBAQEHFQIKY2hlY2tSYW5nZQAABHRlc3QAAQo4AicAA0AgACABRgRAQX8PCyAALQAAIAJGBEAgAEEBaiEADAELCyAADwsOAEGAfkHVAEGBAvwLAAs=", + "AGFzbQEAAAABCwJgA39/fwF/YAAAAwMCAAEFBAEBAQEHFQIKY2hlY2tSYW5nZQAABHRlc3QAAQo2AicAA0AgACABRgRAQX8PCyAALQAAIAJGBEAgAEEBaiEADAELCyAADwsMAEESQdUAQQD8CwAL", + "AGFzbQEAAAABCwJgA39/fwF/YAAAAwMCAAEFBAEBAQEHFQIKY2hlY2tSYW5nZQAABHRlc3QAAQo4AicAA0AgACABRgRAQX8PCyAALQAAIAJGBEAgAEEBaiEADAELCyAADwsOAEGAgARB1QBBAPwLAAs=", + "AGFzbQEAAAABCwJgA39/fwF/YAAAAwMCAAEFBAEBAQEHFQIKY2hlY2tSYW5nZQAABHRlc3QAAQo4AicAA0AgACABRgRAQX8PCyAALQAAIAJGBEAgAEEBaiEADAELCyAADwsOAEGAgAhB1QBBAPwLAAs=", + "AGFzbQEAAAABCwJgA39/fwF/YAAAAwMCAAEFBAEBAQEHFQIKY2hlY2tSYW5nZQAABHRlc3QAAQo4AicAA0AgACABRgRAQX8PCyAALQAAIAJGBEAgAEEBaiEADAELCyAADwsOAEEBQaoBQf7/A/wLAAs=", + "AGFzbQEAAAABCwJgA39/fwF/YAAAAwMCAAEFBAEBAQEHFQIKY2hlY2tSYW5nZQAABHRlc3QAAQpAAicAA0AgACABRgRAQX8PCyAALQAAIAJGBEAgAEEBaiEADAELCyAADwsWAEESQdUAQQr8CwBBFUGqAUEE/AsACw==", + "AGFzbQEAAAABDgJgA39/fwF/YAN/f38AAwMCAAEFBAEBAQEHFAIKY2hlY2tSYW5nZQAAA3J1bgABCjUCJwADQCAAIAFGBEBBfw8LIAAtAAAgAkYEQCAAQQFqIQAMAQsLIAAPCwsAIAAgASAC/AsACw==", + "AGFzbQEAAAABCQJgAABgAX8BfwMDAgABBQQBAQEBBxwDB21lbW9yeTACAAR0ZXN0AAAHbG9hZDhfdQABCg0CAwABCwcAIAAtAAALCyEEAEECCwQDAQQBAQQCBwEIAEEMCwUHBQIDBgEFBQkCBwY=", + "AGFzbQEAAAABCQJgAABgAX8BfwMDAgABBQQBAQEBBxwDB21lbW9yeTACAAR0ZXN0AAAHbG9hZDhfdQABDAEEChYCDABBB0EAQQT8CAEACwcAIAAtAAALCyEEAEECCwQDAQQBAQQCBwEIAEEMCwUHBQIDBgEFBQkCBwY=", + "AGFzbQEAAAABCQJgAABgAX8BfwMDAgABBQQBAQEBBxwDB21lbW9yeTACAAR0ZXN0AAAHbG9hZDhfdQABDAEEChYCDABBD0EBQQP8CAMACwcAIAAtAAALCyEEAEECCwQDAQQBAQQCBwEIAEEMCwUHBQIDBgEFBQkCBwY=", + "AGFzbQEAAAABCQJgAABgAX8BfwMDAgABBQQBAQEBBxwDB21lbW9yeTACAAR0ZXN0AAAHbG9hZDhfdQABDAEEClgCTgBBB0EAQQT8CAEA/AkBQQ9BAUED/AgDAPwJA0EUQQ9BBfwKAABBFUEdQQH8CgAAQRhBCkEB/AoAAEENQQtBBPwKAABBE0EUQQX8CgAACwcAIAAtAAALCyEEAEECCwQDAQQBAQQCBwEIAEEMCwUHBQIDBgEFBQkCBwY=", + "AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBCgoBCAD8CQD8CQALCwQBAQE3", + "AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBChIBEAD8CQBB0glBAUEB/AgAAAsLBAEBATc=", + "AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBCg8BDQBB0glBAUEB/AgAAAsLBwEAQQALATc=", + "AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBChgBFgBBAUEAQQH8CAAAQQFBAEEB/AgAAAsLBAEBATc=", + "AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBCg8BDQBB0glBAEEF/AgAAAsLBAEBATc=", + "AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBCg8BDQBB0glBAkED/AgAAAsLBAEBATc=", + "AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBChABDgBB/v8DQQFBA/wIAAALCwQBAQE3", + "AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBCg8BDQBB0glBBEEA/AgAAAsLBAEBATc=", + "AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBCg8BDQBB0glBAUEA/AgAAAsLBAEBATc=", + "AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBChABDgBBgYAEQQBBAPwIAAALCwQBAQE3", + "AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBChABDgBBgIAEQQBBAPwIAAALCwQBAQE3", + "AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBChABDgBBgIAEQQFBAPwIAAALCwQBAQE3", + "AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBChABDgBBgYAEQQRBAPwIAAALCwQBAQE3", + "AGFzbQEAAAABDQJgA39/fwF/YAJ/fwADAwIAAQUEAQEBAQcUAgpjaGVja1JhbmdlAAADcnVuAAEMAQEKNgInAANAIAAgAUYEQEF/DwsgAC0AACACRgRAIABBAWohAAwBCwsgAA8LDAAgAEEAIAH8CAAACwsTAQEQQkJCQkJCQkJCQkJCQkJCQg==", + "AGFzbQEAAAABDQJgA39/fwF/YAJ/fwADAwIAAQUDAQABBxQCCmNoZWNrUmFuZ2UAAANydW4AAQwBAQo2AicAA0AgACABRgRAQX8PCyAALQAAIAJGBEAgAEEBaiEADAELCyAADwsMACAAQQAgAfwIAAALCxMBARBCQkJCQkJCQkJCQkJCQkJC", + "AGFzbQEAAAABBAFgAAADAgEABQMBAAEMAUEKDgEMAEEAQQBBAPwIQAALC4MBQQEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQA=", + "AGFzbQEAAAAFAwEAAQuzARwAQQALAABBAQsEYWJjZABBAAsAAEEACwNhYmMAQQALAABBAQsEYWJjZABBAAsAAEEACwNhYmMAQQALAABBAQsEYWJjZABBAAsAAEEACwNhYmMBAAEEYWJjZABBAAsAAEEBCwRhYmNkAEEACwAAQQALA2FiYwBBAAsAAEEBCwRhYmNkAEEACwAAQQALA2FiYwBBAAsAAEEBCwRhYmNkAEEACwAAQQALA2FiYwEAAQRhYmNk", + "AGFzbQEAAAAFAwEAAQsHAQBBAAsBYQ==", + "AGFzbQEAAAAFAwEAAQsiBQBBAAsBYQBBAwsBYgBB5AALA2NkZQBBBQsBeABBAwsBYw==", + "AGFzbQEAAAACGAEIc3BlY3Rlc3QKZ2xvYmFsX2kzMgN/AAUDAQABCwcBACMACwFh", + "AGFzbQEAAAAFAwEAAQsPAgBBAAsBYQBB//8DCwFi", + "AGFzbQEAAAAFAwEAAgsJAQBB//8HCwFh", + "AGFzbQEAAAAFAwEAAAsGAQBBAAsA", + "AGFzbQEAAAAFBAEBAAALBgEAQQALAA==", + "AGFzbQEAAAAFAwEAAQsIAQBBgIAECwA=", + "AGFzbQEAAAAFAwEAAAsHAQBBAAsBYQ==", + "AGFzbQEAAAAFBAEBAAALBwEAQQALAWE=", + "AGFzbQEAAAAFBAEBAAELBwEAQQALAWE=", + "AGFzbQEAAAAFAwEAAAsGAQBBAQsA", + "AGFzbQEAAAAFBAEBAAELBgEAQQELAA==", + "AGFzbQEAAAACGAEIc3BlY3Rlc3QKZ2xvYmFsX2kzMgN/AAUDAQAACwcBACMACwFh", + "AGFzbQEAAAAFBAEBAQILCQEAQYCABAsBYQ==", + "AGFzbQEAAAAFAwEAAgsJAQBBgIAICwFh", + "AGFzbQEAAAAFBAEBAgMLCQEAQYCACAsBYQ==", + "AGFzbQEAAAAFAwEAAQsHAQBBfwsBYQ==", + "AGFzbQEAAAAFAwEAAgsIAQBBnH8LAWE=", +]; +export const script = "[[\"file\",\"address.wast\"],[\"module\",3,0],[\"invoke\",104,0,\"8u_good1\",[\"i32:0\"]],[\"invoke\",105,0,\"8u_good2\",[\"i32:0\"]],[\"invoke\",106,0,\"8u_good3\",[\"i32:0\"]],[\"invoke\",107,0,\"8u_good4\",[\"i32:0\"]],[\"invoke\",108,0,\"8u_good5\",[\"i32:0\"]],[\"invoke\",110,0,\"8s_good1\",[\"i32:0\"]],[\"invoke\",111,0,\"8s_good2\",[\"i32:0\"]],[\"invoke\",112,0,\"8s_good3\",[\"i32:0\"]],[\"invoke\",113,0,\"8s_good4\",[\"i32:0\"]],[\"invoke\",114,0,\"8s_good5\",[\"i32:0\"]],[\"invoke\",116,0,\"16u_good1\",[\"i32:0\"]],[\"invoke\",117,0,\"16u_good2\",[\"i32:0\"]],[\"invoke\",118,0,\"16u_good3\",[\"i32:0\"]],[\"invoke\",119,0,\"16u_good4\",[\"i32:0\"]],[\"invoke\",120,0,\"16u_good5\",[\"i32:0\"]],[\"invoke\",122,0,\"16s_good1\",[\"i32:0\"]],[\"invoke\",123,0,\"16s_good2\",[\"i32:0\"]],[\"invoke\",124,0,\"16s_good3\",[\"i32:0\"]],[\"invoke\",125,0,\"16s_good4\",[\"i32:0\"]],[\"invoke\",126,0,\"16s_good5\",[\"i32:0\"]],[\"invoke\",128,0,\"32_good1\",[\"i32:0\"]],[\"invoke\",129,0,\"32_good2\",[\"i32:0\"]],[\"invoke\",130,0,\"32_good3\",[\"i32:0\"]],[\"invoke\",131,0,\"32_good4\",[\"i32:0\"]],[\"invoke\",132,0,\"32_good5\",[\"i32:0\"]],[\"invoke\",134,0,\"8u_good1\",[\"i32:65507\"]],[\"invoke\",135,0,\"8u_good2\",[\"i32:65507\"]],[\"invoke\",136,0,\"8u_good3\",[\"i32:65507\"]],[\"invoke\",137,0,\"8u_good4\",[\"i32:65507\"]],[\"invoke\",138,0,\"8u_good5\",[\"i32:65507\"]],[\"invoke\",140,0,\"8s_good1\",[\"i32:65507\"]],[\"invoke\",141,0,\"8s_good2\",[\"i32:65507\"]],[\"invoke\",142,0,\"8s_good3\",[\"i32:65507\"]],[\"invoke\",143,0,\"8s_good4\",[\"i32:65507\"]],[\"invoke\",144,0,\"8s_good5\",[\"i32:65507\"]],[\"invoke\",146,0,\"16u_good1\",[\"i32:65507\"]],[\"invoke\",147,0,\"16u_good2\",[\"i32:65507\"]],[\"invoke\",148,0,\"16u_good3\",[\"i32:65507\"]],[\"invoke\",149,0,\"16u_good4\",[\"i32:65507\"]],[\"invoke\",150,0,\"16u_good5\",[\"i32:65507\"]],[\"invoke\",152,0,\"16s_good1\",[\"i32:65507\"]],[\"invoke\",153,0,\"16s_good2\",[\"i32:65507\"]],[\"invoke\",154,0,\"16s_good3\",[\"i32:65507\"]],[\"invoke\",155,0,\"16s_good4\",[\"i32:65507\"]],[\"invoke\",156,0,\"16s_good5\",[\"i32:65507\"]],[\"invoke\",158,0,\"32_good1\",[\"i32:65507\"]],[\"invoke\",159,0,\"32_good2\",[\"i32:65507\"]],[\"invoke\",160,0,\"32_good3\",[\"i32:65507\"]],[\"invoke\",161,0,\"32_good4\",[\"i32:65507\"]],[\"invoke\",162,0,\"32_good5\",[\"i32:65507\"]],[\"invoke\",164,0,\"8u_good1\",[\"i32:65508\"]],[\"invoke\",165,0,\"8u_good2\",[\"i32:65508\"]],[\"invoke\",166,0,\"8u_good3\",[\"i32:65508\"]],[\"invoke\",167,0,\"8u_good4\",[\"i32:65508\"]],[\"invoke\",168,0,\"8u_good5\",[\"i32:65508\"]],[\"invoke\",170,0,\"8s_good1\",[\"i32:65508\"]],[\"invoke\",171,0,\"8s_good2\",[\"i32:65508\"]],[\"invoke\",172,0,\"8s_good3\",[\"i32:65508\"]],[\"invoke\",173,0,\"8s_good4\",[\"i32:65508\"]],[\"invoke\",174,0,\"8s_good5\",[\"i32:65508\"]],[\"invoke\",176,0,\"16u_good1\",[\"i32:65508\"]],[\"invoke\",177,0,\"16u_good2\",[\"i32:65508\"]],[\"invoke\",178,0,\"16u_good3\",[\"i32:65508\"]],[\"invoke\",179,0,\"16u_good4\",[\"i32:65508\"]],[\"invoke\",180,0,\"16u_good5\",[\"i32:65508\"]],[\"invoke\",182,0,\"16s_good1\",[\"i32:65508\"]],[\"invoke\",183,0,\"16s_good2\",[\"i32:65508\"]],[\"invoke\",184,0,\"16s_good3\",[\"i32:65508\"]],[\"invoke\",185,0,\"16s_good4\",[\"i32:65508\"]],[\"invoke\",186,0,\"16s_good5\",[\"i32:65508\"]],[\"invoke\",188,0,\"32_good1\",[\"i32:65508\"]],[\"invoke\",189,0,\"32_good2\",[\"i32:65508\"]],[\"invoke\",190,0,\"32_good3\",[\"i32:65508\"]],[\"invoke\",191,0,\"32_good4\",[\"i32:65508\"]],[\"invoke\",192,0,\"32_good5\",[\"i32:65508\"]],[\"invoke\",194,0,\"8u_good3\",[\"i32:4294967295\"]],[\"invoke\",195,0,\"8s_good3\",[\"i32:4294967295\"]],[\"invoke\",196,0,\"16u_good3\",[\"i32:4294967295\"]],[\"invoke\",197,0,\"16s_good3\",[\"i32:4294967295\"]],[\"invoke\",198,0,\"32_good3\",[\"i32:4294967295\"]],[\"invoke\",199,0,\"32_good3\",[\"i32:4294967295\"]],[\"invoke\",201,0,\"8u_bad\",[\"i32:0\"]],[\"invoke\",202,0,\"8s_bad\",[\"i32:0\"]],[\"invoke\",203,0,\"16u_bad\",[\"i32:0\"]],[\"invoke\",204,0,\"16s_bad\",[\"i32:0\"]],[\"invoke\",205,0,\"32_bad\",[\"i32:0\"]],[\"invoke\",207,0,\"8u_bad\",[\"i32:1\"]],[\"invoke\",208,0,\"8s_bad\",[\"i32:1\"]],[\"invoke\",209,0,\"16u_bad\",[\"i32:1\"]],[\"invoke\",210,0,\"16s_bad\",[\"i32:1\"]],[\"invoke\",211,0,\"32_bad\",[\"i32:1\"]],[\"module\",223,1],[\"invoke\",362,0,\"8u_good1\",[\"i32:0\"]],[\"invoke\",363,0,\"8u_good2\",[\"i32:0\"]],[\"invoke\",364,0,\"8u_good3\",[\"i32:0\"]],[\"invoke\",365,0,\"8u_good4\",[\"i32:0\"]],[\"invoke\",366,0,\"8u_good5\",[\"i32:0\"]],[\"invoke\",368,0,\"8s_good1\",[\"i32:0\"]],[\"invoke\",369,0,\"8s_good2\",[\"i32:0\"]],[\"invoke\",370,0,\"8s_good3\",[\"i32:0\"]],[\"invoke\",371,0,\"8s_good4\",[\"i32:0\"]],[\"invoke\",372,0,\"8s_good5\",[\"i32:0\"]],[\"invoke\",374,0,\"16u_good1\",[\"i32:0\"]],[\"invoke\",375,0,\"16u_good2\",[\"i32:0\"]],[\"invoke\",376,0,\"16u_good3\",[\"i32:0\"]],[\"invoke\",377,0,\"16u_good4\",[\"i32:0\"]],[\"invoke\",378,0,\"16u_good5\",[\"i32:0\"]],[\"invoke\",380,0,\"16s_good1\",[\"i32:0\"]],[\"invoke\",381,0,\"16s_good2\",[\"i32:0\"]],[\"invoke\",382,0,\"16s_good3\",[\"i32:0\"]],[\"invoke\",383,0,\"16s_good4\",[\"i32:0\"]],[\"invoke\",384,0,\"16s_good5\",[\"i32:0\"]],[\"invoke\",386,0,\"32u_good1\",[\"i32:0\"]],[\"invoke\",387,0,\"32u_good2\",[\"i32:0\"]],[\"invoke\",388,0,\"32u_good3\",[\"i32:0\"]],[\"invoke\",389,0,\"32u_good4\",[\"i32:0\"]],[\"invoke\",390,0,\"32u_good5\",[\"i32:0\"]],[\"invoke\",392,0,\"32s_good1\",[\"i32:0\"]],[\"invoke\",393,0,\"32s_good2\",[\"i32:0\"]],[\"invoke\",394,0,\"32s_good3\",[\"i32:0\"]],[\"invoke\",395,0,\"32s_good4\",[\"i32:0\"]],[\"invoke\",396,0,\"32s_good5\",[\"i32:0\"]],[\"invoke\",398,0,\"64_good1\",[\"i32:0\"]],[\"invoke\",399,0,\"64_good2\",[\"i32:0\"]],[\"invoke\",400,0,\"64_good3\",[\"i32:0\"]],[\"invoke\",401,0,\"64_good4\",[\"i32:0\"]],[\"invoke\",402,0,\"64_good5\",[\"i32:0\"]],[\"invoke\",404,0,\"8u_good1\",[\"i32:65503\"]],[\"invoke\",405,0,\"8u_good2\",[\"i32:65503\"]],[\"invoke\",406,0,\"8u_good3\",[\"i32:65503\"]],[\"invoke\",407,0,\"8u_good4\",[\"i32:65503\"]],[\"invoke\",408,0,\"8u_good5\",[\"i32:65503\"]],[\"invoke\",410,0,\"8s_good1\",[\"i32:65503\"]],[\"invoke\",411,0,\"8s_good2\",[\"i32:65503\"]],[\"invoke\",412,0,\"8s_good3\",[\"i32:65503\"]],[\"invoke\",413,0,\"8s_good4\",[\"i32:65503\"]],[\"invoke\",414,0,\"8s_good5\",[\"i32:65503\"]],[\"invoke\",416,0,\"16u_good1\",[\"i32:65503\"]],[\"invoke\",417,0,\"16u_good2\",[\"i32:65503\"]],[\"invoke\",418,0,\"16u_good3\",[\"i32:65503\"]],[\"invoke\",419,0,\"16u_good4\",[\"i32:65503\"]],[\"invoke\",420,0,\"16u_good5\",[\"i32:65503\"]],[\"invoke\",422,0,\"16s_good1\",[\"i32:65503\"]],[\"invoke\",423,0,\"16s_good2\",[\"i32:65503\"]],[\"invoke\",424,0,\"16s_good3\",[\"i32:65503\"]],[\"invoke\",425,0,\"16s_good4\",[\"i32:65503\"]],[\"invoke\",426,0,\"16s_good5\",[\"i32:65503\"]],[\"invoke\",428,0,\"32u_good1\",[\"i32:65503\"]],[\"invoke\",429,0,\"32u_good2\",[\"i32:65503\"]],[\"invoke\",430,0,\"32u_good3\",[\"i32:65503\"]],[\"invoke\",431,0,\"32u_good4\",[\"i32:65503\"]],[\"invoke\",432,0,\"32u_good5\",[\"i32:65503\"]],[\"invoke\",434,0,\"32s_good1\",[\"i32:65503\"]],[\"invoke\",435,0,\"32s_good2\",[\"i32:65503\"]],[\"invoke\",436,0,\"32s_good3\",[\"i32:65503\"]],[\"invoke\",437,0,\"32s_good4\",[\"i32:65503\"]],[\"invoke\",438,0,\"32s_good5\",[\"i32:65503\"]],[\"invoke\",440,0,\"64_good1\",[\"i32:65503\"]],[\"invoke\",441,0,\"64_good2\",[\"i32:65503\"]],[\"invoke\",442,0,\"64_good3\",[\"i32:65503\"]],[\"invoke\",443,0,\"64_good4\",[\"i32:65503\"]],[\"invoke\",444,0,\"64_good5\",[\"i32:65503\"]],[\"invoke\",446,0,\"8u_good1\",[\"i32:65504\"]],[\"invoke\",447,0,\"8u_good2\",[\"i32:65504\"]],[\"invoke\",448,0,\"8u_good3\",[\"i32:65504\"]],[\"invoke\",449,0,\"8u_good4\",[\"i32:65504\"]],[\"invoke\",450,0,\"8u_good5\",[\"i32:65504\"]],[\"invoke\",452,0,\"8s_good1\",[\"i32:65504\"]],[\"invoke\",453,0,\"8s_good2\",[\"i32:65504\"]],[\"invoke\",454,0,\"8s_good3\",[\"i32:65504\"]],[\"invoke\",455,0,\"8s_good4\",[\"i32:65504\"]],[\"invoke\",456,0,\"8s_good5\",[\"i32:65504\"]],[\"invoke\",458,0,\"16u_good1\",[\"i32:65504\"]],[\"invoke\",459,0,\"16u_good2\",[\"i32:65504\"]],[\"invoke\",460,0,\"16u_good3\",[\"i32:65504\"]],[\"invoke\",461,0,\"16u_good4\",[\"i32:65504\"]],[\"invoke\",462,0,\"16u_good5\",[\"i32:65504\"]],[\"invoke\",464,0,\"16s_good1\",[\"i32:65504\"]],[\"invoke\",465,0,\"16s_good2\",[\"i32:65504\"]],[\"invoke\",466,0,\"16s_good3\",[\"i32:65504\"]],[\"invoke\",467,0,\"16s_good4\",[\"i32:65504\"]],[\"invoke\",468,0,\"16s_good5\",[\"i32:65504\"]],[\"invoke\",470,0,\"32u_good1\",[\"i32:65504\"]],[\"invoke\",471,0,\"32u_good2\",[\"i32:65504\"]],[\"invoke\",472,0,\"32u_good3\",[\"i32:65504\"]],[\"invoke\",473,0,\"32u_good4\",[\"i32:65504\"]],[\"invoke\",474,0,\"32u_good5\",[\"i32:65504\"]],[\"invoke\",476,0,\"32s_good1\",[\"i32:65504\"]],[\"invoke\",477,0,\"32s_good2\",[\"i32:65504\"]],[\"invoke\",478,0,\"32s_good3\",[\"i32:65504\"]],[\"invoke\",479,0,\"32s_good4\",[\"i32:65504\"]],[\"invoke\",480,0,\"32s_good5\",[\"i32:65504\"]],[\"invoke\",482,0,\"64_good1\",[\"i32:65504\"]],[\"invoke\",483,0,\"64_good2\",[\"i32:65504\"]],[\"invoke\",484,0,\"64_good3\",[\"i32:65504\"]],[\"invoke\",485,0,\"64_good4\",[\"i32:65504\"]],[\"invoke\",486,0,\"64_good5\",[\"i32:65504\"]],[\"invoke\",488,0,\"8u_good3\",[\"i32:4294967295\"]],[\"invoke\",489,0,\"8s_good3\",[\"i32:4294967295\"]],[\"invoke\",490,0,\"16u_good3\",[\"i32:4294967295\"]],[\"invoke\",491,0,\"16s_good3\",[\"i32:4294967295\"]],[\"invoke\",492,0,\"32u_good3\",[\"i32:4294967295\"]],[\"invoke\",493,0,\"32s_good3\",[\"i32:4294967295\"]],[\"invoke\",494,0,\"64_good3\",[\"i32:4294967295\"]],[\"invoke\",496,0,\"8u_bad\",[\"i32:0\"]],[\"invoke\",497,0,\"8s_bad\",[\"i32:0\"]],[\"invoke\",498,0,\"16u_bad\",[\"i32:0\"]],[\"invoke\",499,0,\"16s_bad\",[\"i32:0\"]],[\"invoke\",500,0,\"32u_bad\",[\"i32:0\"]],[\"invoke\",501,0,\"32s_bad\",[\"i32:0\"]],[\"invoke\",502,0,\"64_bad\",[\"i32:0\"]],[\"invoke\",504,0,\"8u_bad\",[\"i32:1\"]],[\"invoke\",505,0,\"8s_bad\",[\"i32:1\"]],[\"invoke\",506,0,\"16u_bad\",[\"i32:1\"]],[\"invoke\",507,0,\"16s_bad\",[\"i32:1\"]],[\"invoke\",508,0,\"32u_bad\",[\"i32:0\"]],[\"invoke\",509,0,\"32s_bad\",[\"i32:0\"]],[\"invoke\",510,0,\"64_bad\",[\"i32:1\"]],[\"module\",514,2],[\"invoke\",538,0,\"32_good1\",[\"i32:0\"]],[\"invoke\",539,0,\"32_good2\",[\"i32:0\"]],[\"invoke\",540,0,\"32_good3\",[\"i32:0\"]],[\"invoke\",541,0,\"32_good4\",[\"i32:0\"]],[\"invoke\",542,0,\"32_good5\",[\"i32:0\"]],[\"invoke\",544,0,\"32_good1\",[\"i32:65524\"]],[\"invoke\",545,0,\"32_good2\",[\"i32:65524\"]],[\"invoke\",546,0,\"32_good3\",[\"i32:65524\"]],[\"invoke\",547,0,\"32_good4\",[\"i32:65524\"]],[\"invoke\",548,0,\"32_good5\",[\"i32:65524\"]],[\"invoke\",550,0,\"32_good1\",[\"i32:65525\"]],[\"invoke\",551,0,\"32_good2\",[\"i32:65525\"]],[\"invoke\",552,0,\"32_good3\",[\"i32:65525\"]],[\"invoke\",553,0,\"32_good4\",[\"i32:65525\"]],[\"invoke\",554,0,\"32_good5\",[\"i32:65525\"]],[\"invoke\",556,0,\"32_good3\",[\"i32:4294967295\"]],[\"invoke\",557,0,\"32_good3\",[\"i32:4294967295\"]],[\"invoke\",559,0,\"32_bad\",[\"i32:0\"]],[\"invoke\",560,0,\"32_bad\",[\"i32:1\"]],[\"module\",564,3],[\"invoke\",588,0,\"64_good1\",[\"i32:0\"]],[\"invoke\",589,0,\"64_good2\",[\"i32:0\"]],[\"invoke\",590,0,\"64_good3\",[\"i32:0\"]],[\"invoke\",591,0,\"64_good4\",[\"i32:0\"]],[\"invoke\",592,0,\"64_good5\",[\"i32:0\"]],[\"invoke\",594,0,\"64_good1\",[\"i32:65510\"]],[\"invoke\",595,0,\"64_good2\",[\"i32:65510\"]],[\"invoke\",596,0,\"64_good3\",[\"i32:65510\"]],[\"invoke\",597,0,\"64_good4\",[\"i32:65510\"]],[\"invoke\",598,0,\"64_good5\",[\"i32:65510\"]],[\"invoke\",600,0,\"64_good1\",[\"i32:65511\"]],[\"invoke\",601,0,\"64_good2\",[\"i32:65511\"]],[\"invoke\",602,0,\"64_good3\",[\"i32:65511\"]],[\"invoke\",603,0,\"64_good4\",[\"i32:65511\"]],[\"invoke\",604,0,\"64_good5\",[\"i32:65511\"]],[\"invoke\",606,0,\"64_good3\",[\"i32:4294967295\"]],[\"invoke\",607,0,\"64_good3\",[\"i32:4294967295\"]],[\"invoke\",609,0,\"64_bad\",[\"i32:0\"]],[\"invoke\",610,0,\"64_bad\",[\"i32:1\"]],[\"file\",\"align.wast\"],[\"module\",3,4],[\"module\",4,5],[\"module\",5,6],[\"module\",6,7],[\"module\",7,8],[\"module\",8,9],[\"module\",9,10],[\"module\",10,11],[\"module\",11,12],[\"module\",12,13],[\"module\",13,14],[\"module\",14,15],[\"module\",15,16],[\"module\",16,17],[\"module\",17,18],[\"module\",18,19],[\"module\",19,20],[\"module\",20,21],[\"module\",21,22],[\"module\",22,23],[\"module\",23,24],[\"module\",24,25],[\"module\",25,26],[\"module\",458,27],[\"invoke\",802,0,\"f32_align_switch\",[\"i32:0\"]],[\"invoke\",803,0,\"f32_align_switch\",[\"i32:1\"]],[\"invoke\",804,0,\"f32_align_switch\",[\"i32:2\"]],[\"invoke\",805,0,\"f32_align_switch\",[\"i32:3\"]],[\"invoke\",807,0,\"f64_align_switch\",[\"i32:0\"]],[\"invoke\",808,0,\"f64_align_switch\",[\"i32:1\"]],[\"invoke\",809,0,\"f64_align_switch\",[\"i32:2\"]],[\"invoke\",810,0,\"f64_align_switch\",[\"i32:3\"]],[\"invoke\",811,0,\"f64_align_switch\",[\"i32:4\"]],[\"invoke\",813,0,\"i32_align_switch\",[\"i32:0\",\"i32:0\"]],[\"invoke\",814,0,\"i32_align_switch\",[\"i32:0\",\"i32:1\"]],[\"invoke\",815,0,\"i32_align_switch\",[\"i32:1\",\"i32:0\"]],[\"invoke\",816,0,\"i32_align_switch\",[\"i32:1\",\"i32:1\"]],[\"invoke\",817,0,\"i32_align_switch\",[\"i32:2\",\"i32:0\"]],[\"invoke\",818,0,\"i32_align_switch\",[\"i32:2\",\"i32:1\"]],[\"invoke\",819,0,\"i32_align_switch\",[\"i32:2\",\"i32:2\"]],[\"invoke\",820,0,\"i32_align_switch\",[\"i32:3\",\"i32:0\"]],[\"invoke\",821,0,\"i32_align_switch\",[\"i32:3\",\"i32:1\"]],[\"invoke\",822,0,\"i32_align_switch\",[\"i32:3\",\"i32:2\"]],[\"invoke\",823,0,\"i32_align_switch\",[\"i32:4\",\"i32:0\"]],[\"invoke\",824,0,\"i32_align_switch\",[\"i32:4\",\"i32:1\"]],[\"invoke\",825,0,\"i32_align_switch\",[\"i32:4\",\"i32:2\"]],[\"invoke\",826,0,\"i32_align_switch\",[\"i32:4\",\"i32:4\"]],[\"invoke\",828,0,\"i64_align_switch\",[\"i32:0\",\"i32:0\"]],[\"invoke\",829,0,\"i64_align_switch\",[\"i32:0\",\"i32:1\"]],[\"invoke\",830,0,\"i64_align_switch\",[\"i32:1\",\"i32:0\"]],[\"invoke\",831,0,\"i64_align_switch\",[\"i32:1\",\"i32:1\"]],[\"invoke\",832,0,\"i64_align_switch\",[\"i32:2\",\"i32:0\"]],[\"invoke\",833,0,\"i64_align_switch\",[\"i32:2\",\"i32:1\"]],[\"invoke\",834,0,\"i64_align_switch\",[\"i32:2\",\"i32:2\"]],[\"invoke\",835,0,\"i64_align_switch\",[\"i32:3\",\"i32:0\"]],[\"invoke\",836,0,\"i64_align_switch\",[\"i32:3\",\"i32:1\"]],[\"invoke\",837,0,\"i64_align_switch\",[\"i32:3\",\"i32:2\"]],[\"invoke\",838,0,\"i64_align_switch\",[\"i32:4\",\"i32:0\"]],[\"invoke\",839,0,\"i64_align_switch\",[\"i32:4\",\"i32:1\"]],[\"invoke\",840,0,\"i64_align_switch\",[\"i32:4\",\"i32:2\"]],[\"invoke\",841,0,\"i64_align_switch\",[\"i32:4\",\"i32:4\"]],[\"invoke\",842,0,\"i64_align_switch\",[\"i32:5\",\"i32:0\"]],[\"invoke\",843,0,\"i64_align_switch\",[\"i32:5\",\"i32:1\"]],[\"invoke\",844,0,\"i64_align_switch\",[\"i32:5\",\"i32:2\"]],[\"invoke\",845,0,\"i64_align_switch\",[\"i32:5\",\"i32:4\"]],[\"invoke\",846,0,\"i64_align_switch\",[\"i32:6\",\"i32:0\"]],[\"invoke\",847,0,\"i64_align_switch\",[\"i32:6\",\"i32:1\"]],[\"invoke\",848,0,\"i64_align_switch\",[\"i32:6\",\"i32:2\"]],[\"invoke\",849,0,\"i64_align_switch\",[\"i32:6\",\"i32:4\"]],[\"invoke\",850,0,\"i64_align_switch\",[\"i32:6\",\"i32:8\"]],[\"module\",854,28],[\"invoke\",864,0,\"store\",[\"i32:65532\",\"i64:18446744073709551615\"]],[\"invoke\",866,0,\"load\",[\"i32:65532\"]],[\"file\",\"endianness.wast\"],[\"module\",1,29],[\"invoke\",133,0,\"i32_load16_s\",[\"i32:4294967295\"]],[\"invoke\",134,0,\"i32_load16_s\",[\"i32:4294963054\"]],[\"invoke\",135,0,\"i32_load16_s\",[\"i32:42\"]],[\"invoke\",136,0,\"i32_load16_s\",[\"i32:12816\"]],[\"invoke\",138,0,\"i32_load16_u\",[\"i32:4294967295\"]],[\"invoke\",139,0,\"i32_load16_u\",[\"i32:4294963054\"]],[\"invoke\",140,0,\"i32_load16_u\",[\"i32:42\"]],[\"invoke\",141,0,\"i32_load16_u\",[\"i32:51966\"]],[\"invoke\",143,0,\"i32_load\",[\"i32:4294967295\"]],[\"invoke\",144,0,\"i32_load\",[\"i32:4252543054\"]],[\"invoke\",145,0,\"i32_load\",[\"i32:42424242\"]],[\"invoke\",146,0,\"i32_load\",[\"i32:2880249322\"]],[\"invoke\",148,0,\"i64_load16_s\",[\"i64:18446744073709551615\"]],[\"invoke\",149,0,\"i64_load16_s\",[\"i64:18446744073709547374\"]],[\"invoke\",150,0,\"i64_load16_s\",[\"i64:42\"]],[\"invoke\",151,0,\"i64_load16_s\",[\"i64:12816\"]],[\"invoke\",153,0,\"i64_load16_u\",[\"i64:18446744073709551615\"]],[\"invoke\",154,0,\"i64_load16_u\",[\"i64:18446744073709547374\"]],[\"invoke\",155,0,\"i64_load16_u\",[\"i64:42\"]],[\"invoke\",156,0,\"i64_load16_u\",[\"i64:51966\"]],[\"invoke\",158,0,\"i64_load32_s\",[\"i64:18446744073709551615\"]],[\"invoke\",159,0,\"i64_load32_s\",[\"i64:18446744073667127374\"]],[\"invoke\",160,0,\"i64_load32_s\",[\"i64:42424242\"]],[\"invoke\",161,0,\"i64_load32_s\",[\"i64:305419896\"]],[\"invoke\",163,0,\"i64_load32_u\",[\"i64:18446744073709551615\"]],[\"invoke\",164,0,\"i64_load32_u\",[\"i64:18446744073667127374\"]],[\"invoke\",165,0,\"i64_load32_u\",[\"i64:42424242\"]],[\"invoke\",166,0,\"i64_load32_u\",[\"i64:2880249322\"]],[\"invoke\",168,0,\"i64_load\",[\"i64:18446744073709551615\"]],[\"invoke\",169,0,\"i64_load\",[\"i64:18446744073667127374\"]],[\"invoke\",170,0,\"i64_load\",[\"i64:2880249322\"]],[\"invoke\",171,0,\"i64_load\",[\"i64:12370766947463011818\"]],[\"invoke\",173,0,\"f32_load\",[\"f32:3212836864\"]],[\"invoke\",174,0,\"f32_load\",[\"f32:1011494326\"]],[\"invoke\",175,0,\"f32_load\",[\"f32:1166316389\"]],[\"invoke\",176,0,\"f32_load\",[\"f32:2139095039\"]],[\"invoke\",178,0,\"f64_load\",[\"f64:13830554455654793216\"]],[\"invoke\",179,0,\"f64_load\",[\"f64:4653144502447687399\"]],[\"invoke\",180,0,\"f64_load\",[\"f64:4691032041816096430\"]],[\"invoke\",181,0,\"f64_load\",[\"f64:9218868437227405311\"]],[\"invoke\",184,0,\"i32_store16\",[\"i32:4294967295\"]],[\"invoke\",185,0,\"i32_store16\",[\"i32:4294963054\"]],[\"invoke\",186,0,\"i32_store16\",[\"i32:42\"]],[\"invoke\",187,0,\"i32_store16\",[\"i32:51966\"]],[\"invoke\",189,0,\"i32_store\",[\"i32:4294967295\"]],[\"invoke\",190,0,\"i32_store\",[\"i32:4294963054\"]],[\"invoke\",191,0,\"i32_store\",[\"i32:42424242\"]],[\"invoke\",192,0,\"i32_store\",[\"i32:3735931646\"]],[\"invoke\",194,0,\"i64_store16\",[\"i64:18446744073709551615\"]],[\"invoke\",195,0,\"i64_store16\",[\"i64:18446744073709547374\"]],[\"invoke\",196,0,\"i64_store16\",[\"i64:42\"]],[\"invoke\",197,0,\"i64_store16\",[\"i64:51966\"]],[\"invoke\",199,0,\"i64_store32\",[\"i64:18446744073709551615\"]],[\"invoke\",200,0,\"i64_store32\",[\"i64:18446744073709547374\"]],[\"invoke\",201,0,\"i64_store32\",[\"i64:42424242\"]],[\"invoke\",202,0,\"i64_store32\",[\"i64:3735931646\"]],[\"invoke\",204,0,\"i64_store\",[\"i64:18446744073709551615\"]],[\"invoke\",205,0,\"i64_store\",[\"i64:18446744073667127374\"]],[\"invoke\",206,0,\"i64_store\",[\"i64:2880249322\"]],[\"invoke\",207,0,\"i64_store\",[\"i64:12370766947463011818\"]],[\"invoke\",209,0,\"f32_store\",[\"f32:3212836864\"]],[\"invoke\",210,0,\"f32_store\",[\"f32:1011494326\"]],[\"invoke\",211,0,\"f32_store\",[\"f32:1166316389\"]],[\"invoke\",212,0,\"f32_store\",[\"f32:2139095039\"]],[\"invoke\",214,0,\"f64_store\",[\"f64:13830554455654793216\"]],[\"invoke\",215,0,\"f64_store\",[\"f64:4653144502447687399\"]],[\"invoke\",216,0,\"f64_store\",[\"f64:4691032041816096430\"]],[\"invoke\",217,0,\"f64_store\",[\"f64:9218868437227405311\"]],[\"file\",\"float_memory.wast\"],[\"module\",5,30],[\"invoke\",15,0,\"i32.load\",[]],[\"invoke\",16,0,\"f32.load\",[]],[\"invoke\",17,0,\"reset\",[]],[\"invoke\",18,0,\"i32.load\",[]],[\"invoke\",19,0,\"f32.load\",[]],[\"invoke\",20,0,\"f32.store\",[]],[\"invoke\",21,0,\"i32.load\",[]],[\"invoke\",22,0,\"f32.load\",[]],[\"invoke\",23,0,\"reset\",[]],[\"invoke\",24,0,\"i32.load\",[]],[\"invoke\",25,0,\"f32.load\",[]],[\"invoke\",26,0,\"i32.store\",[]],[\"invoke\",27,0,\"i32.load\",[]],[\"invoke\",28,0,\"f32.load\",[]],[\"module\",30,31],[\"invoke\",40,0,\"i64.load\",[]],[\"invoke\",41,0,\"f64.load\",[]],[\"invoke\",42,0,\"reset\",[]],[\"invoke\",43,0,\"i64.load\",[]],[\"invoke\",44,0,\"f64.load\",[]],[\"invoke\",45,0,\"f64.store\",[]],[\"invoke\",46,0,\"i64.load\",[]],[\"invoke\",47,0,\"f64.load\",[]],[\"invoke\",48,0,\"reset\",[]],[\"invoke\",49,0,\"i64.load\",[]],[\"invoke\",50,0,\"f64.load\",[]],[\"invoke\",51,0,\"i64.store\",[]],[\"invoke\",52,0,\"i64.load\",[]],[\"invoke\",53,0,\"f64.load\",[]],[\"module\",57,32],[\"invoke\",67,0,\"i32.load\",[]],[\"invoke\",68,0,\"f32.load\",[]],[\"invoke\",69,0,\"reset\",[]],[\"invoke\",70,0,\"i32.load\",[]],[\"invoke\",71,0,\"f32.load\",[]],[\"invoke\",72,0,\"f32.store\",[]],[\"invoke\",73,0,\"i32.load\",[]],[\"invoke\",74,0,\"f32.load\",[]],[\"invoke\",75,0,\"reset\",[]],[\"invoke\",76,0,\"i32.load\",[]],[\"invoke\",77,0,\"f32.load\",[]],[\"invoke\",78,0,\"i32.store\",[]],[\"invoke\",79,0,\"i32.load\",[]],[\"invoke\",80,0,\"f32.load\",[]],[\"module\",82,33],[\"invoke\",92,0,\"i64.load\",[]],[\"invoke\",93,0,\"f64.load\",[]],[\"invoke\",94,0,\"reset\",[]],[\"invoke\",95,0,\"i64.load\",[]],[\"invoke\",96,0,\"f64.load\",[]],[\"invoke\",97,0,\"f64.store\",[]],[\"invoke\",98,0,\"i64.load\",[]],[\"invoke\",99,0,\"f64.load\",[]],[\"invoke\",100,0,\"reset\",[]],[\"invoke\",101,0,\"i64.load\",[]],[\"invoke\",102,0,\"f64.load\",[]],[\"invoke\",103,0,\"i64.store\",[]],[\"invoke\",104,0,\"i64.load\",[]],[\"invoke\",105,0,\"f64.load\",[]],[\"module\",109,34],[\"invoke\",119,0,\"i32.load\",[]],[\"invoke\",120,0,\"f32.load\",[]],[\"invoke\",121,0,\"reset\",[]],[\"invoke\",122,0,\"i32.load\",[]],[\"invoke\",123,0,\"f32.load\",[]],[\"invoke\",124,0,\"f32.store\",[]],[\"invoke\",125,0,\"i32.load\",[]],[\"invoke\",126,0,\"f32.load\",[]],[\"invoke\",127,0,\"reset\",[]],[\"invoke\",128,0,\"i32.load\",[]],[\"invoke\",129,0,\"f32.load\",[]],[\"invoke\",130,0,\"i32.store\",[]],[\"invoke\",131,0,\"i32.load\",[]],[\"invoke\",132,0,\"f32.load\",[]],[\"module\",134,35],[\"invoke\",144,0,\"i64.load\",[]],[\"invoke\",145,0,\"f64.load\",[]],[\"invoke\",146,0,\"reset\",[]],[\"invoke\",147,0,\"i64.load\",[]],[\"invoke\",148,0,\"f64.load\",[]],[\"invoke\",149,0,\"f64.store\",[]],[\"invoke\",150,0,\"i64.load\",[]],[\"invoke\",151,0,\"f64.load\",[]],[\"invoke\",152,0,\"reset\",[]],[\"invoke\",153,0,\"i64.load\",[]],[\"invoke\",154,0,\"f64.load\",[]],[\"invoke\",155,0,\"i64.store\",[]],[\"invoke\",156,0,\"i64.load\",[]],[\"invoke\",157,0,\"f64.load\",[]],[\"file\",\"load.wast\"],[\"module\",3,36],[\"invoke\",161,0,\"as-br-value\",[]],[\"invoke\",163,0,\"as-br_if-cond\",[]],[\"invoke\",164,0,\"as-br_if-value\",[]],[\"invoke\",165,0,\"as-br_if-value-cond\",[]],[\"invoke\",167,0,\"as-br_table-index\",[]],[\"invoke\",168,0,\"as-br_table-value\",[]],[\"invoke\",169,0,\"as-br_table-value-index\",[]],[\"invoke\",171,0,\"as-return-value\",[]],[\"invoke\",173,0,\"as-if-cond\",[]],[\"invoke\",174,0,\"as-if-then\",[]],[\"invoke\",175,0,\"as-if-else\",[]],[\"invoke\",177,0,\"as-select-first\",[\"i32:0\",\"i32:1\"]],[\"invoke\",178,0,\"as-select-second\",[\"i32:0\",\"i32:0\"]],[\"invoke\",179,0,\"as-select-cond\",[]],[\"invoke\",181,0,\"as-call-first\",[]],[\"invoke\",182,0,\"as-call-mid\",[]],[\"invoke\",183,0,\"as-call-last\",[]],[\"invoke\",185,0,\"as-call_indirect-first\",[]],[\"invoke\",186,0,\"as-call_indirect-mid\",[]],[\"invoke\",187,0,\"as-call_indirect-last\",[]],[\"invoke\",188,0,\"as-call_indirect-index\",[]],[\"invoke\",190,0,\"as-local.set-value\",[]],[\"invoke\",191,0,\"as-local.tee-value\",[]],[\"invoke\",192,0,\"as-global.set-value\",[]],[\"invoke\",194,0,\"as-load-address\",[]],[\"invoke\",195,0,\"as-loadN-address\",[]],[\"invoke\",196,0,\"as-store-address\",[]],[\"invoke\",197,0,\"as-store-value\",[]],[\"invoke\",198,0,\"as-storeN-address\",[]],[\"invoke\",199,0,\"as-storeN-value\",[]],[\"invoke\",201,0,\"as-unary-operand\",[]],[\"invoke\",203,0,\"as-binary-left\",[]],[\"invoke\",204,0,\"as-binary-right\",[]],[\"invoke\",206,0,\"as-test-operand\",[]],[\"invoke\",208,0,\"as-compare-left\",[]],[\"invoke\",209,0,\"as-compare-right\",[]],[\"invoke\",211,0,\"as-memory.grow-size\",[]],[\"file\",\"store.wast\"],[\"module\",3,37],[\"invoke\",44,0,\"as-block-value\",[]],[\"invoke\",45,0,\"as-loop-value\",[]],[\"invoke\",47,0,\"as-br-value\",[]],[\"invoke\",48,0,\"as-br_if-value\",[]],[\"invoke\",49,0,\"as-br_if-value-cond\",[]],[\"invoke\",50,0,\"as-br_table-value\",[]],[\"invoke\",52,0,\"as-return-value\",[]],[\"invoke\",54,0,\"as-if-then\",[]],[\"invoke\",55,0,\"as-if-else\",[]],[\"file\",\"memory.wast\"],[\"module\",3,38],[\"module\",4,39],[\"module\",5,40],[\"module\",6,41],[\"module\",7,42],[\"module\",8,43],[\"module\",13,44],[\"invoke\",14,0,\"memsize\",[]],[\"module\",15,44],[\"invoke\",16,0,\"memsize\",[]],[\"module\",17,45],[\"invoke\",18,0,\"memsize\",[]],[\"module\",92,46],[\"invoke\",180,0,\"data\",[]],[\"invoke\",181,0,\"cast\",[]],[\"invoke\",183,0,\"i32_load8_s\",[\"i32:4294967295\"]],[\"invoke\",184,0,\"i32_load8_u\",[\"i32:4294967295\"]],[\"invoke\",185,0,\"i32_load16_s\",[\"i32:4294967295\"]],[\"invoke\",186,0,\"i32_load16_u\",[\"i32:4294967295\"]],[\"invoke\",188,0,\"i32_load8_s\",[\"i32:100\"]],[\"invoke\",189,0,\"i32_load8_u\",[\"i32:200\"]],[\"invoke\",190,0,\"i32_load16_s\",[\"i32:20000\"]],[\"invoke\",191,0,\"i32_load16_u\",[\"i32:40000\"]],[\"invoke\",193,0,\"i32_load8_s\",[\"i32:4275856707\"]],[\"invoke\",194,0,\"i32_load8_s\",[\"i32:878104047\"]],[\"invoke\",195,0,\"i32_load8_u\",[\"i32:4275856707\"]],[\"invoke\",196,0,\"i32_load8_u\",[\"i32:878104047\"]],[\"invoke\",197,0,\"i32_load16_s\",[\"i32:4275856707\"]],[\"invoke\",198,0,\"i32_load16_s\",[\"i32:878104047\"]],[\"invoke\",199,0,\"i32_load16_u\",[\"i32:4275856707\"]],[\"invoke\",200,0,\"i32_load16_u\",[\"i32:878104047\"]],[\"invoke\",202,0,\"i64_load8_s\",[\"i64:18446744073709551615\"]],[\"invoke\",203,0,\"i64_load8_u\",[\"i64:18446744073709551615\"]],[\"invoke\",204,0,\"i64_load16_s\",[\"i64:18446744073709551615\"]],[\"invoke\",205,0,\"i64_load16_u\",[\"i64:18446744073709551615\"]],[\"invoke\",206,0,\"i64_load32_s\",[\"i64:18446744073709551615\"]],[\"invoke\",207,0,\"i64_load32_u\",[\"i64:18446744073709551615\"]],[\"invoke\",209,0,\"i64_load8_s\",[\"i64:100\"]],[\"invoke\",210,0,\"i64_load8_u\",[\"i64:200\"]],[\"invoke\",211,0,\"i64_load16_s\",[\"i64:20000\"]],[\"invoke\",212,0,\"i64_load16_u\",[\"i64:40000\"]],[\"invoke\",213,0,\"i64_load32_s\",[\"i64:20000\"]],[\"invoke\",214,0,\"i64_load32_u\",[\"i64:40000\"]],[\"invoke\",216,0,\"i64_load8_s\",[\"i64:18364758543954109763\"]],[\"invoke\",217,0,\"i64_load8_s\",[\"i64:3771275841602506223\"]],[\"invoke\",218,0,\"i64_load8_u\",[\"i64:18364758543954109763\"]],[\"invoke\",219,0,\"i64_load8_u\",[\"i64:3771275841602506223\"]],[\"invoke\",220,0,\"i64_load16_s\",[\"i64:18364758543954109763\"]],[\"invoke\",221,0,\"i64_load16_s\",[\"i64:3771275841602506223\"]],[\"invoke\",222,0,\"i64_load16_u\",[\"i64:18364758543954109763\"]],[\"invoke\",223,0,\"i64_load16_u\",[\"i64:3771275841602506223\"]],[\"invoke\",224,0,\"i64_load32_s\",[\"i64:18364758543954109763\"]],[\"invoke\",225,0,\"i64_load32_s\",[\"i64:3771275841602506223\"]],[\"invoke\",226,0,\"i64_load32_u\",[\"i64:18364758543954109763\"]],[\"invoke\",227,0,\"i64_load32_u\",[\"i64:3771275841602506223\"]],[\"module\",246,47],[\"invoke\",260,0,\"load\",[\"i32:0\"]],[\"invoke\",261,0,\"load\",[\"i32:10000\"]],[\"invoke\",262,0,\"load\",[\"i32:20000\"]],[\"invoke\",263,0,\"load\",[\"i32:30000\"]],[\"invoke\",264,0,\"load\",[\"i32:40000\"]],[\"invoke\",265,0,\"load\",[\"i32:50000\"]],[\"invoke\",266,0,\"load\",[\"i32:60000\"]],[\"invoke\",267,0,\"load\",[\"i32:65535\"]],[\"file\",\"memory_grow.wast\"],[\"module\",1,48],[\"invoke\",14,0,\"size\",[]],[\"invoke\",15,0,\"store_at_zero\",[]],[\"invoke\",16,0,\"load_at_zero\",[]],[\"invoke\",17,0,\"store_at_page_size\",[]],[\"invoke\",18,0,\"load_at_page_size\",[]],[\"invoke\",19,0,\"grow\",[\"i32:1\"]],[\"invoke\",20,0,\"size\",[]],[\"invoke\",21,0,\"load_at_zero\",[]],[\"invoke\",22,0,\"store_at_zero\",[]],[\"invoke\",23,0,\"load_at_zero\",[]],[\"invoke\",24,0,\"store_at_page_size\",[]],[\"invoke\",25,0,\"load_at_page_size\",[]],[\"invoke\",26,0,\"grow\",[\"i32:4\"]],[\"invoke\",27,0,\"size\",[]],[\"invoke\",28,0,\"load_at_zero\",[]],[\"invoke\",29,0,\"store_at_zero\",[]],[\"invoke\",30,0,\"load_at_zero\",[]],[\"invoke\",31,0,\"load_at_page_size\",[]],[\"invoke\",32,0,\"store_at_page_size\",[]],[\"invoke\",33,0,\"load_at_page_size\",[]],[\"module\",36,49],[\"invoke\",41,0,\"grow\",[\"i32:0\"]],[\"invoke\",42,0,\"grow\",[\"i32:1\"]],[\"invoke\",43,0,\"grow\",[\"i32:0\"]],[\"invoke\",44,0,\"grow\",[\"i32:2\"]],[\"invoke\",45,0,\"grow\",[\"i32:800\"]],[\"invoke\",46,0,\"grow\",[\"i32:65536\"]],[\"invoke\",47,0,\"grow\",[\"i32:64736\"]],[\"invoke\",48,0,\"grow\",[\"i32:1\"]],[\"module\",50,50],[\"invoke\",55,0,\"grow\",[\"i32:0\"]],[\"invoke\",56,0,\"grow\",[\"i32:1\"]],[\"invoke\",57,0,\"grow\",[\"i32:1\"]],[\"invoke\",58,0,\"grow\",[\"i32:2\"]],[\"invoke\",59,0,\"grow\",[\"i32:6\"]],[\"invoke\",60,0,\"grow\",[\"i32:0\"]],[\"invoke\",61,0,\"grow\",[\"i32:1\"]],[\"invoke\",62,0,\"grow\",[\"i32:65536\"]],[\"module\",66,51],[\"invoke\",87,0,\"check-memory-zero\",[\"i32:0\",\"i32:65535\"]],[\"invoke\",88,0,\"grow\",[\"i32:1\"]],[\"invoke\",89,0,\"check-memory-zero\",[\"i32:65536\",\"i32:131071\"]],[\"invoke\",90,0,\"grow\",[\"i32:1\"]],[\"invoke\",91,0,\"check-memory-zero\",[\"i32:131072\",\"i32:196607\"]],[\"invoke\",92,0,\"grow\",[\"i32:1\"]],[\"invoke\",93,0,\"check-memory-zero\",[\"i32:196608\",\"i32:262143\"]],[\"invoke\",94,0,\"grow\",[\"i32:1\"]],[\"invoke\",95,0,\"check-memory-zero\",[\"i32:262144\",\"i32:327679\"]],[\"invoke\",96,0,\"grow\",[\"i32:1\"]],[\"invoke\",97,0,\"check-memory-zero\",[\"i32:327680\",\"i32:393215\"]],[\"module\",101,52],[\"invoke\",259,0,\"as-br-value\",[]],[\"invoke\",261,0,\"as-br_if-cond\",[]],[\"invoke\",262,0,\"as-br_if-value\",[]],[\"invoke\",263,0,\"as-br_if-value-cond\",[]],[\"invoke\",265,0,\"as-br_table-index\",[]],[\"invoke\",266,0,\"as-br_table-value\",[]],[\"invoke\",267,0,\"as-br_table-value-index\",[]],[\"invoke\",269,0,\"as-return-value\",[]],[\"invoke\",271,0,\"as-if-cond\",[]],[\"invoke\",272,0,\"as-if-then\",[]],[\"invoke\",273,0,\"as-if-else\",[]],[\"invoke\",275,0,\"as-select-first\",[\"i32:0\",\"i32:1\"]],[\"invoke\",276,0,\"as-select-second\",[\"i32:0\",\"i32:0\"]],[\"invoke\",277,0,\"as-select-cond\",[]],[\"invoke\",279,0,\"as-call-first\",[]],[\"invoke\",280,0,\"as-call-mid\",[]],[\"invoke\",281,0,\"as-call-last\",[]],[\"invoke\",283,0,\"as-call_indirect-first\",[]],[\"invoke\",284,0,\"as-call_indirect-mid\",[]],[\"invoke\",285,0,\"as-call_indirect-last\",[]],[\"invoke\",286,0,\"as-call_indirect-index\",[]],[\"invoke\",288,0,\"as-local.set-value\",[]],[\"invoke\",289,0,\"as-local.tee-value\",[]],[\"invoke\",290,0,\"as-global.set-value\",[]],[\"invoke\",292,0,\"as-load-address\",[]],[\"invoke\",293,0,\"as-loadN-address\",[]],[\"invoke\",294,0,\"as-store-address\",[]],[\"invoke\",295,0,\"as-store-value\",[]],[\"invoke\",296,0,\"as-storeN-address\",[]],[\"invoke\",297,0,\"as-storeN-value\",[]],[\"invoke\",299,0,\"as-unary-operand\",[]],[\"invoke\",301,0,\"as-binary-left\",[]],[\"invoke\",302,0,\"as-binary-right\",[]],[\"invoke\",304,0,\"as-test-operand\",[]],[\"invoke\",306,0,\"as-compare-left\",[]],[\"invoke\",307,0,\"as-compare-right\",[]],[\"invoke\",309,0,\"as-memory.grow-size\",[]],[\"module\",312,53,\"$Mgm\"],[\"register\",\"grown-memory\",\"$Mgm\"],[\"invoke\",317,\"$Mgm\",\"grow\",[]],[\"module\",318,54,\"$Mgim1\"],[\"register\",\"grown-imported-memory\",\"$Mgim1\"],[\"invoke\",324,\"$Mgim1\",\"grow\",[]],[\"module\",325,55,\"$Mgim2\"],[\"invoke\",330,\"$Mgim2\",\"size\",[]],[\"file\",\"memory_redundancy.wast\"],[\"module\",5,56],[\"invoke\",59,0,\"test_store_to_load\",[]],[\"invoke\",60,0,\"zero_everything\",[]],[\"invoke\",61,0,\"test_redundant_load\",[]],[\"invoke\",62,0,\"zero_everything\",[]],[\"invoke\",63,0,\"test_dead_store\",[]],[\"invoke\",64,0,\"zero_everything\",[]],[\"invoke\",65,0,\"malloc_aliasing\",[]],[\"file\",\"memory_size.wast\"],[\"module\",1,57],[\"invoke\",7,0,\"size\",[]],[\"invoke\",8,0,\"grow\",[\"i32:1\"]],[\"invoke\",9,0,\"size\",[]],[\"invoke\",10,0,\"grow\",[\"i32:4\"]],[\"invoke\",11,0,\"size\",[]],[\"invoke\",12,0,\"grow\",[\"i32:0\"]],[\"invoke\",13,0,\"size\",[]],[\"module\",15,58],[\"invoke\",21,0,\"size\",[]],[\"invoke\",22,0,\"grow\",[\"i32:1\"]],[\"invoke\",23,0,\"size\",[]],[\"invoke\",24,0,\"grow\",[\"i32:4\"]],[\"invoke\",25,0,\"size\",[]],[\"invoke\",26,0,\"grow\",[\"i32:0\"]],[\"invoke\",27,0,\"size\",[]],[\"module\",29,59],[\"invoke\",35,0,\"size\",[]],[\"invoke\",36,0,\"grow\",[\"i32:3\"]],[\"invoke\",37,0,\"size\",[]],[\"invoke\",38,0,\"grow\",[\"i32:1\"]],[\"invoke\",39,0,\"size\",[]],[\"invoke\",40,0,\"grow\",[\"i32:0\"]],[\"invoke\",41,0,\"size\",[]],[\"invoke\",42,0,\"grow\",[\"i32:4\"]],[\"invoke\",43,0,\"size\",[]],[\"invoke\",44,0,\"grow\",[\"i32:1\"]],[\"invoke\",45,0,\"size\",[]],[\"module\",47,60],[\"invoke\",53,0,\"size\",[]],[\"invoke\",54,0,\"grow\",[\"i32:1\"]],[\"invoke\",55,0,\"size\",[]],[\"invoke\",56,0,\"grow\",[\"i32:3\"]],[\"invoke\",57,0,\"size\",[]],[\"invoke\",58,0,\"grow\",[\"i32:0\"]],[\"invoke\",59,0,\"size\",[]],[\"invoke\",60,0,\"grow\",[\"i32:2\"]],[\"invoke\",61,0,\"size\",[]],[\"invoke\",62,0,\"grow\",[\"i32:1\"]],[\"invoke\",63,0,\"size\",[]],[\"file\",\"memory_trap.wast\"],[\"module\",1,61],[\"invoke\",21,0,\"store\",[\"i32:4294967292\",\"i32:42\"]],[\"invoke\",22,0,\"load\",[\"i32:4294967292\"]],[\"invoke\",23,0,\"store\",[\"i32:4294967293\",\"i32:305419896\"]],[\"invoke\",24,0,\"load\",[\"i32:4294967293\"]],[\"invoke\",25,0,\"store\",[\"i32:4294967294\",\"i32:13\"]],[\"invoke\",26,0,\"load\",[\"i32:4294967294\"]],[\"invoke\",27,0,\"store\",[\"i32:4294967295\",\"i32:13\"]],[\"invoke\",28,0,\"load\",[\"i32:4294967295\"]],[\"invoke\",29,0,\"store\",[\"i32:0\",\"i32:13\"]],[\"invoke\",30,0,\"load\",[\"i32:0\"]],[\"invoke\",31,0,\"store\",[\"i32:2147483648\",\"i32:13\"]],[\"invoke\",32,0,\"load\",[\"i32:2147483648\"]],[\"invoke\",33,0,\"memory.grow\",[\"i32:65537\"]],[\"module\",35,62],[\"invoke\",111,0,\"i32.store\",[\"i32:65536\",\"i32:0\"]],[\"invoke\",112,0,\"i32.store\",[\"i32:65535\",\"i32:0\"]],[\"invoke\",113,0,\"i32.store\",[\"i32:65534\",\"i32:0\"]],[\"invoke\",114,0,\"i32.store\",[\"i32:65533\",\"i32:0\"]],[\"invoke\",115,0,\"i32.store\",[\"i32:4294967295\",\"i32:0\"]],[\"invoke\",116,0,\"i32.store\",[\"i32:4294967294\",\"i32:0\"]],[\"invoke\",117,0,\"i32.store\",[\"i32:4294967293\",\"i32:0\"]],[\"invoke\",118,0,\"i32.store\",[\"i32:4294967292\",\"i32:0\"]],[\"invoke\",119,0,\"i64.store\",[\"i32:65536\",\"i64:0\"]],[\"invoke\",120,0,\"i64.store\",[\"i32:65535\",\"i64:0\"]],[\"invoke\",121,0,\"i64.store\",[\"i32:65534\",\"i64:0\"]],[\"invoke\",122,0,\"i64.store\",[\"i32:65533\",\"i64:0\"]],[\"invoke\",123,0,\"i64.store\",[\"i32:65532\",\"i64:0\"]],[\"invoke\",124,0,\"i64.store\",[\"i32:65531\",\"i64:0\"]],[\"invoke\",125,0,\"i64.store\",[\"i32:65530\",\"i64:0\"]],[\"invoke\",126,0,\"i64.store\",[\"i32:65529\",\"i64:0\"]],[\"invoke\",127,0,\"i64.store\",[\"i32:4294967295\",\"i64:0\"]],[\"invoke\",128,0,\"i64.store\",[\"i32:4294967294\",\"i64:0\"]],[\"invoke\",129,0,\"i64.store\",[\"i32:4294967293\",\"i64:0\"]],[\"invoke\",130,0,\"i64.store\",[\"i32:4294967292\",\"i64:0\"]],[\"invoke\",131,0,\"i64.store\",[\"i32:4294967291\",\"i64:0\"]],[\"invoke\",132,0,\"i64.store\",[\"i32:4294967290\",\"i64:0\"]],[\"invoke\",133,0,\"i64.store\",[\"i32:4294967289\",\"i64:0\"]],[\"invoke\",134,0,\"i64.store\",[\"i32:4294967288\",\"i64:0\"]],[\"invoke\",135,0,\"f32.store\",[\"i32:65536\",\"f32:0\"]],[\"invoke\",136,0,\"f32.store\",[\"i32:65535\",\"f32:0\"]],[\"invoke\",137,0,\"f32.store\",[\"i32:65534\",\"f32:0\"]],[\"invoke\",138,0,\"f32.store\",[\"i32:65533\",\"f32:0\"]],[\"invoke\",139,0,\"f32.store\",[\"i32:4294967295\",\"f32:0\"]],[\"invoke\",140,0,\"f32.store\",[\"i32:4294967294\",\"f32:0\"]],[\"invoke\",141,0,\"f32.store\",[\"i32:4294967293\",\"f32:0\"]],[\"invoke\",142,0,\"f32.store\",[\"i32:4294967292\",\"f32:0\"]],[\"invoke\",143,0,\"f64.store\",[\"i32:65536\",\"f64:0\"]],[\"invoke\",144,0,\"f64.store\",[\"i32:65535\",\"f64:0\"]],[\"invoke\",145,0,\"f64.store\",[\"i32:65534\",\"f64:0\"]],[\"invoke\",146,0,\"f64.store\",[\"i32:65533\",\"f64:0\"]],[\"invoke\",147,0,\"f64.store\",[\"i32:65532\",\"f64:0\"]],[\"invoke\",148,0,\"f64.store\",[\"i32:65531\",\"f64:0\"]],[\"invoke\",149,0,\"f64.store\",[\"i32:65530\",\"f64:0\"]],[\"invoke\",150,0,\"f64.store\",[\"i32:65529\",\"f64:0\"]],[\"invoke\",151,0,\"f64.store\",[\"i32:4294967295\",\"f64:0\"]],[\"invoke\",152,0,\"f64.store\",[\"i32:4294967294\",\"f64:0\"]],[\"invoke\",153,0,\"f64.store\",[\"i32:4294967293\",\"f64:0\"]],[\"invoke\",154,0,\"f64.store\",[\"i32:4294967292\",\"f64:0\"]],[\"invoke\",155,0,\"f64.store\",[\"i32:4294967291\",\"f64:0\"]],[\"invoke\",156,0,\"f64.store\",[\"i32:4294967290\",\"f64:0\"]],[\"invoke\",157,0,\"f64.store\",[\"i32:4294967289\",\"f64:0\"]],[\"invoke\",158,0,\"f64.store\",[\"i32:4294967288\",\"f64:0\"]],[\"invoke\",159,0,\"i32.store8\",[\"i32:65536\",\"i32:0\"]],[\"invoke\",160,0,\"i32.store8\",[\"i32:4294967295\",\"i32:0\"]],[\"invoke\",161,0,\"i32.store16\",[\"i32:65536\",\"i32:0\"]],[\"invoke\",162,0,\"i32.store16\",[\"i32:65535\",\"i32:0\"]],[\"invoke\",163,0,\"i32.store16\",[\"i32:4294967295\",\"i32:0\"]],[\"invoke\",164,0,\"i32.store16\",[\"i32:4294967294\",\"i32:0\"]],[\"invoke\",165,0,\"i64.store8\",[\"i32:65536\",\"i64:0\"]],[\"invoke\",166,0,\"i64.store8\",[\"i32:4294967295\",\"i64:0\"]],[\"invoke\",167,0,\"i64.store16\",[\"i32:65536\",\"i64:0\"]],[\"invoke\",168,0,\"i64.store16\",[\"i32:65535\",\"i64:0\"]],[\"invoke\",169,0,\"i64.store16\",[\"i32:4294967295\",\"i64:0\"]],[\"invoke\",170,0,\"i64.store16\",[\"i32:4294967294\",\"i64:0\"]],[\"invoke\",171,0,\"i64.store32\",[\"i32:65536\",\"i64:0\"]],[\"invoke\",172,0,\"i64.store32\",[\"i32:65535\",\"i64:0\"]],[\"invoke\",173,0,\"i64.store32\",[\"i32:65534\",\"i64:0\"]],[\"invoke\",174,0,\"i64.store32\",[\"i32:65533\",\"i64:0\"]],[\"invoke\",175,0,\"i64.store32\",[\"i32:4294967295\",\"i64:0\"]],[\"invoke\",176,0,\"i64.store32\",[\"i32:4294967294\",\"i64:0\"]],[\"invoke\",177,0,\"i64.store32\",[\"i32:4294967293\",\"i64:0\"]],[\"invoke\",178,0,\"i64.store32\",[\"i32:4294967292\",\"i64:0\"]],[\"invoke\",179,0,\"i32.load\",[\"i32:65536\"]],[\"invoke\",180,0,\"i32.load\",[\"i32:65535\"]],[\"invoke\",181,0,\"i32.load\",[\"i32:65534\"]],[\"invoke\",182,0,\"i32.load\",[\"i32:65533\"]],[\"invoke\",183,0,\"i32.load\",[\"i32:4294967295\"]],[\"invoke\",184,0,\"i32.load\",[\"i32:4294967294\"]],[\"invoke\",185,0,\"i32.load\",[\"i32:4294967293\"]],[\"invoke\",186,0,\"i32.load\",[\"i32:4294967292\"]],[\"invoke\",187,0,\"i64.load\",[\"i32:65536\"]],[\"invoke\",188,0,\"i64.load\",[\"i32:65535\"]],[\"invoke\",189,0,\"i64.load\",[\"i32:65534\"]],[\"invoke\",190,0,\"i64.load\",[\"i32:65533\"]],[\"invoke\",191,0,\"i64.load\",[\"i32:65532\"]],[\"invoke\",192,0,\"i64.load\",[\"i32:65531\"]],[\"invoke\",193,0,\"i64.load\",[\"i32:65530\"]],[\"invoke\",194,0,\"i64.load\",[\"i32:65529\"]],[\"invoke\",195,0,\"i64.load\",[\"i32:4294967295\"]],[\"invoke\",196,0,\"i64.load\",[\"i32:4294967294\"]],[\"invoke\",197,0,\"i64.load\",[\"i32:4294967293\"]],[\"invoke\",198,0,\"i64.load\",[\"i32:4294967292\"]],[\"invoke\",199,0,\"i64.load\",[\"i32:4294967291\"]],[\"invoke\",200,0,\"i64.load\",[\"i32:4294967290\"]],[\"invoke\",201,0,\"i64.load\",[\"i32:4294967289\"]],[\"invoke\",202,0,\"i64.load\",[\"i32:4294967288\"]],[\"invoke\",203,0,\"f32.load\",[\"i32:65536\"]],[\"invoke\",204,0,\"f32.load\",[\"i32:65535\"]],[\"invoke\",205,0,\"f32.load\",[\"i32:65534\"]],[\"invoke\",206,0,\"f32.load\",[\"i32:65533\"]],[\"invoke\",207,0,\"f32.load\",[\"i32:4294967295\"]],[\"invoke\",208,0,\"f32.load\",[\"i32:4294967294\"]],[\"invoke\",209,0,\"f32.load\",[\"i32:4294967293\"]],[\"invoke\",210,0,\"f32.load\",[\"i32:4294967292\"]],[\"invoke\",211,0,\"f64.load\",[\"i32:65536\"]],[\"invoke\",212,0,\"f64.load\",[\"i32:65535\"]],[\"invoke\",213,0,\"f64.load\",[\"i32:65534\"]],[\"invoke\",214,0,\"f64.load\",[\"i32:65533\"]],[\"invoke\",215,0,\"f64.load\",[\"i32:65532\"]],[\"invoke\",216,0,\"f64.load\",[\"i32:65531\"]],[\"invoke\",217,0,\"f64.load\",[\"i32:65530\"]],[\"invoke\",218,0,\"f64.load\",[\"i32:65529\"]],[\"invoke\",219,0,\"f64.load\",[\"i32:4294967295\"]],[\"invoke\",220,0,\"f64.load\",[\"i32:4294967294\"]],[\"invoke\",221,0,\"f64.load\",[\"i32:4294967293\"]],[\"invoke\",222,0,\"f64.load\",[\"i32:4294967292\"]],[\"invoke\",223,0,\"f64.load\",[\"i32:4294967291\"]],[\"invoke\",224,0,\"f64.load\",[\"i32:4294967290\"]],[\"invoke\",225,0,\"f64.load\",[\"i32:4294967289\"]],[\"invoke\",226,0,\"f64.load\",[\"i32:4294967288\"]],[\"invoke\",227,0,\"i32.load8_s\",[\"i32:65536\"]],[\"invoke\",228,0,\"i32.load8_s\",[\"i32:4294967295\"]],[\"invoke\",229,0,\"i32.load8_u\",[\"i32:65536\"]],[\"invoke\",230,0,\"i32.load8_u\",[\"i32:4294967295\"]],[\"invoke\",231,0,\"i32.load16_s\",[\"i32:65536\"]],[\"invoke\",232,0,\"i32.load16_s\",[\"i32:65535\"]],[\"invoke\",233,0,\"i32.load16_s\",[\"i32:4294967295\"]],[\"invoke\",234,0,\"i32.load16_s\",[\"i32:4294967294\"]],[\"invoke\",235,0,\"i32.load16_u\",[\"i32:65536\"]],[\"invoke\",236,0,\"i32.load16_u\",[\"i32:65535\"]],[\"invoke\",237,0,\"i32.load16_u\",[\"i32:4294967295\"]],[\"invoke\",238,0,\"i32.load16_u\",[\"i32:4294967294\"]],[\"invoke\",239,0,\"i64.load8_s\",[\"i32:65536\"]],[\"invoke\",240,0,\"i64.load8_s\",[\"i32:4294967295\"]],[\"invoke\",241,0,\"i64.load8_u\",[\"i32:65536\"]],[\"invoke\",242,0,\"i64.load8_u\",[\"i32:4294967295\"]],[\"invoke\",243,0,\"i64.load16_s\",[\"i32:65536\"]],[\"invoke\",244,0,\"i64.load16_s\",[\"i32:65535\"]],[\"invoke\",245,0,\"i64.load16_s\",[\"i32:4294967295\"]],[\"invoke\",246,0,\"i64.load16_s\",[\"i32:4294967294\"]],[\"invoke\",247,0,\"i64.load16_u\",[\"i32:65536\"]],[\"invoke\",248,0,\"i64.load16_u\",[\"i32:65535\"]],[\"invoke\",249,0,\"i64.load16_u\",[\"i32:4294967295\"]],[\"invoke\",250,0,\"i64.load16_u\",[\"i32:4294967294\"]],[\"invoke\",251,0,\"i64.load32_s\",[\"i32:65536\"]],[\"invoke\",252,0,\"i64.load32_s\",[\"i32:65535\"]],[\"invoke\",253,0,\"i64.load32_s\",[\"i32:65534\"]],[\"invoke\",254,0,\"i64.load32_s\",[\"i32:65533\"]],[\"invoke\",255,0,\"i64.load32_s\",[\"i32:4294967295\"]],[\"invoke\",256,0,\"i64.load32_s\",[\"i32:4294967294\"]],[\"invoke\",257,0,\"i64.load32_s\",[\"i32:4294967293\"]],[\"invoke\",258,0,\"i64.load32_s\",[\"i32:4294967292\"]],[\"invoke\",259,0,\"i64.load32_u\",[\"i32:65536\"]],[\"invoke\",260,0,\"i64.load32_u\",[\"i32:65535\"]],[\"invoke\",261,0,\"i64.load32_u\",[\"i32:65534\"]],[\"invoke\",262,0,\"i64.load32_u\",[\"i32:65533\"]],[\"invoke\",263,0,\"i64.load32_u\",[\"i32:4294967295\"]],[\"invoke\",264,0,\"i64.load32_u\",[\"i32:4294967294\"]],[\"invoke\",265,0,\"i64.load32_u\",[\"i32:4294967293\"]],[\"invoke\",266,0,\"i64.load32_u\",[\"i32:4294967292\"]],[\"invoke\",269,0,\"i64.load\",[\"i32:65528\"]],[\"invoke\",270,0,\"i64.load\",[\"i32:0\"]],[\"invoke\",274,0,\"i64.store\",[\"i32:65528\",\"i64:0\"]],[\"invoke\",275,0,\"i32.store\",[\"i32:65533\",\"i32:305419896\"]],[\"invoke\",276,0,\"i32.load\",[\"i32:65532\"]],[\"invoke\",277,0,\"i64.store\",[\"i32:65529\",\"i64:1311768467294899695\"]],[\"invoke\",278,0,\"i64.load\",[\"i32:65528\"]],[\"invoke\",279,0,\"f32.store\",[\"i32:65533\",\"f32:1301390004\"]],[\"invoke\",280,0,\"f32.load\",[\"i32:65532\"]],[\"invoke\",281,0,\"f64.store\",[\"i32:65529\",\"f64:4878018892390247374\"]],[\"invoke\",282,0,\"f64.load\",[\"i32:65528\"]],[\"file\",\"bulk.wast\"],[\"module\",2,63],[\"module\",6,64],[\"module\",13,65],[\"invoke\",27,0,\"fill\",[\"i32:1\",\"i32:255\",\"i32:3\"]],[\"invoke\",28,0,\"load8_u\",[\"i32:0\"]],[\"invoke\",29,0,\"load8_u\",[\"i32:1\"]],[\"invoke\",30,0,\"load8_u\",[\"i32:2\"]],[\"invoke\",31,0,\"load8_u\",[\"i32:3\"]],[\"invoke\",32,0,\"load8_u\",[\"i32:4\"]],[\"invoke\",35,0,\"fill\",[\"i32:0\",\"i32:48042\",\"i32:2\"]],[\"invoke\",36,0,\"load8_u\",[\"i32:0\"]],[\"invoke\",37,0,\"load8_u\",[\"i32:1\"]],[\"invoke\",40,0,\"fill\",[\"i32:0\",\"i32:0\",\"i32:65536\"]],[\"invoke\",43,0,\"fill\",[\"i32:65280\",\"i32:1\",\"i32:257\"]],[\"invoke\",45,0,\"load8_u\",[\"i32:65280\"]],[\"invoke\",46,0,\"load8_u\",[\"i32:65535\"]],[\"invoke\",49,0,\"fill\",[\"i32:65536\",\"i32:0\",\"i32:0\"]],[\"invoke\",52,0,\"fill\",[\"i32:65537\",\"i32:0\",\"i32:0\"]],[\"module\",57,66],[\"invoke\",71,0,\"copy\",[\"i32:10\",\"i32:0\",\"i32:4\"]],[\"invoke\",73,0,\"load8_u\",[\"i32:9\"]],[\"invoke\",74,0,\"load8_u\",[\"i32:10\"]],[\"invoke\",75,0,\"load8_u\",[\"i32:11\"]],[\"invoke\",76,0,\"load8_u\",[\"i32:12\"]],[\"invoke\",77,0,\"load8_u\",[\"i32:13\"]],[\"invoke\",78,0,\"load8_u\",[\"i32:14\"]],[\"invoke\",81,0,\"copy\",[\"i32:8\",\"i32:10\",\"i32:4\"]],[\"invoke\",82,0,\"load8_u\",[\"i32:8\"]],[\"invoke\",83,0,\"load8_u\",[\"i32:9\"]],[\"invoke\",84,0,\"load8_u\",[\"i32:10\"]],[\"invoke\",85,0,\"load8_u\",[\"i32:11\"]],[\"invoke\",86,0,\"load8_u\",[\"i32:12\"]],[\"invoke\",87,0,\"load8_u\",[\"i32:13\"]],[\"invoke\",90,0,\"copy\",[\"i32:10\",\"i32:7\",\"i32:6\"]],[\"invoke\",91,0,\"load8_u\",[\"i32:10\"]],[\"invoke\",92,0,\"load8_u\",[\"i32:11\"]],[\"invoke\",93,0,\"load8_u\",[\"i32:12\"]],[\"invoke\",94,0,\"load8_u\",[\"i32:13\"]],[\"invoke\",95,0,\"load8_u\",[\"i32:14\"]],[\"invoke\",96,0,\"load8_u\",[\"i32:15\"]],[\"invoke\",97,0,\"load8_u\",[\"i32:16\"]],[\"invoke\",100,0,\"copy\",[\"i32:65280\",\"i32:0\",\"i32:256\"]],[\"invoke\",101,0,\"copy\",[\"i32:65024\",\"i32:65280\",\"i32:256\"]],[\"invoke\",104,0,\"copy\",[\"i32:65536\",\"i32:0\",\"i32:0\"]],[\"invoke\",105,0,\"copy\",[\"i32:0\",\"i32:65536\",\"i32:0\"]],[\"invoke\",108,0,\"copy\",[\"i32:65537\",\"i32:0\",\"i32:0\"]],[\"invoke\",110,0,\"copy\",[\"i32:0\",\"i32:65537\",\"i32:0\"]],[\"module\",115,67],[\"invoke\",129,0,\"init\",[\"i32:0\",\"i32:1\",\"i32:2\"]],[\"invoke\",130,0,\"load8_u\",[\"i32:0\"]],[\"invoke\",131,0,\"load8_u\",[\"i32:1\"]],[\"invoke\",132,0,\"load8_u\",[\"i32:2\"]],[\"invoke\",135,0,\"init\",[\"i32:65532\",\"i32:0\",\"i32:4\"]],[\"invoke\",138,0,\"init\",[\"i32:65534\",\"i32:0\",\"i32:3\"]],[\"invoke\",140,0,\"load8_u\",[\"i32:65534\"]],[\"invoke\",141,0,\"load8_u\",[\"i32:65535\"]],[\"invoke\",144,0,\"init\",[\"i32:65536\",\"i32:0\",\"i32:0\"]],[\"invoke\",145,0,\"init\",[\"i32:0\",\"i32:4\",\"i32:0\"]],[\"invoke\",148,0,\"init\",[\"i32:65537\",\"i32:0\",\"i32:0\"]],[\"invoke\",150,0,\"init\",[\"i32:0\",\"i32:5\",\"i32:0\"]],[\"module\",154,68],[\"invoke\",168,0,\"init_passive\",[\"i32:1\"]],[\"invoke\",169,0,\"drop_passive\",[]],[\"invoke\",170,0,\"drop_passive\",[]],[\"invoke\",171,0,\"init_passive\",[\"i32:0\"]],[\"invoke\",172,0,\"init_passive\",[\"i32:1\"]],[\"invoke\",173,0,\"init_passive\",[\"i32:0\"]],[\"invoke\",174,0,\"drop_active\",[]],[\"invoke\",175,0,\"init_active\",[\"i32:0\"]],[\"invoke\",176,0,\"init_active\",[\"i32:1\"]],[\"invoke\",177,0,\"init_active\",[\"i32:0\"]],[\"module\",181,69],[\"module\",196,70],[\"module\",199,71],[\"invoke\",219,0,\"init\",[\"i32:2\",\"i32:0\",\"i32:2\"]],[\"invoke\",221,0,\"call\",[\"i32:2\"]],[\"invoke\",224,0,\"init\",[\"i32:0\",\"i32:1\",\"i32:2\"]],[\"invoke\",225,0,\"call\",[\"i32:0\"]],[\"invoke\",226,0,\"call\",[\"i32:1\"]],[\"invoke\",227,0,\"call\",[\"i32:2\"]],[\"invoke\",230,0,\"init\",[\"i32:1\",\"i32:2\",\"i32:2\"]],[\"invoke\",233,0,\"init\",[\"i32:3\",\"i32:0\",\"i32:0\"]],[\"invoke\",234,0,\"init\",[\"i32:0\",\"i32:4\",\"i32:0\"]],[\"invoke\",237,0,\"init\",[\"i32:4\",\"i32:0\",\"i32:0\"]],[\"invoke\",239,0,\"init\",[\"i32:0\",\"i32:5\",\"i32:0\"]],[\"module\",244,72],[\"invoke\",261,0,\"init_passive\",[\"i32:1\"]],[\"invoke\",262,0,\"drop_passive\",[]],[\"invoke\",263,0,\"drop_passive\",[]],[\"invoke\",264,0,\"init_passive\",[\"i32:0\"]],[\"invoke\",265,0,\"init_passive\",[\"i32:1\"]],[\"invoke\",266,0,\"init_passive\",[\"i32:0\"]],[\"invoke\",267,0,\"drop_active\",[]],[\"invoke\",268,0,\"init_active\",[\"i32:0\"]],[\"invoke\",269,0,\"init_active\",[\"i32:1\"]],[\"invoke\",270,0,\"init_active\",[\"i32:0\"]],[\"module\",274,73],[\"module\",297,74],[\"module\",300,75],[\"invoke\",319,0,\"copy\",[\"i32:3\",\"i32:0\",\"i32:3\"]],[\"invoke\",321,0,\"call\",[\"i32:3\"]],[\"invoke\",322,0,\"call\",[\"i32:4\"]],[\"invoke\",323,0,\"call\",[\"i32:5\"]],[\"invoke\",326,0,\"copy\",[\"i32:0\",\"i32:1\",\"i32:3\"]],[\"invoke\",328,0,\"call\",[\"i32:0\"]],[\"invoke\",329,0,\"call\",[\"i32:1\"]],[\"invoke\",330,0,\"call\",[\"i32:2\"]],[\"invoke\",333,0,\"copy\",[\"i32:2\",\"i32:0\",\"i32:3\"]],[\"invoke\",335,0,\"call\",[\"i32:2\"]],[\"invoke\",336,0,\"call\",[\"i32:3\"]],[\"invoke\",337,0,\"call\",[\"i32:4\"]],[\"invoke\",340,0,\"copy\",[\"i32:6\",\"i32:8\",\"i32:2\"]],[\"invoke\",341,0,\"copy\",[\"i32:8\",\"i32:6\",\"i32:2\"]],[\"invoke\",344,0,\"copy\",[\"i32:10\",\"i32:0\",\"i32:0\"]],[\"invoke\",345,0,\"copy\",[\"i32:0\",\"i32:10\",\"i32:0\"]],[\"invoke\",348,0,\"copy\",[\"i32:11\",\"i32:0\",\"i32:0\"]],[\"invoke\",350,0,\"copy\",[\"i32:0\",\"i32:11\",\"i32:0\"]],[\"file\",\"memory_copy.wast\"],[\"module\",6,76],[\"invoke\",15,0,\"test\",[]],[\"invoke\",27,0,\"load8_u\",[\"i32:10\"]],[\"invoke\",38,0,\"load8_u\",[\"i32:21\"]],[\"module\",48,77],[\"invoke\",60,0,\"load8_u\",[\"i32:1\"]],[\"invoke\",71,0,\"load8_u\",[\"i32:12\"]],[\"invoke\",82,0,\"load8_u\",[\"i32:23\"]],[\"module\",90,78],[\"invoke\",104,0,\"load8_u\",[\"i32:3\"]],[\"invoke\",115,0,\"load8_u\",[\"i32:14\"]],[\"invoke\",126,0,\"load8_u\",[\"i32:25\"]],[\"module\",132,79],[\"invoke\",147,0,\"load8_u\",[\"i32:4\"]],[\"invoke\",158,0,\"load8_u\",[\"i32:15\"]],[\"invoke\",169,0,\"load8_u\",[\"i32:26\"]],[\"module\",174,80],[\"invoke\",191,0,\"load8_u\",[\"i32:6\"]],[\"invoke\",202,0,\"load8_u\",[\"i32:17\"]],[\"invoke\",213,0,\"load8_u\",[\"i32:28\"]],[\"module\",216,81],[\"invoke\",235,0,\"load8_u\",[\"i32:8\"]],[\"invoke\",246,0,\"load8_u\",[\"i32:19\"]],[\"module\",258,82],[\"invoke\",267,0,\"test\",[]],[\"invoke\",278,0,\"load8_u\",[\"i32:9\"]],[\"invoke\",289,0,\"load8_u\",[\"i32:20\"]],[\"module\",300,83],[\"invoke\",311,0,\"load8_u\",[\"i32:0\"]],[\"invoke\",322,0,\"load8_u\",[\"i32:11\"]],[\"invoke\",333,0,\"load8_u\",[\"i32:22\"]],[\"module\",342,84],[\"invoke\",355,0,\"load8_u\",[\"i32:2\"]],[\"invoke\",366,0,\"load8_u\",[\"i32:13\"]],[\"invoke\",377,0,\"load8_u\",[\"i32:1014\"]],[\"invoke\",387,0,\"load8_u\",[\"i32:3004\"]],[\"invoke\",398,0,\"load8_u\",[\"i32:5193\"]],[\"invoke\",409,0,\"load8_u\",[\"i32:7382\"]],[\"invoke\",420,0,\"load8_u\",[\"i32:9571\"]],[\"invoke\",431,0,\"load8_u\",[\"i32:11760\"]],[\"invoke\",442,0,\"load8_u\",[\"i32:13949\"]],[\"invoke\",453,0,\"load8_u\",[\"i32:16138\"]],[\"invoke\",464,0,\"load8_u\",[\"i32:18327\"]],[\"invoke\",475,0,\"load8_u\",[\"i32:20516\"]],[\"invoke\",485,0,\"load8_u\",[\"i32:22506\"]],[\"invoke\",496,0,\"load8_u\",[\"i32:24695\"]],[\"invoke\",507,0,\"load8_u\",[\"i32:26884\"]],[\"invoke\",518,0,\"load8_u\",[\"i32:29073\"]],[\"invoke\",529,0,\"load8_u\",[\"i32:31262\"]],[\"invoke\",540,0,\"load8_u\",[\"i32:33451\"]],[\"invoke\",551,0,\"load8_u\",[\"i32:35640\"]],[\"invoke\",562,0,\"load8_u\",[\"i32:37829\"]],[\"invoke\",572,0,\"load8_u\",[\"i32:39819\"]],[\"invoke\",583,0,\"load8_u\",[\"i32:42008\"]],[\"invoke\",594,0,\"load8_u\",[\"i32:44197\"]],[\"invoke\",605,0,\"load8_u\",[\"i32:46386\"]],[\"invoke\",616,0,\"load8_u\",[\"i32:48575\"]],[\"invoke\",627,0,\"load8_u\",[\"i32:50764\"]],[\"invoke\",638,0,\"load8_u\",[\"i32:52953\"]],[\"invoke\",649,0,\"load8_u\",[\"i32:55142\"]],[\"invoke\",660,0,\"load8_u\",[\"i32:57331\"]],[\"invoke\",670,0,\"load8_u\",[\"i32:59321\"]],[\"invoke\",681,0,\"load8_u\",[\"i32:61510\"]],[\"invoke\",692,0,\"load8_u\",[\"i32:63699\"]],[\"module\",703,85],[\"invoke\",714,0,\"load8_u\",[\"i32:0\"]],[\"invoke\",725,0,\"load8_u\",[\"i32:11\"]],[\"invoke\",736,0,\"load8_u\",[\"i32:418\"]],[\"invoke\",747,0,\"load8_u\",[\"i32:2607\"]],[\"invoke\",758,0,\"load8_u\",[\"i32:4796\"]],[\"invoke\",768,0,\"load8_u\",[\"i32:6786\"]],[\"invoke\",779,0,\"load8_u\",[\"i32:8975\"]],[\"invoke\",790,0,\"load8_u\",[\"i32:11164\"]],[\"invoke\",801,0,\"load8_u\",[\"i32:13353\"]],[\"invoke\",812,0,\"load8_u\",[\"i32:15542\"]],[\"invoke\",823,0,\"load8_u\",[\"i32:17731\"]],[\"invoke\",834,0,\"load8_u\",[\"i32:19920\"]],[\"invoke\",845,0,\"load8_u\",[\"i32:22109\"]],[\"invoke\",856,0,\"load8_u\",[\"i32:24298\"]],[\"invoke\",866,0,\"load8_u\",[\"i32:26288\"]],[\"invoke\",877,0,\"load8_u\",[\"i32:28477\"]],[\"invoke\",888,0,\"load8_u\",[\"i32:30666\"]],[\"invoke\",899,0,\"load8_u\",[\"i32:32855\"]],[\"invoke\",910,0,\"load8_u\",[\"i32:35044\"]],[\"invoke\",921,0,\"load8_u\",[\"i32:37233\"]],[\"invoke\",932,0,\"load8_u\",[\"i32:39422\"]],[\"invoke\",943,0,\"load8_u\",[\"i32:41611\"]],[\"invoke\",953,0,\"load8_u\",[\"i32:43601\"]],[\"invoke\",964,0,\"load8_u\",[\"i32:45790\"]],[\"invoke\",975,0,\"load8_u\",[\"i32:47979\"]],[\"invoke\",986,0,\"load8_u\",[\"i32:50168\"]],[\"invoke\",997,0,\"load8_u\",[\"i32:52357\"]],[\"invoke\",1008,0,\"load8_u\",[\"i32:54546\"]],[\"invoke\",1019,0,\"load8_u\",[\"i32:56735\"]],[\"invoke\",1030,0,\"load8_u\",[\"i32:58924\"]],[\"invoke\",1041,0,\"load8_u\",[\"i32:61113\"]],[\"invoke\",1051,0,\"load8_u\",[\"i32:63103\"]],[\"invoke\",1062,0,\"load8_u\",[\"i32:65292\"]],[\"module\",1065,86],[\"invoke\",1084,0,\"load8_u\",[\"i32:1790\"]],[\"invoke\",1095,0,\"load8_u\",[\"i32:3979\"]],[\"invoke\",1106,0,\"load8_u\",[\"i32:6168\"]],[\"invoke\",1117,0,\"load8_u\",[\"i32:8357\"]],[\"invoke\",1128,0,\"load8_u\",[\"i32:10546\"]],[\"invoke\",1139,0,\"load8_u\",[\"i32:12735\"]],[\"invoke\",1149,0,\"load8_u\",[\"i32:14725\"]],[\"invoke\",1160,0,\"load8_u\",[\"i32:16914\"]],[\"invoke\",1171,0,\"load8_u\",[\"i32:19103\"]],[\"invoke\",1182,0,\"load8_u\",[\"i32:21292\"]],[\"invoke\",1193,0,\"load8_u\",[\"i32:23481\"]],[\"invoke\",1204,0,\"load8_u\",[\"i32:25670\"]],[\"invoke\",1215,0,\"load8_u\",[\"i32:27859\"]],[\"invoke\",1226,0,\"load8_u\",[\"i32:30048\"]],[\"invoke\",1237,0,\"load8_u\",[\"i32:32237\"]],[\"invoke\",1247,0,\"load8_u\",[\"i32:34227\"]],[\"invoke\",1258,0,\"load8_u\",[\"i32:36416\"]],[\"invoke\",1269,0,\"load8_u\",[\"i32:38605\"]],[\"invoke\",1280,0,\"load8_u\",[\"i32:40794\"]],[\"invoke\",1291,0,\"load8_u\",[\"i32:42983\"]],[\"invoke\",1302,0,\"load8_u\",[\"i32:45172\"]],[\"invoke\",1313,0,\"load8_u\",[\"i32:47361\"]],[\"invoke\",1324,0,\"load8_u\",[\"i32:49550\"]],[\"invoke\",1334,0,\"load8_u\",[\"i32:51540\"]],[\"invoke\",1345,0,\"load8_u\",[\"i32:53729\"]],[\"invoke\",1356,0,\"load8_u\",[\"i32:55918\"]],[\"invoke\",1367,0,\"load8_u\",[\"i32:58107\"]],[\"invoke\",1378,0,\"load8_u\",[\"i32:60296\"]],[\"invoke\",1389,0,\"load8_u\",[\"i32:62485\"]],[\"invoke\",1400,0,\"load8_u\",[\"i32:64674\"]],[\"invoke\",1411,0,\"load8_u\",[\"i32:65522\"]],[\"invoke\",1422,0,\"load8_u\",[\"i32:65533\"]],[\"module\",1426,87],[\"invoke\",1443,0,\"load8_u\",[\"i32:1392\"]],[\"invoke\",1454,0,\"load8_u\",[\"i32:3581\"]],[\"invoke\",1465,0,\"load8_u\",[\"i32:5770\"]],[\"invoke\",1476,0,\"load8_u\",[\"i32:7959\"]],[\"invoke\",1487,0,\"load8_u\",[\"i32:10148\"]],[\"invoke\",1498,0,\"load8_u\",[\"i32:12337\"]],[\"invoke\",1509,0,\"load8_u\",[\"i32:14526\"]],[\"invoke\",1520,0,\"load8_u\",[\"i32:16715\"]],[\"invoke\",1530,0,\"load8_u\",[\"i32:18705\"]],[\"invoke\",1541,0,\"load8_u\",[\"i32:20894\"]],[\"invoke\",1552,0,\"load8_u\",[\"i32:23083\"]],[\"invoke\",1563,0,\"load8_u\",[\"i32:25272\"]],[\"invoke\",1574,0,\"load8_u\",[\"i32:27461\"]],[\"invoke\",1585,0,\"load8_u\",[\"i32:29650\"]],[\"invoke\",1596,0,\"load8_u\",[\"i32:31839\"]],[\"invoke\",1607,0,\"load8_u\",[\"i32:34028\"]],[\"invoke\",1618,0,\"load8_u\",[\"i32:36217\"]],[\"invoke\",1628,0,\"load8_u\",[\"i32:38207\"]],[\"invoke\",1639,0,\"load8_u\",[\"i32:40396\"]],[\"invoke\",1650,0,\"load8_u\",[\"i32:42585\"]],[\"invoke\",1661,0,\"load8_u\",[\"i32:44774\"]],[\"invoke\",1672,0,\"load8_u\",[\"i32:46963\"]],[\"invoke\",1683,0,\"load8_u\",[\"i32:49152\"]],[\"invoke\",1694,0,\"load8_u\",[\"i32:51341\"]],[\"invoke\",1705,0,\"load8_u\",[\"i32:53530\"]],[\"invoke\",1715,0,\"load8_u\",[\"i32:55520\"]],[\"invoke\",1726,0,\"load8_u\",[\"i32:57709\"]],[\"invoke\",1737,0,\"load8_u\",[\"i32:59898\"]],[\"invoke\",1748,0,\"load8_u\",[\"i32:62087\"]],[\"invoke\",1759,0,\"load8_u\",[\"i32:64276\"]],[\"invoke\",1770,0,\"load8_u\",[\"i32:65519\"]],[\"invoke\",1781,0,\"load8_u\",[\"i32:65530\"]],[\"module\",1788,88],[\"invoke\",1803,0,\"load8_u\",[\"i32:994\"]],[\"invoke\",1814,0,\"load8_u\",[\"i32:3183\"]],[\"invoke\",1824,0,\"load8_u\",[\"i32:5173\"]],[\"invoke\",1835,0,\"load8_u\",[\"i32:7362\"]],[\"invoke\",1846,0,\"load8_u\",[\"i32:9551\"]],[\"invoke\",1857,0,\"load8_u\",[\"i32:11740\"]],[\"invoke\",1868,0,\"load8_u\",[\"i32:13929\"]],[\"invoke\",1879,0,\"load8_u\",[\"i32:16118\"]],[\"invoke\",1890,0,\"load8_u\",[\"i32:18307\"]],[\"invoke\",1901,0,\"load8_u\",[\"i32:20496\"]],[\"invoke\",1911,0,\"load8_u\",[\"i32:22486\"]],[\"invoke\",1922,0,\"load8_u\",[\"i32:24675\"]],[\"invoke\",1933,0,\"load8_u\",[\"i32:26864\"]],[\"invoke\",1944,0,\"load8_u\",[\"i32:29053\"]],[\"invoke\",1955,0,\"load8_u\",[\"i32:31242\"]],[\"invoke\",1966,0,\"load8_u\",[\"i32:33431\"]],[\"invoke\",1977,0,\"load8_u\",[\"i32:35620\"]],[\"invoke\",1988,0,\"load8_u\",[\"i32:37809\"]],[\"invoke\",1999,0,\"load8_u\",[\"i32:39998\"]],[\"invoke\",2009,0,\"load8_u\",[\"i32:41988\"]],[\"invoke\",2020,0,\"load8_u\",[\"i32:44177\"]],[\"invoke\",2031,0,\"load8_u\",[\"i32:46366\"]],[\"invoke\",2042,0,\"load8_u\",[\"i32:48555\"]],[\"invoke\",2053,0,\"load8_u\",[\"i32:50744\"]],[\"invoke\",2064,0,\"load8_u\",[\"i32:52933\"]],[\"invoke\",2075,0,\"load8_u\",[\"i32:55122\"]],[\"invoke\",2086,0,\"load8_u\",[\"i32:57311\"]],[\"invoke\",2096,0,\"load8_u\",[\"i32:59301\"]],[\"invoke\",2107,0,\"load8_u\",[\"i32:61490\"]],[\"invoke\",2118,0,\"load8_u\",[\"i32:63679\"]],[\"invoke\",2129,0,\"load8_u\",[\"i32:65487\"]],[\"invoke\",2140,0,\"load8_u\",[\"i32:65498\"]],[\"module\",2149,86],[\"invoke\",2162,0,\"load8_u\",[\"i32:596\"]],[\"invoke\",2173,0,\"load8_u\",[\"i32:2785\"]],[\"invoke\",2184,0,\"load8_u\",[\"i32:4974\"]],[\"invoke\",2195,0,\"load8_u\",[\"i32:7163\"]],[\"invoke\",2205,0,\"load8_u\",[\"i32:9153\"]],[\"invoke\",2216,0,\"load8_u\",[\"i32:11342\"]],[\"invoke\",2227,0,\"load8_u\",[\"i32:13531\"]],[\"invoke\",2238,0,\"load8_u\",[\"i32:15720\"]],[\"invoke\",2249,0,\"load8_u\",[\"i32:17909\"]],[\"invoke\",2260,0,\"load8_u\",[\"i32:20098\"]],[\"invoke\",2271,0,\"load8_u\",[\"i32:22287\"]],[\"invoke\",2282,0,\"load8_u\",[\"i32:24476\"]],[\"invoke\",2292,0,\"load8_u\",[\"i32:26466\"]],[\"invoke\",2303,0,\"load8_u\",[\"i32:28655\"]],[\"invoke\",2314,0,\"load8_u\",[\"i32:30844\"]],[\"invoke\",2325,0,\"load8_u\",[\"i32:33033\"]],[\"invoke\",2336,0,\"load8_u\",[\"i32:35222\"]],[\"invoke\",2347,0,\"load8_u\",[\"i32:37411\"]],[\"invoke\",2358,0,\"load8_u\",[\"i32:39600\"]],[\"invoke\",2369,0,\"load8_u\",[\"i32:41789\"]],[\"invoke\",2380,0,\"load8_u\",[\"i32:43978\"]],[\"invoke\",2390,0,\"load8_u\",[\"i32:45968\"]],[\"invoke\",2401,0,\"load8_u\",[\"i32:48157\"]],[\"invoke\",2412,0,\"load8_u\",[\"i32:50346\"]],[\"invoke\",2423,0,\"load8_u\",[\"i32:52535\"]],[\"invoke\",2434,0,\"load8_u\",[\"i32:54724\"]],[\"invoke\",2445,0,\"load8_u\",[\"i32:56913\"]],[\"invoke\",2456,0,\"load8_u\",[\"i32:59102\"]],[\"invoke\",2467,0,\"load8_u\",[\"i32:61291\"]],[\"invoke\",2477,0,\"load8_u\",[\"i32:63281\"]],[\"invoke\",2488,0,\"load8_u\",[\"i32:65470\"]],[\"invoke\",2499,0,\"load8_u\",[\"i32:65526\"]],[\"module\",2510,89],[\"invoke\",2521,0,\"load8_u\",[\"i32:198\"]],[\"invoke\",2532,0,\"load8_u\",[\"i32:2387\"]],[\"invoke\",2543,0,\"load8_u\",[\"i32:4576\"]],[\"invoke\",2554,0,\"load8_u\",[\"i32:6765\"]],[\"invoke\",2565,0,\"load8_u\",[\"i32:8954\"]],[\"invoke\",2576,0,\"load8_u\",[\"i32:11143\"]],[\"invoke\",2586,0,\"load8_u\",[\"i32:13133\"]],[\"invoke\",2597,0,\"load8_u\",[\"i32:15322\"]],[\"invoke\",2608,0,\"load8_u\",[\"i32:17511\"]],[\"invoke\",2619,0,\"load8_u\",[\"i32:19700\"]],[\"invoke\",2630,0,\"load8_u\",[\"i32:21889\"]],[\"invoke\",2641,0,\"load8_u\",[\"i32:24078\"]],[\"invoke\",2652,0,\"load8_u\",[\"i32:26267\"]],[\"invoke\",2663,0,\"load8_u\",[\"i32:28456\"]],[\"invoke\",2673,0,\"load8_u\",[\"i32:30446\"]],[\"invoke\",2684,0,\"load8_u\",[\"i32:32635\"]],[\"invoke\",2695,0,\"load8_u\",[\"i32:34824\"]],[\"invoke\",2706,0,\"load8_u\",[\"i32:37013\"]],[\"invoke\",2717,0,\"load8_u\",[\"i32:39202\"]],[\"invoke\",2728,0,\"load8_u\",[\"i32:41391\"]],[\"invoke\",2739,0,\"load8_u\",[\"i32:43580\"]],[\"invoke\",2750,0,\"load8_u\",[\"i32:45769\"]],[\"invoke\",2761,0,\"load8_u\",[\"i32:47958\"]],[\"invoke\",2771,0,\"load8_u\",[\"i32:49948\"]],[\"invoke\",2782,0,\"load8_u\",[\"i32:52137\"]],[\"invoke\",2793,0,\"load8_u\",[\"i32:54326\"]],[\"invoke\",2804,0,\"load8_u\",[\"i32:56515\"]],[\"invoke\",2815,0,\"load8_u\",[\"i32:58704\"]],[\"invoke\",2826,0,\"load8_u\",[\"i32:60893\"]],[\"invoke\",2837,0,\"load8_u\",[\"i32:63082\"]],[\"invoke\",2848,0,\"load8_u\",[\"i32:65271\"]],[\"invoke\",2858,0,\"load8_u\",[\"i32:65514\"]],[\"invoke\",2869,0,\"load8_u\",[\"i32:65525\"]],[\"module\",2871,86],[\"invoke\",2891,0,\"load8_u\",[\"i32:1989\"]],[\"invoke\",2902,0,\"load8_u\",[\"i32:4178\"]],[\"invoke\",2913,0,\"load8_u\",[\"i32:6367\"]],[\"invoke\",2924,0,\"load8_u\",[\"i32:8556\"]],[\"invoke\",2935,0,\"load8_u\",[\"i32:10745\"]],[\"invoke\",2946,0,\"load8_u\",[\"i32:12934\"]],[\"invoke\",2957,0,\"load8_u\",[\"i32:15123\"]],[\"invoke\",2967,0,\"load8_u\",[\"i32:17113\"]],[\"invoke\",2978,0,\"load8_u\",[\"i32:19302\"]],[\"invoke\",2989,0,\"load8_u\",[\"i32:21491\"]],[\"invoke\",3000,0,\"load8_u\",[\"i32:23680\"]],[\"invoke\",3011,0,\"load8_u\",[\"i32:25869\"]],[\"invoke\",3022,0,\"load8_u\",[\"i32:28058\"]],[\"invoke\",3033,0,\"load8_u\",[\"i32:30247\"]],[\"invoke\",3044,0,\"load8_u\",[\"i32:32436\"]],[\"invoke\",3054,0,\"load8_u\",[\"i32:34426\"]],[\"invoke\",3065,0,\"load8_u\",[\"i32:36615\"]],[\"invoke\",3076,0,\"load8_u\",[\"i32:38804\"]],[\"invoke\",3087,0,\"load8_u\",[\"i32:40993\"]],[\"invoke\",3098,0,\"load8_u\",[\"i32:43182\"]],[\"invoke\",3109,0,\"load8_u\",[\"i32:45371\"]],[\"invoke\",3120,0,\"load8_u\",[\"i32:47560\"]],[\"invoke\",3131,0,\"load8_u\",[\"i32:49749\"]],[\"invoke\",3142,0,\"load8_u\",[\"i32:51938\"]],[\"invoke\",3152,0,\"load8_u\",[\"i32:53928\"]],[\"invoke\",3163,0,\"load8_u\",[\"i32:56117\"]],[\"invoke\",3174,0,\"load8_u\",[\"i32:58306\"]],[\"invoke\",3185,0,\"load8_u\",[\"i32:60495\"]],[\"invoke\",3196,0,\"load8_u\",[\"i32:62684\"]],[\"invoke\",3207,0,\"load8_u\",[\"i32:64873\"]],[\"invoke\",3218,0,\"load8_u\",[\"i32:65523\"]],[\"invoke\",3229,0,\"load8_u\",[\"i32:65534\"]],[\"module\",3232,86],[\"invoke\",3250,0,\"load8_u\",[\"i32:1591\"]],[\"invoke\",3261,0,\"load8_u\",[\"i32:3780\"]],[\"invoke\",3272,0,\"load8_u\",[\"i32:5969\"]],[\"invoke\",3283,0,\"load8_u\",[\"i32:8158\"]],[\"invoke\",3294,0,\"load8_u\",[\"i32:10347\"]],[\"invoke\",3305,0,\"load8_u\",[\"i32:12536\"]],[\"invoke\",3316,0,\"load8_u\",[\"i32:14725\"]],[\"invoke\",3327,0,\"load8_u\",[\"i32:16914\"]],[\"invoke\",3338,0,\"load8_u\",[\"i32:19103\"]],[\"invoke\",3348,0,\"load8_u\",[\"i32:21093\"]],[\"invoke\",3359,0,\"load8_u\",[\"i32:23282\"]],[\"invoke\",3370,0,\"load8_u\",[\"i32:25471\"]],[\"invoke\",3381,0,\"load8_u\",[\"i32:27660\"]],[\"invoke\",3392,0,\"load8_u\",[\"i32:29849\"]],[\"invoke\",3403,0,\"load8_u\",[\"i32:32038\"]],[\"invoke\",3414,0,\"load8_u\",[\"i32:34227\"]],[\"invoke\",3425,0,\"load8_u\",[\"i32:36416\"]],[\"invoke\",3435,0,\"load8_u\",[\"i32:38406\"]],[\"invoke\",3446,0,\"load8_u\",[\"i32:40595\"]],[\"invoke\",3457,0,\"load8_u\",[\"i32:42784\"]],[\"invoke\",3468,0,\"load8_u\",[\"i32:44973\"]],[\"invoke\",3479,0,\"load8_u\",[\"i32:47162\"]],[\"invoke\",3490,0,\"load8_u\",[\"i32:49351\"]],[\"invoke\",3501,0,\"load8_u\",[\"i32:51540\"]],[\"invoke\",3512,0,\"load8_u\",[\"i32:53729\"]],[\"invoke\",3523,0,\"load8_u\",[\"i32:55918\"]],[\"invoke\",3533,0,\"load8_u\",[\"i32:57908\"]],[\"invoke\",3544,0,\"load8_u\",[\"i32:60097\"]],[\"invoke\",3555,0,\"load8_u\",[\"i32:62286\"]],[\"invoke\",3566,0,\"load8_u\",[\"i32:64475\"]],[\"invoke\",3577,0,\"load8_u\",[\"i32:65521\"]],[\"invoke\",3588,0,\"load8_u\",[\"i32:65532\"]],[\"module\",3593,90],[\"invoke\",3610,0,\"load8_u\",[\"i32:1392\"]],[\"invoke\",3621,0,\"load8_u\",[\"i32:3581\"]],[\"invoke\",3631,0,\"load8_u\",[\"i32:5571\"]],[\"invoke\",3642,0,\"load8_u\",[\"i32:7760\"]],[\"invoke\",3653,0,\"load8_u\",[\"i32:9949\"]],[\"invoke\",3664,0,\"load8_u\",[\"i32:12138\"]],[\"invoke\",3675,0,\"load8_u\",[\"i32:14327\"]],[\"invoke\",3686,0,\"load8_u\",[\"i32:16516\"]],[\"invoke\",3697,0,\"load8_u\",[\"i32:18705\"]],[\"invoke\",3708,0,\"load8_u\",[\"i32:20894\"]],[\"invoke\",3719,0,\"load8_u\",[\"i32:23083\"]],[\"invoke\",3729,0,\"load8_u\",[\"i32:25073\"]],[\"invoke\",3740,0,\"load8_u\",[\"i32:27262\"]],[\"invoke\",3751,0,\"load8_u\",[\"i32:29451\"]],[\"invoke\",3762,0,\"load8_u\",[\"i32:31640\"]],[\"invoke\",3773,0,\"load8_u\",[\"i32:33829\"]],[\"invoke\",3784,0,\"load8_u\",[\"i32:36018\"]],[\"invoke\",3795,0,\"load8_u\",[\"i32:38207\"]],[\"invoke\",3806,0,\"load8_u\",[\"i32:40396\"]],[\"invoke\",3816,0,\"load8_u\",[\"i32:42386\"]],[\"invoke\",3827,0,\"load8_u\",[\"i32:44575\"]],[\"invoke\",3838,0,\"load8_u\",[\"i32:46764\"]],[\"invoke\",3849,0,\"load8_u\",[\"i32:48953\"]],[\"invoke\",3860,0,\"load8_u\",[\"i32:51142\"]],[\"invoke\",3871,0,\"load8_u\",[\"i32:53331\"]],[\"invoke\",3882,0,\"load8_u\",[\"i32:55520\"]],[\"invoke\",3893,0,\"load8_u\",[\"i32:57709\"]],[\"invoke\",3904,0,\"load8_u\",[\"i32:59898\"]],[\"invoke\",3914,0,\"load8_u\",[\"i32:61888\"]],[\"invoke\",3925,0,\"load8_u\",[\"i32:64077\"]],[\"invoke\",3936,0,\"load8_u\",[\"i32:65519\"]],[\"invoke\",3947,0,\"load8_u\",[\"i32:65530\"]],[\"module\",3954,91],[\"invoke\",3969,0,\"load8_u\",[\"i32:994\"]],[\"invoke\",3980,0,\"load8_u\",[\"i32:3183\"]],[\"invoke\",3991,0,\"load8_u\",[\"i32:5372\"]],[\"invoke\",4002,0,\"load8_u\",[\"i32:7561\"]],[\"invoke\",4012,0,\"load8_u\",[\"i32:9551\"]],[\"invoke\",4023,0,\"load8_u\",[\"i32:11740\"]],[\"invoke\",4034,0,\"load8_u\",[\"i32:13929\"]],[\"invoke\",4045,0,\"load8_u\",[\"i32:16118\"]],[\"invoke\",4056,0,\"load8_u\",[\"i32:18307\"]],[\"invoke\",4067,0,\"load8_u\",[\"i32:20496\"]],[\"invoke\",4078,0,\"load8_u\",[\"i32:22685\"]],[\"invoke\",4089,0,\"load8_u\",[\"i32:24874\"]],[\"invoke\",4100,0,\"load8_u\",[\"i32:27063\"]],[\"invoke\",4110,0,\"load8_u\",[\"i32:29053\"]],[\"invoke\",4121,0,\"load8_u\",[\"i32:31242\"]],[\"invoke\",4132,0,\"load8_u\",[\"i32:33431\"]],[\"invoke\",4143,0,\"load8_u\",[\"i32:35620\"]],[\"invoke\",4154,0,\"load8_u\",[\"i32:37809\"]],[\"invoke\",4165,0,\"load8_u\",[\"i32:39998\"]],[\"invoke\",4176,0,\"load8_u\",[\"i32:42187\"]],[\"invoke\",4187,0,\"load8_u\",[\"i32:44376\"]],[\"invoke\",4197,0,\"load8_u\",[\"i32:46366\"]],[\"invoke\",4208,0,\"load8_u\",[\"i32:48555\"]],[\"invoke\",4219,0,\"load8_u\",[\"i32:50744\"]],[\"invoke\",4230,0,\"load8_u\",[\"i32:52933\"]],[\"invoke\",4241,0,\"load8_u\",[\"i32:55122\"]],[\"invoke\",4252,0,\"load8_u\",[\"i32:57311\"]],[\"invoke\",4263,0,\"load8_u\",[\"i32:59500\"]],[\"invoke\",4274,0,\"load8_u\",[\"i32:61441\"]],[\"invoke\",4285,0,\"load8_u\",[\"i32:61452\"]],[\"invoke\",4295,0,\"load8_u\",[\"i32:61908\"]],[\"invoke\",4306,0,\"load8_u\",[\"i32:64097\"]],[\"module\",4763,92],[\"invoke\",4786,0,\"checkRange\",[\"i32:20\",\"i32:65536\",\"i32:0\"]],[\"module\",4789,93],[\"module\",4815,94],[\"module\",4821,95],[\"module\",4827,96],[\"module\",4833,97],[\"module\",4839,98],[\"invoke\",4861,0,\"checkRange\",[\"i32:32768\",\"i32:65536\",\"i32:170\"]],[\"module\",4863,99],[\"module\",4869,100],[\"module\",4875,101],[\"module\",4881,102],[\"module\",4887,103],[\"module\",4893,104],[\"module\",4899,105],[\"invoke\",5123,0,\"checkRange\",[\"i32:2132\",\"i32:2827\",\"i32:10\"]],[\"invoke\",5145,0,\"checkRange\",[\"i32:5691\",\"i32:6552\",\"i32:83\"]],[\"invoke\",5167,0,\"checkRange\",[\"i32:10087\",\"i32:10206\",\"i32:5\"]],[\"invoke\",5189,0,\"checkRange\",[\"i32:13114\",\"i32:14133\",\"i32:59\"]],[\"invoke\",5209,0,\"checkRange\",[\"i32:15871\",\"i32:15998\",\"i32:95\"]],[\"invoke\",5231,0,\"checkRange\",[\"i32:18553\",\"i32:18744\",\"i32:42\"]],[\"invoke\",5253,0,\"checkRange\",[\"i32:21468\",\"i32:21492\",\"i32:93\"]],[\"invoke\",5275,0,\"checkRange\",[\"i32:24348\",\"i32:24515\",\"i32:92\"]],[\"invoke\",5297,0,\"checkRange\",[\"i32:27503\",\"i32:27573\",\"i32:77\"]],[\"invoke\",5319,0,\"checkRange\",[\"i32:31379\",\"i32:31403\",\"i32:57\"]],[\"invoke\",5341,0,\"checkRange\",[\"i32:33056\",\"i32:33660\",\"i32:89\"]],[\"invoke\",5363,0,\"checkRange\",[\"i32:35648\",\"i32:36547\",\"i32:83\"]],[\"invoke\",5385,0,\"checkRange\",[\"i32:39187\",\"i32:39859\",\"i32:92\"]],[\"invoke\",5405,0,\"checkRange\",[\"i32:42945\",\"i32:43115\",\"i32:77\"]],[\"invoke\",5427,0,\"checkRange\",[\"i32:45575\",\"i32:45640\",\"i32:77\"]],[\"invoke\",5449,0,\"checkRange\",[\"i32:47951\",\"i32:48632\",\"i32:100\"]],[\"invoke\",5471,0,\"checkRange\",[\"i32:50370\",\"i32:51358\",\"i32:92\"]],[\"invoke\",5493,0,\"checkRange\",[\"i32:55198\",\"i32:55389\",\"i32:42\"]],[\"invoke\",5515,0,\"checkRange\",[\"i32:58516\",\"i32:58835\",\"i32:95\"]],[\"invoke\",5537,0,\"checkRange\",[\"i32:60777\",\"i32:60834\",\"i32:76\"]],[\"invoke\",5559,0,\"checkRange\",[\"i32:63525\",\"i32:63605\",\"i32:74\"]],[\"file\",\"memory_fill.wast\"],[\"module\",6,106],[\"invoke\",22,0,\"test\",[]],[\"invoke\",24,0,\"checkRange\",[\"i32:0\",\"i32:65280\",\"i32:0\"]],[\"invoke\",26,0,\"checkRange\",[\"i32:65280\",\"i32:65536\",\"i32:85\"]],[\"module\",28,107],[\"invoke\",44,0,\"test\",[]],[\"module\",46,108],[\"invoke\",62,0,\"test\",[]],[\"module\",64,109],[\"invoke\",80,0,\"test\",[]],[\"invoke\",82,0,\"checkRange\",[\"i32:0\",\"i32:65536\",\"i32:0\"]],[\"module\",84,110],[\"invoke\",100,0,\"test\",[]],[\"module\",102,111],[\"invoke\",118,0,\"test\",[]],[\"module\",120,112],[\"invoke\",136,0,\"test\",[]],[\"invoke\",138,0,\"checkRange\",[\"i32:0\",\"i32:1\",\"i32:0\"]],[\"invoke\",140,0,\"checkRange\",[\"i32:1\",\"i32:65535\",\"i32:170\"]],[\"invoke\",142,0,\"checkRange\",[\"i32:65535\",\"i32:65536\",\"i32:0\"]],[\"module\",145,113],[\"invoke\",162,0,\"test\",[]],[\"invoke\",164,0,\"checkRange\",[\"i32:0\",\"i32:18\",\"i32:0\"]],[\"invoke\",166,0,\"checkRange\",[\"i32:18\",\"i32:21\",\"i32:85\"]],[\"invoke\",168,0,\"checkRange\",[\"i32:21\",\"i32:25\",\"i32:170\"]],[\"invoke\",170,0,\"checkRange\",[\"i32:25\",\"i32:28\",\"i32:85\"]],[\"invoke\",172,0,\"checkRange\",[\"i32:28\",\"i32:65536\",\"i32:0\"]],[\"module\",621,114],[\"invoke\",638,0,\"run\",[\"i32:65280\",\"i32:37\",\"i32:512\"]],[\"invoke\",641,0,\"checkRange\",[\"i32:0\",\"i32:1\",\"i32:0\"]],[\"module\",643,114],[\"invoke\",660,0,\"run\",[\"i32:65279\",\"i32:37\",\"i32:514\"]],[\"invoke\",663,0,\"checkRange\",[\"i32:0\",\"i32:1\",\"i32:0\"]],[\"module\",665,114],[\"invoke\",682,0,\"run\",[\"i32:65279\",\"i32:37\",\"i32:4294967295\"]],[\"invoke\",685,0,\"checkRange\",[\"i32:0\",\"i32:1\",\"i32:0\"]],[\"file\",\"memory_init.wast\"],[\"module\",6,115],[\"invoke\",17,0,\"test\",[]],[\"invoke\",19,0,\"load8_u\",[\"i32:0\"]],[\"invoke\",20,0,\"load8_u\",[\"i32:1\"]],[\"invoke\",21,0,\"load8_u\",[\"i32:2\"]],[\"invoke\",22,0,\"load8_u\",[\"i32:3\"]],[\"invoke\",23,0,\"load8_u\",[\"i32:4\"]],[\"invoke\",24,0,\"load8_u\",[\"i32:5\"]],[\"invoke\",25,0,\"load8_u\",[\"i32:6\"]],[\"invoke\",26,0,\"load8_u\",[\"i32:7\"]],[\"invoke\",27,0,\"load8_u\",[\"i32:8\"]],[\"invoke\",28,0,\"load8_u\",[\"i32:9\"]],[\"invoke\",29,0,\"load8_u\",[\"i32:10\"]],[\"invoke\",30,0,\"load8_u\",[\"i32:11\"]],[\"invoke\",31,0,\"load8_u\",[\"i32:12\"]],[\"invoke\",32,0,\"load8_u\",[\"i32:13\"]],[\"invoke\",33,0,\"load8_u\",[\"i32:14\"]],[\"invoke\",34,0,\"load8_u\",[\"i32:15\"]],[\"invoke\",35,0,\"load8_u\",[\"i32:16\"]],[\"invoke\",36,0,\"load8_u\",[\"i32:17\"]],[\"invoke\",37,0,\"load8_u\",[\"i32:18\"]],[\"invoke\",38,0,\"load8_u\",[\"i32:19\"]],[\"invoke\",39,0,\"load8_u\",[\"i32:20\"]],[\"invoke\",40,0,\"load8_u\",[\"i32:21\"]],[\"invoke\",41,0,\"load8_u\",[\"i32:22\"]],[\"invoke\",42,0,\"load8_u\",[\"i32:23\"]],[\"invoke\",43,0,\"load8_u\",[\"i32:24\"]],[\"invoke\",44,0,\"load8_u\",[\"i32:25\"]],[\"invoke\",45,0,\"load8_u\",[\"i32:26\"]],[\"invoke\",46,0,\"load8_u\",[\"i32:27\"]],[\"invoke\",47,0,\"load8_u\",[\"i32:28\"]],[\"invoke\",48,0,\"load8_u\",[\"i32:29\"]],[\"module\",50,116],[\"invoke\",61,0,\"test\",[]],[\"invoke\",63,0,\"load8_u\",[\"i32:0\"]],[\"invoke\",64,0,\"load8_u\",[\"i32:1\"]],[\"invoke\",65,0,\"load8_u\",[\"i32:2\"]],[\"invoke\",66,0,\"load8_u\",[\"i32:3\"]],[\"invoke\",67,0,\"load8_u\",[\"i32:4\"]],[\"invoke\",68,0,\"load8_u\",[\"i32:5\"]],[\"invoke\",69,0,\"load8_u\",[\"i32:6\"]],[\"invoke\",70,0,\"load8_u\",[\"i32:7\"]],[\"invoke\",71,0,\"load8_u\",[\"i32:8\"]],[\"invoke\",72,0,\"load8_u\",[\"i32:9\"]],[\"invoke\",73,0,\"load8_u\",[\"i32:10\"]],[\"invoke\",74,0,\"load8_u\",[\"i32:11\"]],[\"invoke\",75,0,\"load8_u\",[\"i32:12\"]],[\"invoke\",76,0,\"load8_u\",[\"i32:13\"]],[\"invoke\",77,0,\"load8_u\",[\"i32:14\"]],[\"invoke\",78,0,\"load8_u\",[\"i32:15\"]],[\"invoke\",79,0,\"load8_u\",[\"i32:16\"]],[\"invoke\",80,0,\"load8_u\",[\"i32:17\"]],[\"invoke\",81,0,\"load8_u\",[\"i32:18\"]],[\"invoke\",82,0,\"load8_u\",[\"i32:19\"]],[\"invoke\",83,0,\"load8_u\",[\"i32:20\"]],[\"invoke\",84,0,\"load8_u\",[\"i32:21\"]],[\"invoke\",85,0,\"load8_u\",[\"i32:22\"]],[\"invoke\",86,0,\"load8_u\",[\"i32:23\"]],[\"invoke\",87,0,\"load8_u\",[\"i32:24\"]],[\"invoke\",88,0,\"load8_u\",[\"i32:25\"]],[\"invoke\",89,0,\"load8_u\",[\"i32:26\"]],[\"invoke\",90,0,\"load8_u\",[\"i32:27\"]],[\"invoke\",91,0,\"load8_u\",[\"i32:28\"]],[\"invoke\",92,0,\"load8_u\",[\"i32:29\"]],[\"module\",94,117],[\"invoke\",105,0,\"test\",[]],[\"invoke\",107,0,\"load8_u\",[\"i32:0\"]],[\"invoke\",108,0,\"load8_u\",[\"i32:1\"]],[\"invoke\",109,0,\"load8_u\",[\"i32:2\"]],[\"invoke\",110,0,\"load8_u\",[\"i32:3\"]],[\"invoke\",111,0,\"load8_u\",[\"i32:4\"]],[\"invoke\",112,0,\"load8_u\",[\"i32:5\"]],[\"invoke\",113,0,\"load8_u\",[\"i32:6\"]],[\"invoke\",114,0,\"load8_u\",[\"i32:7\"]],[\"invoke\",115,0,\"load8_u\",[\"i32:8\"]],[\"invoke\",116,0,\"load8_u\",[\"i32:9\"]],[\"invoke\",117,0,\"load8_u\",[\"i32:10\"]],[\"invoke\",118,0,\"load8_u\",[\"i32:11\"]],[\"invoke\",119,0,\"load8_u\",[\"i32:12\"]],[\"invoke\",120,0,\"load8_u\",[\"i32:13\"]],[\"invoke\",121,0,\"load8_u\",[\"i32:14\"]],[\"invoke\",122,0,\"load8_u\",[\"i32:15\"]],[\"invoke\",123,0,\"load8_u\",[\"i32:16\"]],[\"invoke\",124,0,\"load8_u\",[\"i32:17\"]],[\"invoke\",125,0,\"load8_u\",[\"i32:18\"]],[\"invoke\",126,0,\"load8_u\",[\"i32:19\"]],[\"invoke\",127,0,\"load8_u\",[\"i32:20\"]],[\"invoke\",128,0,\"load8_u\",[\"i32:21\"]],[\"invoke\",129,0,\"load8_u\",[\"i32:22\"]],[\"invoke\",130,0,\"load8_u\",[\"i32:23\"]],[\"invoke\",131,0,\"load8_u\",[\"i32:24\"]],[\"invoke\",132,0,\"load8_u\",[\"i32:25\"]],[\"invoke\",133,0,\"load8_u\",[\"i32:26\"]],[\"invoke\",134,0,\"load8_u\",[\"i32:27\"]],[\"invoke\",135,0,\"load8_u\",[\"i32:28\"]],[\"invoke\",136,0,\"load8_u\",[\"i32:29\"]],[\"module\",138,118],[\"invoke\",157,0,\"test\",[]],[\"invoke\",159,0,\"load8_u\",[\"i32:0\"]],[\"invoke\",160,0,\"load8_u\",[\"i32:1\"]],[\"invoke\",161,0,\"load8_u\",[\"i32:2\"]],[\"invoke\",162,0,\"load8_u\",[\"i32:3\"]],[\"invoke\",163,0,\"load8_u\",[\"i32:4\"]],[\"invoke\",164,0,\"load8_u\",[\"i32:5\"]],[\"invoke\",165,0,\"load8_u\",[\"i32:6\"]],[\"invoke\",166,0,\"load8_u\",[\"i32:7\"]],[\"invoke\",167,0,\"load8_u\",[\"i32:8\"]],[\"invoke\",168,0,\"load8_u\",[\"i32:9\"]],[\"invoke\",169,0,\"load8_u\",[\"i32:10\"]],[\"invoke\",170,0,\"load8_u\",[\"i32:11\"]],[\"invoke\",171,0,\"load8_u\",[\"i32:12\"]],[\"invoke\",172,0,\"load8_u\",[\"i32:13\"]],[\"invoke\",173,0,\"load8_u\",[\"i32:14\"]],[\"invoke\",174,0,\"load8_u\",[\"i32:15\"]],[\"invoke\",175,0,\"load8_u\",[\"i32:16\"]],[\"invoke\",176,0,\"load8_u\",[\"i32:17\"]],[\"invoke\",177,0,\"load8_u\",[\"i32:18\"]],[\"invoke\",178,0,\"load8_u\",[\"i32:19\"]],[\"invoke\",179,0,\"load8_u\",[\"i32:20\"]],[\"invoke\",180,0,\"load8_u\",[\"i32:21\"]],[\"invoke\",181,0,\"load8_u\",[\"i32:22\"]],[\"invoke\",182,0,\"load8_u\",[\"i32:23\"]],[\"invoke\",183,0,\"load8_u\",[\"i32:24\"]],[\"invoke\",184,0,\"load8_u\",[\"i32:25\"]],[\"invoke\",185,0,\"load8_u\",[\"i32:26\"]],[\"invoke\",186,0,\"load8_u\",[\"i32:27\"]],[\"invoke\",187,0,\"load8_u\",[\"i32:28\"]],[\"invoke\",188,0,\"load8_u\",[\"i32:29\"]],[\"module\",203,119],[\"invoke\",209,0,\"test\",[]],[\"module\",211,120],[\"invoke\",217,0,\"test\",[]],[\"module\",219,121],[\"invoke\",224,0,\"test\",[]],[\"module\",240,122],[\"invoke\",246,0,\"test\",[]],[\"module\",248,123],[\"invoke\",253,0,\"test\",[]],[\"module\",255,124],[\"invoke\",260,0,\"test\",[]],[\"module\",262,125],[\"invoke\",267,0,\"test\",[]],[\"module\",269,126],[\"invoke\",274,0,\"test\",[]],[\"module\",276,127],[\"invoke\",281,0,\"test\",[]],[\"module\",283,128],[\"invoke\",288,0,\"test\",[]],[\"module\",290,129],[\"invoke\",295,0,\"test\",[]],[\"module\",297,130],[\"invoke\",302,0,\"test\",[]],[\"module\",304,131],[\"invoke\",309,0,\"test\",[]],[\"module\",815,132],[\"invoke\",833,0,\"run\",[\"i32:65528\",\"i32:16\"]],[\"invoke\",836,0,\"checkRange\",[\"i32:0\",\"i32:1\",\"i32:0\"]],[\"module\",838,132],[\"invoke\",856,0,\"run\",[\"i32:65527\",\"i32:16\"]],[\"invoke\",859,0,\"checkRange\",[\"i32:0\",\"i32:1\",\"i32:0\"]],[\"module\",861,132],[\"invoke\",879,0,\"run\",[\"i32:65472\",\"i32:30\"]],[\"invoke\",882,0,\"checkRange\",[\"i32:0\",\"i32:1\",\"i32:0\"]],[\"module\",884,132],[\"invoke\",902,0,\"run\",[\"i32:65473\",\"i32:31\"]],[\"invoke\",905,0,\"checkRange\",[\"i32:0\",\"i32:1\",\"i32:0\"]],[\"module\",907,133],[\"invoke\",925,0,\"run\",[\"i32:65528\",\"i32:4294967040\"]],[\"invoke\",928,0,\"checkRange\",[\"i32:0\",\"i32:1\",\"i32:0\"]],[\"module\",930,133],[\"invoke\",948,0,\"run\",[\"i32:0\",\"i32:4294967292\"]],[\"invoke\",951,0,\"checkRange\",[\"i32:0\",\"i32:1\",\"i32:0\"]],[\"module\",954,134],[\"file\",\"data.wast\"],[\"module\",5,135],[\"module\",39,136],[\"module\",48,137],[\"module\",66,138],[\"module\",77,138],[\"module\",100,139],[\"module\",111,140],[\"module\",116,141],[\"module\",125,142],[\"module\",130,143],[\"module\",135,141],[\"module\",144,142],[\"instantiate\",184,144],[\"instantiate\",192,145],[\"instantiate\",200,146],[\"instantiate\",207,147],[\"instantiate\",214,148],[\"instantiate\",231,149],[\"instantiate\",240,150],[\"instantiate\",255,151],[\"instantiate\",263,152],[\"instantiate\",271,153],[\"instantiate\",286,154]]"; diff --git a/tests/corpus/webassembly-spec-memory/main.mjs b/tests/corpus/webassembly-spec-memory/main.mjs new file mode 100644 index 000000000..f45ccc584 --- /dev/null +++ b/tests/corpus/webassembly-spec-memory/main.mjs @@ -0,0 +1,5 @@ +// Executes WebAssembly spec tests (16 files); see tests/wasm-spec/runner.mjs. +import { run } from "../../wasm-spec/runner.mjs"; +import { modules, script } from "./data.mjs"; + +run(modules, script); diff --git a/tests/corpus/webassembly-spec-table/data.mjs b/tests/corpus/webassembly-spec-table/data.mjs new file mode 100644 index 000000000..15dbf08aa --- /dev/null +++ b/tests/corpus/webassembly-spec-table/data.mjs @@ -0,0 +1,193 @@ +/* Generated by tests/wasm-spec/gen.mjs from the WebAssembly 2.0 core test + * suite (WebAssembly/spec wg-2.0, Apache-2.0) with wabt 1.0.37 wast2json: + * table_copy.wast, table_fill.wast, table_get.wast, table_grow.wast, table_init.wast, table_set.wast, table_size.wast, elem.wast, ref_func.wast, ref_is_null.wast, ref_null.wast, exports.wast, linking.wast. Do not edit. */ +export const modules = [ + "AGFzbQEAAAABBQFgAAF/AwYFAAAAAAAHHwUDZWYwAAADZWYxAAEDZWYyAAIDZWYzAAMDZWY0AAQKGgUEAEEACwQAQQELBABBAgsEAEEDCwQAQQQL", + "AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8CKQUBYQNlZjAAAAFhA2VmMQAAAWEDZWYyAAABYQNlZjMAAAFhA2VmNAAAAwkIAAAAAAABAgIECQJwAR4ecAEeHgceAwR0ZXN0AAoIY2hlY2tfdDAACwhjaGVja190MQAMCToGAEECCwQDAQQBAQAEAgcBCABBDAsFBwUCAwYBAAUFCQIHBgIBQQMLAAQBAwEEAgFBCwsABQYDAgUHCi4IBABBBQsEAEEGCwQAQQcLBABBCAsEAEEJCwMAAQsHACAAEQAACwcAIAARAAEL", + "AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8CKQUBYQNlZjAAAAFhA2VmMQAAAWEDZWYyAAABYQNlZjMAAAFhA2VmNAAAAwkIAAAAAAABAgIECQJwAR4ecAEeHgceAwR0ZXN0AAoIY2hlY2tfdDAACwhjaGVja190MQAMCToGAEECCwQDAQQBAQAEAgcBCABBDAsFBwUCAwYBAAUFCQIHBgIBQQMLAAQBAwEEAgFBCwsABQYDAgUHCjcIBABBBQsEAEEGCwQAQQcLBABBCAsEAEEJCwwAQQ1BAkED/A4AAAsHACAAEQAACwcAIAARAAEL", + "AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8CKQUBYQNlZjAAAAFhA2VmMQAAAWEDZWYyAAABYQNlZjMAAAFhA2VmNAAAAwkIAAAAAAABAgIECQJwAR4ecAEeHgceAwR0ZXN0AAoIY2hlY2tfdDAACwhjaGVja190MQAMCToGAEECCwQDAQQBAQAEAgcBCABBDAsFBwUCAwYBAAUFCQIHBgIBQQMLAAQBAwEEAgFBCwsABQYDAgUHCjcIBABBBQsEAEEGCwQAQQcLBABBCAsEAEEJCwwAQRlBD0EC/A4AAAsHACAAEQAACwcAIAARAAEL", + "AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8CKQUBYQNlZjAAAAFhA2VmMQAAAWEDZWYyAAABYQNlZjMAAAFhA2VmNAAAAwkIAAAAAAABAgIECQJwAR4ecAEeHgceAwR0ZXN0AAoIY2hlY2tfdDAACwhjaGVja190MQAMCToGAEECCwQDAQQBAQAEAgcBCABBDAsFBwUCAwYBAAUFCQIHBgIBQQMLAAQBAwEEAgFBCwsABQYDAgUHCjcIBABBBQsEAEEGCwQAQQcLBABBCAsEAEEJCwwAQQ1BGUED/A4AAAsHACAAEQAACwcAIAARAAEL", + "AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8CKQUBYQNlZjAAAAFhA2VmMQAAAWEDZWYyAAABYQNlZjMAAAFhA2VmNAAAAwkIAAAAAAABAgIECQJwAR4ecAEeHgceAwR0ZXN0AAoIY2hlY2tfdDAACwhjaGVja190MQAMCToGAEECCwQDAQQBAQAEAgcBCABBDAsFBwUCAwYBAAUFCQIHBgIBQQMLAAQBAwEEAgFBCwsABQYDAgUHCjcIBABBBQsEAEEGCwQAQQcLBABBCAsEAEEJCwwAQRRBFkEE/A4AAAsHACAAEQAACwcAIAARAAEL", + "AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8CKQUBYQNlZjAAAAFhA2VmMQAAAWEDZWYyAAABYQNlZjMAAAFhA2VmNAAAAwkIAAAAAAABAgIECQJwAR4ecAEeHgceAwR0ZXN0AAoIY2hlY2tfdDAACwhjaGVja190MQAMCToGAEECCwQDAQQBAQAEAgcBCABBDAsFBwUCAwYBAAUFCQIHBgIBQQMLAAQBAwEEAgFBCwsABQYDAgUHCjcIBABBBQsEAEEGCwQAQQcLBABBCAsEAEEJCwwAQRlBAUED/A4AAAsHACAAEQAACwcAIAARAAEL", + "AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8CKQUBYQNlZjAAAAFhA2VmMQAAAWEDZWYyAAABYQNlZjMAAAFhA2VmNAAAAwkIAAAAAAABAgIECQJwAR4ecAEeHgceAwR0ZXN0AAoIY2hlY2tfdDAACwhjaGVja190MQAMCToGAEECCwQDAQQBAQAEAgcBCABBDAsFBwUCAwYBAAUFCQIHBgIBQQMLAAQBAwEEAgFBCwsABQYDAgUHCjcIBABBBQsEAEEGCwQAQQcLBABBCAsEAEEJCwwAQQpBDEEH/A4AAAsHACAAEQAACwcAIAARAAEL", + "AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8CKQUBYQNlZjAAAAFhA2VmMQAAAWEDZWYyAAABYQNlZjMAAAFhA2VmNAAAAwkIAAAAAAABAgIECQJwAR4ecAEeHgceAwR0ZXN0AAoIY2hlY2tfdDAACwhjaGVja190MQAMCToGAEECCwQDAQQBAQAEAgcBCABBDAsFBwUCAwYBAAUFCQIHBgIBQQMLAAQBAwEEAgFBCwsABQYDAgUHCjcIBABBBQsEAEEGCwQAQQcLBABBCAsEAEEJCwwAQQxBCkEH/A4AAAsHACAAEQAACwcAIAARAAEL", + "AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8CKQUBYQNlZjAAAAFhA2VmMQAAAWEDZWYyAAABYQNlZjMAAAFhA2VmNAAAAwkIAAAAAAABAgIECQJwAR4ecAEeHgceAwR0ZXN0AAoIY2hlY2tfdDAACwhjaGVja190MQAMCToGAEECCwQDAQQBAQAEAgcBCABBDAsFBwUCAwYBAAUFCQIHBgIBQQMLAAQBAwEEAgFBCwsABQYDAgUHCjcIBABBBQsEAEEGCwQAQQcLBABBCAsEAEEJCwwAQQpBAEEU/A4BAAsHACAAEQAACwcAIAARAAEL", + "AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8CKQUBYQNlZjAAAAFhA2VmMQAAAWEDZWYyAAABYQNlZjMAAAFhA2VmNAAAAwkIAAAAAAABAgIECQJwAR4ecAEeHgceAwR0ZXN0AAoIY2hlY2tfdDAACwhjaGVja190MQAMCToGAgFBAgsABAMBBAEBAAQCBwEIAgFBDAsABQcFAgMGAQAFBQkCBwYAQQMLBAEDAQQAQQsLBQYDAgUHCi4IBABBBQsEAEEGCwQAQQcLBABBCAsEAEEJCwMAAQsHACAAEQABCwcAIAARAAAL", + "AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8CKQUBYQNlZjAAAAFhA2VmMQAAAWEDZWYyAAABYQNlZjMAAAFhA2VmNAAAAwkIAAAAAAABAgIECQJwAR4ecAEeHgceAwR0ZXN0AAoIY2hlY2tfdDAACwhjaGVja190MQAMCToGAgFBAgsABAMBBAEBAAQCBwEIAgFBDAsABQcFAgMGAQAFBQkCBwYAQQMLBAEDAQQAQQsLBQYDAgUHCjcIBABBBQsEAEEGCwQAQQcLBABBCAsEAEEJCwwAQQ1BAkED/A4BAQsHACAAEQABCwcAIAARAAAL", + "AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8CKQUBYQNlZjAAAAFhA2VmMQAAAWEDZWYyAAABYQNlZjMAAAFhA2VmNAAAAwkIAAAAAAABAgIECQJwAR4ecAEeHgceAwR0ZXN0AAoIY2hlY2tfdDAACwhjaGVja190MQAMCToGAgFBAgsABAMBBAEBAAQCBwEIAgFBDAsABQcFAgMGAQAFBQkCBwYAQQMLBAEDAQQAQQsLBQYDAgUHCjcIBABBBQsEAEEGCwQAQQcLBABBCAsEAEEJCwwAQRlBD0EC/A4BAQsHACAAEQABCwcAIAARAAAL", + "AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8CKQUBYQNlZjAAAAFhA2VmMQAAAWEDZWYyAAABYQNlZjMAAAFhA2VmNAAAAwkIAAAAAAABAgIECQJwAR4ecAEeHgceAwR0ZXN0AAoIY2hlY2tfdDAACwhjaGVja190MQAMCToGAgFBAgsABAMBBAEBAAQCBwEIAgFBDAsABQcFAgMGAQAFBQkCBwYAQQMLBAEDAQQAQQsLBQYDAgUHCjcIBABBBQsEAEEGCwQAQQcLBABBCAsEAEEJCwwAQQ1BGUED/A4BAQsHACAAEQABCwcAIAARAAAL", + "AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8CKQUBYQNlZjAAAAFhA2VmMQAAAWEDZWYyAAABYQNlZjMAAAFhA2VmNAAAAwkIAAAAAAABAgIECQJwAR4ecAEeHgceAwR0ZXN0AAoIY2hlY2tfdDAACwhjaGVja190MQAMCToGAgFBAgsABAMBBAEBAAQCBwEIAgFBDAsABQcFAgMGAQAFBQkCBwYAQQMLBAEDAQQAQQsLBQYDAgUHCjcIBABBBQsEAEEGCwQAQQcLBABBCAsEAEEJCwwAQRRBFkEE/A4BAQsHACAAEQABCwcAIAARAAAL", + "AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8CKQUBYQNlZjAAAAFhA2VmMQAAAWEDZWYyAAABYQNlZjMAAAFhA2VmNAAAAwkIAAAAAAABAgIECQJwAR4ecAEeHgceAwR0ZXN0AAoIY2hlY2tfdDAACwhjaGVja190MQAMCToGAgFBAgsABAMBBAEBAAQCBwEIAgFBDAsABQcFAgMGAQAFBQkCBwYAQQMLBAEDAQQAQQsLBQYDAgUHCjcIBABBBQsEAEEGCwQAQQcLBABBCAsEAEEJCwwAQRlBAUED/A4BAQsHACAAEQABCwcAIAARAAAL", + "AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8CKQUBYQNlZjAAAAFhA2VmMQAAAWEDZWYyAAABYQNlZjMAAAFhA2VmNAAAAwkIAAAAAAABAgIECQJwAR4ecAEeHgceAwR0ZXN0AAoIY2hlY2tfdDAACwhjaGVja190MQAMCToGAgFBAgsABAMBBAEBAAQCBwEIAgFBDAsABQcFAgMGAQAFBQkCBwYAQQMLBAEDAQQAQQsLBQYDAgUHCjcIBABBBQsEAEEGCwQAQQcLBABBCAsEAEEJCwwAQQpBDEEH/A4BAQsHACAAEQABCwcAIAARAAAL", + "AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8CKQUBYQNlZjAAAAFhA2VmMQAAAWEDZWYyAAABYQNlZjMAAAFhA2VmNAAAAwkIAAAAAAABAgIECQJwAR4ecAEeHgceAwR0ZXN0AAoIY2hlY2tfdDAACwhjaGVja190MQAMCToGAgFBAgsABAMBBAEBAAQCBwEIAgFBDAsABQcFAgMGAQAFBQkCBwYAQQMLBAEDAQQAQQsLBQYDAgUHCjcIBABBBQsEAEEGCwQAQQcLBABBCAsEAEEJCwwAQQxBCkEH/A4BAQsHACAAEQABCwcAIAARAAAL", + "AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8CKQUBYQNlZjAAAAFhA2VmMQAAAWEDZWYyAAABYQNlZjMAAAFhA2VmNAAAAwkIAAAAAAABAgIECQJwAR4ecAEeHgceAwR0ZXN0AAoIY2hlY2tfdDAACwhjaGVja190MQAMCToGAgFBAgsABAMBBAEBAAQCBwEIAgFBDAsABQcFAgMGAQAFBQkCBwYAQQMLBAEDAQQAQQsLBQYDAgUHCjcIBABBBQsEAEEGCwQAQQcLBABBCAsEAEEJCwwAQQpBAEEU/A4AAQsHACAAEQABCwcAIAARAAAL", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEeHgcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEcQQFBA/wOAAAL", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEeHgcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEF+QQFBAvwOAAAL", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEeHgcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEPQRlBBvwOAAAL", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEeHgcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEPQX5BAvwOAAAL", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEeHgcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEPQRlBAPwOAAAL", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEeHgcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEeQQ9BAPwOAAAL", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEeHgcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEfQQ9BAPwOAAAL", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEeHgcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEPQR5BAPwOAAAL", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEeHgcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEPQR9BAPwOAAAL", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEeHgcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEeQR5BAPwOAAAL", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEeHgcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEfQR9BAPwOAAAL", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEeHgcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEcQQFBA/wOAQAL", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEeHgcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEF+QQFBAvwOAQAL", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEeHgcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEPQRlBBvwOAQAL", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEeHgcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEPQX5BAvwOAQAL", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEeHgcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEPQRlBAPwOAQAL", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEeHgcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEeQQ9BAPwOAQAL", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEeHgcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEfQQ9BAPwOAQAL", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEeHgcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEPQR5BAPwOAQAL", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEeHgcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEPQR9BAPwOAQAL", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEeHgcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEeQR5BAPwOAQAL", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEeHgcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEfQR9BAPwOAQAL", + "AGFzbQEAAAABEANgAAF/YAF/AX9gA39/fwADExIAAAAAAAAAAAAAAAAAAAAAAQIEBQFwASBAB2QSAmYwAAACZjEAAQJmMgACAmYzAAMCZjQABAJmNQAFAmY2AAYCZjcABwJmOAAIAmY5AAkDZjEwAAoDZjExAAsDZjEyAAwDZjEzAA0DZjE0AA4DZjE1AA8EdGVzdAAQA3J1bgARCQ4BAEEACwgAAQIDBAUGBwpmEgQAQQALBABBAQsEAEECCwQAQQMLBABBBAsEAEEFCwQAQQYLBABBBwsEAEEICwQAQQkLBABBCgsEAEELCwQAQQwLBABBDQsEAEEOCwQAQQ8LBwAgABEAAAsMACAAIAEgAvwOAAAL", + "AGFzbQEAAAABEANgAAF/YAF/AX9gA39/fwADExIAAAAAAAAAAAAAAAAAAAAAAQIEBQFwASBAB2QSAmYwAAACZjEAAQJmMgACAmYzAAMCZjQABAJmNQAFAmY2AAYCZjcABwJmOAAIAmY5AAkDZjEwAAoDZjExAAsDZjEyAAwDZjEzAA0DZjE0AA4DZjE1AA8EdGVzdAAQA3J1bgARCQ8BAEEACwkAAQIDBAUGBwgKZhIEAEEACwQAQQELBABBAgsEAEEDCwQAQQQLBABBBQsEAEEGCwQAQQcLBABBCAsEAEEJCwQAQQoLBABBCwsEAEEMCwQAQQ0LBABBDgsEAEEPCwcAIAARAAALDAAgACABIAL8DgAACw==", + "AGFzbQEAAAABEANgAAF/YAF/AX9gA39/fwADExIAAAAAAAAAAAAAAAAAAAAAAQIEBQFwASBAB2QSAmYwAAACZjEAAQJmMgACAmYzAAMCZjQABAJmNQAFAmY2AAYCZjcABwJmOAAIAmY5AAkDZjEwAAoDZjExAAsDZjEyAAwDZjEzAA0DZjE0AA4DZjE1AA8EdGVzdAAQA3J1bgARCQ4BAEEYCwgAAQIDBAUGBwpmEgQAQQALBABBAQsEAEECCwQAQQMLBABBBAsEAEEFCwQAQQYLBABBBwsEAEEICwQAQQkLBABBCgsEAEELCwQAQQwLBABBDQsEAEEOCwQAQQ8LBwAgABEAAAsMACAAIAEgAvwOAAAL", + "AGFzbQEAAAABEANgAAF/YAF/AX9gA39/fwADExIAAAAAAAAAAAAAAAAAAAAAAQIEBQFwASBAB2QSAmYwAAACZjEAAQJmMgACAmYzAAMCZjQABAJmNQAFAmY2AAYCZjcABwJmOAAIAmY5AAkDZjEwAAoDZjExAAsDZjEyAAwDZjEzAA0DZjE0AA4DZjE1AA8EdGVzdAAQA3J1bgARCQ8BAEEXCwkAAQIDBAUGBwgKZhIEAEEACwQAQQELBABBAgsEAEEDCwQAQQQLBABBBQsEAEEGCwQAQQcLBABBCAsEAEEJCwQAQQoLBABBCwsEAEEMCwQAQQ0LBABBDgsEAEEPCwcAIAARAAALDAAgACABIAL8DgAACw==", + "AGFzbQEAAAABEANgAAF/YAF/AX9gA39/fwADExIAAAAAAAAAAAAAAAAAAAAAAQIEBQFwASBAB2QSAmYwAAACZjEAAQJmMgACAmYzAAMCZjQABAJmNQAFAmY2AAYCZjcABwJmOAAIAmY5AAkDZjEwAAoDZjExAAsDZjEyAAwDZjEzAA0DZjE0AA4DZjE1AA8EdGVzdAAQA3J1bgARCQ4BAEELCwgAAQIDBAUGBwpmEgQAQQALBABBAQsEAEECCwQAQQMLBABBBAsEAEEFCwQAQQYLBABBBwsEAEEICwQAQQkLBABBCgsEAEELCwQAQQwLBABBDQsEAEEOCwQAQQ8LBwAgABEAAAsMACAAIAEgAvwOAAAL", + "AGFzbQEAAAABEANgAAF/YAF/AX9gA39/fwADExIAAAAAAAAAAAAAAAAAAAAAAQIEBQFwASBAB2QSAmYwAAACZjEAAQJmMgACAmYzAAMCZjQABAJmNQAFAmY2AAYCZjcABwJmOAAIAmY5AAkDZjEwAAoDZjExAAsDZjEyAAwDZjEzAA0DZjE0AA4DZjE1AA8EdGVzdAAQA3J1bgARCQ4BAEEVCwgAAQIDBAUGBwpmEgQAQQALBABBAQsEAEECCwQAQQMLBABBBAsEAEEFCwQAQQYLBABBBwsEAEEICwQAQQkLBABBCgsEAEELCwQAQQwLBABBDQsEAEEOCwQAQQ8LBwAgABEAAAsMACAAIAEgAvwOAAAL", + "AGFzbQEAAAABEANgAAF/YAF/AX9gA39/fwADExIAAAAAAAAAAAAAAAAAAAAAAQIEBQFwASBAB2QSAmYwAAACZjEAAQJmMgACAmYzAAMCZjQABAJmNQAFAmY2AAYCZjcABwJmOAAIAmY5AAkDZjEwAAoDZjExAAsDZjEyAAwDZjEzAA0DZjE0AA4DZjE1AA8EdGVzdAAQA3J1bgARCREBAEEVCwsAAQIDBAUGBwgJCgpmEgQAQQALBABBAQsEAEECCwQAQQMLBABBBAsEAEEFCwQAQQYLBABBBwsEAEEICwQAQQkLBABBCgsEAEELCwQAQQwLBABBDQsEAEEOCwQAQQ8LBwAgABEAAAsMACAAIAEgAvwOAAAL", + "AGFzbQEAAAABEANgAAF/YAF/AX9gA39/fwADExIAAAAAAAAAAAAAAAAAAAAAAQIEBwFwAYABgAEHZBICZjAAAAJmMQABAmYyAAICZjMAAwJmNAAEAmY1AAUCZjYABgJmNwAHAmY4AAgCZjkACQNmMTAACgNmMTEACwNmMTIADANmMTMADQNmMTQADgNmMTUADwR0ZXN0ABADcnVuABEJFwEAQfAACxAAAQIDBAUGBwgJCgsMDQ4PCmYSBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsEAEEKCwQAQQsLBABBDAsEAEENCwQAQQ4LBABBDwsHACAAEQAACwwAIAAgASAC/A4AAAs=", + "AGFzbQEAAAABEANgAAF/YAF/AX9gA39/fwADExIAAAAAAAAAAAAAAAAAAAAAAQIEBwFwAYABgAEHZBICZjAAAAJmMQABAmYyAAICZjMAAwJmNAAEAmY1AAUCZjYABgJmNwAHAmY4AAgCZjkACQNmMTAACgNmMTEACwNmMTIADANmMTMADQNmMTQADgNmMTUADwR0ZXN0ABADcnVuABEJFgEAQQALEAABAgMEBQYHCAkKCwwNDg8KZhIEAEEACwQAQQELBABBAgsEAEEDCwQAQQQLBABBBQsEAEEGCwQAQQcLBABBCAsEAEEJCwQAQQoLBABBCwsEAEEMCwQAQQ0LBABBDgsEAEEPCwcAIAARAAALDAAgACABIAL8DgAACw==", + "AGFzbQEAAAABDAJgA39vfwBgAX8BbwMEAwAAAQQEAW8ACgccAwRmaWxsAAALZmlsbC1hYmJyZXYAAQNnZXQAAgogAwsAIAAgASAC/BEACwsAIAAgASAC/BEACwYAIAAlAAs=", + "AGFzbQEAAAABFwVgAABgAW8AYAF/AW9gAX8BcGABfwF/AwYFAAECAwQEBwJvAAJwAAMHOAQEaW5pdAABDWdldC1leHRlcm5yZWYAAgtnZXQtZnVuY3JlZgADD2lzX251bGwtZnVuY3JlZgAECQkBAgFBAQsAAQAKKwUCAAsQAEEBIAAmAEECQQElASYBCwYAIAAlAAsGACAAJQELBwAgABAD0Qs=", + "AGFzbQEAAAABFQRgAX8Bb2ACf28AYAJ/bwF/YAABfwMGBQABAgIDBAQBbwAABykFA2dldAAAA3NldAABBGdyb3cAAgtncm93LWFiYnJldgADBHNpemUABAorBQYAIAAlAAsIACAAIAEmAAsJACABIAD8DwALCQAgASAA/A8ACwUA/BAACw==", + "AGFzbQEAAAABBQFgAAF/AwIBAAQEAXAAEAcIAQRncm93AAAJBQEDAAEACgsBCQDSAEFw/A8ACw==", + "AGFzbQEAAAABBgFgAX8BfwMCAQAEBAFvAAAHCAEEZ3JvdwAACgsBCQDQbyAA/A8ACw==", + "AGFzbQEAAAABBgFgAX8BfwMCAQAEBQFvAQAKBwgBBGdyb3cAAAoLAQkA0G8gAPwPAAs=", + "AGFzbQEAAAABDAJgAX8Bf2ACf38BcAMDAgABBAQBcAAKBxsCBGdyb3cAABBjaGVjay10YWJsZS1udWxsAAEJBQEDAAEBCj0CCQDQcCAA/A8ACzEBAXDSASECAkADQCAAJQAhAiAC0UUNASAAIAFPDQEgAEEBaiEAIAAgAU0NAAsLIAIL", + "AGFzbQEAAAABBQFgAAF/AwIBAAQEAXAAAQcQAgV0YWJsZQEABGdyb3cAAAoLAQkA0HBBAfwPAAs=", + "AGFzbQEAAAABBQFgAAF/AhcBC2dyb3duLXRhYmxlBXRhYmxlAXAAAgMCAQAHEAIFdGFibGUBAARncm93AAAKCwEJANBwQQH8DwAL", + "AGFzbQEAAAABBQFgAAF/AiABFGdyb3duLWltcG9ydGVkLXRhYmxlBXRhYmxlAXAAAwMCAQAHCAEEc2l6ZQAACgcBBQD8EAAL", + "AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8CKQUBYQNlZjAAAAFhA2VmMQAAAWEDZWYyAAABYQNlZjMAAAFhA2VmNAAAAwgHAAAAAAABAgQJAnABHh5wAR4eBxACBHRlc3QACgVjaGVjawALCSMEAEECCwQDAQQBAQAEAgcBCABBDAsFBwUCAwYBAAUFCQIHBgovBwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEHQQBBBPwMAQALBwAgABEAAAs=", + "AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8CKQUBYQNlZjAAAAFhA2VmMQAAAWEDZWYyAAABYQNlZjMAAAFhA2VmNAAAAwgHAAAAAAABAgQJAnABHh5wAR4eBxACBHRlc3QACgVjaGVjawALCSMEAEECCwQDAQQBAQAEAgcBCABBDAsFBwUCAwYBAAUFCQIHBgovBwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEPQQFBA/wMAwALBwAgABEAAAs=", + "AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8CKQUBYQNlZjAAAAFhA2VmMQAAAWEDZWYyAAABYQNlZjMAAAFhA2VmNAAAAwgHAAAAAAABAgQJAnABHh5wAR4eBxACBHRlc3QACgVjaGVjawALCSMEAEECCwQDAQQBAQAEAgcBCABBDAsFBwUCAwYBAAUFCQIHBgpxBwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQtOAEEHQQBBBPwMAQD8DQFBD0EBQQP8DAMA/A0DQRRBD0EF/A4AAEEVQR1BAfwOAABBGEEKQQH8DgAAQQ1BC0EE/A4AAEETQRRBBfwOAAALBwAgABEAAAs=", + "AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8CKQUBYQNlZjAAAAFhA2VmMQAAAWEDZWYyAAABYQNlZjMAAAFhA2VmNAAAAwgHAAAAAAABAgQJAnABHh5wAR4eBxACBHRlc3QACgVjaGVjawALCScEAgFBAgsABAMBBAEBAAQCBwEIAgFBDAsABQcFAgMGAQAFBQkCBwYKLwcEAEEFCwQAQQYLBABBBwsEAEEICwQAQQkLDABBB0EAQQT8DAEBCwcAIAARAAEL", + "AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8CKQUBYQNlZjAAAAFhA2VmMQAAAWEDZWYyAAABYQNlZjMAAAFhA2VmNAAAAwgHAAAAAAABAgQJAnABHh5wAR4eBxACBHRlc3QACgVjaGVjawALCScEAgFBAgsABAMBBAEBAAQCBwEIAgFBDAsABQcFAgMGAQAFBQkCBwYKLwcEAEEFCwQAQQYLBABBBwsEAEEICwQAQQkLDABBD0EBQQP8DAMBCwcAIAARAAEL", + "AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8CKQUBYQNlZjAAAAFhA2VmMQAAAWEDZWYyAAABYQNlZjMAAAFhA2VmNAAAAwgHAAAAAAABAgQJAnABHh5wAR4eBxACBHRlc3QACgVjaGVjawALCScEAgFBAgsABAMBBAEBAAQCBwEIAgFBDAsABQcFAgMGAQAFBQkCBwYKcQcEAEEFCwQAQQYLBABBBwsEAEEICwQAQQkLTgBBB0EAQQT8DAEB/A0BQQ9BAUED/AwDAfwNA0EUQQ9BBfwOAQFBFUEdQQH8DgEBQRhBCkEB/A4BAUENQQtBBPwOAQFBE0EUQQX8DgEBCwcAIAARAAEL", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEcHAcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCjkLBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsFAPwNAgs=", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEcHAcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEMQQFBAfwMAgAL", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEcHAcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkoLBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsWAEEMQQFBAfwMAQBBFUEBQQH8DAEACw==", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEcHAcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCjwLBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsIAPwNAfwNAQs=", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEcHAcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkMLBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsPAPwNAUEMQQFBAfwMAQAL", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEcHAcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEMQQBBBfwMAQAL", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEcHAcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEMQQJBA/wMAQAL", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEcHAcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEcQQFBA/wMAQAL", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEcHAcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEMQQRBAPwMAQAL", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEcHAcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEMQQVBAPwMAQAL", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEcHAcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEeQQJBAPwMAQAL", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEcHAcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEfQQJBAPwMAQAL", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEcHAcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEeQQRBAPwMAQAL", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEcHAcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEfQQVBAPwMAQAL", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEcHAcIAQR0ZXN0AAoJJwQCAUECCwAEAwEEAQEABAIHAQgCAUEMCwAFBwUCAwYBAAUFCQIHBgpACwQAQQALBABBAQsEAEECCwQAQQMLBABBBAsEAEEFCwQAQQYLBABBBwsEAEEICwQAQQkLDABBGkEBQQP8DAEBCw==", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEcHAcIAQR0ZXN0AAoJJwQCAUECCwAEAwEEAQEABAIHAQgCAUEMCwAFBwUCAwYBAAUFCQIHBgpACwQAQQALBABBAQsEAEECCwQAQQMLBABBBAsEAEEFCwQAQQYLBABBBwsEAEEICwQAQQkLDABBDEEEQQD8DAEBCw==", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEcHAcIAQR0ZXN0AAoJJwQCAUECCwAEAwEEAQEABAIHAQgCAUEMCwAFBwUCAwYBAAUFCQIHBgpACwQAQQALBABBAQsEAEECCwQAQQMLBABBBAsEAEEFCwQAQQYLBABBBwsEAEEICwQAQQkLDABBDEEFQQD8DAEBCw==", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEcHAcIAQR0ZXN0AAoJJwQCAUECCwAEAwEEAQEABAIHAQgCAUEMCwAFBwUCAwYBAAUFCQIHBgpACwQAQQALBABBAQsEAEECCwQAQQMLBABBBAsEAEEFCwQAQQYLBABBBwsEAEEICwQAQQkLDABBHEECQQD8DAEBCw==", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEcHAcIAQR0ZXN0AAoJJwQCAUECCwAEAwEEAQEABAIHAQgCAUEMCwAFBwUCAwYBAAUFCQIHBgpACwQAQQALBABBAQsEAEECCwQAQQMLBABBBAsEAEEFCwQAQQYLBABBBwsEAEEICwQAQQkLDABBHUECQQD8DAEBCw==", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEcHAcIAQR0ZXN0AAoJJwQCAUECCwAEAwEEAQEABAIHAQgCAUEMCwAFBwUCAwYBAAUFCQIHBgpACwQAQQALBABBAQsEAEECCwQAQQMLBABBBAsEAEEFCwQAQQYLBABBBwsEAEEICwQAQQkLDABBHEEEQQD8DAEBCw==", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEcHAcIAQR0ZXN0AAoJJwQCAUECCwAEAwEEAQEABAIHAQgCAUEMCwAFBwUCAwYBAAUFCQIHBgpACwQAQQALBABBAQsEAEECCwQAQQMLBABBBAsEAEEFCwQAQQYLBABBBwsEAEEICwQAQQkLDABBHUEFQQD8DAEBCw==", + "AGFzbQEAAAABDwNgAAF/YAF/AX9gAn9/AAMTEgAAAAAAAAAAAAAAAAAAAAABAgQFAXABIEAHZBICZjAAAAJmMQABAmYyAAICZjMAAwJmNAAEAmY1AAUCZjYABgJmNwAHAmY4AAgCZjkACQNmMTAACgNmMTEACwNmMTIADANmMTMADQNmMTQADgNmMTUADwR0ZXN0ABADcnVuABEJFAEBABAAAQIDBAUGBwgJCgsMDQ4PCmYSBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsEAEEKCwQAQQsLBABBDAsEAEENCwQAQQ4LBABBDwsHACAAEQAACwwAIABBACAB/AwAAAs=", + "AGFzbQEAAAABDwNgAAF/YAF/AX9gAn9/AAMTEgAAAAAAAAAAAAAAAAAAAAABAgQHAXABoAHAAgdkEgJmMAAAAmYxAAECZjIAAgJmMwADAmY0AAQCZjUABQJmNgAGAmY3AAcCZjgACAJmOQAJA2YxMAAKA2YxMQALA2YxMgAMA2YxMwANA2YxNAAOA2YxNQAPBHRlc3QAEANydW4AEQkUAQEAEAABAgMEBQYHCAkKCwwNDg8KZhIEAEEACwQAQQELBABBAgsEAEEDCwQAQQQLBABBBQsEAEEGCwQAQQcLBABBCAsEAEEJCwQAQQoLBABBCwsEAEEMCwQAQQ0LBABBDgsEAEEPCwcAIAARAAALDAAgAEEAIAH8DAAACw==", + "AGFzbQEAAAABDwNgAAF/YAF/AX9gAn9/AAMTEgAAAAAAAAAAAAAAAAAAAAABAgQFAXABQEAHZBICZjAAAAJmMQABAmYyAAICZjMAAwJmNAAEAmY1AAUCZjYABgJmNwAHAmY4AAgCZjkACQNmMTAACgNmMTEACwNmMTIADANmMTMADQNmMTQADgNmMTUADwR0ZXN0ABADcnVuABEJFAEBABAAAQIDBAUGBwgJCgsMDQ4PCmYSBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsEAEEKCwQAQQsLBABBDAsEAEENCwQAQQ4LBABBDwsHACAAEQAACwwAIABBACAB/AwAAAs=", + "AGFzbQEAAAABDwNgAAF/YAF/AX9gAn9/AAMTEgAAAAAAAAAAAAAAAAAAAAABAgQFAXABEBAHZBICZjAAAAJmMQABAmYyAAICZjMAAwJmNAAEAmY1AAUCZjYABgJmNwAHAmY4AAgCZjkACQNmMTAACgNmMTEACwNmMTIADANmMTMADQNmMTQADgNmMTUADwR0ZXN0ABADcnVuABEJFAEBABAAAQIDBAUGBwgJCgsMDQ4PCmYSBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsEAEEKCwQAQQsLBABBDAsEAEENCwQAQQ4LBABBDwsHACAAEQAACwwAIABBCCAB/AwAAAs=", + "AGFzbQEAAAABBAFgAAADAgEABAQBcAABCcQBQQEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAoOAQwAQQBBAEEA/AxAAAs=", + "AGFzbQEAAAABIgdgAABgAX8Bb2ABfwFwYAJ/bwBgAn9wAGACf38AYAF/AX8DCAcAAQIDBAUGBAcCbwABcAACB2IGDWdldC1leHRlcm5yZWYAAQtnZXQtZnVuY3JlZgACDXNldC1leHRlcm5yZWYAAwtzZXQtZnVuY3JlZgAEEHNldC1mdW5jcmVmLWZyb20ABQ9pc19udWxsLWZ1bmNyZWYABgkJAQIBQQELAAEACjcHAgALBgAgACUACwYAIAAlAQsIACAAIAEmAAsIACAAIAEmAQsKACAAIAElASYBCwcAIAAQAtEL", + "AGFzbQEAAAABCQJgAAF/YAF/AAMJCAAAAAABAQEBBA8EbwAAbwABbwEAAm8BAwgHUQgHc2l6ZS10MAAAB3NpemUtdDEAAQdzaXplLXQyAAIHc2l6ZS10MwADB2dyb3ctdDAABAdncm93LXQxAAUHZ3Jvdy10MgAGB2dyb3ctdDMABwpFCAUA/BAACwUA/BABCwUA/BACCwUA/BADCwoA0G8gAPwPABoLCgDQbyAA/A8BGgsKANBvIAD8DwIaCwoA0G8gAPwPAxoL", + "AGFzbQEAAAABBAFgAAADAwIAAAQEAXAACgmkAz0BAAAFcATSAAvSAAvQcAvSAQsBAAABAAQAAAEBAQAABXAE0gAL0gAL0HAL0gELAQAAAQAEAAABAQBBAAsABEEACwLSAAvQcAsAQQALAABBAAsCAAEAQQALAABBAAsCAAEAQQALAABBAAsCAAAAQQALAABBAAsCAAAAQQALAABBAAsCAAAAQQALAABBAAsCAAAAQQALAARBAAsC0gAL0HALAEEACwIAAABBAAsCAAAAQQALAARBAAsC0gAL0HALAEEACwIAAABBAAsCAAAEQQALAtIAC9BwCwBBAAsABEEACwLSAAvQcAsAQQALAABBAAsCAAEAQQALAABBAAsCAAEAQQALAABBAAsCAAAAQQALAABBAAsCAAAAQQALAABBAAsCAAAAQQALAABBAAsCAAAAQQALAARBAAsC0gAL0HALAEEACwIAAABBAAsCAAAAQQALAARBAAsC0gAL0HALAEEACwIAAABBAAsCAAADAAAHcATSAAvSAAvQcAvSAQsDAAADAAQAAAEBAwAAB3AE0gAL0gAL0HAL0gELAwAAAwAEAAABAQoHAgIACwIACw==", + "AGFzbQEAAAABBAFgAAADAwIAAAQFAXABAwMJDwEEQQALA9IAC9BwC9IBCwoHAgIACwIACw==", + "AGFzbQEAAAABBAFgAAADAgEABAQBcAAKCQcBAEEACwEACgQBAgAL", + "AGFzbQEAAAABBAFgAAADAgEABAQBcAAKCR8FAEEACwEAAEEDCwEAAEEHCwEAAEEFCwEAAEEDCwEACgQBAgAL", + "AGFzbQEAAAABBAFgAAACGAEIc3BlY3Rlc3QKZ2xvYmFsX2kzMgN/AAMCAQAEBQFwAOgHCQcBACMACwEACgQBAgAL", + "AGFzbQEAAAABBQFgAAF/AwUEAAAAAAQEAXAACgcTAgZjYWxsLTcAAgZjYWxsLTkAAwkNAgBBBwsBAABBCQsBAQodBAUAQcEACwUAQcIACwcAQQcRAAALBwBBCREAAAs=", + "AGFzbQEAAAABBQFgAAF/AwUEAAAAAAQEAXAACwcTAgZjYWxsLTcAAgZjYWxsLTkAAwkXAgRBBgsC0HAL0gALBEEJCwLSAQvQcAsKHQQFAEHBAAsFAEHCAAsHAEEHEQAACwcAQQkRAAAL", + "AGFzbQEAAAABBAFgAAADAgEABAQBcAAKCQcBAEEJCwEACgQBAgAL", + "AGFzbQEAAAAEBAFwAAAJBgEAQQALAA==", + "AGFzbQEAAAAEBQFwAQAACQYBAEEACwA=", + "AGFzbQEAAAAEBAFwABQJBgEAQRQLAA==", + "AGFzbQEAAAABBAFgAAADAgEABAQBcAAACQcBAEEACwEACgQBAgAL", + "AGFzbQEAAAABBAFgAAADAgEABAUBcAEAAAkHAQBBAAsBAAoEAQIACw==", + "AGFzbQEAAAABBAFgAAADAgEABAUBcAEAAQkHAQBBAAsBAAoEAQIACw==", + "AGFzbQEAAAAEBAFwAAAJBgEAQQELAA==", + "AGFzbQEAAAABBAFgAAADAgEABAQBcAAKCQcBAEEKCwEACgQBAgAL", + "AGFzbQEAAAABBAFgAAADAgEABAUBcAEKFAkHAQBBCgsBAAoEAQIACw==", + "AGFzbQEAAAABBAFgAAADAgEABAQBcAAKCQcBAEF/CwEACgQBAgAL", + "AGFzbQEAAAABBAFgAAADAgEABAQBcAAKCQcBAEF2CwEACgQBAgAL", + "AGFzbQEAAAABBAFgAAADAwIAAAQEAXAACgcIAQRpbml0AAEJBwEAQQALAQAKEQICAAsMAEEAQQBBAfwMAAAL", + "AGFzbQEAAAABBAFgAAADAwIAAAQEAXAACgcIAQRpbml0AAEJBQEDAAEAChECAgALDABBAEEAQQH8DAAACw==", + "AGFzbQEAAAABBQFgAAF/AwQDAAAABAQBcAAKBxQBEGNhbGwtb3ZlcndyaXR0ZW4AAgkNAgBBCQsBAABBCQsBAQoVAwUAQcEACwUAQcIACwcAQQkRAAAL", + "AGFzbQEAAAABBQFgAAF/AwYFAAAAAAAEBAFwAAoHKwQMc2hhcmVkLXRhYmxlAQAGY2FsbC03AAIGY2FsbC04AAMGY2FsbC05AAQJDQIAQQgLAQAAQQkLAQEKJQUFAEHBAAsFAEHCAAsHAEEHEQAACwcAQQgRAAALBwBBCREAAAs=", + "AGFzbQEAAAABBQFgAAF/AhoBB21vZHVsZTEMc2hhcmVkLXRhYmxlAXAACgMDAgAACQ0CAEEHCwEAAEEICwEBCg0CBQBBwwALBQBBxAAL", + "AGFzbQEAAAABBQFgAAF/AhoBB21vZHVsZTEMc2hhcmVkLXRhYmxlAXAACgMDAgAACQ0CAEEICwEAAEEJCwEBCg0CBQBBxQALBQBBxgAL", + "AGFzbQEAAAABCwJgAX8Bb2ACf28AAwMCAAEEBAFvAAIHFQMFdGFibGUBAANnZXQAAANzZXQAAQoRAgYAIAAlAAsIACAAIAEmAAs=", + "AGFzbQEAAAACFAEIZXhwb3J0ZXIFdGFibGUBbwACCQsBBgBBAAtvAdBvCw==", + "AGFzbQEAAAABBQFgAAF/AwIBAAYGAXAA0gALBwUBAWYDAAoGAQQAQSoL", + "AGFzbQEAAAABBQFgAAF/Ag4BB21vZHVsZTQBZgNwAAMCAQAEBAFwAAoHFgESY2FsbF9pbXBvcnRlZF9lbGVtAAAJCQEEQQALASMACwoJAQcAQQARAAAL", + "AGFzbQEAAAABBgFgAX8BfwMCAQAHBQEBZgAACgYBBAAgAAs=", + "AGFzbQEAAAABDQNgAX8Bf2AAAGAAAX8CBwEBTQFmAAADDw4AAQEBAQECAgIBAQAAAAQEAXAAAQYaBXAA0gALcADSAQtwAdIAC3AA0gMLcADSBAsHUAgJaXNfbnVsbC1mAAcJaXNfbnVsbC1nAAgJaXNfbnVsbC12AAkFc2V0LWYACgVzZXQtZwALBmNhbGwtZgAMBmNhbGwtZwANBmNhbGwtdgAOCRADAwACAwUDAAIEBgMAAgABCm4OBwAgAEEBagsIANIFGtIGGgsCAAsCAAsCAAsCAAsFANIA0QsFANIB0QsFACMC0QsGANIAJAILBgDSASQCCw8AQQDSACYAIABBABEAAAsPAEEA0gEmACAAQQARAAALDwBBACMCJgAgAEEAEQAACw==", + "AGFzbQEAAAABBAFgAAADCAcAAAAAAAAABAQBcAABBgYBcADSAAsHBQEBZgABCRUEAEEACwECAEEACwEDAQABBAEAAQUKIwcCAAsCAAsCAAsCAAsCAAsCAAsPANIA0gHSAtID0gTSBQ8L", + "AGFzbQEAAAABFwVgAXABf2ABbwF/YAAAYAFvAGABfwF/AwgHAAECAwIEBAQHAnAAAm8AAgdHBgdmdW5jcmVmAAAJZXh0ZXJucmVmAAEEaW5pdAADBmRlaW5pdAAEDGZ1bmNyZWYtZWxlbQAFDmV4dGVybnJlZi1lbGVtAAYJBwEAQQELAQIKOgcFACAA0QsFACAA0QsCAAsIAEEBIAAmAQsOAEEB0HAmAEEB0G8mAQsIACAAJQAQAAsIACAAJQEQAQs=", + "AGFzbQEAAAABCQJgAAFvYAABcAMDAgABBgsCbwDQbwtwANBwCwcXAglleHRlcm5yZWYAAAdmdW5jcmVmAAEKCwIEANBvCwQA0HAL", + "AGFzbQEAAAABBAFgAAADAgEABwUBAWEAAAoEAQIACw==", + "AGFzbQEAAAABBAFgAAADAgEABwkCAWEAAAFiAAAKBAECAAs=", + "AGFzbQEAAAABBAFgAAADAwIAAAcJAgFhAAABYgABCgcCAgALAgAL", + "AGFzbQEAAAABBAFgAAADAgEABw0DAWEAAAFiAAABYwAACgQBAgAL", + "AGFzbQEAAAABBQFgAX8AAwIBAAcJAgFhAAABYgAACgQBAgAL", + "AGFzbQEAAAABBgFgAX8BfwMCAQAHBQEBZQAACgoBCAAgAEEBag8L", + "AGFzbQEAAAA=", + "AGFzbQEAAAABBQFgAAF/AwIBAAcNAwFhAAABYgAAAWMAAAoGAQQAQSoL", + "AGFzbQEAAAAGBgF/AEEACwcFAQFhAwA=", + "AGFzbQEAAAAGBgF/AEEACwcJAgFhAwABYgMA", + "AGFzbQEAAAAGCwJ/AEEAC38AQQALBwkCAWEDAAFiAwE=", + "AGFzbQEAAAAGBgF/AEEqCwcFAQFlAwA=", + "AGFzbQEAAAAEBAFwAAAHBQEBYQEA", + "AGFzbQEAAAAEBAFwAAAHCQIBYQEAAWIBAA==", + "AGFzbQEAAAAEBwJwAABwAAAHCQIBYQEAAWIBAQ==", + "AGFzbQEAAAAEBQFwAQABBwUBAWEBAA==", + "AGFzbQEAAAAFAwEAAAcFAQFhAgA=", + "AGFzbQEAAAAFAwEAAAcJAgFhAgABYgIA", + "AGFzbQEAAAAFBAEBAAEHBQEBYQIA", + "AGFzbQEAAAABBQFgAAF/AwMCAAAHCAEEY2FsbAAACgsCBAAQAQsEAEECCw==", + "AGFzbQEAAAABBQFgAAF/AgsBAk1mBGNhbGwAAAMEAwAAAAchAwdNZi5jYWxsAAAMY2FsbCBNZi5jYWxsAAEEY2FsbAACChADBAAQAAsEABADCwQAQQML", + "AGFzbQEAAAABBQFgAX8AAhYBCHNwZWN0ZXN0CXByaW50X2kzMgAABwkBBXByaW50AAA=", + "AGFzbQEAAAABBQFgAX4AAhQBCnJlZXhwb3J0X2YFcHJpbnQAAA==", + "AGFzbQEAAAABBgFgAX8BfwIUAQpyZWV4cG9ydF9mBXByaW50AAA=", + "AGFzbQEAAAABCQJgAAF/YAF/AAMEAwAAAQYMAn8AQSoLfwFBjgELBy0FBGdsb2IDAANnZXQAAAhtdXRfZ2xvYgMBB2dldF9tdXQAAQdzZXRfbXV0AAIKEgMEACMACwQAIwELBgAgACQBCw==", + "AGFzbQEAAAABCQJgAAF/YAF/AAI+BQJNZwRnbG9iA38AAk1nCG11dF9nbG9iA38BAk1nA2dldAAAAk1nB2dldF9tdXQAAAJNZwdzZXRfbXV0AAEDAgEABgYBfwBBKwsHSQcHTWcuZ2xvYgMABk1nLmdldAAABGdsb2IDAgNnZXQAAwtNZy5tdXRfZ2xvYgMBCk1nLmdldF9tdXQAAQpNZy5zZXRfbXV0AAIKBgEEACMCCw==", + "AGFzbQEAAAACEAECTWcIbXV0X2dsb2IDfwA=", + "AGFzbQEAAAACDAECTWcEZ2xvYgN/AQ==", + "AGFzbQEAAAAGFQRwANBwC3AB0HALbwDQbwtvAdBvCwc9BAxnLWNvbnN0LWZ1bmMDAApnLXZhci1mdW5jAwEOZy1jb25zdC1leHRlcm4DAgxnLXZhci1leHRlcm4DAw==", + "AGFzbQEAAAACYQQHTXJlZl9leAxnLWNvbnN0LWZ1bmMDcAAHTXJlZl9leA5nLWNvbnN0LWV4dGVybgNvAAdNcmVmX2V4CmctdmFyLWZ1bmMDcAEHTXJlZl9leAxnLXZhci1leHRlcm4DbwE=", + "AGFzbQEAAAACGwEHTXJlZl9leA5nLWNvbnN0LWV4dGVybgNwAA==", + "AGFzbQEAAAACGQEHTXJlZl9leAxnLWNvbnN0LWZ1bmMDbwA=", + "AGFzbQEAAAACFwEHTXJlZl9leApnLXZhci1mdW5jA28B", + "AGFzbQEAAAACGQEHTXJlZl9leAxnLXZhci1leHRlcm4DcAE=", + "AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8DBAMAAAIEBAFwAAoHEgMDdGFiAQABaAABBGNhbGwAAgkKAQBBAgsEAAAAAAoTAwQAQQQLBABBfAsHACAAEQAACw==", + "AGFzbQEAAAABDQNgAABgAAF/YAF/AX8CEgICTXQEY2FsbAACAk10AWgAAQMEAwECAgQFAXABBQUHIQMHTXQuY2FsbAAADGNhbGwgTXQuY2FsbAADBGNhbGwABAkLAQBBAAsFAgICAQAKFQMEAEEFCwYAIAAQAAsHACAAEQEACw==", + "AGFzbQEAAAABCgJgAAF/YAF/AX8CEwICTXQBaAAAAk10A3RhYgFwAAUDAwIAAQcIAQRjYWxsAAIJCAEAQQELAgEACg4CBABBBgsHACAAEQAACw==", + "AGFzbQEAAAABBAFgAAACDAECTXQDdGFiAXAAAAMCAQAJBwEAQQkLAQAKBAECAAs=", + "AGFzbQEAAAAGBgF/AEEFCwcFAQFnAwA=", + "AGFzbQEAAAACCQECRzEBZwN/AAYGAX8AIwALBwUBAWcDAQ==", + "AGFzbQEAAAABBAFgAAACDAECTXQDdGFiAXAAAAMCAQAJBwEAQQoLAQAKBAECAAs=", + "AGFzbQEAAAABBQFgAAF/AhYCAk10A3RhYgFwAAoCTXQDbWVtAgABAwIBAAkNAgBBBwsBAABBCQsBAAoGAQQAQQAL", + "AGFzbQEAAAABBQFgAAF/AgwBAk10A3RhYgFwAAoDAgEACRECAEEHCwEAAEEICwUAAAAAAAoGAQQAQQAL", + "AGFzbQEAAAABBQFgAAF/AgwBAk10A3RhYgFwAAoDAgEABQMBAAEJBwEAQQcLAQAKBgEEAEEACwsJAQBBgIAECwFk", + "AGFzbQEAAAAEBwJwAAFvAAEHFQIGdC1mdW5jAQAIdC1leHRlcm4BAQ==", + "AGFzbQEAAAACLQIJTXRhYmxlX2V4BnQtZnVuYwFwAAEJTXRhYmxlX2V4CHQtZXh0ZXJuAW8AAQ==", + "AGFzbQEAAAACFgEJTXRhYmxlX2V4BnQtZnVuYwFvAAE=", + "AGFzbQEAAAACGAEJTXRhYmxlX2V4CHQtZXh0ZXJuAXAAAQ==", + "AGFzbQEAAAABBgFgAX8BfwMCAQAFBAEBAQUHDgIDbWVtAgAEbG9hZAAACgkBBwAgAC0AAAsLEAEAQQoLCgABAgMEBQYHCAk=", + "AGFzbQEAAAABBgFgAX8BfwILAQJNbQRsb2FkAAADAgEABQMBAAEHEgIHTW0ubG9hZAAABGxvYWQAAQoJAQcAIAAtAAALCwwBAEEKCwbw8fLz9PU=", + "AGFzbQEAAAABBgFgAX8BfwILAQJNbQNtZW0CAAEDAgEABwgBBGxvYWQAAAoJAQcAIAAtAAALCw4BAEEFCwigoaKjpKWmpw==", + "AGFzbQEAAAACCwECTW0DbWVtAgAACwkBAEH//wMLAWE=", + "AGFzbQEAAAACCwECTW0DbWVtAgAACwkBAEGAgAQLAWE=", + "AGFzbQEAAAABBgFgAX8BfwIMAQJNbQNtZW0CAQEIAwIBAAcIAQRncm93AAAKCAEGACAAQAAL", + "AGFzbQEAAAABBAFgAAACJwMIc3BlY3Rlc3QFcHJpbnQAAAJNbQNtZW0CAAECTW0DdGFiAXAAAAsJAQBBAAsDYWJj", + "AGFzbQEAAAACCwECTW0DbWVtAgABCyICAEEACwNhYmMAQfb/EwsSenp6enp6enp6enp6enp6enp6", + "AGFzbQEAAAABBAFgAAACCwECTW0DbWVtAgABAwIBAAQEAXAAAAkHAQBBAAsBAAoEAQIACwsJAQBBAAsDYWJj", + "AGFzbQEAAAABBQFgAAF/AwMCAAAEBAFwAAEFAwEAAQcxBAZtZW1vcnkCAAV0YWJsZQEADWdldCBtZW1vcnlbMF0AAAxnZXQgdGFibGVbMF0AAQoRAgcAQQAtAAALBwBBABEAAAs=", + "AGFzbQEAAAABCAJgAAF/YAAAAhsCAk1zBm1lbW9yeQIAAQJNcwV0YWJsZQFwAAEDAwIAAQgBAQkHAQBBAAsBAAoMAgYAQa29AwsDAAALCwsBAEEACwVoZWxsbw==", +]; +export const script = "[[\"file\",\"table_copy.wast\"],[\"module\",6,0],[\"register\",\"a\"],[\"module\",15,1],[\"invoke\",45,0,\"test\",[]],[\"invoke\",50,0,\"check_t0\",[\"i32:4\"]],[\"invoke\",54,0,\"check_t0\",[\"i32:8\"]],[\"invoke\",58,0,\"check_t0\",[\"i32:12\"]],[\"invoke\",62,0,\"check_t0\",[\"i32:16\"]],[\"invoke\",66,0,\"check_t0\",[\"i32:20\"]],[\"invoke\",71,0,\"check_t0\",[\"i32:25\"]],[\"invoke\",75,0,\"check_t0\",[\"i32:29\"]],[\"invoke\",79,0,\"check_t1\",[\"i32:3\"]],[\"invoke\",83,0,\"check_t1\",[\"i32:7\"]],[\"invoke\",87,0,\"check_t1\",[\"i32:11\"]],[\"invoke\",92,0,\"check_t1\",[\"i32:16\"]],[\"invoke\",96,0,\"check_t1\",[\"i32:20\"]],[\"invoke\",100,0,\"check_t1\",[\"i32:24\"]],[\"invoke\",104,0,\"check_t1\",[\"i32:28\"]],[\"module\",107,2],[\"invoke\",139,0,\"check_t0\",[\"i32:1\"]],[\"invoke\",143,0,\"check_t0\",[\"i32:5\"]],[\"invoke\",148,0,\"check_t0\",[\"i32:10\"]],[\"invoke\",152,0,\"check_t0\",[\"i32:14\"]],[\"invoke\",156,0,\"check_t0\",[\"i32:18\"]],[\"invoke\",160,0,\"check_t0\",[\"i32:22\"]],[\"invoke\",164,0,\"check_t0\",[\"i32:26\"]],[\"invoke\",169,0,\"check_t1\",[\"i32:1\"]],[\"invoke\",173,0,\"check_t1\",[\"i32:5\"]],[\"invoke\",177,0,\"check_t1\",[\"i32:9\"]],[\"invoke\",181,0,\"check_t1\",[\"i32:13\"]],[\"invoke\",185,0,\"check_t1\",[\"i32:17\"]],[\"invoke\",190,0,\"check_t1\",[\"i32:22\"]],[\"invoke\",194,0,\"check_t1\",[\"i32:26\"]],[\"module\",199,3],[\"invoke\",229,0,\"test\",[]],[\"invoke\",233,0,\"check_t0\",[\"i32:3\"]],[\"invoke\",237,0,\"check_t0\",[\"i32:7\"]],[\"invoke\",241,0,\"check_t0\",[\"i32:11\"]],[\"invoke\",246,0,\"check_t0\",[\"i32:16\"]],[\"invoke\",250,0,\"check_t0\",[\"i32:20\"]],[\"invoke\",254,0,\"check_t0\",[\"i32:24\"]],[\"invoke\",258,0,\"check_t0\",[\"i32:28\"]],[\"invoke\",262,0,\"check_t1\",[\"i32:2\"]],[\"invoke\",267,0,\"check_t1\",[\"i32:7\"]],[\"invoke\",271,0,\"check_t1\",[\"i32:11\"]],[\"invoke\",275,0,\"check_t1\",[\"i32:15\"]],[\"invoke\",279,0,\"check_t1\",[\"i32:19\"]],[\"invoke\",283,0,\"check_t1\",[\"i32:23\"]],[\"invoke\",288,0,\"check_t1\",[\"i32:28\"]],[\"module\",291,4],[\"invoke\",323,0,\"check_t0\",[\"i32:1\"]],[\"invoke\",327,0,\"check_t0\",[\"i32:5\"]],[\"invoke\",331,0,\"check_t0\",[\"i32:9\"]],[\"invoke\",335,0,\"check_t0\",[\"i32:13\"]],[\"invoke\",339,0,\"check_t0\",[\"i32:17\"]],[\"invoke\",344,0,\"check_t0\",[\"i32:22\"]],[\"invoke\",348,0,\"check_t0\",[\"i32:26\"]],[\"invoke\",352,0,\"check_t1\",[\"i32:0\"]],[\"invoke\",356,0,\"check_t1\",[\"i32:4\"]],[\"invoke\",360,0,\"check_t1\",[\"i32:8\"]],[\"invoke\",365,0,\"check_t1\",[\"i32:13\"]],[\"invoke\",369,0,\"check_t1\",[\"i32:17\"]],[\"invoke\",373,0,\"check_t1\",[\"i32:21\"]],[\"invoke\",377,0,\"check_t1\",[\"i32:25\"]],[\"invoke\",381,0,\"check_t1\",[\"i32:29\"]],[\"module\",383,5],[\"invoke\",417,0,\"check_t0\",[\"i32:3\"]],[\"invoke\",421,0,\"check_t0\",[\"i32:7\"]],[\"invoke\",425,0,\"check_t0\",[\"i32:11\"]],[\"invoke\",429,0,\"check_t0\",[\"i32:15\"]],[\"invoke\",433,0,\"check_t0\",[\"i32:19\"]],[\"invoke\",437,0,\"check_t0\",[\"i32:23\"]],[\"invoke\",442,0,\"check_t0\",[\"i32:28\"]],[\"invoke\",446,0,\"check_t1\",[\"i32:2\"]],[\"invoke\",450,0,\"check_t1\",[\"i32:6\"]],[\"invoke\",454,0,\"check_t1\",[\"i32:10\"]],[\"invoke\",458,0,\"check_t1\",[\"i32:14\"]],[\"invoke\",463,0,\"check_t1\",[\"i32:19\"]],[\"invoke\",467,0,\"check_t1\",[\"i32:23\"]],[\"invoke\",471,0,\"check_t1\",[\"i32:27\"]],[\"module\",475,6],[\"invoke\",506,0,\"check_t0\",[\"i32:0\"]],[\"invoke\",510,0,\"check_t0\",[\"i32:4\"]],[\"invoke\",515,0,\"check_t0\",[\"i32:9\"]],[\"invoke\",519,0,\"check_t0\",[\"i32:13\"]],[\"invoke\",523,0,\"check_t0\",[\"i32:17\"]],[\"invoke\",527,0,\"check_t0\",[\"i32:21\"]],[\"invoke\",531,0,\"check_t0\",[\"i32:25\"]],[\"invoke\",535,0,\"check_t0\",[\"i32:29\"]],[\"invoke\",540,0,\"check_t1\",[\"i32:4\"]],[\"invoke\",544,0,\"check_t1\",[\"i32:8\"]],[\"invoke\",548,0,\"check_t1\",[\"i32:12\"]],[\"invoke\",552,0,\"check_t1\",[\"i32:16\"]],[\"invoke\",556,0,\"check_t1\",[\"i32:20\"]],[\"invoke\",561,0,\"check_t1\",[\"i32:25\"]],[\"invoke\",565,0,\"check_t1\",[\"i32:29\"]],[\"module\",567,7],[\"invoke\",600,0,\"check_t0\",[\"i32:2\"]],[\"invoke\",604,0,\"check_t0\",[\"i32:6\"]],[\"invoke\",608,0,\"check_t0\",[\"i32:10\"]],[\"invoke\",613,0,\"check_t0\",[\"i32:15\"]],[\"invoke\",617,0,\"check_t0\",[\"i32:19\"]],[\"invoke\",621,0,\"check_t0\",[\"i32:23\"]],[\"invoke\",625,0,\"check_t0\",[\"i32:27\"]],[\"invoke\",629,0,\"check_t1\",[\"i32:1\"]],[\"invoke\",633,0,\"check_t1\",[\"i32:5\"]],[\"invoke\",638,0,\"check_t1\",[\"i32:10\"]],[\"invoke\",642,0,\"check_t1\",[\"i32:14\"]],[\"invoke\",646,0,\"check_t1\",[\"i32:18\"]],[\"invoke\",650,0,\"check_t1\",[\"i32:22\"]],[\"invoke\",654,0,\"check_t1\",[\"i32:26\"]],[\"module\",659,8],[\"invoke\",690,0,\"check_t0\",[\"i32:0\"]],[\"invoke\",694,0,\"check_t0\",[\"i32:4\"]],[\"invoke\",698,0,\"check_t0\",[\"i32:8\"]],[\"invoke\",702,0,\"check_t0\",[\"i32:12\"]],[\"invoke\",706,0,\"check_t0\",[\"i32:16\"]],[\"invoke\",711,0,\"check_t0\",[\"i32:21\"]],[\"invoke\",715,0,\"check_t0\",[\"i32:25\"]],[\"invoke\",719,0,\"check_t0\",[\"i32:29\"]],[\"invoke\",723,0,\"check_t1\",[\"i32:3\"]],[\"invoke\",727,0,\"check_t1\",[\"i32:7\"]],[\"invoke\",731,0,\"check_t1\",[\"i32:11\"]],[\"invoke\",736,0,\"check_t1\",[\"i32:16\"]],[\"invoke\",740,0,\"check_t1\",[\"i32:20\"]],[\"invoke\",744,0,\"check_t1\",[\"i32:24\"]],[\"invoke\",748,0,\"check_t1\",[\"i32:28\"]],[\"module\",751,9],[\"invoke\",783,0,\"check_t0\",[\"i32:1\"]],[\"invoke\",788,0,\"check_t0\",[\"i32:6\"]],[\"invoke\",792,0,\"check_t0\",[\"i32:10\"]],[\"invoke\",796,0,\"check_t0\",[\"i32:14\"]],[\"invoke\",800,0,\"check_t0\",[\"i32:18\"]],[\"invoke\",804,0,\"check_t0\",[\"i32:22\"]],[\"invoke\",809,0,\"check_t0\",[\"i32:27\"]],[\"invoke\",813,0,\"check_t1\",[\"i32:1\"]],[\"invoke\",817,0,\"check_t1\",[\"i32:5\"]],[\"invoke\",821,0,\"check_t1\",[\"i32:9\"]],[\"invoke\",825,0,\"check_t1\",[\"i32:13\"]],[\"invoke\",829,0,\"check_t1\",[\"i32:17\"]],[\"invoke\",834,0,\"check_t1\",[\"i32:22\"]],[\"invoke\",838,0,\"check_t1\",[\"i32:26\"]],[\"module\",843,10],[\"invoke\",873,0,\"test\",[]],[\"invoke\",877,0,\"check_t0\",[\"i32:3\"]],[\"invoke\",881,0,\"check_t0\",[\"i32:7\"]],[\"invoke\",886,0,\"check_t0\",[\"i32:12\"]],[\"invoke\",890,0,\"check_t0\",[\"i32:16\"]],[\"invoke\",894,0,\"check_t0\",[\"i32:20\"]],[\"invoke\",898,0,\"check_t0\",[\"i32:24\"]],[\"invoke\",902,0,\"check_t0\",[\"i32:28\"]],[\"invoke\",907,0,\"check_t1\",[\"i32:3\"]],[\"invoke\",911,0,\"check_t1\",[\"i32:7\"]],[\"invoke\",915,0,\"check_t1\",[\"i32:11\"]],[\"invoke\",919,0,\"check_t1\",[\"i32:15\"]],[\"invoke\",923,0,\"check_t1\",[\"i32:19\"]],[\"invoke\",927,0,\"check_t1\",[\"i32:23\"]],[\"invoke\",932,0,\"check_t1\",[\"i32:28\"]],[\"module\",935,11],[\"invoke\",967,0,\"check_t0\",[\"i32:1\"]],[\"invoke\",971,0,\"check_t0\",[\"i32:5\"]],[\"invoke\",975,0,\"check_t0\",[\"i32:9\"]],[\"invoke\",979,0,\"check_t0\",[\"i32:13\"]],[\"invoke\",984,0,\"check_t0\",[\"i32:18\"]],[\"invoke\",988,0,\"check_t0\",[\"i32:22\"]],[\"invoke\",992,0,\"check_t0\",[\"i32:26\"]],[\"invoke\",996,0,\"check_t1\",[\"i32:0\"]],[\"invoke\",1000,0,\"check_t1\",[\"i32:4\"]],[\"invoke\",1005,0,\"check_t1\",[\"i32:9\"]],[\"invoke\",1009,0,\"check_t1\",[\"i32:13\"]],[\"invoke\",1013,0,\"check_t1\",[\"i32:17\"]],[\"invoke\",1017,0,\"check_t1\",[\"i32:21\"]],[\"invoke\",1021,0,\"check_t1\",[\"i32:25\"]],[\"invoke\",1025,0,\"check_t1\",[\"i32:29\"]],[\"module\",1027,12],[\"invoke\",1061,0,\"check_t0\",[\"i32:3\"]],[\"invoke\",1065,0,\"check_t0\",[\"i32:7\"]],[\"invoke\",1069,0,\"check_t0\",[\"i32:11\"]],[\"invoke\",1073,0,\"check_t0\",[\"i32:15\"]],[\"invoke\",1077,0,\"check_t0\",[\"i32:19\"]],[\"invoke\",1082,0,\"check_t0\",[\"i32:24\"]],[\"invoke\",1086,0,\"check_t0\",[\"i32:28\"]],[\"invoke\",1090,0,\"check_t1\",[\"i32:2\"]],[\"invoke\",1094,0,\"check_t1\",[\"i32:6\"]],[\"invoke\",1098,0,\"check_t1\",[\"i32:10\"]],[\"invoke\",1103,0,\"check_t1\",[\"i32:15\"]],[\"invoke\",1107,0,\"check_t1\",[\"i32:19\"]],[\"invoke\",1111,0,\"check_t1\",[\"i32:23\"]],[\"invoke\",1115,0,\"check_t1\",[\"i32:27\"]],[\"module\",1119,13],[\"invoke\",1150,0,\"check_t0\",[\"i32:0\"]],[\"invoke\",1154,0,\"check_t0\",[\"i32:4\"]],[\"invoke\",1159,0,\"check_t0\",[\"i32:9\"]],[\"invoke\",1163,0,\"check_t0\",[\"i32:13\"]],[\"invoke\",1167,0,\"check_t0\",[\"i32:17\"]],[\"invoke\",1171,0,\"check_t0\",[\"i32:21\"]],[\"invoke\",1175,0,\"check_t0\",[\"i32:25\"]],[\"invoke\",1180,0,\"check_t1\",[\"i32:0\"]],[\"invoke\",1184,0,\"check_t1\",[\"i32:4\"]],[\"invoke\",1188,0,\"check_t1\",[\"i32:8\"]],[\"invoke\",1192,0,\"check_t1\",[\"i32:12\"]],[\"invoke\",1196,0,\"check_t1\",[\"i32:16\"]],[\"invoke\",1201,0,\"check_t1\",[\"i32:21\"]],[\"invoke\",1205,0,\"check_t1\",[\"i32:25\"]],[\"invoke\",1209,0,\"check_t1\",[\"i32:29\"]],[\"module\",1211,14],[\"invoke\",1244,0,\"check_t0\",[\"i32:2\"]],[\"invoke\",1248,0,\"check_t0\",[\"i32:6\"]],[\"invoke\",1252,0,\"check_t0\",[\"i32:10\"]],[\"invoke\",1257,0,\"check_t0\",[\"i32:15\"]],[\"invoke\",1261,0,\"check_t0\",[\"i32:19\"]],[\"invoke\",1265,0,\"check_t0\",[\"i32:23\"]],[\"invoke\",1269,0,\"check_t0\",[\"i32:27\"]],[\"invoke\",1273,0,\"check_t1\",[\"i32:1\"]],[\"invoke\",1278,0,\"check_t1\",[\"i32:6\"]],[\"invoke\",1282,0,\"check_t1\",[\"i32:10\"]],[\"invoke\",1286,0,\"check_t1\",[\"i32:14\"]],[\"invoke\",1290,0,\"check_t1\",[\"i32:18\"]],[\"invoke\",1294,0,\"check_t1\",[\"i32:22\"]],[\"invoke\",1299,0,\"check_t1\",[\"i32:27\"]],[\"module\",1303,15],[\"invoke\",1334,0,\"check_t0\",[\"i32:0\"]],[\"invoke\",1338,0,\"check_t0\",[\"i32:4\"]],[\"invoke\",1342,0,\"check_t0\",[\"i32:8\"]],[\"invoke\",1346,0,\"check_t0\",[\"i32:12\"]],[\"invoke\",1350,0,\"check_t0\",[\"i32:16\"]],[\"invoke\",1355,0,\"check_t0\",[\"i32:21\"]],[\"invoke\",1359,0,\"check_t0\",[\"i32:25\"]],[\"invoke\",1363,0,\"check_t0\",[\"i32:29\"]],[\"invoke\",1367,0,\"check_t1\",[\"i32:3\"]],[\"invoke\",1371,0,\"check_t1\",[\"i32:7\"]],[\"invoke\",1376,0,\"check_t1\",[\"i32:12\"]],[\"invoke\",1380,0,\"check_t1\",[\"i32:16\"]],[\"invoke\",1384,0,\"check_t1\",[\"i32:20\"]],[\"invoke\",1388,0,\"check_t1\",[\"i32:24\"]],[\"invoke\",1392,0,\"check_t1\",[\"i32:28\"]],[\"module\",1395,16],[\"invoke\",1428,0,\"check_t0\",[\"i32:2\"]],[\"invoke\",1432,0,\"check_t0\",[\"i32:6\"]],[\"invoke\",1436,0,\"check_t0\",[\"i32:10\"]],[\"invoke\",1440,0,\"check_t0\",[\"i32:14\"]],[\"invoke\",1444,0,\"check_t0\",[\"i32:18\"]],[\"invoke\",1448,0,\"check_t0\",[\"i32:22\"]],[\"invoke\",1453,0,\"check_t0\",[\"i32:27\"]],[\"invoke\",1457,0,\"check_t1\",[\"i32:1\"]],[\"invoke\",1461,0,\"check_t1\",[\"i32:5\"]],[\"invoke\",1465,0,\"check_t1\",[\"i32:9\"]],[\"invoke\",1469,0,\"check_t1\",[\"i32:13\"]],[\"invoke\",1474,0,\"check_t1\",[\"i32:18\"]],[\"invoke\",1478,0,\"check_t1\",[\"i32:22\"]],[\"invoke\",1482,0,\"check_t1\",[\"i32:26\"]],[\"module\",1487,17],[\"invoke\",1517,0,\"test\",[]],[\"invoke\",1521,0,\"check_t0\",[\"i32:3\"]],[\"invoke\",1526,0,\"check_t0\",[\"i32:8\"]],[\"invoke\",1530,0,\"check_t0\",[\"i32:12\"]],[\"invoke\",1534,0,\"check_t0\",[\"i32:16\"]],[\"invoke\",1538,0,\"check_t0\",[\"i32:20\"]],[\"invoke\",1542,0,\"check_t0\",[\"i32:24\"]],[\"invoke\",1546,0,\"check_t0\",[\"i32:28\"]],[\"invoke\",1551,0,\"check_t1\",[\"i32:3\"]],[\"invoke\",1555,0,\"check_t1\",[\"i32:7\"]],[\"invoke\",1559,0,\"check_t1\",[\"i32:11\"]],[\"invoke\",1563,0,\"check_t1\",[\"i32:15\"]],[\"invoke\",1567,0,\"check_t1\",[\"i32:19\"]],[\"invoke\",1572,0,\"check_t1\",[\"i32:24\"]],[\"invoke\",1576,0,\"check_t1\",[\"i32:28\"]],[\"module\",1579,18],[\"invoke\",1611,0,\"check_t0\",[\"i32:1\"]],[\"invoke\",1615,0,\"check_t0\",[\"i32:5\"]],[\"invoke\",1619,0,\"check_t0\",[\"i32:9\"]],[\"invoke\",1624,0,\"check_t0\",[\"i32:14\"]],[\"invoke\",1628,0,\"check_t0\",[\"i32:18\"]],[\"invoke\",1632,0,\"check_t0\",[\"i32:22\"]],[\"invoke\",1636,0,\"check_t0\",[\"i32:26\"]],[\"invoke\",1640,0,\"check_t1\",[\"i32:0\"]],[\"invoke\",1644,0,\"check_t1\",[\"i32:4\"]],[\"invoke\",1649,0,\"check_t1\",[\"i32:9\"]],[\"invoke\",1653,0,\"check_t1\",[\"i32:13\"]],[\"invoke\",1657,0,\"check_t1\",[\"i32:17\"]],[\"invoke\",1661,0,\"check_t1\",[\"i32:21\"]],[\"invoke\",1665,0,\"check_t1\",[\"i32:25\"]],[\"module\",1671,19],[\"invoke\",1694,0,\"test\",[]],[\"module\",1696,20],[\"module\",1721,21],[\"module\",1746,22],[\"module\",1771,23],[\"invoke\",1794,0,\"test\",[]],[\"module\",1796,24],[\"module\",1821,25],[\"module\",1846,26],[\"module\",1871,27],[\"invoke\",1894,0,\"test\",[]],[\"module\",1896,28],[\"module\",1921,29],[\"module\",1946,30],[\"module\",1971,31],[\"invoke\",1994,0,\"test\",[]],[\"module\",1996,32],[\"module\",2021,33],[\"module\",2046,34],[\"module\",2071,35],[\"invoke\",2094,0,\"test\",[]],[\"module\",2096,36],[\"module\",2121,37],[\"module\",2146,38],[\"module\",2171,39],[\"module\",2196,40],[\"invoke\",2219,0,\"test\",[]],[\"module\",2221,41],[\"invoke\",2251,0,\"test\",[\"i32:2\"]],[\"invoke\",2255,0,\"test\",[\"i32:6\"]],[\"invoke\",2259,0,\"test\",[\"i32:10\"]],[\"invoke\",2263,0,\"test\",[\"i32:14\"]],[\"invoke\",2267,0,\"test\",[\"i32:18\"]],[\"invoke\",2272,0,\"test\",[\"i32:23\"]],[\"invoke\",2276,0,\"test\",[\"i32:27\"]],[\"invoke\",2280,0,\"test\",[\"i32:31\"]],[\"module\",2282,42],[\"invoke\",2312,0,\"test\",[\"i32:2\"]],[\"invoke\",2316,0,\"test\",[\"i32:6\"]],[\"invoke\",2321,0,\"test\",[\"i32:11\"]],[\"invoke\",2325,0,\"test\",[\"i32:15\"]],[\"invoke\",2329,0,\"test\",[\"i32:19\"]],[\"invoke\",2333,0,\"test\",[\"i32:23\"]],[\"invoke\",2337,0,\"test\",[\"i32:27\"]],[\"module\",2343,43],[\"invoke\",2369,0,\"run\",[\"i32:0\",\"i32:24\",\"i32:16\"]],[\"invoke\",2374,0,\"test\",[\"i32:3\"]],[\"invoke\",2378,0,\"test\",[\"i32:7\"]],[\"invoke\",2382,0,\"test\",[\"i32:11\"]],[\"invoke\",2386,0,\"test\",[\"i32:15\"]],[\"invoke\",2390,0,\"test\",[\"i32:19\"]],[\"invoke\",2395,0,\"test\",[\"i32:24\"]],[\"invoke\",2399,0,\"test\",[\"i32:28\"]],[\"module\",2404,44],[\"invoke\",2430,0,\"run\",[\"i32:0\",\"i32:23\",\"i32:15\"]],[\"invoke\",2435,0,\"test\",[\"i32:3\"]],[\"invoke\",2439,0,\"test\",[\"i32:7\"]],[\"invoke\",2444,0,\"test\",[\"i32:12\"]],[\"invoke\",2448,0,\"test\",[\"i32:16\"]],[\"invoke\",2452,0,\"test\",[\"i32:20\"]],[\"invoke\",2456,0,\"test\",[\"i32:24\"]],[\"invoke\",2460,0,\"test\",[\"i32:28\"]],[\"module\",2465,45],[\"invoke\",2493,0,\"test\",[\"i32:0\"]],[\"invoke\",2497,0,\"test\",[\"i32:4\"]],[\"invoke\",2501,0,\"test\",[\"i32:8\"]],[\"invoke\",2505,0,\"test\",[\"i32:12\"]],[\"invoke\",2509,0,\"test\",[\"i32:16\"]],[\"invoke\",2513,0,\"test\",[\"i32:20\"]],[\"invoke\",2518,0,\"test\",[\"i32:25\"]],[\"invoke\",2522,0,\"test\",[\"i32:29\"]],[\"module\",2526,43],[\"invoke\",2554,0,\"test\",[\"i32:0\"]],[\"invoke\",2558,0,\"test\",[\"i32:4\"]],[\"invoke\",2562,0,\"test\",[\"i32:8\"]],[\"invoke\",2567,0,\"test\",[\"i32:13\"]],[\"invoke\",2571,0,\"test\",[\"i32:17\"]],[\"invoke\",2575,0,\"test\",[\"i32:21\"]],[\"invoke\",2579,0,\"test\",[\"i32:25\"]],[\"invoke\",2583,0,\"test\",[\"i32:29\"]],[\"module\",2587,46],[\"invoke\",2616,0,\"test\",[\"i32:1\"]],[\"invoke\",2620,0,\"test\",[\"i32:5\"]],[\"invoke\",2624,0,\"test\",[\"i32:9\"]],[\"invoke\",2628,0,\"test\",[\"i32:13\"]],[\"invoke\",2632,0,\"test\",[\"i32:17\"]],[\"invoke\",2636,0,\"test\",[\"i32:21\"]],[\"invoke\",2641,0,\"test\",[\"i32:26\"]],[\"invoke\",2645,0,\"test\",[\"i32:30\"]],[\"module\",2648,43],[\"invoke\",2677,0,\"test\",[\"i32:1\"]],[\"invoke\",2681,0,\"test\",[\"i32:5\"]],[\"invoke\",2685,0,\"test\",[\"i32:9\"]],[\"invoke\",2690,0,\"test\",[\"i32:14\"]],[\"invoke\",2694,0,\"test\",[\"i32:18\"]],[\"invoke\",2698,0,\"test\",[\"i32:22\"]],[\"invoke\",2702,0,\"test\",[\"i32:26\"]],[\"invoke\",2706,0,\"test\",[\"i32:30\"]],[\"module\",2709,47],[\"invoke\",2739,0,\"test\",[\"i32:2\"]],[\"invoke\",2743,0,\"test\",[\"i32:6\"]],[\"invoke\",2747,0,\"test\",[\"i32:10\"]],[\"invoke\",2751,0,\"test\",[\"i32:14\"]],[\"invoke\",2755,0,\"test\",[\"i32:18\"]],[\"invoke\",2759,0,\"test\",[\"i32:22\"]],[\"invoke\",2764,0,\"test\",[\"i32:27\"]],[\"invoke\",2768,0,\"test\",[\"i32:31\"]],[\"module\",2770,48],[\"invoke\",2800,0,\"test\",[\"i32:2\"]],[\"invoke\",2804,0,\"test\",[\"i32:6\"]],[\"invoke\",2808,0,\"test\",[\"i32:10\"]],[\"invoke\",2813,0,\"test\",[\"i32:15\"]],[\"invoke\",2817,0,\"test\",[\"i32:19\"]],[\"invoke\",2821,0,\"test\",[\"i32:23\"]],[\"invoke\",2825,0,\"test\",[\"i32:27\"]],[\"invoke\",2829,0,\"test\",[\"i32:31\"]],[\"invoke\",2834,0,\"test\",[\"i32:36\"]],[\"invoke\",2838,0,\"test\",[\"i32:40\"]],[\"invoke\",2842,0,\"test\",[\"i32:44\"]],[\"invoke\",2846,0,\"test\",[\"i32:48\"]],[\"invoke\",2850,0,\"test\",[\"i32:52\"]],[\"invoke\",2854,0,\"test\",[\"i32:56\"]],[\"invoke\",2859,0,\"test\",[\"i32:61\"]],[\"invoke\",2863,0,\"test\",[\"i32:65\"]],[\"invoke\",2867,0,\"test\",[\"i32:69\"]],[\"invoke\",2871,0,\"test\",[\"i32:73\"]],[\"invoke\",2875,0,\"test\",[\"i32:77\"]],[\"invoke\",2880,0,\"test\",[\"i32:82\"]],[\"invoke\",2884,0,\"test\",[\"i32:86\"]],[\"invoke\",2888,0,\"test\",[\"i32:90\"]],[\"invoke\",2892,0,\"test\",[\"i32:94\"]],[\"invoke\",2896,0,\"test\",[\"i32:98\"]],[\"invoke\",2901,0,\"test\",[\"i32:103\"]],[\"invoke\",2905,0,\"test\",[\"i32:107\"]],[\"invoke\",2909,0,\"test\",[\"i32:111\"]],[\"invoke\",2913,0,\"test\",[\"i32:115\"]],[\"invoke\",2917,0,\"test\",[\"i32:119\"]],[\"invoke\",2921,0,\"test\",[\"i32:123\"]],[\"module\",2927,49],[\"invoke\",2953,0,\"run\",[\"i32:112\",\"i32:0\",\"i32:4294967264\"]],[\"invoke\",2958,0,\"test\",[\"i32:3\"]],[\"invoke\",2962,0,\"test\",[\"i32:7\"]],[\"invoke\",2966,0,\"test\",[\"i32:11\"]],[\"invoke\",2970,0,\"test\",[\"i32:15\"]],[\"invoke\",2975,0,\"test\",[\"i32:20\"]],[\"invoke\",2979,0,\"test\",[\"i32:24\"]],[\"invoke\",2983,0,\"test\",[\"i32:28\"]],[\"invoke\",2987,0,\"test\",[\"i32:32\"]],[\"invoke\",2991,0,\"test\",[\"i32:36\"]],[\"invoke\",2996,0,\"test\",[\"i32:41\"]],[\"invoke\",3000,0,\"test\",[\"i32:45\"]],[\"invoke\",3004,0,\"test\",[\"i32:49\"]],[\"invoke\",3008,0,\"test\",[\"i32:53\"]],[\"invoke\",3012,0,\"test\",[\"i32:57\"]],[\"invoke\",3016,0,\"test\",[\"i32:61\"]],[\"invoke\",3021,0,\"test\",[\"i32:66\"]],[\"invoke\",3025,0,\"test\",[\"i32:70\"]],[\"invoke\",3029,0,\"test\",[\"i32:74\"]],[\"invoke\",3033,0,\"test\",[\"i32:78\"]],[\"invoke\",3037,0,\"test\",[\"i32:82\"]],[\"invoke\",3042,0,\"test\",[\"i32:87\"]],[\"invoke\",3046,0,\"test\",[\"i32:91\"]],[\"invoke\",3050,0,\"test\",[\"i32:95\"]],[\"invoke\",3054,0,\"test\",[\"i32:99\"]],[\"invoke\",3058,0,\"test\",[\"i32:103\"]],[\"invoke\",3063,0,\"test\",[\"i32:108\"]],[\"invoke\",3067,0,\"test\",[\"i32:112\"]],[\"invoke\",3071,0,\"test\",[\"i32:116\"]],[\"invoke\",3075,0,\"test\",[\"i32:120\"]],[\"invoke\",3079,0,\"test\",[\"i32:124\"]],[\"file\",\"table_fill.wast\"],[\"module\",1,50],[\"invoke\",17,0,\"get\",[\"i32:1\"]],[\"invoke\",18,0,\"get\",[\"i32:2\"]],[\"invoke\",19,0,\"get\",[\"i32:3\"]],[\"invoke\",20,0,\"get\",[\"i32:4\"]],[\"invoke\",21,0,\"get\",[\"i32:5\"]],[\"invoke\",23,0,\"fill\",[\"i32:2\",\"externref:1\",\"i32:3\"]],[\"invoke\",24,0,\"get\",[\"i32:1\"]],[\"invoke\",25,0,\"get\",[\"i32:2\"]],[\"invoke\",26,0,\"get\",[\"i32:3\"]],[\"invoke\",27,0,\"get\",[\"i32:4\"]],[\"invoke\",28,0,\"get\",[\"i32:5\"]],[\"invoke\",30,0,\"fill\",[\"i32:4\",\"externref:2\",\"i32:2\"]],[\"invoke\",31,0,\"get\",[\"i32:3\"]],[\"invoke\",32,0,\"get\",[\"i32:4\"]],[\"invoke\",33,0,\"get\",[\"i32:5\"]],[\"invoke\",34,0,\"get\",[\"i32:6\"]],[\"invoke\",36,0,\"fill\",[\"i32:4\",\"externref:3\",\"i32:0\"]],[\"invoke\",37,0,\"get\",[\"i32:3\"]],[\"invoke\",38,0,\"get\",[\"i32:4\"]],[\"invoke\",39,0,\"get\",[\"i32:5\"]],[\"invoke\",41,0,\"fill\",[\"i32:8\",\"externref:4\",\"i32:2\"]],[\"invoke\",42,0,\"get\",[\"i32:7\"]],[\"invoke\",43,0,\"get\",[\"i32:8\"]],[\"invoke\",44,0,\"get\",[\"i32:9\"]],[\"invoke\",46,0,\"fill-abbrev\",[\"i32:9\",\"externref:null\",\"i32:1\"]],[\"invoke\",47,0,\"get\",[\"i32:8\"]],[\"invoke\",48,0,\"get\",[\"i32:9\"]],[\"invoke\",50,0,\"fill\",[\"i32:10\",\"externref:5\",\"i32:0\"]],[\"invoke\",51,0,\"get\",[\"i32:9\"]],[\"invoke\",54,0,\"fill\",[\"i32:8\",\"externref:6\",\"i32:3\"]],[\"invoke\",57,0,\"get\",[\"i32:7\"]],[\"invoke\",58,0,\"get\",[\"i32:8\"]],[\"invoke\",59,0,\"get\",[\"i32:9\"]],[\"invoke\",62,0,\"fill\",[\"i32:11\",\"externref:null\",\"i32:0\"]],[\"invoke\",67,0,\"fill\",[\"i32:11\",\"externref:null\",\"i32:10\"]],[\"file\",\"table_get.wast\"],[\"module\",1,51],[\"invoke\",24,0,\"init\",[\"externref:1\"]],[\"invoke\",26,0,\"get-externref\",[\"i32:0\"]],[\"invoke\",27,0,\"get-externref\",[\"i32:1\"]],[\"invoke\",29,0,\"get-funcref\",[\"i32:0\"]],[\"invoke\",30,0,\"is_null-funcref\",[\"i32:1\"]],[\"invoke\",31,0,\"is_null-funcref\",[\"i32:2\"]],[\"invoke\",33,0,\"get-externref\",[\"i32:2\"]],[\"invoke\",34,0,\"get-funcref\",[\"i32:3\"]],[\"invoke\",35,0,\"get-externref\",[\"i32:4294967295\"]],[\"invoke\",36,0,\"get-funcref\",[\"i32:4294967295\"]],[\"file\",\"table_grow.wast\"],[\"module\",1,52],[\"invoke\",16,0,\"size\",[]],[\"invoke\",17,0,\"set\",[\"i32:0\",\"externref:2\"]],[\"invoke\",18,0,\"get\",[\"i32:0\"]],[\"invoke\",20,0,\"grow\",[\"i32:1\",\"externref:null\"]],[\"invoke\",21,0,\"size\",[]],[\"invoke\",22,0,\"get\",[\"i32:0\"]],[\"invoke\",23,0,\"set\",[\"i32:0\",\"externref:2\"]],[\"invoke\",24,0,\"get\",[\"i32:0\"]],[\"invoke\",25,0,\"set\",[\"i32:1\",\"externref:2\"]],[\"invoke\",26,0,\"get\",[\"i32:1\"]],[\"invoke\",28,0,\"grow-abbrev\",[\"i32:4\",\"externref:3\"]],[\"invoke\",29,0,\"size\",[]],[\"invoke\",30,0,\"get\",[\"i32:0\"]],[\"invoke\",31,0,\"set\",[\"i32:0\",\"externref:2\"]],[\"invoke\",32,0,\"get\",[\"i32:0\"]],[\"invoke\",33,0,\"get\",[\"i32:1\"]],[\"invoke\",34,0,\"get\",[\"i32:4\"]],[\"invoke\",35,0,\"set\",[\"i32:4\",\"externref:4\"]],[\"invoke\",36,0,\"get\",[\"i32:4\"]],[\"invoke\",37,0,\"set\",[\"i32:5\",\"externref:2\"]],[\"invoke\",38,0,\"get\",[\"i32:5\"]],[\"module\",42,53],[\"invoke\",50,0,\"grow\",[]],[\"module\",53,54],[\"invoke\",60,0,\"grow\",[\"i32:0\"]],[\"invoke\",61,0,\"grow\",[\"i32:1\"]],[\"invoke\",62,0,\"grow\",[\"i32:0\"]],[\"invoke\",63,0,\"grow\",[\"i32:2\"]],[\"invoke\",64,0,\"grow\",[\"i32:800\"]],[\"module\",67,55],[\"invoke\",74,0,\"grow\",[\"i32:0\"]],[\"invoke\",75,0,\"grow\",[\"i32:1\"]],[\"invoke\",76,0,\"grow\",[\"i32:1\"]],[\"invoke\",77,0,\"grow\",[\"i32:2\"]],[\"invoke\",78,0,\"grow\",[\"i32:6\"]],[\"invoke\",79,0,\"grow\",[\"i32:0\"]],[\"invoke\",80,0,\"grow\",[\"i32:1\"]],[\"invoke\",81,0,\"grow\",[\"i32:65536\"]],[\"module\",84,56],[\"invoke\",106,0,\"check-table-null\",[\"i32:0\",\"i32:9\"]],[\"invoke\",107,0,\"grow\",[\"i32:10\"]],[\"invoke\",108,0,\"check-table-null\",[\"i32:0\",\"i32:19\"]],[\"module\",111,57,\"$Tgt\"],[\"register\",\"grown-table\",\"$Tgt\"],[\"invoke\",116,\"$Tgt\",\"grow\",[]],[\"module\",117,58,\"$Tgit1\"],[\"register\",\"grown-imported-table\",\"$Tgit1\"],[\"invoke\",123,\"$Tgit1\",\"grow\",[]],[\"module\",124,59,\"$Tgit2\"],[\"invoke\",129,\"$Tgit2\",\"size\",[]],[\"file\",\"table_init.wast\"],[\"module\",6,0],[\"register\",\"a\"],[\"module\",15,60],[\"invoke\",41,0,\"test\",[]],[\"invoke\",43,0,\"check\",[\"i32:1\"]],[\"invoke\",45,0,\"check\",[\"i32:3\"]],[\"invoke\",47,0,\"check\",[\"i32:5\"]],[\"invoke\",48,0,\"check\",[\"i32:6\"]],[\"invoke\",50,0,\"check\",[\"i32:8\"]],[\"invoke\",52,0,\"check\",[\"i32:10\"]],[\"invoke\",53,0,\"check\",[\"i32:11\"]],[\"invoke\",55,0,\"check\",[\"i32:13\"]],[\"invoke\",57,0,\"check\",[\"i32:15\"]],[\"invoke\",58,0,\"check\",[\"i32:16\"]],[\"invoke\",60,0,\"check\",[\"i32:18\"]],[\"invoke\",62,0,\"check\",[\"i32:20\"]],[\"invoke\",64,0,\"check\",[\"i32:22\"]],[\"invoke\",65,0,\"check\",[\"i32:23\"]],[\"invoke\",67,0,\"check\",[\"i32:25\"]],[\"invoke\",69,0,\"check\",[\"i32:27\"]],[\"invoke\",70,0,\"check\",[\"i32:28\"]],[\"module\",73,61],[\"invoke\",99,0,\"test\",[]],[\"invoke\",101,0,\"check\",[\"i32:1\"]],[\"invoke\",102,0,\"check\",[\"i32:2\"]],[\"invoke\",104,0,\"check\",[\"i32:4\"]],[\"invoke\",106,0,\"check\",[\"i32:6\"]],[\"invoke\",107,0,\"check\",[\"i32:7\"]],[\"invoke\",109,0,\"check\",[\"i32:9\"]],[\"invoke\",111,0,\"check\",[\"i32:11\"]],[\"invoke\",113,0,\"check\",[\"i32:13\"]],[\"invoke\",114,0,\"check\",[\"i32:14\"]],[\"invoke\",116,0,\"check\",[\"i32:16\"]],[\"invoke\",118,0,\"check\",[\"i32:18\"]],[\"invoke\",119,0,\"check\",[\"i32:19\"]],[\"invoke\",121,0,\"check\",[\"i32:21\"]],[\"invoke\",123,0,\"check\",[\"i32:23\"]],[\"invoke\",124,0,\"check\",[\"i32:24\"]],[\"invoke\",126,0,\"check\",[\"i32:26\"]],[\"invoke\",128,0,\"check\",[\"i32:28\"]],[\"invoke\",129,0,\"check\",[\"i32:29\"]],[\"module\",131,62],[\"invoke\",166,0,\"check\",[\"i32:0\"]],[\"invoke\",168,0,\"check\",[\"i32:2\"]],[\"invoke\",170,0,\"check\",[\"i32:4\"]],[\"invoke\",171,0,\"check\",[\"i32:5\"]],[\"invoke\",173,0,\"check\",[\"i32:7\"]],[\"invoke\",175,0,\"check\",[\"i32:9\"]],[\"invoke\",176,0,\"check\",[\"i32:10\"]],[\"invoke\",178,0,\"check\",[\"i32:12\"]],[\"invoke\",180,0,\"check\",[\"i32:14\"]],[\"invoke\",181,0,\"check\",[\"i32:15\"]],[\"invoke\",183,0,\"check\",[\"i32:17\"]],[\"invoke\",185,0,\"check\",[\"i32:19\"]],[\"invoke\",186,0,\"check\",[\"i32:20\"]],[\"invoke\",188,0,\"check\",[\"i32:22\"]],[\"invoke\",190,0,\"check\",[\"i32:24\"]],[\"invoke\",192,0,\"check\",[\"i32:26\"]],[\"invoke\",193,0,\"check\",[\"i32:27\"]],[\"invoke\",195,0,\"check\",[\"i32:29\"]],[\"module\",197,63],[\"invoke\",224,0,\"check\",[\"i32:0\"]],[\"invoke\",225,0,\"check\",[\"i32:1\"]],[\"invoke\",227,0,\"check\",[\"i32:3\"]],[\"invoke\",229,0,\"check\",[\"i32:5\"]],[\"invoke\",230,0,\"check\",[\"i32:6\"]],[\"invoke\",232,0,\"check\",[\"i32:8\"]],[\"invoke\",234,0,\"check\",[\"i32:10\"]],[\"invoke\",235,0,\"check\",[\"i32:11\"]],[\"invoke\",237,0,\"check\",[\"i32:13\"]],[\"invoke\",239,0,\"check\",[\"i32:15\"]],[\"invoke\",241,0,\"check\",[\"i32:17\"]],[\"invoke\",242,0,\"check\",[\"i32:18\"]],[\"invoke\",244,0,\"check\",[\"i32:20\"]],[\"invoke\",246,0,\"check\",[\"i32:22\"]],[\"invoke\",247,0,\"check\",[\"i32:23\"]],[\"invoke\",249,0,\"check\",[\"i32:25\"]],[\"invoke\",251,0,\"check\",[\"i32:27\"]],[\"invoke\",252,0,\"check\",[\"i32:28\"]],[\"module\",255,64],[\"invoke\",281,0,\"test\",[]],[\"invoke\",283,0,\"check\",[\"i32:1\"]],[\"invoke\",284,0,\"check\",[\"i32:2\"]],[\"invoke\",286,0,\"check\",[\"i32:4\"]],[\"invoke\",288,0,\"check\",[\"i32:6\"]],[\"invoke\",290,0,\"check\",[\"i32:8\"]],[\"invoke\",291,0,\"check\",[\"i32:9\"]],[\"invoke\",293,0,\"check\",[\"i32:11\"]],[\"invoke\",295,0,\"check\",[\"i32:13\"]],[\"invoke\",296,0,\"check\",[\"i32:14\"]],[\"invoke\",298,0,\"check\",[\"i32:16\"]],[\"invoke\",300,0,\"check\",[\"i32:18\"]],[\"invoke\",301,0,\"check\",[\"i32:19\"]],[\"invoke\",303,0,\"check\",[\"i32:21\"]],[\"invoke\",305,0,\"check\",[\"i32:23\"]],[\"invoke\",306,0,\"check\",[\"i32:24\"]],[\"invoke\",308,0,\"check\",[\"i32:26\"]],[\"invoke\",310,0,\"check\",[\"i32:28\"]],[\"module\",313,65],[\"invoke\",347,0,\"test\",[]],[\"invoke\",348,0,\"check\",[\"i32:0\"]],[\"invoke\",350,0,\"check\",[\"i32:2\"]],[\"invoke\",352,0,\"check\",[\"i32:4\"]],[\"invoke\",353,0,\"check\",[\"i32:5\"]],[\"invoke\",355,0,\"check\",[\"i32:7\"]],[\"invoke\",357,0,\"check\",[\"i32:9\"]],[\"invoke\",358,0,\"check\",[\"i32:10\"]],[\"invoke\",360,0,\"check\",[\"i32:12\"]],[\"invoke\",362,0,\"check\",[\"i32:14\"]],[\"invoke\",363,0,\"check\",[\"i32:15\"]],[\"invoke\",365,0,\"check\",[\"i32:17\"]],[\"invoke\",367,0,\"check\",[\"i32:19\"]],[\"invoke\",369,0,\"check\",[\"i32:21\"]],[\"invoke\",370,0,\"check\",[\"i32:22\"]],[\"invoke\",372,0,\"check\",[\"i32:24\"]],[\"invoke\",374,0,\"check\",[\"i32:26\"]],[\"invoke\",375,0,\"check\",[\"i32:27\"]],[\"invoke\",377,0,\"check\",[\"i32:29\"]],[\"module\",407,66],[\"module\",431,67],[\"invoke\",453,0,\"test\",[]],[\"module\",455,68],[\"invoke\",477,0,\"test\",[]],[\"module\",479,69],[\"module\",503,70],[\"invoke\",525,0,\"test\",[]],[\"module\",527,71],[\"module\",551,72],[\"invoke\",573,0,\"test\",[]],[\"module\",575,73],[\"invoke\",597,0,\"test\",[]],[\"module\",599,74],[\"module\",623,75],[\"invoke\",645,0,\"test\",[]],[\"module\",647,76],[\"module\",671,77],[\"invoke\",693,0,\"test\",[]],[\"module\",695,78],[\"module\",719,79],[\"invoke\",741,0,\"test\",[]],[\"module\",743,80],[\"invoke\",765,0,\"test\",[]],[\"module\",767,81],[\"module\",791,82],[\"invoke\",813,0,\"test\",[]],[\"module\",815,83],[\"module\",839,84],[\"invoke\",861,0,\"test\",[]],[\"module\",863,85],[\"invoke\",885,0,\"test\",[]],[\"module\",887,86],[\"module\",1478,87],[\"invoke\",1506,0,\"run\",[\"i32:24\",\"i32:16\"]],[\"invoke\",1508,0,\"test\",[\"i32:1\"]],[\"invoke\",1509,0,\"test\",[\"i32:2\"]],[\"invoke\",1511,0,\"test\",[\"i32:4\"]],[\"invoke\",1513,0,\"test\",[\"i32:6\"]],[\"invoke\",1514,0,\"test\",[\"i32:7\"]],[\"invoke\",1516,0,\"test\",[\"i32:9\"]],[\"invoke\",1518,0,\"test\",[\"i32:11\"]],[\"invoke\",1520,0,\"test\",[\"i32:13\"]],[\"invoke\",1521,0,\"test\",[\"i32:14\"]],[\"invoke\",1523,0,\"test\",[\"i32:16\"]],[\"invoke\",1525,0,\"test\",[\"i32:18\"]],[\"invoke\",1526,0,\"test\",[\"i32:19\"]],[\"invoke\",1528,0,\"test\",[\"i32:21\"]],[\"invoke\",1530,0,\"test\",[\"i32:23\"]],[\"invoke\",1531,0,\"test\",[\"i32:24\"]],[\"invoke\",1533,0,\"test\",[\"i32:26\"]],[\"invoke\",1535,0,\"test\",[\"i32:28\"]],[\"invoke\",1536,0,\"test\",[\"i32:29\"]],[\"invoke\",1538,0,\"test\",[\"i32:31\"]],[\"module\",1540,87],[\"invoke\",1569,0,\"test\",[\"i32:0\"]],[\"invoke\",1571,0,\"test\",[\"i32:2\"]],[\"invoke\",1572,0,\"test\",[\"i32:3\"]],[\"invoke\",1574,0,\"test\",[\"i32:5\"]],[\"invoke\",1576,0,\"test\",[\"i32:7\"]],[\"invoke\",1577,0,\"test\",[\"i32:8\"]],[\"invoke\",1579,0,\"test\",[\"i32:10\"]],[\"invoke\",1581,0,\"test\",[\"i32:12\"]],[\"invoke\",1582,0,\"test\",[\"i32:13\"]],[\"invoke\",1584,0,\"test\",[\"i32:15\"]],[\"invoke\",1586,0,\"test\",[\"i32:17\"]],[\"invoke\",1587,0,\"test\",[\"i32:18\"]],[\"invoke\",1589,0,\"test\",[\"i32:20\"]],[\"invoke\",1591,0,\"test\",[\"i32:22\"]],[\"invoke\",1593,0,\"test\",[\"i32:24\"]],[\"invoke\",1594,0,\"test\",[\"i32:25\"]],[\"invoke\",1596,0,\"test\",[\"i32:27\"]],[\"invoke\",1598,0,\"test\",[\"i32:29\"]],[\"invoke\",1599,0,\"test\",[\"i32:30\"]],[\"module\",1602,88],[\"invoke\",1630,0,\"run\",[\"i32:96\",\"i32:32\"]],[\"invoke\",1632,0,\"test\",[\"i32:1\"]],[\"invoke\",1633,0,\"test\",[\"i32:2\"]],[\"invoke\",1635,0,\"test\",[\"i32:4\"]],[\"invoke\",1637,0,\"test\",[\"i32:6\"]],[\"invoke\",1638,0,\"test\",[\"i32:7\"]],[\"invoke\",1640,0,\"test\",[\"i32:9\"]],[\"invoke\",1642,0,\"test\",[\"i32:11\"]],[\"invoke\",1644,0,\"test\",[\"i32:13\"]],[\"invoke\",1645,0,\"test\",[\"i32:14\"]],[\"invoke\",1647,0,\"test\",[\"i32:16\"]],[\"invoke\",1649,0,\"test\",[\"i32:18\"]],[\"invoke\",1650,0,\"test\",[\"i32:19\"]],[\"invoke\",1652,0,\"test\",[\"i32:21\"]],[\"invoke\",1654,0,\"test\",[\"i32:23\"]],[\"invoke\",1655,0,\"test\",[\"i32:24\"]],[\"invoke\",1657,0,\"test\",[\"i32:26\"]],[\"invoke\",1659,0,\"test\",[\"i32:28\"]],[\"invoke\",1660,0,\"test\",[\"i32:29\"]],[\"invoke\",1662,0,\"test\",[\"i32:31\"]],[\"invoke\",1664,0,\"test\",[\"i32:33\"]],[\"invoke\",1666,0,\"test\",[\"i32:35\"]],[\"invoke\",1667,0,\"test\",[\"i32:36\"]],[\"invoke\",1669,0,\"test\",[\"i32:38\"]],[\"invoke\",1671,0,\"test\",[\"i32:40\"]],[\"invoke\",1672,0,\"test\",[\"i32:41\"]],[\"invoke\",1674,0,\"test\",[\"i32:43\"]],[\"invoke\",1676,0,\"test\",[\"i32:45\"]],[\"invoke\",1677,0,\"test\",[\"i32:46\"]],[\"invoke\",1679,0,\"test\",[\"i32:48\"]],[\"invoke\",1681,0,\"test\",[\"i32:50\"]],[\"invoke\",1682,0,\"test\",[\"i32:51\"]],[\"invoke\",1684,0,\"test\",[\"i32:53\"]],[\"invoke\",1686,0,\"test\",[\"i32:55\"]],[\"invoke\",1688,0,\"test\",[\"i32:57\"]],[\"invoke\",1689,0,\"test\",[\"i32:58\"]],[\"invoke\",1691,0,\"test\",[\"i32:60\"]],[\"invoke\",1693,0,\"test\",[\"i32:62\"]],[\"invoke\",1694,0,\"test\",[\"i32:63\"]],[\"invoke\",1696,0,\"test\",[\"i32:65\"]],[\"invoke\",1698,0,\"test\",[\"i32:67\"]],[\"invoke\",1699,0,\"test\",[\"i32:68\"]],[\"invoke\",1701,0,\"test\",[\"i32:70\"]],[\"invoke\",1703,0,\"test\",[\"i32:72\"]],[\"invoke\",1704,0,\"test\",[\"i32:73\"]],[\"invoke\",1706,0,\"test\",[\"i32:75\"]],[\"invoke\",1708,0,\"test\",[\"i32:77\"]],[\"invoke\",1710,0,\"test\",[\"i32:79\"]],[\"invoke\",1711,0,\"test\",[\"i32:80\"]],[\"invoke\",1713,0,\"test\",[\"i32:82\"]],[\"invoke\",1715,0,\"test\",[\"i32:84\"]],[\"invoke\",1716,0,\"test\",[\"i32:85\"]],[\"invoke\",1718,0,\"test\",[\"i32:87\"]],[\"invoke\",1720,0,\"test\",[\"i32:89\"]],[\"invoke\",1721,0,\"test\",[\"i32:90\"]],[\"invoke\",1723,0,\"test\",[\"i32:92\"]],[\"invoke\",1725,0,\"test\",[\"i32:94\"]],[\"invoke\",1726,0,\"test\",[\"i32:95\"]],[\"invoke\",1728,0,\"test\",[\"i32:97\"]],[\"invoke\",1730,0,\"test\",[\"i32:99\"]],[\"invoke\",1732,0,\"test\",[\"i32:101\"]],[\"invoke\",1733,0,\"test\",[\"i32:102\"]],[\"invoke\",1735,0,\"test\",[\"i32:104\"]],[\"invoke\",1737,0,\"test\",[\"i32:106\"]],[\"invoke\",1738,0,\"test\",[\"i32:107\"]],[\"invoke\",1740,0,\"test\",[\"i32:109\"]],[\"invoke\",1742,0,\"test\",[\"i32:111\"]],[\"invoke\",1743,0,\"test\",[\"i32:112\"]],[\"invoke\",1745,0,\"test\",[\"i32:114\"]],[\"invoke\",1747,0,\"test\",[\"i32:116\"]],[\"invoke\",1748,0,\"test\",[\"i32:117\"]],[\"invoke\",1750,0,\"test\",[\"i32:119\"]],[\"invoke\",1752,0,\"test\",[\"i32:121\"]],[\"invoke\",1754,0,\"test\",[\"i32:123\"]],[\"invoke\",1755,0,\"test\",[\"i32:124\"]],[\"invoke\",1757,0,\"test\",[\"i32:126\"]],[\"invoke\",1759,0,\"test\",[\"i32:128\"]],[\"invoke\",1760,0,\"test\",[\"i32:129\"]],[\"invoke\",1762,0,\"test\",[\"i32:131\"]],[\"invoke\",1764,0,\"test\",[\"i32:133\"]],[\"invoke\",1765,0,\"test\",[\"i32:134\"]],[\"invoke\",1767,0,\"test\",[\"i32:136\"]],[\"invoke\",1769,0,\"test\",[\"i32:138\"]],[\"invoke\",1770,0,\"test\",[\"i32:139\"]],[\"invoke\",1772,0,\"test\",[\"i32:141\"]],[\"invoke\",1774,0,\"test\",[\"i32:143\"]],[\"invoke\",1776,0,\"test\",[\"i32:145\"]],[\"invoke\",1777,0,\"test\",[\"i32:146\"]],[\"invoke\",1779,0,\"test\",[\"i32:148\"]],[\"invoke\",1781,0,\"test\",[\"i32:150\"]],[\"invoke\",1782,0,\"test\",[\"i32:151\"]],[\"invoke\",1784,0,\"test\",[\"i32:153\"]],[\"invoke\",1786,0,\"test\",[\"i32:155\"]],[\"invoke\",1787,0,\"test\",[\"i32:156\"]],[\"invoke\",1789,0,\"test\",[\"i32:158\"]],[\"module\",1792,88],[\"invoke\",1820,0,\"run\",[\"i32:97\",\"i32:31\"]],[\"invoke\",1821,0,\"test\",[\"i32:0\"]],[\"invoke\",1823,0,\"test\",[\"i32:2\"]],[\"invoke\",1825,0,\"test\",[\"i32:4\"]],[\"invoke\",1827,0,\"test\",[\"i32:6\"]],[\"invoke\",1828,0,\"test\",[\"i32:7\"]],[\"invoke\",1830,0,\"test\",[\"i32:9\"]],[\"invoke\",1832,0,\"test\",[\"i32:11\"]],[\"invoke\",1833,0,\"test\",[\"i32:12\"]],[\"invoke\",1835,0,\"test\",[\"i32:14\"]],[\"invoke\",1837,0,\"test\",[\"i32:16\"]],[\"invoke\",1838,0,\"test\",[\"i32:17\"]],[\"invoke\",1840,0,\"test\",[\"i32:19\"]],[\"invoke\",1842,0,\"test\",[\"i32:21\"]],[\"invoke\",1843,0,\"test\",[\"i32:22\"]],[\"invoke\",1845,0,\"test\",[\"i32:24\"]],[\"invoke\",1847,0,\"test\",[\"i32:26\"]],[\"invoke\",1849,0,\"test\",[\"i32:28\"]],[\"invoke\",1850,0,\"test\",[\"i32:29\"]],[\"invoke\",1852,0,\"test\",[\"i32:31\"]],[\"invoke\",1854,0,\"test\",[\"i32:33\"]],[\"invoke\",1855,0,\"test\",[\"i32:34\"]],[\"invoke\",1857,0,\"test\",[\"i32:36\"]],[\"invoke\",1859,0,\"test\",[\"i32:38\"]],[\"invoke\",1860,0,\"test\",[\"i32:39\"]],[\"invoke\",1862,0,\"test\",[\"i32:41\"]],[\"invoke\",1864,0,\"test\",[\"i32:43\"]],[\"invoke\",1865,0,\"test\",[\"i32:44\"]],[\"invoke\",1867,0,\"test\",[\"i32:46\"]],[\"invoke\",1869,0,\"test\",[\"i32:48\"]],[\"invoke\",1871,0,\"test\",[\"i32:50\"]],[\"invoke\",1872,0,\"test\",[\"i32:51\"]],[\"invoke\",1874,0,\"test\",[\"i32:53\"]],[\"invoke\",1876,0,\"test\",[\"i32:55\"]],[\"invoke\",1877,0,\"test\",[\"i32:56\"]],[\"invoke\",1879,0,\"test\",[\"i32:58\"]],[\"invoke\",1881,0,\"test\",[\"i32:60\"]],[\"invoke\",1882,0,\"test\",[\"i32:61\"]],[\"invoke\",1884,0,\"test\",[\"i32:63\"]],[\"invoke\",1886,0,\"test\",[\"i32:65\"]],[\"invoke\",1887,0,\"test\",[\"i32:66\"]],[\"invoke\",1889,0,\"test\",[\"i32:68\"]],[\"invoke\",1891,0,\"test\",[\"i32:70\"]],[\"invoke\",1893,0,\"test\",[\"i32:72\"]],[\"invoke\",1894,0,\"test\",[\"i32:73\"]],[\"invoke\",1896,0,\"test\",[\"i32:75\"]],[\"invoke\",1898,0,\"test\",[\"i32:77\"]],[\"invoke\",1899,0,\"test\",[\"i32:78\"]],[\"invoke\",1901,0,\"test\",[\"i32:80\"]],[\"invoke\",1903,0,\"test\",[\"i32:82\"]],[\"invoke\",1904,0,\"test\",[\"i32:83\"]],[\"invoke\",1906,0,\"test\",[\"i32:85\"]],[\"invoke\",1908,0,\"test\",[\"i32:87\"]],[\"invoke\",1909,0,\"test\",[\"i32:88\"]],[\"invoke\",1911,0,\"test\",[\"i32:90\"]],[\"invoke\",1913,0,\"test\",[\"i32:92\"]],[\"invoke\",1915,0,\"test\",[\"i32:94\"]],[\"invoke\",1916,0,\"test\",[\"i32:95\"]],[\"invoke\",1918,0,\"test\",[\"i32:97\"]],[\"invoke\",1920,0,\"test\",[\"i32:99\"]],[\"invoke\",1921,0,\"test\",[\"i32:100\"]],[\"invoke\",1923,0,\"test\",[\"i32:102\"]],[\"invoke\",1925,0,\"test\",[\"i32:104\"]],[\"invoke\",1926,0,\"test\",[\"i32:105\"]],[\"invoke\",1928,0,\"test\",[\"i32:107\"]],[\"invoke\",1930,0,\"test\",[\"i32:109\"]],[\"invoke\",1931,0,\"test\",[\"i32:110\"]],[\"invoke\",1933,0,\"test\",[\"i32:112\"]],[\"invoke\",1935,0,\"test\",[\"i32:114\"]],[\"invoke\",1937,0,\"test\",[\"i32:116\"]],[\"invoke\",1938,0,\"test\",[\"i32:117\"]],[\"invoke\",1940,0,\"test\",[\"i32:119\"]],[\"invoke\",1942,0,\"test\",[\"i32:121\"]],[\"invoke\",1943,0,\"test\",[\"i32:122\"]],[\"invoke\",1945,0,\"test\",[\"i32:124\"]],[\"invoke\",1947,0,\"test\",[\"i32:126\"]],[\"invoke\",1948,0,\"test\",[\"i32:127\"]],[\"invoke\",1950,0,\"test\",[\"i32:129\"]],[\"invoke\",1952,0,\"test\",[\"i32:131\"]],[\"invoke\",1953,0,\"test\",[\"i32:132\"]],[\"invoke\",1955,0,\"test\",[\"i32:134\"]],[\"invoke\",1957,0,\"test\",[\"i32:136\"]],[\"invoke\",1959,0,\"test\",[\"i32:138\"]],[\"invoke\",1960,0,\"test\",[\"i32:139\"]],[\"invoke\",1962,0,\"test\",[\"i32:141\"]],[\"invoke\",1964,0,\"test\",[\"i32:143\"]],[\"invoke\",1965,0,\"test\",[\"i32:144\"]],[\"invoke\",1967,0,\"test\",[\"i32:146\"]],[\"invoke\",1969,0,\"test\",[\"i32:148\"]],[\"invoke\",1970,0,\"test\",[\"i32:149\"]],[\"invoke\",1972,0,\"test\",[\"i32:151\"]],[\"invoke\",1974,0,\"test\",[\"i32:153\"]],[\"invoke\",1975,0,\"test\",[\"i32:154\"]],[\"invoke\",1977,0,\"test\",[\"i32:156\"]],[\"invoke\",1979,0,\"test\",[\"i32:158\"]],[\"module\",1982,89],[\"invoke\",2010,0,\"run\",[\"i32:48\",\"i32:4294967280\"]],[\"invoke\",2011,0,\"test\",[\"i32:0\"]],[\"invoke\",2013,0,\"test\",[\"i32:2\"]],[\"invoke\",2015,0,\"test\",[\"i32:4\"]],[\"invoke\",2016,0,\"test\",[\"i32:5\"]],[\"invoke\",2018,0,\"test\",[\"i32:7\"]],[\"invoke\",2020,0,\"test\",[\"i32:9\"]],[\"invoke\",2021,0,\"test\",[\"i32:10\"]],[\"invoke\",2023,0,\"test\",[\"i32:12\"]],[\"invoke\",2025,0,\"test\",[\"i32:14\"]],[\"invoke\",2026,0,\"test\",[\"i32:15\"]],[\"invoke\",2028,0,\"test\",[\"i32:17\"]],[\"invoke\",2030,0,\"test\",[\"i32:19\"]],[\"invoke\",2032,0,\"test\",[\"i32:21\"]],[\"invoke\",2033,0,\"test\",[\"i32:22\"]],[\"invoke\",2035,0,\"test\",[\"i32:24\"]],[\"invoke\",2037,0,\"test\",[\"i32:26\"]],[\"invoke\",2038,0,\"test\",[\"i32:27\"]],[\"invoke\",2040,0,\"test\",[\"i32:29\"]],[\"invoke\",2042,0,\"test\",[\"i32:31\"]],[\"invoke\",2043,0,\"test\",[\"i32:32\"]],[\"invoke\",2045,0,\"test\",[\"i32:34\"]],[\"invoke\",2047,0,\"test\",[\"i32:36\"]],[\"invoke\",2048,0,\"test\",[\"i32:37\"]],[\"invoke\",2050,0,\"test\",[\"i32:39\"]],[\"invoke\",2052,0,\"test\",[\"i32:41\"]],[\"invoke\",2054,0,\"test\",[\"i32:43\"]],[\"invoke\",2055,0,\"test\",[\"i32:44\"]],[\"invoke\",2057,0,\"test\",[\"i32:46\"]],[\"invoke\",2059,0,\"test\",[\"i32:48\"]],[\"invoke\",2060,0,\"test\",[\"i32:49\"]],[\"invoke\",2062,0,\"test\",[\"i32:51\"]],[\"invoke\",2064,0,\"test\",[\"i32:53\"]],[\"invoke\",2065,0,\"test\",[\"i32:54\"]],[\"invoke\",2067,0,\"test\",[\"i32:56\"]],[\"invoke\",2069,0,\"test\",[\"i32:58\"]],[\"invoke\",2070,0,\"test\",[\"i32:59\"]],[\"invoke\",2072,0,\"test\",[\"i32:61\"]],[\"invoke\",2074,0,\"test\",[\"i32:63\"]],[\"module\",2076,90],[\"invoke\",2105,0,\"test\",[\"i32:0\"]],[\"invoke\",2106,0,\"test\",[\"i32:1\"]],[\"invoke\",2108,0,\"test\",[\"i32:3\"]],[\"invoke\",2110,0,\"test\",[\"i32:5\"]],[\"invoke\",2111,0,\"test\",[\"i32:6\"]],[\"invoke\",2113,0,\"test\",[\"i32:8\"]],[\"invoke\",2115,0,\"test\",[\"i32:10\"]],[\"invoke\",2116,0,\"test\",[\"i32:11\"]],[\"invoke\",2118,0,\"test\",[\"i32:13\"]],[\"invoke\",2120,0,\"test\",[\"i32:15\"]],[\"module\",2122,91],[\"file\",\"table_set.wast\"],[\"module\",1,92],[\"invoke\",29,0,\"get-externref\",[\"i32:0\"]],[\"invoke\",30,0,\"set-externref\",[\"i32:0\",\"externref:1\"]],[\"invoke\",31,0,\"get-externref\",[\"i32:0\"]],[\"invoke\",32,0,\"set-externref\",[\"i32:0\",\"externref:null\"]],[\"invoke\",33,0,\"get-externref\",[\"i32:0\"]],[\"invoke\",35,0,\"get-funcref\",[\"i32:0\"]],[\"invoke\",36,0,\"set-funcref-from\",[\"i32:0\",\"i32:1\"]],[\"invoke\",37,0,\"is_null-funcref\",[\"i32:0\"]],[\"invoke\",38,0,\"set-funcref\",[\"i32:0\",\"funcref:null\"]],[\"invoke\",39,0,\"get-funcref\",[\"i32:0\"]],[\"invoke\",41,0,\"set-externref\",[\"i32:2\",\"externref:null\"]],[\"invoke\",42,0,\"set-funcref\",[\"i32:3\",\"funcref:null\"]],[\"invoke\",43,0,\"set-externref\",[\"i32:4294967295\",\"externref:null\"]],[\"invoke\",44,0,\"set-funcref\",[\"i32:4294967295\",\"funcref:null\"]],[\"invoke\",46,0,\"set-externref\",[\"i32:2\",\"externref:0\"]],[\"invoke\",47,0,\"set-funcref-from\",[\"i32:3\",\"i32:1\"]],[\"invoke\",48,0,\"set-externref\",[\"i32:4294967295\",\"externref:0\"]],[\"invoke\",49,0,\"set-funcref-from\",[\"i32:4294967295\",\"i32:1\"]],[\"file\",\"table_size.wast\"],[\"module\",1,93],[\"invoke\",26,0,\"size-t0\",[]],[\"invoke\",27,0,\"grow-t0\",[\"i32:1\"]],[\"invoke\",28,0,\"size-t0\",[]],[\"invoke\",29,0,\"grow-t0\",[\"i32:4\"]],[\"invoke\",30,0,\"size-t0\",[]],[\"invoke\",31,0,\"grow-t0\",[\"i32:0\"]],[\"invoke\",32,0,\"size-t0\",[]],[\"invoke\",34,0,\"size-t1\",[]],[\"invoke\",35,0,\"grow-t1\",[\"i32:1\"]],[\"invoke\",36,0,\"size-t1\",[]],[\"invoke\",37,0,\"grow-t1\",[\"i32:4\"]],[\"invoke\",38,0,\"size-t1\",[]],[\"invoke\",39,0,\"grow-t1\",[\"i32:0\"]],[\"invoke\",40,0,\"size-t1\",[]],[\"invoke\",42,0,\"size-t2\",[]],[\"invoke\",43,0,\"grow-t2\",[\"i32:3\"]],[\"invoke\",44,0,\"size-t2\",[]],[\"invoke\",45,0,\"grow-t2\",[\"i32:1\"]],[\"invoke\",46,0,\"size-t2\",[]],[\"invoke\",47,0,\"grow-t2\",[\"i32:0\"]],[\"invoke\",48,0,\"size-t2\",[]],[\"invoke\",49,0,\"grow-t2\",[\"i32:4\"]],[\"invoke\",50,0,\"size-t2\",[]],[\"invoke\",51,0,\"grow-t2\",[\"i32:1\"]],[\"invoke\",52,0,\"size-t2\",[]],[\"invoke\",54,0,\"size-t3\",[]],[\"invoke\",55,0,\"grow-t3\",[\"i32:1\"]],[\"invoke\",56,0,\"size-t3\",[]],[\"invoke\",57,0,\"grow-t3\",[\"i32:3\"]],[\"invoke\",58,0,\"size-t3\",[]],[\"invoke\",59,0,\"grow-t3\",[\"i32:0\"]],[\"invoke\",60,0,\"size-t3\",[]],[\"invoke\",61,0,\"grow-t3\",[\"i32:2\"]],[\"invoke\",62,0,\"size-t3\",[]],[\"invoke\",63,0,\"grow-t3\",[\"i32:1\"]],[\"invoke\",64,0,\"size-t3\",[]],[\"file\",\"elem.wast\"],[\"module\",4,94],[\"module\",80,95],[\"module\",90,96],[\"module\",101,97],[\"module\",120,98],[\"module\",127,98],[\"module\",134,99],[\"invoke\",148,0,\"call-7\",[]],[\"invoke\",149,0,\"call-9\",[]],[\"module\",153,100],[\"invoke\",167,0,\"call-7\",[]],[\"invoke\",168,0,\"call-9\",[]],[\"module\",182,101],[\"module\",193,102],[\"module\",202,103],[\"module\",207,104],[\"instantiate\",239,105],[\"instantiate\",248,106],[\"instantiate\",257,107],[\"instantiate\",266,108],[\"instantiate\",273,109],[\"instantiate\",290,110],[\"instantiate\",307,111],[\"instantiate\",324,112],[\"module\",342,113],[\"invoke\",350,0,\"init\",[]],[\"module\",352,114],[\"invoke\",360,0,\"init\",[]],[\"module\",537,115],[\"invoke\",548,0,\"call-overwritten\",[]],[\"module\",565,116,\"$module1\"],[\"register\",\"module1\",\"$module1\"],[\"invoke\",585,\"$module1\",\"call-7\",[]],[\"invoke\",586,\"$module1\",\"call-8\",[]],[\"invoke\",587,\"$module1\",\"call-9\",[]],[\"module\",589,117,\"$module2\"],[\"invoke\",598,\"$module1\",\"call-7\",[]],[\"invoke\",599,\"$module1\",\"call-8\",[]],[\"invoke\",600,\"$module1\",\"call-9\",[]],[\"module\",602,118,\"$module3\"],[\"invoke\",611,\"$module1\",\"call-7\",[]],[\"invoke\",612,\"$module1\",\"call-8\",[]],[\"invoke\",613,\"$module1\",\"call-9\",[]],[\"module\",646,119,\"$m\"],[\"register\",\"exporter\",\"$m\"],[\"invoke\",655,\"$m\",\"get\",[\"i32:0\"]],[\"invoke\",656,\"$m\",\"get\",[\"i32:1\"]],[\"invoke\",658,\"$m\",\"set\",[\"i32:0\",\"externref:42\"]],[\"invoke\",659,\"$m\",\"set\",[\"i32:1\",\"externref:137\"]],[\"invoke\",661,\"$m\",\"get\",[\"i32:0\"]],[\"invoke\",662,\"$m\",\"get\",[\"i32:1\"]],[\"module\",664,120],[\"invoke\",668,\"$m\",\"get\",[\"i32:0\"]],[\"invoke\",669,\"$m\",\"get\",[\"i32:1\"]],[\"module\",673,121,\"$module4\"],[\"register\",\"module4\",\"$module4\"],[\"module\",682,122],[\"invoke\",692,0,\"call_imported_elem\",[]],[\"file\",\"ref_func.wast\"],[\"module\",1,123],[\"register\",\"M\"],[\"module\",6,124],[\"invoke\",56,0,\"is_null-f\",[]],[\"invoke\",57,0,\"is_null-g\",[]],[\"invoke\",58,0,\"is_null-v\",[]],[\"invoke\",60,0,\"call-f\",[\"i32:4\"]],[\"invoke\",61,0,\"call-g\",[\"i32:4\"]],[\"invoke\",62,0,\"call-v\",[\"i32:4\"]],[\"invoke\",63,0,\"set-g\",[]],[\"invoke\",64,0,\"call-v\",[\"i32:4\"]],[\"invoke\",65,0,\"set-f\",[]],[\"invoke\",66,0,\"call-v\",[\"i32:4\"]],[\"module\",80,125],[\"file\",\"ref_is_null.wast\"],[\"module\",1,126],[\"invoke\",30,0,\"funcref\",[\"funcref:null\"]],[\"invoke\",31,0,\"externref\",[\"externref:null\"]],[\"invoke\",33,0,\"externref\",[\"externref:1\"]],[\"invoke\",35,0,\"init\",[\"externref:0\"]],[\"invoke\",37,0,\"funcref-elem\",[\"i32:0\"]],[\"invoke\",38,0,\"externref-elem\",[\"i32:0\"]],[\"invoke\",40,0,\"funcref-elem\",[\"i32:1\"]],[\"invoke\",41,0,\"externref-elem\",[\"i32:1\"]],[\"invoke\",43,0,\"deinit\",[]],[\"invoke\",45,0,\"funcref-elem\",[\"i32:0\"]],[\"invoke\",46,0,\"externref-elem\",[\"i32:0\"]],[\"invoke\",48,0,\"funcref-elem\",[\"i32:1\"]],[\"invoke\",49,0,\"externref-elem\",[\"i32:1\"]],[\"file\",\"ref_null.wast\"],[\"module\",1,127],[\"invoke\",9,0,\"externref\",[]],[\"invoke\",10,0,\"funcref\",[]],[\"file\",\"exports.wast\"],[\"module\",3,128],[\"module\",4,129],[\"module\",5,130],[\"module\",7,128],[\"module\",8,131],[\"module\",9,132],[\"module\",10,128],[\"module\",11,128],[\"module\",12,128],[\"module\",13,128],[\"module\",14,128],[\"module\",16,133,\"$Func\"],[\"invoke\",22,0,\"e\",[\"i32:42\"]],[\"invoke\",23,\"$Func\",\"e\",[\"i32:42\"]],[\"module\",24,134],[\"module\",25,134,\"$Other1\"],[\"invoke\",26,\"$Func\",\"e\",[\"i32:42\"]],[\"module\",28,135],[\"invoke\",34,0,\"a\",[]],[\"invoke\",35,0,\"b\",[]],[\"invoke\",36,0,\"c\",[]],[\"module\",74,136],[\"module\",75,137],[\"module\",76,138],[\"module\",78,136],[\"module\",79,136],[\"module\",80,136],[\"module\",81,136],[\"module\",82,136],[\"module\",83,136],[\"module\",85,139,\"$Global\"],[\"get\",89,0,\"e\",[]],[\"get\",90,\"$Global\",\"e\",[]],[\"module\",91,134],[\"module\",92,134,\"$Other2\"],[\"get\",93,\"$Global\",\"e\",[]],[\"module\",131,140],[\"module\",132,141],[\"module\",133,142],[\"module\",135,140],[\"module\",136,143],[\"module\",137,140],[\"module\",138,143],[\"module\",139,140],[\"module\",140,143],[\"module\",141,140],[\"module\",142,143],[\"module\",143,140],[\"module\",144,143],[\"module\",145,140],[\"module\",146,143],[\"module\",186,144],[\"module\",187,145],[\"module\",191,144],[\"module\",192,146],[\"module\",193,144],[\"module\",194,146],[\"module\",195,144],[\"module\",196,146],[\"module\",197,144],[\"module\",198,146],[\"module\",199,144],[\"module\",200,146],[\"module\",201,144],[\"module\",202,146],[\"file\",\"linking.wast\"],[\"module\",3,147,\"$Mf\"],[\"register\",\"Mf\",\"$Mf\"],[\"module\",9,148,\"$Nf\"],[\"invoke\",17,\"$Mf\",\"call\",[]],[\"invoke\",18,\"$Nf\",\"Mf.call\",[]],[\"invoke\",19,\"$Nf\",\"call\",[]],[\"invoke\",20,\"$Nf\",\"call Mf.call\",[]],[\"module\",22,149],[\"register\",\"reexport_f\"],[\"instantiate\",28,150],[\"instantiate\",32,151],[\"module\",39,152,\"$Mg\"],[\"register\",\"Mg\",\"$Mg\"],[\"module\",50,153,\"$Ng\"],[\"get\",67,\"$Mg\",\"glob\",[]],[\"get\",68,\"$Ng\",\"Mg.glob\",[]],[\"get\",69,\"$Ng\",\"glob\",[]],[\"invoke\",70,\"$Mg\",\"get\",[]],[\"invoke\",71,\"$Ng\",\"Mg.get\",[]],[\"invoke\",72,\"$Ng\",\"get\",[]],[\"get\",74,\"$Mg\",\"mut_glob\",[]],[\"get\",75,\"$Ng\",\"Mg.mut_glob\",[]],[\"invoke\",76,\"$Mg\",\"get_mut\",[]],[\"invoke\",77,\"$Ng\",\"Mg.get_mut\",[]],[\"invoke\",79,\"$Mg\",\"set_mut\",[\"i32:241\"]],[\"get\",80,\"$Mg\",\"mut_glob\",[]],[\"get\",81,\"$Ng\",\"Mg.mut_glob\",[]],[\"invoke\",82,\"$Mg\",\"get_mut\",[]],[\"invoke\",83,\"$Ng\",\"Mg.get_mut\",[]],[\"instantiate\",87,154],[\"instantiate\",91,155],[\"module\",96,156,\"$Mref_ex\"],[\"register\",\"Mref_ex\",\"$Mref_ex\"],[\"module\",104,157,\"$Mref_im\"],[\"instantiate\",113,158],[\"instantiate\",117,159],[\"instantiate\",123,160],[\"instantiate\",127,161],[\"module\",134,162,\"$Mt\"],[\"register\",\"Mt\",\"$Mt\"],[\"module\",149,163,\"$Nt\"],[\"invoke\",168,\"$Mt\",\"call\",[\"i32:2\"]],[\"invoke\",169,\"$Nt\",\"Mt.call\",[\"i32:2\"]],[\"invoke\",170,\"$Nt\",\"call\",[\"i32:2\"]],[\"invoke\",171,\"$Nt\",\"call Mt.call\",[\"i32:2\"]],[\"invoke\",173,\"$Mt\",\"call\",[\"i32:1\"]],[\"invoke\",174,\"$Nt\",\"Mt.call\",[\"i32:1\"]],[\"invoke\",175,\"$Nt\",\"call\",[\"i32:1\"]],[\"invoke\",176,\"$Nt\",\"call Mt.call\",[\"i32:1\"]],[\"invoke\",178,\"$Mt\",\"call\",[\"i32:0\"]],[\"invoke\",179,\"$Nt\",\"Mt.call\",[\"i32:0\"]],[\"invoke\",180,\"$Nt\",\"call\",[\"i32:0\"]],[\"invoke\",181,\"$Nt\",\"call Mt.call\",[\"i32:0\"]],[\"invoke\",183,\"$Mt\",\"call\",[\"i32:20\"]],[\"invoke\",184,\"$Nt\",\"Mt.call\",[\"i32:20\"]],[\"invoke\",185,\"$Nt\",\"call\",[\"i32:7\"]],[\"invoke\",186,\"$Nt\",\"call Mt.call\",[\"i32:20\"]],[\"invoke\",188,\"$Nt\",\"call\",[\"i32:3\"]],[\"invoke\",189,\"$Nt\",\"call\",[\"i32:4\"]],[\"module\",191,164,\"$Ot\"],[\"invoke\",204,\"$Mt\",\"call\",[\"i32:3\"]],[\"invoke\",205,\"$Nt\",\"Mt.call\",[\"i32:3\"]],[\"invoke\",206,\"$Nt\",\"call Mt.call\",[\"i32:3\"]],[\"invoke\",207,\"$Ot\",\"call\",[\"i32:3\"]],[\"invoke\",209,\"$Mt\",\"call\",[\"i32:2\"]],[\"invoke\",210,\"$Nt\",\"Mt.call\",[\"i32:2\"]],[\"invoke\",211,\"$Nt\",\"call\",[\"i32:2\"]],[\"invoke\",212,\"$Nt\",\"call Mt.call\",[\"i32:2\"]],[\"invoke\",213,\"$Ot\",\"call\",[\"i32:2\"]],[\"invoke\",215,\"$Mt\",\"call\",[\"i32:1\"]],[\"invoke\",216,\"$Nt\",\"Mt.call\",[\"i32:1\"]],[\"invoke\",217,\"$Nt\",\"call\",[\"i32:1\"]],[\"invoke\",218,\"$Nt\",\"call Mt.call\",[\"i32:1\"]],[\"invoke\",219,\"$Ot\",\"call\",[\"i32:1\"]],[\"invoke\",221,\"$Mt\",\"call\",[\"i32:0\"]],[\"invoke\",222,\"$Nt\",\"Mt.call\",[\"i32:0\"]],[\"invoke\",223,\"$Nt\",\"call\",[\"i32:0\"]],[\"invoke\",224,\"$Nt\",\"call Mt.call\",[\"i32:0\"]],[\"invoke\",225,\"$Ot\",\"call\",[\"i32:0\"]],[\"invoke\",227,\"$Ot\",\"call\",[\"i32:20\"]],[\"module\",229,165],[\"module\",235,166,\"$G1\"],[\"register\",\"G1\",\"$G1\"],[\"module\",237,167,\"$G2\"],[\"get\",241,\"$G2\",\"g\",[]],[\"instantiate\",244,168],[\"instantiate\",253,169],[\"invoke\",262,\"$Mt\",\"call\",[\"i32:7\"]],[\"instantiate\",267,170],[\"invoke\",275,\"$Mt\",\"call\",[\"i32:7\"]],[\"invoke\",276,\"$Mt\",\"call\",[\"i32:8\"]],[\"instantiate\",279,171],[\"invoke\",288,\"$Mt\",\"call\",[\"i32:7\"]],[\"module\",291,172,\"$Mtable_ex\"],[\"register\",\"Mtable_ex\",\"$Mtable_ex\"],[\"module\",297,173],[\"instantiate\",303,174],[\"instantiate\",307,175],[\"module\",314,176,\"$Mm\"],[\"register\",\"Mm\",\"$Mm\"],[\"module\",324,177,\"$Nm\"],[\"invoke\",336,\"$Mm\",\"load\",[\"i32:12\"]],[\"invoke\",337,\"$Nm\",\"Mm.load\",[\"i32:12\"]],[\"invoke\",338,\"$Nm\",\"load\",[\"i32:12\"]],[\"module\",340,178,\"$Om\"],[\"invoke\",349,\"$Mm\",\"load\",[\"i32:12\"]],[\"invoke\",350,\"$Nm\",\"Mm.load\",[\"i32:12\"]],[\"invoke\",351,\"$Nm\",\"load\",[\"i32:12\"]],[\"invoke\",352,\"$Om\",\"load\",[\"i32:12\"]],[\"module\",354,179],[\"instantiate\",360,180],[\"module\",367,181,\"$Pm\"],[\"invoke\",375,\"$Pm\",\"grow\",[\"i32:0\"]],[\"invoke\",376,\"$Pm\",\"grow\",[\"i32:2\"]],[\"invoke\",377,\"$Pm\",\"grow\",[\"i32:0\"]],[\"invoke\",378,\"$Pm\",\"grow\",[\"i32:1\"]],[\"invoke\",379,\"$Pm\",\"grow\",[\"i32:1\"]],[\"invoke\",380,\"$Pm\",\"grow\",[\"i32:0\"]],[\"invoke\",381,\"$Pm\",\"grow\",[\"i32:1\"]],[\"invoke\",382,\"$Pm\",\"grow\",[\"i32:0\"]],[\"instantiate\",385,182],[\"invoke\",393,\"$Mm\",\"load\",[\"i32:0\"]],[\"instantiate\",398,183],[\"invoke\",406,\"$Mm\",\"load\",[\"i32:0\"]],[\"invoke\",407,\"$Mm\",\"load\",[\"i32:327670\"]],[\"instantiate\",410,184],[\"invoke\",419,\"$Mm\",\"load\",[\"i32:0\"]],[\"module\",422,185,\"$Ms\"],[\"register\",\"Ms\",\"$Ms\"],[\"instantiate\",436,186],[\"invoke\",452,\"$Ms\",\"get memory[0]\",[]],[\"invoke\",453,\"$Ms\",\"get table[0]\",[]]]"; diff --git a/tests/corpus/webassembly-spec-table/main.mjs b/tests/corpus/webassembly-spec-table/main.mjs new file mode 100644 index 000000000..38c7c67d5 --- /dev/null +++ b/tests/corpus/webassembly-spec-table/main.mjs @@ -0,0 +1,5 @@ +// Executes WebAssembly spec tests (13 files); see tests/wasm-spec/runner.mjs. +import { run } from "../../wasm-spec/runner.mjs"; +import { modules, script } from "./data.mjs"; + +run(modules, script); diff --git a/tests/corpus/webassembly-typed.ts b/tests/corpus/webassembly-typed.ts new file mode 100644 index 000000000..967996135 --- /dev/null +++ b/tests/corpus/webassembly-typed.ts @@ -0,0 +1,51 @@ +// The WebAssembly API from TypeScript: lib.dom's types (declared by +// scriptc.d.ts), exports read through casts, and the error classes in +// catch clauses. The module text is tests/wasm-spec/corpus/api.wat. +const bytes: Uint8Array = Buffer.from("AGFzbQEAAAABLwlgAX8Bf2ACf38Bf2ABfwBgAABgAAF/YAJ+fgF+YAJ/fwJ/f2ABfQF9YAN/f38AAi0EA2VudgNsb2cAAgNlbnYEZmFpbAAAA2VudgRiYXNlA38AA2VudgNiaWcDfgADEhEAAAMBAQQFBgcAAAQAAAAICAQFAXABAggFBAEBAQQGFwN/AUEAC3wARAAAAAAAAApAC34AIwELB5kBEwZtZW1vcnkCAAV0YWJsZQEAB2NvdW50ZXIDAgJwaQMDBGJpZzIDBANhZGQABQVhcHBseQAGBGJ1bXAABwVtdWw2NAAIBmRpdm1vZAAJBm5hcnJvdwAKBnN0cmxlbgALBGdyb3cADAVwYWdlcwANCGNhbGxGYWlsAA4EdHJhcAAPB3JlY3Vyc2UAEARmaWxsABEEY29weQASCAEECQgBAEEACwICAwqHAhEHACAAQQJsCwcAIAAgAGwLBgAjABAACwcAIAAgAWoLCQAgASAAEQAACwsAIwJBAWokAiMCCwcAIAAgAX4LDAAgACABbSAAIAFvCwQAIAALIAEBfwJAA0AgACABai0AAEUNASABQQFqIQEMAAsLIAELBgAgAEAACwQAPwALCQAgABABQQFqC1kAAkACQAJAAkACQAJAAkAgAA4GAAECAwQFBgsAC0EBQQBuDwtB/////wcoAgAPC0EBQQJBABEBAA8LQQFBBxEAAA8LRAAAAAAAAPh/qg8LQYCAgIB4QX9tCwwAIABBAWoQEEEBagsLACAAIAEgAvwLAAsMACAAIAEgAvwKAAALCw4BAEEQCwhzY3JpcHRjAA==", "base64"); + +const logs: string[] = []; +// lib.dom's ImportValue has no bigint; Node accepts one for an i64 global. +const imports = { + env: { + log: (v: number): void => { + logs.push(`start ${v}`); + }, + fail: (v: number): number => v + 1, + base: 7, + big: 1n, + }, +} as unknown as WebAssembly.Imports; + +async function main(): Promise { + const { module, instance } = await WebAssembly.instantiate(bytes, imports); + console.log(logs.join(","), module instanceof WebAssembly.Module); + const exports = instance.exports; + const add = exports.add as (a: number, b: number) => number; + const mul64 = exports.mul64 as (a: bigint, b: bigint) => bigint; + const strlen = exports.strlen as (p: number) => number; + const trap = exports.trap as (kind: number) => number; + console.log(add(20, 22), mul64(3n, 4n), strlen(16)); + const memory = exports.memory as WebAssembly.Memory; + const text = new Uint8Array(memory.buffer, 16, strlen(16)); + console.log(new TextDecoder().decode(text), memory.buffer.byteLength); + const counter = exports.counter as WebAssembly.Global; + counter.value = 41; + console.log((exports.bump as () => number)(), counter.value); + for (let kind = 0; kind < 3; kind++) { + try { + trap(kind); + } catch (e) { + if (e instanceof WebAssembly.RuntimeError) console.log(`RuntimeError: ${e.message}`); + else throw e; + } + } + const second = new WebAssembly.Instance(module, imports); + console.log((second.exports.add as (a: number, b: number) => number)(1, 2), WebAssembly.validate(bytes)); + try { + await WebAssembly.compile(new Uint8Array([0, 97, 115, 109])); + } catch (e) { + console.log(e instanceof WebAssembly.CompileError); + } +} + +await main(); diff --git a/tests/corpus/webassembly-worker.ts b/tests/corpus/webassembly-worker.ts new file mode 100644 index 000000000..02bdf22c6 --- /dev/null +++ b/tests/corpus/webassembly-worker.ts @@ -0,0 +1,68 @@ +import { Worker, isMainThread, parentPort, workerData } from "node:worker_threads"; + +// The codemode shape: the main thread compiles a module once and sends the +// WebAssembly.Module to a fresh Worker per job, through workerData and +// through postMessage; each worker instantiates it with its own imports, +// runs it, and reports back. Traps in a worker (out-of-bounds memory and +// stack exhaustion, both caught by the per-thread fault handling) stay in +// that worker. The module text is tests/wasm-spec/corpus/api.wat. +const bytes = Buffer.from("AGFzbQEAAAABLwlgAX8Bf2ACf38Bf2ABfwBgAABgAAF/YAJ+fgF+YAJ/fwJ/f2ABfQF9YAN/f38AAi0EA2VudgNsb2cAAgNlbnYEZmFpbAAAA2VudgRiYXNlA38AA2VudgNiaWcDfgADEhEAAAMBAQQFBgcAAAQAAAAICAQFAXABAggFBAEBAQQGFwN/AUEAC3wARAAAAAAAAApAC34AIwELB5kBEwZtZW1vcnkCAAV0YWJsZQEAB2NvdW50ZXIDAgJwaQMDBGJpZzIDBANhZGQABQVhcHBseQAGBGJ1bXAABwVtdWw2NAAIBmRpdm1vZAAJBm5hcnJvdwAKBnN0cmxlbgALBGdyb3cADAVwYWdlcwANCGNhbGxGYWlsAA4EdHJhcAAPB3JlY3Vyc2UAEARmaWxsABEEY29weQASCAEECQgBAEEACwICAwqHAhEHACAAQQJsCwcAIAAgAGwLBgAjABAACwcAIAAgAWoLCQAgASAAEQAACwsAIwJBAWokAiMCCwcAIAAgAX4LDAAgACABbSAAIAFvCwQAIAALIAEBfwJAA0AgACABai0AAEUNASABQQFqIQEMAAsLIAELBgAgAEAACwQAPwALCQAgABABQQFqC1kAAkACQAJAAkACQAJAAkAgAA4GAAECAwQFBgsAC0EBQQBuDwtB/////wcoAgAPC0EBQQJBABEBAA8LQQFBBxEAAA8LRAAAAAAAAPh/qg8LQYCAgIB4QX9tCwwAIABBAWoQEEEBagsLACAAIAEgAvwLAAsMACAAIAEgAvwKAAALCw4BAEEQCwhzY3JpcHRjAA==", "base64"); + +interface Job { + module: WebAssembly.Module; + input: number; +} + +function run(module: WebAssembly.Module, input: number): string { + const seen: number[] = []; + const imports = { + env: { + log: (v: number): void => { + seen.push(v); + }, + fail: (v: number): number => v * 2, + base: input, + big: BigInt(input), + }, + } as unknown as WebAssembly.Imports; + const instance = new WebAssembly.Instance(module, imports); + const exports = instance.exports; + const apply = exports.apply as (fn: number, v: number) => number; + const callFail = exports.callFail as (v: number) => number; + const trap = exports.trap as (kind: number) => number; + const recurse = exports.recurse as (v: number) => number; + const traps: string[] = []; + for (const attempt of [() => trap(2), () => recurse(0), () => trap(1)]) { + try { + attempt(); + } catch (e) { + traps.push(e instanceof Error ? `${e.name}: ${e.message}` : String(e)); + } + } + return `job ${input}: start ${seen.join(",")} apply ${apply(1, input + 3)} callFail ${callFail(input)} traps ${traps.join(" | ")}`; +} + +if (isMainThread) { + const module = await WebAssembly.compile(bytes); + console.log(run(module, 0)); + for (let input = 1; input <= 3; input++) { + const line = await new Promise((resolve, reject) => { + const worker = new Worker(new URL(import.meta.url), { workerData: { module, input } satisfies Job }); + worker.once("message", (message: string) => resolve(message)); + worker.once("error", reject); + }); + console.log(line); + } + const worker = new Worker(new URL(import.meta.url), { workerData: null }); + const reply = new Promise((resolve) => worker.once("message", (message: string) => resolve(message))); + worker.postMessage({ module, input: 9 } satisfies Job); + console.log(await reply); + console.log("main still works:", run(module, 4).slice(0, 18)); +} else if (workerData !== null) { + const job = workerData as Job; + parentPort!.postMessage(`${job.module instanceof WebAssembly.Module} ${run(job.module, job.input)}`); +} else { + parentPort!.once("message", (job: Job) => { + parentPort!.postMessage(run(job.module, job.input)); + }); +} diff --git a/tests/diagnostics/webassembly-fences.ts b/tests/diagnostics/webassembly-fences.ts index 56b2991de..ffa2f96e3 100644 --- a/tests/diagnostics/webassembly-fences.ts +++ b/tests/diagnostics/webassembly-fences.ts @@ -1,30 +1,18 @@ -// No WebAssembly module compiles to native code yet: every reached use of -// the API refuses at its site, in each spelling, while declarations that -// only mention its types compile. -interface LoaderOptions { - wasm?: BufferSource | WebAssembly.Module; - imports?: WebAssembly.Imports; -} - -async function load(options: LoaderOptions, bytes: Uint8Array): Promise { - console.log(WebAssembly.validate(bytes)); +// The parts of the WebAssembly API scriptc does not implement yet refuse at +// each reached use, in each spelling. compile, instantiate, validate, +// Module, Instance and the error classes compile (tests/corpus/webassembly-*). +async function load(bytes: Uint8Array): Promise { const module = await WebAssembly.compile(bytes); - const { instance } = await WebAssembly.instantiate(bytes, options.imports); - console.log(instance instanceof WebAssembly.Instance, module); - const direct = new WebAssembly.Instance(new WebAssembly.Module(bytes)); - console.log(direct.exports); const memory = new WebAssembly.Memory({ initial: 1 }); - console.log(memory.buffer.byteLength); - const api = globalThis.WebAssembly; - console.log(api); + const table = new WebAssembly.Table({ initial: 1, element: "anyfunc" }); + const global = new WebAssembly.Global({ value: "i32", mutable: true }, 1); + console.log(memory.buffer.byteLength, table.length, global.value); console.log(WebAssembly.Module.imports(module).length); + console.log(WebAssembly.Module.customSections(module, "name").length); + const streamed = await WebAssembly.instantiateStreaming(fetch("https://example.com/x.wasm")); + console.log(streamed.instance); + const Ctor = WebAssembly.Module; + console.log(Ctor); } -// pi's codemode spelling: the global object viewed through a cast. -interface WebAssemblyGlobal { - compile(bytes: Uint8Array): Promise; -} -const { WebAssembly: viewed } = globalThis as unknown as { WebAssembly: WebAssemblyGlobal }; -console.log(typeof viewed.compile); - -await load({}, new Uint8Array([0, 97, 115, 109, 1, 0, 0, 0])); +await load(new Uint8Array([0, 97, 115, 109, 1, 0, 0, 0])); diff --git a/tests/harness/__snapshots__/coverage-wasm.txt b/tests/harness/__snapshots__/coverage-wasm.txt index 5b5bea36a..0a56ccd1d 100644 --- a/tests/harness/__snapshots__/coverage-wasm.txt +++ b/tests/harness/__snapshots__/coverage-wasm.txt @@ -1,13 +1,13 @@ scriptc coverage tests/coverage-fixtures/wasm/main.ts statements analyzed 8 - compile statically 6 (75%) + compile statically 8 (100%) in dynamic tier 2 (operate on values typed only at runtime; --explain-dynamic lists them) npm package compilation: wasm-fixture-pkg static - WebAssembly modules: (found at build time; none compile to native code yet) + WebAssembly modules: (found at build time; valid ones compile to native code) inline module 39 bytes valid 1 function, 0 imports, 1 export found: inline at tests/coverage-fixtures/wasm/glue.js:5 @@ -22,10 +22,4 @@ scriptc coverage tests/coverage-fixtures/wasm/main.ts features: simd found: configured by tests/coverage-fixtures/wasm/package.json - in unreached code (never lowered — cannot fail a build) - ×1 SC2004 uses of 'instance' inherit the blocker on its declaration - at tests/coverage-fixtures/wasm/glue.js:17:10 - hint: the declaration of 'instance' did not compile — fix the diagnostic reported there and these sites clear with it - ×1 SC2020 'WebAssembly.instantiate' is not supported yet: scriptc does not compile WebAssembly modules to native code yet - at tests/coverage-fixtures/wasm/glue.js:16:30 - hint: WebAssembly support is planned as translation of each module whose bytes the build can see (an inline literal, or a file read from a path the compiler resolves) into native code; 'scriptc coverage' lists the modules this program would load (https://scriptc.dev/limitations) \ No newline at end of file + fully static — all analyzed statements compile to native code. \ No newline at end of file diff --git a/tests/harness/__snapshots__/webassembly-fences.ts.txt b/tests/harness/__snapshots__/webassembly-fences.ts.txt index da38a2c57..f4a1eeae1 100644 --- a/tests/harness/__snapshots__/webassembly-fences.ts.txt +++ b/tests/harness/__snapshots__/webassembly-fences.ts.txt @@ -1,98 +1,71 @@ -webassembly-fences.ts:10:15 - error SC2020: 'WebAssembly.validate' is not supported yet: scriptc does not compile WebAssembly modules to native code yet +webassembly-fences.ts:6:22 - error SC2020: 'new WebAssembly.Memory' is not supported yet: scriptc implements WebAssembly.compile, instantiate, validate, Module, Instance and the error classes over modules translated at build time, but not this part of the API - 9 | async function load(options: LoaderOptions, bytes: Uint8Array): Promise { - 10 | console.log(WebAssembly.validate(bytes)); - | ^~~~~~~~~~~~~~~~~~~~ - 11 | const module = await WebAssembly.compile(bytes); + 5 | const module = await WebAssembly.compile(bytes); + 6 | const memory = new WebAssembly.Memory({ initial: 1 }); + | ^~~~~~~~~~~~~~~~~~ + 7 | const table = new WebAssembly.Table({ initial: 1, element: "anyfunc" }); - hint: WebAssembly support is planned as translation of each module whose bytes the build can see (an inline literal, or a file read from a path the compiler resolves) into native code; 'scriptc coverage' lists the modules this program would load (https://scriptc.dev/limitations) + hint: the Memory, Table and Global constructors, Module.imports, exports and customSections, and the streaming functions are planned; 'scriptc coverage' lists the modules this program loads (https://scriptc.dev/limitations) -webassembly-fences.ts:11:24 - error SC2020: 'WebAssembly.compile' is not supported yet: scriptc does not compile WebAssembly modules to native code yet +webassembly-fences.ts:7:21 - error SC2020: 'new WebAssembly.Table' is not supported yet: scriptc implements WebAssembly.compile, instantiate, validate, Module, Instance and the error classes over modules translated at build time, but not this part of the API - 10 | console.log(WebAssembly.validate(bytes)); - 11 | const module = await WebAssembly.compile(bytes); - | ^~~~~~~~~~~~~~~~~~~ - 12 | const { instance } = await WebAssembly.instantiate(bytes, options.imports); + 6 | const memory = new WebAssembly.Memory({ initial: 1 }); + 7 | const table = new WebAssembly.Table({ initial: 1, element: "anyfunc" }); + | ^~~~~~~~~~~~~~~~~ + 8 | const global = new WebAssembly.Global({ value: "i32", mutable: true }, 1); - hint: WebAssembly support is planned as translation of each module whose bytes the build can see (an inline literal, or a file read from a path the compiler resolves) into native code; 'scriptc coverage' lists the modules this program would load (https://scriptc.dev/limitations) + hint: the Memory, Table and Global constructors, Module.imports, exports and customSections, and the streaming functions are planned; 'scriptc coverage' lists the modules this program loads (https://scriptc.dev/limitations) -webassembly-fences.ts:12:30 - error SC2020: 'WebAssembly.instantiate' is not supported yet: scriptc does not compile WebAssembly modules to native code yet +webassembly-fences.ts:8:22 - error SC2020: 'new WebAssembly.Global' is not supported yet: scriptc implements WebAssembly.compile, instantiate, validate, Module, Instance and the error classes over modules translated at build time, but not this part of the API - 11 | const module = await WebAssembly.compile(bytes); - 12 | const { instance } = await WebAssembly.instantiate(bytes, options.imports); - | ^~~~~~~~~~~~~~~~~~~~~~~ - 13 | console.log(instance instanceof WebAssembly.Instance, module); + 7 | const table = new WebAssembly.Table({ initial: 1, element: "anyfunc" }); + 8 | const global = new WebAssembly.Global({ value: "i32", mutable: true }, 1); + | ^~~~~~~~~~~~~~~~~~ + 9 | console.log(memory.buffer.byteLength, table.length, global.value); - hint: WebAssembly support is planned as translation of each module whose bytes the build can see (an inline literal, or a file read from a path the compiler resolves) into native code; 'scriptc coverage' lists the modules this program would load (https://scriptc.dev/limitations) + hint: the Memory, Table and Global constructors, Module.imports, exports and customSections, and the streaming functions are planned; 'scriptc coverage' lists the modules this program loads (https://scriptc.dev/limitations) -webassembly-fences.ts:13:35 - error SC2020: 'WebAssembly.Instance' is not supported yet: scriptc does not compile WebAssembly modules to native code yet +webassembly-fences.ts:9:15 - error SC2004: uses of 'memory' inherit the blocker on its declaration - 12 | const { instance } = await WebAssembly.instantiate(bytes, options.imports); - 13 | console.log(instance instanceof WebAssembly.Instance, module); - | ^~~~~~~~~~~~~~~~~~~~ - 14 | const direct = new WebAssembly.Instance(new WebAssembly.Module(bytes)); - - hint: WebAssembly support is planned as translation of each module whose bytes the build can see (an inline literal, or a file read from a path the compiler resolves) into native code; 'scriptc coverage' lists the modules this program would load (https://scriptc.dev/limitations) - -webassembly-fences.ts:14:22 - error SC2020: 'new WebAssembly.Instance' is not supported yet: scriptc does not compile WebAssembly modules to native code yet - - 13 | console.log(instance instanceof WebAssembly.Instance, module); - 14 | const direct = new WebAssembly.Instance(new WebAssembly.Module(bytes)); - | ^~~~~~~~~~~~~~~~~~~~ - 15 | console.log(direct.exports); - - hint: WebAssembly support is planned as translation of each module whose bytes the build can see (an inline literal, or a file read from a path the compiler resolves) into native code; 'scriptc coverage' lists the modules this program would load (https://scriptc.dev/limitations) - -webassembly-fences.ts:16:22 - error SC2020: 'new WebAssembly.Memory' is not supported yet: scriptc does not compile WebAssembly modules to native code yet - - 15 | console.log(direct.exports); - 16 | const memory = new WebAssembly.Memory({ initial: 1 }); - | ^~~~~~~~~~~~~~~~~~ - 17 | console.log(memory.buffer.byteLength); - - hint: WebAssembly support is planned as translation of each module whose bytes the build can see (an inline literal, or a file read from a path the compiler resolves) into native code; 'scriptc coverage' lists the modules this program would load (https://scriptc.dev/limitations) - -webassembly-fences.ts:17:15 - error SC2004: uses of 'memory' inherit the blocker on its declaration - - 16 | const memory = new WebAssembly.Memory({ initial: 1 }); - 17 | console.log(memory.buffer.byteLength); + 8 | const global = new WebAssembly.Global({ value: "i32", mutable: true }, 1); + 9 | console.log(memory.buffer.byteLength, table.length, global.value); | ^~~~~~~~~~~~~~~~~~~~~~~~ - 18 | const api = globalThis.WebAssembly; + 10 | console.log(WebAssembly.Module.imports(module).length); hint: the declaration of 'memory' did not compile — fix the diagnostic reported there and these sites clear with it -webassembly-fences.ts:18:15 - error SC2020: 'globalThis.WebAssembly' is not supported yet: scriptc does not compile WebAssembly modules to native code yet +webassembly-fences.ts:10:15 - error SC2020: 'WebAssembly.Module.imports' is not supported yet: scriptc implements WebAssembly.compile, instantiate, validate, Module, Instance and the error classes over modules translated at build time, but not this part of the API - 17 | console.log(memory.buffer.byteLength); - 18 | const api = globalThis.WebAssembly; - | ^~~~~~~~~~~~~~~~~~~~~~ - 19 | console.log(api); - - hint: WebAssembly support is planned as translation of each module whose bytes the build can see (an inline literal, or a file read from a path the compiler resolves) into native code; 'scriptc coverage' lists the modules this program would load (https://scriptc.dev/limitations) - -webassembly-fences.ts:19:15 - error SC2004: uses of 'api' inherit the blocker on its declaration - - 18 | const api = globalThis.WebAssembly; - 19 | console.log(api); - | ^~~ - 20 | console.log(WebAssembly.Module.imports(module).length); - - hint: the declaration of 'api' did not compile — fix the diagnostic reported there and these sites clear with it - -webassembly-fences.ts:20:15 - error SC2020: 'WebAssembly.Module.imports' is not supported yet: scriptc does not compile WebAssembly modules to native code yet - - 19 | console.log(api); - 20 | console.log(WebAssembly.Module.imports(module).length); + 9 | console.log(memory.buffer.byteLength, table.length, global.value); + 10 | console.log(WebAssembly.Module.imports(module).length); | ^~~~~~~~~~~~~~~~~~~~~~~~~~ - 21 | } + 11 | console.log(WebAssembly.Module.customSections(module, "name").length); - hint: WebAssembly support is planned as translation of each module whose bytes the build can see (an inline literal, or a file read from a path the compiler resolves) into native code; 'scriptc coverage' lists the modules this program would load (https://scriptc.dev/limitations) + hint: the Memory, Table and Global constructors, Module.imports, exports and customSections, and the streaming functions are planned; 'scriptc coverage' lists the modules this program loads (https://scriptc.dev/limitations) -webassembly-fences.ts:27:33 - error SC2020: 'globalThis.WebAssembly' is not supported yet: scriptc does not compile WebAssembly modules to native code yet +webassembly-fences.ts:11:15 - error SC2020: 'WebAssembly.Module.customSections' is not supported yet: scriptc implements WebAssembly.compile, instantiate, validate, Module, Instance and the error classes over modules translated at build time, but not this part of the API - 26 | } - 27 | const { WebAssembly: viewed } = globalThis as unknown as { WebAssembly: WebAssemblyGlobal }; - | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - 28 | console.log(typeof viewed.compile); + 10 | console.log(WebAssembly.Module.imports(module).length); + 11 | console.log(WebAssembly.Module.customSections(module, "name").length); + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 12 | const streamed = await WebAssembly.instantiateStreaming(fetch("https://example.com/x.wasm")); - hint: WebAssembly support is planned as translation of each module whose bytes the build can see (an inline literal, or a file read from a path the compiler resolves) into native code; 'scriptc coverage' lists the modules this program would load (https://scriptc.dev/limitations) \ No newline at end of file + hint: the Memory, Table and Global constructors, Module.imports, exports and customSections, and the streaming functions are planned; 'scriptc coverage' lists the modules this program loads (https://scriptc.dev/limitations) + +webassembly-fences.ts:12:26 - error SC2020: 'WebAssembly.instantiateStreaming' is not supported yet: scriptc implements WebAssembly.compile, instantiate, validate, Module, Instance and the error classes over modules translated at build time, but not this part of the API + + 11 | console.log(WebAssembly.Module.customSections(module, "name").length); + 12 | const streamed = await WebAssembly.instantiateStreaming(fetch("https://example.com/x.wasm")); + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 13 | console.log(streamed.instance); + + hint: the Memory, Table and Global constructors, Module.imports, exports and customSections, and the streaming functions are planned; 'scriptc coverage' lists the modules this program loads (https://scriptc.dev/limitations) + +webassembly-fences.ts:14:16 - error SC2020: 'WebAssembly.Module' is not supported yet: scriptc implements WebAssembly.compile, instantiate, validate, Module, Instance and the error classes over modules translated at build time, but not this part of the API + + 13 | console.log(streamed.instance); + 14 | const Ctor = WebAssembly.Module; + | ^~~~~~~~~~~~~~~~~~ + 15 | console.log(Ctor); + + hint: the Memory, Table and Global constructors, Module.imports, exports and customSections, and the streaming functions are planned; 'scriptc coverage' lists the modules this program loads (https://scriptc.dev/limitations) \ No newline at end of file diff --git a/tests/wasm-spec/corpus/api.wat b/tests/wasm-spec/corpus/api.wat new file mode 100644 index 000000000..353a8421c --- /dev/null +++ b/tests/wasm-spec/corpus/api.wat @@ -0,0 +1,52 @@ +(module + (import "env" "log" (func $log (param i32))) + (import "env" "fail" (func $fail (param i32) (result i32))) + (import "env" "base" (global $base i32)) + (import "env" "big" (global $big i64)) + (memory (export "memory") 1 4) + (table (export "table") 2 8 funcref) + (global $counter (export "counter") (mut i32) (i32.const 0)) + (global (export "pi") f64 (f64.const 3.25)) + (global (export "big2") i64 (global.get $big)) + (data (i32.const 16) "scriptc\00") + (elem (i32.const 0) $double $square) + (type $unop (func (param i32) (result i32))) + (type $binop (func (param i32 i32) (result i32))) + (func $double (type $unop) (i32.mul (local.get 0) (i32.const 2))) + (func $square (type $unop) (i32.mul (local.get 0) (local.get 0))) + (start $init) + (func $init (call $log (global.get $base))) + (func (export "add") (type $binop) (i32.add (local.get 0) (local.get 1))) + (func (export "apply") (param i32 i32) (result i32) + (call_indirect (type $unop) (local.get 1) (local.get 0))) + (func (export "bump") (result i32) + (global.set $counter (i32.add (global.get $counter) (i32.const 1))) + (global.get $counter)) + (func (export "mul64") (param i64 i64) (result i64) (i64.mul (local.get 0) (local.get 1))) + (func (export "divmod") (param i32 i32) (result i32 i32) + (i32.div_s (local.get 0) (local.get 1)) (i32.rem_s (local.get 0) (local.get 1))) + (func (export "narrow") (param f32) (result f32) (local.get 0)) + (func (export "strlen") (param $p i32) (result i32) (local $n i32) + (block $done (loop $next + (br_if $done (i32.eqz (i32.load8_u (i32.add (local.get $p) (local.get $n))))) + (local.set $n (i32.add (local.get $n) (i32.const 1))) + (br $next))) + (local.get $n)) + (func (export "grow") (param i32) (result i32) (memory.grow (local.get 0))) + (func (export "pages") (result i32) (memory.size)) + (func (export "callFail") (param i32) (result i32) (i32.add (call $fail (local.get 0)) (i32.const 1))) + (func (export "trap") (param i32) (result i32) + (block $b6 (block $b5 (block $b4 (block $b3 (block $b2 (block $b1 (block $b0 + (br_table $b0 $b1 $b2 $b3 $b4 $b5 $b6 (local.get 0))) + unreachable) + (return (i32.div_u (i32.const 1) (i32.const 0)))) + (return (i32.load (i32.const 0x7fffffff)))) + (return (call_indirect (type $binop) (i32.const 1) (i32.const 2) (i32.const 0)))) + (return (call_indirect (type $unop) (i32.const 1) (i32.const 7)))) + (return (i32.trunc_f64_s (f64.const nan)))) + (i32.div_s (i32.const 0x80000000) (i32.const -1))) + (func $rec (export "recurse") (param i32) (result i32) + (i32.add (call $rec (i32.add (local.get 0) (i32.const 1))) (i32.const 1))) + (func (export "fill") (param i32 i32 i32) (memory.fill (local.get 0) (local.get 1) (local.get 2))) + (func (export "copy") (param i32 i32 i32) (memory.copy (local.get 0) (local.get 1) (local.get 2))) +) diff --git a/tests/wasm-spec/corpus/linked.wat b/tests/wasm-spec/corpus/linked.wat new file mode 100644 index 000000000..28834b006 --- /dev/null +++ b/tests/wasm-spec/corpus/linked.wat @@ -0,0 +1,12 @@ +(module + (import "api" "memory" (memory 1)) + (import "api" "table" (table 2 funcref)) + (import "api" "counter" (global $c (mut i32))) + (import "api" "add" (func $add (param i32 i32) (result i32))) + (func (export "read") (param i32) (result i32) (i32.load8_u (local.get 0))) + (func (export "useTable") (param i32 i32) (result i32) + (call_indirect (param i32) (result i32) (local.get 1) (local.get 0))) + (func (export "setCounter") (param i32) (global.set $c (local.get 0))) + (func (export "add3") (param i32 i32 i32) (result i32) + (call $add (call $add (local.get 0) (local.get 1)) (local.get 2))) +) diff --git a/tests/wasm-spec/corpus/refs.wat b/tests/wasm-spec/corpus/refs.wat new file mode 100644 index 000000000..52516fa30 --- /dev/null +++ b/tests/wasm-spec/corpus/refs.wat @@ -0,0 +1,10 @@ +(module + (table $t (export "refs") 1 10 externref) + (global (export "slot") (mut externref) (ref.null extern)) + (func (export "keep") (param externref) (result externref) (local.get 0)) + (func (export "store") (param i32 externref) (table.set $t (local.get 0) (local.get 1))) + (func (export "load") (param i32) (result externref) (table.get $t (local.get 0))) + (func (export "isNull") (param externref) (result i32) (ref.is_null (local.get 0))) + (func (export "grow") (param externref i32) (result i32) (table.grow $t (local.get 0) (local.get 1))) + (func (export "size") (result i32) (table.size $t)) +) diff --git a/tests/wasm-spec/gen.mjs b/tests/wasm-spec/gen.mjs new file mode 100644 index 000000000..3019c56f5 --- /dev/null +++ b/tests/wasm-spec/gen.mjs @@ -0,0 +1,272 @@ +/* Regenerates the executed WebAssembly spec-test corpus programs + * (tests/corpus/webassembly-spec-/data.mjs): the binary modules and + * the commands of selected .wast files of the WebAssembly 2.0 core suite, + * which runner.mjs instantiates and invokes under Node and as a compiled + * binary. The differential harness compares the two outputs, so V8 is the + * oracle; the modules are inline base64 literals, which the build + * discovers and translates. + * + * node tests/wasm-spec/gen.mjs --wast2json /bin/wast2json \ + * --spec /test/core + * + * Left out: modules given as text, modules the decoder does not accept + * within the translatable feature level, modules that import a spectest + * memory or table (the Memory and Table constructors are not supported + * yet) and every command that targets them, and commands with v128 + * values. Also left out, because they test NaN payloads, which neither + * side preserves the way the other does: actions whose f32 or f64 + * arguments are NaNs other than the canonical quiet NaN (V8 and scriptc + * carry NaN payloads through JavaScript numbers differently), and + * float_exprs.wast's actions that pass a signaling NaN's bit pattern to + * arithmetic that must quiet it (LLVM may fold `x - 0.0` to `x`, leaving + * the payload; WebAssembly requires a quiet NaN). Files with more than + * MAX_ACTIONS actions keep an evenly spaced + * sample of them (every module and register command stays), which keeps + * the generated programs small. + * + * Commands are compact tuples (runner.mjs reads them): + * ["file", name] + * ["module", line, moduleIndex, name?] instantiate, make current + * ["instantiate", line, moduleIndex] assert_un{linkable,instantiable} + * ["register", as, name?] + * ["invoke" | "get", line, name | 0, field, args] args: "type:value" + * The suite's tests are Apache-2.0 licensed (WebAssembly Community + * Group). */ +import { execFileSync } from "node:child_process"; +import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { dirname, join } from "node:path"; +import { parseArgs } from "node:util"; +import { analyzeWasm } from "../../packages/compiler/dist/wasm/binary.js"; + +export const GROUPS = { + int: ["i32", "i64", "int_exprs", "int_literals", "conversions", "traps"], + float: [ + "f32", + "f64", + "f32_cmp", + "f64_cmp", + "f32_bitwise", + "f64_bitwise", + "float_exprs", + "float_literals", + "float_misc", + ], + control: [ + "block", + "br", + "br_if", + "br_table", + "call", + "call_indirect", + "fac", + "forward", + "func", + "func_ptrs", + "if", + "labels", + "loop", + "local_get", + "local_set", + "local_tee", + "nop", + "return", + "select", + "stack", + "switch", + "unreachable", + "unwind", + "left-to-right", + "global", + "start", + ], + memory: [ + "address", + "align", + "endianness", + "float_memory", + "load", + "store", + "memory", + "memory_grow", + "memory_redundancy", + "memory_size", + "memory_trap", + "bulk", + "memory_copy", + "memory_fill", + "memory_init", + "data", + ], + table: [ + "table_copy", + "table_fill", + "table_get", + "table_grow", + "table_init", + "table_set", + "table_size", + "elem", + "ref_func", + "ref_is_null", + "ref_null", + "exports", + "linking", + ], +}; + +const MAX_ACTIONS = 400; + +const { values } = parseArgs({ + options: { wast2json: { type: "string" }, spec: { type: "string" } }, +}); +const repo = join(dirname(new URL(import.meta.url).pathname), "../.."); + +function value(v) { + if (v.type === "v128") return null; + return `${v.type}:${v.value}`; +} + +/** Whether a "type:value" argument is a NaN other than the canonical + * quiet one. */ +function oddNaN(arg) { + const [type, text] = arg.split(":"); + if (type === "f32") { + const bits = Number(text); + return (bits & 0x7f800000) === 0x7f800000 && (bits & 0x7fffff) !== 0 && (bits & 0x7fffffff) !== 0x7fc00000; + } + if (type === "f64") { + const bits = BigInt(text); + const exp = 0x7ff0000000000000n; + const mantissa = bits & 0xfffffffffffffn; + return (bits & exp) === exp && mantissa !== 0n && (bits & 0x7fffffffffffffffn) !== 0x7ff8000000000000n; + } + return false; +} + +/** Whether a "type:value" integer argument is the bit pattern of a + * signaling NaN. */ +function signalingPattern(arg) { + const [type, text] = arg.split(":"); + if (type === "i32") { + const bits = Number(text) >>> 0; + return (bits & 0x7fc00000) === 0x7f800000 && (bits & 0x3fffff) !== 0; + } + if (type === "i64") { + const bits = BigInt.asUintN(64, BigInt(text)); + return (bits & 0x7ff8000000000000n) === 0x7ff0000000000000n && (bits & 0x7ffffffffffffn) !== 0n; + } + return false; +} + +/** Keeps every structural command and an evenly spaced sample of at most + * MAX_ACTIONS actions. */ +function sample(commands) { + const actions = commands.filter((c) => c[0] === "invoke" || c[0] === "get").length; + if (actions <= MAX_ACTIONS) return commands; + const step = actions / MAX_ACTIONS; + let seen = 0; + let next = 0; + return commands.filter((c) => { + if (c[0] !== "invoke" && c[0] !== "get") return true; + const keep = seen >= next; + seen++; + if (keep) next += step; + return keep; + }); +} + +for (const [group, names] of Object.entries(GROUPS)) { + const modules = []; + const moduleIndex = new Map(); + const commands = []; + for (const name of names) { + const dir = mkdtempSync(join(tmpdir(), "wasm-spec-")); + try { + execFileSync(values.wast2json, [join(values.spec, `${name}.wast`), "-o", join(dir, "x.json")], { + stdio: "pipe", + }); + const json = JSON.parse(readFileSync(join(dir, "x.json"), "utf8")); + const fileCommands = [["file", `${name}.wast`]]; + // Module names that refer to a module left out: their commands go too. + const skipped = new Set(); + let current = null; + let currentSkipped = false; + for (const c of json.commands) { + const where = c.line; + if (c.type === "module" || c.type === "assert_uninstantiable" || c.type === "assert_unlinkable") { + if (c.module_type === "text" || !c.filename?.endsWith(".wasm")) { + if (c.type === "module") { + currentSkipped = true; + if (c.name) skipped.add(c.name); + } + continue; + } + const bytes = readFileSync(join(dir, c.filename)); + const summary = analyzeWasm(new Uint8Array(bytes)); + const imports = WebAssembly.Module.imports(new WebAssembly.Module(bytes)); + const usable = + summary.status === "valid" && + !imports.some( + (i) => i.module === "spectest" && (i.kind === "memory" || i.kind === "table"), + ); + if (!usable) { + if (c.type === "module") { + currentSkipped = true; + if (c.name) skipped.add(c.name); + } + continue; + } + const b64 = bytes.toString("base64"); + let index = moduleIndex.get(b64); + if (index === undefined) { + index = modules.length; + modules.push(b64); + moduleIndex.set(b64, index); + } + if (c.type === "module") { + current = c.name ?? null; + currentSkipped = false; + fileCommands.push(["module", where, index, ...(c.name ? [c.name] : [])]); + } else { + fileCommands.push(["instantiate", where, index]); + } + continue; + } + if (c.type === "register") { + const target = c.name ?? current; + if ((c.name ? skipped.has(c.name) : currentSkipped)) continue; + fileCommands.push(["register", c.as, ...(target ? [target] : [])]); + continue; + } + if (!c.action) continue; + const a = c.action; + const target = a.module ?? null; + if (target ? skipped.has(target) : currentSkipped) continue; + const args = (a.args ?? []).map(value); + const expected = (c.expected ?? []).map(value); + if (args.includes(null) || expected.includes(null)) continue; + if (args.some(oddNaN)) continue; + if (name === "float_exprs" && args.some(signalingPattern)) continue; + fileCommands.push([a.type === "get" ? "get" : "invoke", where, target ?? 0, a.field, args]); + } + commands.push(...sample(fileCommands)); + } finally { + rmSync(dir, { recursive: true, force: true }); + } + } + const out = join(repo, `tests/corpus/webassembly-spec-${group}`); + mkdirSync(out, { recursive: true }); + const header = `/* Generated by tests/wasm-spec/gen.mjs from the WebAssembly 2.0 core test + * suite (WebAssembly/spec wg-2.0, Apache-2.0) with wabt 1.0.37 wast2json: + * ${names.map((n) => `${n}.wast`).join(", ")}. Do not edit. */\n`; + const body = + `export const modules = [\n${modules.map((m) => ` "${m}",`).join("\n")}\n];\n` + + `export const script = ${JSON.stringify(JSON.stringify(commands))};\n`; + writeFileSync(join(out, "data.mjs"), header + body); + writeFileSync( + join(out, "main.mjs"), + `// Executes WebAssembly spec tests (${names.length} files); see tests/wasm-spec/runner.mjs.\nimport { run } from "../../wasm-spec/runner.mjs";\nimport { modules, script } from "./data.mjs";\n\nrun(modules, script);\n`, + ); + console.log(`${group}: ${modules.length} modules, ${commands.length} commands`); +} diff --git a/tests/wasm-spec/runner.mjs b/tests/wasm-spec/runner.mjs new file mode 100644 index 000000000..5c1b8b6b4 --- /dev/null +++ b/tests/wasm-spec/runner.mjs @@ -0,0 +1,145 @@ +/* Executes WebAssembly spec-test commands (generated by gen.mjs) and prints + * one line per command. The webassembly-spec-* corpus programs call it; + * the differential harness runs them under Node and as compiled binaries + * and compares the output, so V8 decides every expected value, trap and + * error. Module bytes are inline base64 literals in each program's + * data.mjs, which the build translates. */ + +/** The spec harness's `spectest` module, without its memory and table. */ +function spectestImports(log) { + return { + print() { + log("print"); + }, + print_i32(a) { + log(`print_i32 ${a}`); + }, + print_i64(a) { + log(`print_i64 ${a}`); + }, + print_f32(a) { + log(`print_f32 ${show(a)}`); + }, + print_f64(a) { + log(`print_f64 ${show(a)}`); + }, + print_i32_f32(a, b) { + log(`print_i32_f32 ${a} ${show(b)}`); + }, + print_f64_f64(a, b) { + log(`print_f64_f64 ${show(a)} ${show(b)}`); + }, + global_i32: 666, + global_i64: 666n, + global_f32: 666.6, + global_f64: 666.6, + }; +} + +function show(v) { + if (typeof v === "bigint") return `${v}n`; + if (typeof v === "number") return Object.is(v, -0) ? "-0" : String(v); + if (v === null) return "null"; + if (v === undefined) return "undefined"; + if (typeof v === "function") return "function"; + if (Array.isArray(v)) return `[${v.map(show).join(",")}]`; + if (typeof v === "object" && typeof v.ref === "number") return `ref ${v.ref}`; + return typeof v; +} + +const refs = new Map(); + +function decode(arg) { + const colon = arg.indexOf(":"); + const type = arg.slice(0, colon); + const text = arg.slice(colon + 1); + if (type === "i32") return Number(text) | 0; + if (type === "i64") return BigInt.asIntN(64, BigInt(text)); + if (type === "f32") { + const view = new DataView(new ArrayBuffer(4)); + view.setUint32(0, Number(text)); + return view.getFloat32(0); + } + if (type === "f64") { + const view = new DataView(new ArrayBuffer(8)); + view.setBigUint64(0, BigInt(text)); + return view.getFloat64(0); + } + if (text === "null") return null; + const n = Number(text); + let ref = refs.get(n); + if (ref === undefined) { + ref = { ref: n }; + refs.set(n, ref); + } + return ref; +} + +function failure(e) { + return e instanceof Error ? `${e.name}: ${e.message}` : `threw ${show(e)}`; +} + +export function run(modules, script) { + const commands = JSON.parse(script); + const lines = []; + const log = (line) => { + lines.push(line); + }; + const imports = { spectest: spectestImports(log) }; + const named = new Map(); + let current = null; + const instantiate = (index) => + new WebAssembly.Instance(new WebAssembly.Module(Buffer.from(modules[index], "base64")), imports); + let file = ""; + for (const c of commands) { + const op = c[0]; + if (op === "file") { + file = c[1]; + log(`# ${file}`); + continue; + } + if (op === "register") { + const instance = c.length > 2 ? named.get(c[2]) : current; + if (instance) imports[c[1]] = instance.exports; + continue; + } + const at = `${file}:${c[1]}`; + if (op === "module") { + try { + current = instantiate(c[2]); + if (c.length > 3) named.set(c[3], current); + log(`${at} module`); + } catch (e) { + current = null; + log(`${at} module ${failure(e)}`); + } + continue; + } + if (op === "instantiate") { + try { + instantiate(c[2]); + log(`${at} instantiated`); + } catch (e) { + log(`${at} ${e instanceof Error ? e.name : "threw"}`); + } + continue; + } + const instance = c[2] !== 0 ? named.get(c[2]) : current; + if (!instance) { + log(`${at} no instance`); + continue; + } + try { + const target = instance.exports[c[3]]; + if (op === "get") { + log(`${at} ${show(target.value)}`); + continue; + } + const args = c[4].map(decode); + log(`${at} ${show(target(...args))}`); + } catch (e) { + log(`${at} ${failure(e)}`); + } + } + console.log(lines.join("\n")); +} From 809f2cbea1acd7ac3fa9ae9eb00ae668435f6d1f Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Mon, 19 Oct 2026 07:55:06 -0500 Subject: [PATCH 013/122] Merge main --- docs/content/docs/limitations.mdx | 2 + .../builtin-apis/asynclocalstorage.mdx | 1 + .../builtin-apis/iterator-helpers.mdx | 2 +- .../limitations/builtin-apis/test-runner.mdx | 2 + .../generated/node-v24-backlog.json | 21 +- .../generated/node-v24-internal.json | 25 +- internal/compatibility/static-support.json | 4 +- .../ambient/scriptc-node-fallback.d.ts | 53 ++- .../backend/llvm/checked-value-lifetimes.ts | 7 +- .../src/backend/llvm/dyn-handle-tags.test.ts | 39 ++ packages/compiler/src/backend/llvm/dyn.ts | 4 +- packages/compiler/src/backend/llvm/emitter.ts | 17 + .../compiler/src/backend/llvm/expr-records.ts | 12 +- .../compiler/src/backend/llvm/lib-dispatch.ts | 8 +- .../compiler/src/backend/llvm/lib-process.ts | 20 +- .../compiler/src/backend/llvm/lib-shared.ts | 2 + .../src/backend/llvm/reference-effects.ts | 1 + .../src/backend/llvm/reference-writes.ts | 1 + packages/compiler/src/backend/llvm/shapes.ts | 70 +-- packages/compiler/src/backend/may-throw.ts | 4 +- .../src/frontend/lowering/containers/bytes.ts | 16 + .../lowering/containers/iterator-helpers.ts | 434 +++++++++++++++--- .../lowering/containers/match-row-uses.ts | 299 ++++++++++++ .../lowering/containers/string-and-regexp.ts | 3 +- .../frontend/lowering/iterator-adapters.ts | 348 +++++++++++++- .../src/frontend/lowering/lower-calls.ts | 92 +++- .../src/frontend/lowering/lower-classes.ts | 12 +- .../src/frontend/lowering/lower-exprs.ts | 17 +- .../src/frontend/lowering/lower-inspect.ts | 12 + .../src/frontend/lowering/lower-stmts.ts | 22 +- .../src/frontend/lowering/lower-test.ts | 224 +++++++-- .../compiler/src/frontend/lowering/lowerer.ts | 91 ++-- packages/compiler/src/frontend/type-mapper.ts | 5 +- .../compiler/src/ir/builtin-signatures.ts | 3 + .../src/ir/function-references.test.ts | 1 + packages/compiler/src/ir/int32-slots.ts | 1 + .../compiler/src/ir/intrinsic-signatures.ts | 6 + packages/compiler/src/ir/ir.ts | 19 +- packages/compiler/src/ir/runtime-features.ts | 363 ++++++++------- .../compiler/test/match-row-emission.test.ts | 69 +++ .../corpus/5815-als-callback-context.ts.json | 6 + .../tests/corpus/6010-match-metadata.ts.json | 6 + .../corpus/6011-match-metadata-js.js.json | 6 + .../corpus/6071-iterator-sources.ts.json | 6 + .../corpus/6072-iterator-sources-js.js.json | 6 + .../6073-stored-collection-helpers.ts.json | 6 + .../6074-stored-collection-helpers-js.js.json | 6 + .../tests/corpus/6075-print-iterators.ts.json | 6 + .../corpus/6076-print-iterators-js.js.json | 6 + .../6200-http-agent-free-socket-data.ts.json | 6 + .../string-iterator-helpers.ts.json | 6 - packages/runtime/src/scr_alloc.c | 42 +- packages/runtime/src/scr_alloc.test.c | 52 ++- packages/runtime/src/scr_array.c | 6 + packages/runtime/src/scr_array.h | 12 +- packages/runtime/src/scr_async.c | 113 ++++- packages/runtime/src/scr_async.h | 12 + packages/runtime/src/scr_async_dyn.c | 1 + packages/runtime/src/scr_dyn.h | 149 +++--- packages/runtime/src/scr_generator.h | 14 + packages/runtime/src/scr_http.c | 7 + packages/runtime/src/scr_inspect.c | 98 ++++ packages/runtime/src/scr_json.c | 56 +++ packages/runtime/src/scr_message.test.c | 2 +- packages/runtime/src/scr_regex.c | 184 +++++++- packages/runtime/src/scr_regex.h | 14 +- packages/runtime/src/scr_test.c | 20 + packages/runtime/src/scr_test.h | 6 + scripts/zero-cost-gate.mjs | 5 +- tests/corpus/5815-als-callback-context.ts | 76 +++ tests/corpus/6010-match-metadata.ts | 40 ++ tests/corpus/6011-match-metadata-js.js | 31 ++ tests/corpus/6071-iterator-sources.ts | 70 +++ tests/corpus/6072-iterator-sources-js.js | 70 +++ .../corpus/6073-stored-collection-helpers.ts | 60 +++ .../6074-stored-collection-helpers-js.js | 57 +++ tests/corpus/6075-print-iterators.ts | 19 + tests/corpus/6076-print-iterators-js.js | 17 + .../6110-cjs-speculative-wrapper-fences.cjs | 3 +- .../6200-http-agent-free-socket-data.ts | 57 +++ .../collection-iterator-helpers.ts | 18 +- tests/diagnostics/es2025-fences.ts | 6 +- tests/diagnostics/string-iterator-helpers.ts | 3 - .../node-test/cases/test-name-forms.ts | 55 +++ .../collection-iterator-helpers.ts.txt | 22 +- .../__snapshots__/es2025-fences.ts.txt | 8 +- .../string-iterator-helpers.ts.txt | 8 - tests/harness/corpus-costs.json | 34 ++ 88 files changed, 3278 insertions(+), 567 deletions(-) create mode 100644 docs/content/limitations/builtin-apis/asynclocalstorage.mdx create mode 100644 packages/compiler/src/backend/llvm/dyn-handle-tags.test.ts create mode 100644 packages/compiler/src/frontend/lowering/containers/match-row-uses.ts create mode 100644 packages/compiler/test/match-row-emission.test.ts create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5815-als-callback-context.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6010-match-metadata.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6011-match-metadata-js.js.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6071-iterator-sources.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6072-iterator-sources-js.js.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6073-stored-collection-helpers.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6074-stored-collection-helpers-js.js.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6075-print-iterators.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6076-print-iterators-js.js.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6200-http-agent-free-socket-data.ts.json delete mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/string-iterator-helpers.ts.json create mode 100644 tests/corpus/5815-als-callback-context.ts create mode 100644 tests/corpus/6010-match-metadata.ts create mode 100644 tests/corpus/6011-match-metadata-js.js create mode 100644 tests/corpus/6071-iterator-sources.ts create mode 100644 tests/corpus/6072-iterator-sources-js.js create mode 100644 tests/corpus/6073-stored-collection-helpers.ts create mode 100644 tests/corpus/6074-stored-collection-helpers-js.js create mode 100644 tests/corpus/6075-print-iterators.ts create mode 100644 tests/corpus/6076-print-iterators-js.js create mode 100644 tests/corpus/6200-http-agent-free-socket-data.ts delete mode 100644 tests/diagnostics/string-iterator-helpers.ts create mode 100644 tests/fixtures/node-test/cases/test-name-forms.ts delete mode 100644 tests/harness/__snapshots__/string-iterator-helpers.ts.txt diff --git a/docs/content/docs/limitations.mdx b/docs/content/docs/limitations.mdx index ae166836f..d261b6385 100644 --- a/docs/content/docs/limitations.mdx +++ b/docs/content/docs/limitations.mdx @@ -64,6 +64,8 @@ title: "Limitations" ../limitations/builtin-apis/eventemitter.mdx +../limitations/builtin-apis/asynclocalstorage.mdx + ../limitations/builtin-apis/streams-and-readline.mdx ../limitations/builtin-apis/commonjs-metadata.mdx diff --git a/docs/content/limitations/builtin-apis/asynclocalstorage.mdx b/docs/content/limitations/builtin-apis/asynclocalstorage.mdx new file mode 100644 index 000000000..020382915 --- /dev/null +++ b/docs/content/limitations/builtin-apis/asynclocalstorage.mdx @@ -0,0 +1 @@ +- **AsyncLocalStorage:** timer, interval, `setImmediate`, `process.nextTick` and `queueMicrotask` callbacks run with the stores that were active when they were scheduled, as in Node. Completion callbacks of callback-style I/O APIs, such as `fs.readFile` and `crypto.randomBytes`, run with no store, where Node runs them with the stores that were active when the operation started. diff --git a/docs/content/limitations/builtin-apis/iterator-helpers.mdx b/docs/content/limitations/builtin-apis/iterator-helpers.mdx index 01f779cb6..b4c4d8200 100644 --- a/docs/content/limitations/builtin-apis/iterator-helpers.mdx +++ b/docs/content/limitations/builtin-apis/iterator-helpers.mdx @@ -1 +1 @@ -- **Iterator helpers:** `map`, `filter`, `take`, `drop`, `flatMap`, `toArray`, `forEach`, `reduce`, `some`, `every` and `find` compile on generators, on array, Map and Set iterators, and on the objects the helpers return. Helper objects support `.next()`, `for...of`, spreads, `Array.from` and `yield*`, pull their source lazily, and close it (run its `return()`) at the same points as Node: when `take` reaches its count, when a short-circuiting method finds its answer, when a callback throws, and when the loop consuming the helper exits early. `flatMap` callbacks return arrays or generators. Iterator helpers on string iterators, `Iterator.from`, the `Iterator` global, calling `return()` on a helper object in TypeScript, printing helper objects, and helper calls on untyped iterators in JavaScript are unsupported. +- **Iterator helpers:** `map`, `filter`, `take`, `drop`, `flatMap`, `toArray`, `forEach`, `reduce`, `some`, `every` and `find` compile on generators, on array, typed array, string, Map and Set iterators, and on the objects the helpers return. Helper objects support `.next()`, `.return()`, `for...of`, spreads, `Array.from` and `yield*`, pull their source lazily, and close it (run its `return()`) at the same points as Node: when `take` reaches its count, when a short-circuiting method finds its answer, when a callback throws, and when the loop consuming the helper exits early. In TypeScript, `h.return!()` and `h.return?.()` compile when `h` is a helper call or a `const` initialized with one. `flatMap` callbacks return arrays, generators, Sets or Maps; a string result throws Node's TypeError. `Iterator.from` accepts arrays, strings, typed arrays, Sets, Maps and iterator objects such as generators. In JavaScript, untyped array, string, Map and Set iterators answer the same methods. `console.log`, `util.inspect` and `Object.prototype.toString` print iterator objects as Node does: `Object [Iterator Helper] {}`, `Object [Generator] {}`, `Object [Array Iterator] {}`, and Map and Set iterators with the entries they have left. `Iterator.from` over an object with its own `next` method or over an untyped value, the `Iterator` global as a value, and `String()` and template literals of typed iterator objects are unsupported. diff --git a/docs/content/limitations/builtin-apis/test-runner.mdx b/docs/content/limitations/builtin-apis/test-runner.mdx index 77d3d9a84..70143a0c3 100644 --- a/docs/content/limitations/builtin-apis/test-runner.mdx +++ b/docs/content/limitations/builtin-apis/test-runner.mdx @@ -4,3 +4,5 @@ - **Suite and hook contexts:** a `describe` body may take its `SuiteContext`, which has `name`, `fullName`, `filePath`, `attempt`, `passed` and `diagnostic`. Hooks may take their context, as in Node: the suite's for `before` and `after`, the test's for `beforeEach`, `afterEach`, `t.before` and `t.after`. The `SuiteContext` `signal` and `log` members, and hook `done` callbacks, throw `SC2020`. - **Module-level hooks:** `before` hooks outside any suite run at their `before()` call, and the first top-level test waits for the async ones; `after` hooks outside any suite run after the last top-level test. Both get the root's context, named ``. As in Node, a throwing root `before` fails each top-level test with its error, without running its body, and cancels the tests and suites inside each top-level suite. A throwing root `after` reports the entry file as a failed result, and the exit code is unchanged. - **Timeouts:** when a test's `{ timeout }` elapses, its running subtests are cancelled, as in Node. Subtests do not take their parent's `timeout` as their own, where Node's do, and the subtests of a timed-out test with `{ concurrency }` above 1 run to completion before the test reports. A body that keeps running after its timeout and calls `t.test` again runs that subtest, where Node reports it as not started. +- **Thrown values:** a test that throws an object or array that is not an `Error`, such as `throw { code: 42 }`, reports `[object Object]` in the failing section, where Node prints the value as `util.inspect` renders it. +- **Test names:** a test without a name, or with an empty one, takes its function's name or ``, as in Node, when the function's name is known at compile time: a function expression, a function declaration, or a `const` bound to an arrow or anonymous function. A name computed at run time, with a function the program received as a parameter, is used as given, so an empty one stays empty where Node uses the function's name. `test(fn)` and `test("", fn)` with such a function throw `SC2020`. diff --git a/internal/compatibility/generated/node-v24-backlog.json b/internal/compatibility/generated/node-v24-backlog.json index 7d0fcf75c..a0d7bec4b 100644 --- a/internal/compatibility/generated/node-v24-backlog.json +++ b/internal/compatibility/generated/node-v24-backlog.json @@ -880,7 +880,8 @@ "confidence": "high", "source": "compiler-dedicated:AsyncLocalStorage", "tests": [ - "tests/corpus/2213-async-local-storage.cjs" + "tests/corpus/2213-async-local-storage.cjs", + "tests/corpus/5815-als-callback-context.ts" ] } } @@ -932,7 +933,8 @@ "confidence": "high", "source": "compiler-dedicated:asyncLocalStorage.disable", "tests": [ - "tests/corpus/2213-async-local-storage.cjs" + "tests/corpus/2213-async-local-storage.cjs", + "tests/corpus/5815-als-callback-context.ts" ] } } @@ -959,7 +961,8 @@ "confidence": "high", "source": "compiler-dedicated:asyncLocalStorage.getStore", "tests": [ - "tests/corpus/2213-async-local-storage.cjs" + "tests/corpus/2213-async-local-storage.cjs", + "tests/corpus/5815-als-callback-context.ts" ] } } @@ -986,7 +989,8 @@ "confidence": "high", "source": "compiler-dedicated:asyncLocalStorage.enterWith", "tests": [ - "tests/corpus/2213-async-local-storage.cjs" + "tests/corpus/2213-async-local-storage.cjs", + "tests/corpus/5815-als-callback-context.ts" ] } } @@ -1013,7 +1017,8 @@ "confidence": "high", "source": "compiler-dedicated:asyncLocalStorage.run", "tests": [ - "tests/corpus/2213-async-local-storage.cjs" + "tests/corpus/2213-async-local-storage.cjs", + "tests/corpus/5815-als-callback-context.ts" ] } } @@ -1040,7 +1045,8 @@ "confidence": "high", "source": "compiler-dedicated:asyncLocalStorage.exit", "tests": [ - "tests/corpus/2213-async-local-storage.cjs" + "tests/corpus/2213-async-local-storage.cjs", + "tests/corpus/5815-als-callback-context.ts" ] } } @@ -1771,7 +1777,8 @@ "confidence": "high", "source": "compiler-dedicated:AsyncLocalStorage", "tests": [ - "tests/corpus/2213-async-local-storage.cjs" + "tests/corpus/2213-async-local-storage.cjs", + "tests/corpus/5815-als-callback-context.ts" ] } } diff --git a/internal/compatibility/generated/node-v24-internal.json b/internal/compatibility/generated/node-v24-internal.json index c87f98fec..cfbde8940 100644 --- a/internal/compatibility/generated/node-v24-internal.json +++ b/internal/compatibility/generated/node-v24-internal.json @@ -1953,7 +1953,8 @@ "status": "partial", "evidence": "derived:descendants", "tests": [ - "tests/corpus/2213-async-local-storage.cjs" + "tests/corpus/2213-async-local-storage.cjs", + "tests/corpus/5815-als-callback-context.ts" ] }, "anchorSource": "chapter" @@ -2002,7 +2003,8 @@ "status": "partial", "evidence": "compiler-dedicated:AsyncLocalStorage", "tests": [ - "tests/corpus/2213-async-local-storage.cjs" + "tests/corpus/2213-async-local-storage.cjs", + "tests/corpus/5815-als-callback-context.ts" ] }, "anchorSource": "exact" @@ -2074,7 +2076,8 @@ "status": "partial", "evidence": "compiler-dedicated:asyncLocalStorage.disable", "tests": [ - "tests/corpus/2213-async-local-storage.cjs" + "tests/corpus/2213-async-local-storage.cjs", + "tests/corpus/5815-als-callback-context.ts" ] }, "anchorSource": "exact" @@ -2100,7 +2103,8 @@ "status": "partial", "evidence": "compiler-dedicated:asyncLocalStorage.getStore", "tests": [ - "tests/corpus/2213-async-local-storage.cjs" + "tests/corpus/2213-async-local-storage.cjs", + "tests/corpus/5815-als-callback-context.ts" ] }, "anchorSource": "exact" @@ -2126,7 +2130,8 @@ "status": "partial", "evidence": "compiler-dedicated:asyncLocalStorage.enterWith", "tests": [ - "tests/corpus/2213-async-local-storage.cjs" + "tests/corpus/2213-async-local-storage.cjs", + "tests/corpus/5815-als-callback-context.ts" ] }, "anchorSource": "exact" @@ -2152,7 +2157,8 @@ "status": "partial", "evidence": "compiler-dedicated:asyncLocalStorage.run", "tests": [ - "tests/corpus/2213-async-local-storage.cjs" + "tests/corpus/2213-async-local-storage.cjs", + "tests/corpus/5815-als-callback-context.ts" ] }, "anchorSource": "exact" @@ -2178,7 +2184,8 @@ "status": "partial", "evidence": "compiler-dedicated:asyncLocalStorage.exit", "tests": [ - "tests/corpus/2213-async-local-storage.cjs" + "tests/corpus/2213-async-local-storage.cjs", + "tests/corpus/5815-als-callback-context.ts" ] }, "anchorSource": "exact" @@ -2481,6 +2488,7 @@ "evidence": "derived:descendants", "tests": [ "tests/corpus/2213-async-local-storage.cjs", + "tests/corpus/5815-als-callback-context.ts", "tests/harness/node-test.test.ts" ] }, @@ -3042,7 +3050,8 @@ "status": "partial", "evidence": "compiler-dedicated:AsyncLocalStorage", "tests": [ - "tests/corpus/2213-async-local-storage.cjs" + "tests/corpus/2213-async-local-storage.cjs", + "tests/corpus/5815-als-callback-context.ts" ] }, "anchorSource": "exact" diff --git a/internal/compatibility/static-support.json b/internal/compatibility/static-support.json index 584492bf1..2e5eb1c4c 100644 --- a/internal/compatibility/static-support.json +++ b/internal/compatibility/static-support.json @@ -36,7 +36,7 @@ "syntax.for-await-readable-streams": ["tests/corpus/2861-readable-iterator-close.ts", "tests/corpus/2862-web-readable-async-iteration.ts"], "syntax.for-await-class-async-iterators": ["tests/corpus/2863-class-async-iterator.ts"], "node-builtin.assert": ["tests/corpus/1600-assert-passing.ts", "tests/corpus/1609-assert-async.ts"], - "node-builtin.async_hooks": ["tests/corpus/2213-async-local-storage.cjs"], + "node-builtin.async_hooks": ["tests/corpus/2213-async-local-storage.cjs", "tests/corpus/5815-als-callback-context.ts"], "node-builtin.buffer": ["tests/corpus/1402-buffer-encodings.ts", "tests/corpus/1660-buffer-read-write-num.ts", "tests/corpus/2902-bigint-bytes.ts", "tests/corpus/2904-bigint-errors.ts"], "node-builtin.child_process": ["tests/corpus/1361-spawn-events.ts", "tests/corpus/1565-spawn-pipe-streams.ts", "tests/corpus/2911-execfile-callback.ts", "tests/corpus/2939-child-shell-text.ts", "tests/corpus/2946-child-fork-ipc/main.ts", "tests/corpus/3127-spawn-runtime-options.ts", "tests/corpus/3130-child-generic-streams.ts", "tests/corpus/3131-child-rest-listeners.js", "tests/corpus/3132-child-generic-stream-completion.ts"], "node-builtin.cluster": ["tests/corpus/957-builtins-namespace.ts"], @@ -395,7 +395,7 @@ { "symbols": ["crypto.hkdf", "crypto.hkdfSync", "crypto.scrypt", "crypto.scryptSync"], "status": "partial", "evidence": ["tests/corpus/node24-crypto-kdf.ts", "tests/corpus/node24-crypto-kdf-errors.ts", "tests/corpus/node24-crypto-kdf-options.ts", "tests/corpus/5438-crypto-kdf-digests.ts"] }, { "symbols": ["EventEmitter", "emitter.on", "emitter.addListener", "emitter.once", "emitter.prependListener", "emitter.prependOnceListener", "emitter.off", "emitter.removeListener", "emitter.removeAllListeners", "emitter.emit", "emitter.listenerCount", "emitter.listeners", "emitter.rawListeners", "emitter.eventNames", "emitter.setMaxListeners", "emitter.getMaxListeners", "events.once"], "status": "partial", "evidence": ["tests/corpus/1677-emitter-listeners.ts", "tests/corpus/2620-ee-override-once-order.ts"] }, { "symbols": ["events.getEventListeners", "events.getMaxListeners", "events.listenerCount", "events.setMaxListeners", "events.defaultMaxListeners"], "status": "partial", "evidence": ["tests/corpus/5885-events-max-listener-helpers.ts", "tests/corpus/5846-events-esm-default-max.ts", "tests/corpus/5847-events-cjs-default-max.cjs"] }, - { "symbols": ["AsyncLocalStorage", "asyncLocalStorage.run", "asyncLocalStorage.exit", "asyncLocalStorage.getStore", "asyncLocalStorage.enterWith", "asyncLocalStorage.disable"], "status": "partial", "evidence": ["tests/corpus/2213-async-local-storage.cjs"] }, + { "symbols": ["AsyncLocalStorage", "asyncLocalStorage.run", "asyncLocalStorage.exit", "asyncLocalStorage.getStore", "asyncLocalStorage.enterWith", "asyncLocalStorage.disable"], "status": "partial", "evidence": ["tests/corpus/2213-async-local-storage.cjs", "tests/corpus/5815-als-callback-context.ts"] }, { "symbols": ["StringDecoder", "stringDecoder.write", "stringDecoder.end"], "status": "partial", "evidence": ["tests/corpus/1662-string-decoder-encodings.ts", "tests/corpus/node24-string-decoder.ts", "tests/corpus/5882-string-decoder-runtime-encoding.ts"] }, { "symbols": ["setImmediate", "setInterval", "setTimeout", "clearImmediate", "clearInterval", "clearTimeout", "timersPromises.setTimeout", "timersPromises.setImmediate", "timersPromises.setInterval", "immediate.hasRef", "immediate.ref", "immediate.unref", "timeout.hasRef", "timeout.ref", "timeout.unref", "timeout.refresh"], "status": "partial", "evidence": ["tests/corpus/1804-timers-module.ts", "tests/corpus/1805-timer-callback-args.ts", "tests/corpus/2093-timers-promises.ts", "tests/corpus/2852-timers-promises-interval.ts", "tests/corpus/5830-timers-promises-signal.ts"] }, { "symbols": ["timersPromises.scheduler.wait", "timersPromises.scheduler.yield"], "status": "partial", "evidence": ["tests/corpus/5839-timers-promises-scheduler.ts", "tests/corpus/5889-timers-scheduler-signal.ts"] }, diff --git a/packages/compiler/ambient/scriptc-node-fallback.d.ts b/packages/compiler/ambient/scriptc-node-fallback.d.ts index 9655b7580..3e3f66a2a 100644 --- a/packages/compiler/ambient/scriptc-node-fallback.d.ts +++ b/packages/compiler/ambient/scriptc-node-fallback.d.ts @@ -3405,8 +3405,10 @@ declare module "node:test" { readonly tags: readonly string[]; readonly assert: TestContextAssert; readonly mock: MockTracker; - test(name: string, fn?: TestFn): Promise; - test(name: string, options: TestOptions, fn?: TestFn): Promise; + test(name?: string, fn?: TestFn): Promise; + test(name?: string, options?: TestOptions, fn?: TestFn): Promise; + test(options?: TestOptions, fn?: TestFn): Promise; + test(fn?: TestFn): Promise; skip(message?: string): void; todo(message?: string): void; diagnostic(message: string): void; @@ -3436,23 +3438,44 @@ declare module "node:test" { match(value: string, regExp: RegExp, message?: string): void; doesNotMatch(value: string, regExp: RegExp, message?: string): void; } - function test(name: string, fn?: TestFn): Promise; - function test(name: string, options: TestOptions, fn?: TestFn): Promise; + /* The name, options and function are each optional, as in Node: a test + * without a name takes the function's name, or "". */ + function test(name?: string, fn?: TestFn): Promise; + function test(name?: string, options?: TestOptions, fn?: TestFn): Promise; + function test(options?: TestOptions, fn?: TestFn): Promise; + function test(fn?: TestFn): Promise; namespace test { - function skip(name: string, fn?: TestFn): Promise; - function skip(name: string, options: TestOptions, fn?: TestFn): Promise; - function todo(name: string, fn?: TestFn): Promise; - function todo(name: string, options: TestOptions, fn?: TestFn): Promise; - function only(name: string, fn?: TestFn): Promise; - function only(name: string, options: TestOptions, fn?: TestFn): Promise; + function skip(name?: string, fn?: TestFn): Promise; + function skip(name?: string, options?: TestOptions, fn?: TestFn): Promise; + function skip(options?: TestOptions, fn?: TestFn): Promise; + function skip(fn?: TestFn): Promise; + function todo(name?: string, fn?: TestFn): Promise; + function todo(name?: string, options?: TestOptions, fn?: TestFn): Promise; + function todo(options?: TestOptions, fn?: TestFn): Promise; + function todo(fn?: TestFn): Promise; + function only(name?: string, fn?: TestFn): Promise; + function only(name?: string, options?: TestOptions, fn?: TestFn): Promise; + function only(options?: TestOptions, fn?: TestFn): Promise; + function only(fn?: TestFn): Promise; } const it: typeof test; - function describe(name: string, fn?: SuiteFn): void; - function describe(name: string, options: TestOptions, fn?: SuiteFn): void; + function describe(name?: string, fn?: SuiteFn): void; + function describe(name?: string, options?: TestOptions, fn?: SuiteFn): void; + function describe(options?: TestOptions, fn?: SuiteFn): void; + function describe(fn?: SuiteFn): void; namespace describe { - function skip(name: string, fn?: SuiteFn): void; - function todo(name: string, fn?: SuiteFn): void; - function only(name: string, fn?: SuiteFn): void; + function skip(name?: string, fn?: SuiteFn): void; + function skip(name?: string, options?: TestOptions, fn?: SuiteFn): void; + function skip(options?: TestOptions, fn?: SuiteFn): void; + function skip(fn?: SuiteFn): void; + function todo(name?: string, fn?: SuiteFn): void; + function todo(name?: string, options?: TestOptions, fn?: SuiteFn): void; + function todo(options?: TestOptions, fn?: SuiteFn): void; + function todo(fn?: SuiteFn): void; + function only(name?: string, fn?: SuiteFn): void; + function only(name?: string, options?: TestOptions, fn?: SuiteFn): void; + function only(options?: TestOptions, fn?: SuiteFn): void; + function only(fn?: SuiteFn): void; } const suite: typeof describe; function before(fn: HookFn): void; diff --git a/packages/compiler/src/backend/llvm/checked-value-lifetimes.ts b/packages/compiler/src/backend/llvm/checked-value-lifetimes.ts index c359f6175..5f8119079 100644 --- a/packages/compiler/src/backend/llvm/checked-value-lifetimes.ts +++ b/packages/compiler/src/backend/llvm/checked-value-lifetimes.ts @@ -42,5 +42,10 @@ export function borrowsJsonInputs(fn: string): boolean { * loop. Runtime steps borrow that state but may invoke indexed getters, so * this permits stable-local borrowing without preserving reference edges. */ export function borrowsIteratorInputs(fn: string): boolean { - return fn === "dyn.iteratorCanStep" || fn === "dyn.iteratorStep" || fn === "dyn.iteratorStepDone"; + return ( + fn === "dyn.iteratorCanStep" || + fn === "dyn.iteratorStep" || + fn === "dyn.iteratorStepDone" || + fn === "dyn.collectionIteratorIndex" + ); } diff --git a/packages/compiler/src/backend/llvm/dyn-handle-tags.test.ts b/packages/compiler/src/backend/llvm/dyn-handle-tags.test.ts new file mode 100644 index 000000000..8aa69b386 --- /dev/null +++ b/packages/compiler/src/backend/llvm/dyn-handle-tags.test.ts @@ -0,0 +1,39 @@ +import { readFileSync } from "node:fs"; +import { expect, test } from "vitest"; +import { DYN_HANDLE_KINDS } from "../../ir/ir.js"; +import { DYN_HANDLE_TAG_NUM } from "./dyn.js"; + +// ScrDynHandleTag in scr_dyn.h lists its tags by name, each with an explicit +// value, so changes that add tags edit different lines. These checks stand +// in for what the enum's order used to guarantee. +const header = readFileSync(new URL("../../../../runtime/src/scr_dyn.h", import.meta.url), "utf8"); +const body = /typedef enum \{\n([\s\S]*?)\} ScrDynHandleTag;/.exec(header)![1]!; +const tags = [...body.matchAll(/^\s*(SCR_DYNH_[A-Z0-9_]+) = (\d+),/gm)].map(([, name, value]) => ({ + name: name!, + value: Number(value), +})); +const capacity = Number(/#define SCR_DYNH_CAPACITY (\d+)/.exec(header)![1]); + +test("every ScrDynHandleTag has an explicit value, in name order", () => { + const lines = body.split("\n").filter((line) => line.trim().startsWith("SCR_DYNH_")); + expect(tags.length).toBe(lines.length); + expect(tags.map((tag) => tag.name)).toEqual(tags.map((tag) => tag.name).toSorted()); +}); + +test("ScrDynHandleTag values are unique and below SCR_DYNH_CAPACITY", () => { + const owners = new Map(); + for (const { name, value } of tags) { + expect(owners.get(value), `${name} = ${value}`).toBeUndefined(); + owners.set(value, name); + expect(value, name).toBeLessThan(capacity); + } +}); + +test("the backend's handle tag numbers match scr_dyn.h", () => { + const values = new Map(tags.map((tag) => [tag.name, tag.value])); + for (const [kind, num] of Object.entries(DYN_HANDLE_TAG_NUM)) { + const tag = DYN_HANDLE_KINDS.get(kind)?.tag; + expect(tag, kind).toBeDefined(); + expect(values.get(tag!), `${kind} (${tag})`).toBe(num); + } +}); diff --git a/packages/compiler/src/backend/llvm/dyn.ts b/packages/compiler/src/backend/llvm/dyn.ts index 9b11d6240..154b80c02 100644 --- a/packages/compiler/src/backend/llvm/dyn.ts +++ b/packages/compiler/src/backend/llvm/dyn.ts @@ -72,8 +72,8 @@ export function recordDynKeepsIdentity(shape: IrRecordShape): boolean { } /** ScrDynKind values (scr_runtime.h). */ -/** ScrDynHandleTag numeric values (scr_runtime.h's enum order). */ -const DYN_HANDLE_TAG_NUM: Record = { +/** ScrDynHandleTag values (scr_dyn.h's explicit values; dyn-handle-tags.test.ts checks them). */ +export const DYN_HANDLE_TAG_NUM: Record = { httpReq: 0, httpRes: 1, netSocket: 2, diff --git a/packages/compiler/src/backend/llvm/emitter.ts b/packages/compiler/src/backend/llvm/emitter.ts index 588e8c26a..48012704b 100644 --- a/packages/compiler/src/backend/llvm/emitter.ts +++ b/packages/compiler/src/backend/llvm/emitter.ts @@ -968,6 +968,14 @@ export class LlEmitter { /** The program calls mock.timers members (moduleUsesMockTimers): timer * calls emit scr_test.c's mock-aware entry points. */ readonly mockTimers: boolean; + private schedulesInContextValue: boolean | undefined = undefined; + /** Whether timer and tick scheduling goes through the *_als entry + * points (RuntimeFeatures asyncContext). */ + get schedulesInContext(): boolean { + if (this.schedulesInContextValue === undefined) + this.schedulesInContextValue = moduleRuntimeFeatures(this.mod).asyncContext; + return this.schedulesInContextValue; + } /** Emitted ref-kind resolve thunks for new Promise, interned per inner * typeKey → thunk symbol. */ readonly resolveThunks = new Map(); @@ -4636,6 +4644,9 @@ export class LlEmitter { // An iterator helper's return() before its first next() closes the // source generator in its first argument slot. const closesSource = fn.generator.closesSource === true; + if (fn.generator.tag !== undefined) this.declare(`declare void @scr_gen_set_tag(ptr, ptr)`); + if (fn.generator.inheritsTag === true) + this.declare(`declare void @scr_gen_copy_tag(ptr, ptr)`); if (closesSource) { const field = lifted ? 1 : 0; this.declare(`declare void @scr_gen_close(ptr)`); @@ -4668,6 +4679,12 @@ export class LlEmitter { settleAsync === null ? ` %gg = call ptr @${genNew}(ptr @${mangleTrampoline(fn.name)}, ptr %ap, ptr @${mangleGenDrop(fn.name)})` : ` %gg = call ptr @scr_async_gen_new(ptr @${mangleTrampoline(fn.name)}, ptr %ap, ptr @${mangleGenDrop(fn.name)}, ptr @${settleAsync})`, + ...(fn.generator.tag !== undefined + ? [` call void @scr_gen_set_tag(ptr %gg, ptr ${this.cstr(fn.generator.tag)})`] + : []), + ...(fn.generator.inheritsTag === true + ? [` call void @scr_gen_copy_tag(ptr %gg, ptr %a${lifted ? 1 : 0})`] + : []), ...(closesSource ? [ ` call void @scr_gen_set_unstarted_return(ptr %gg, ptr @${mangleGenSourceClose(fn.name)})`, diff --git a/packages/compiler/src/backend/llvm/expr-records.ts b/packages/compiler/src/backend/llvm/expr-records.ts index 3318ae774..0cbfe15dd 100644 --- a/packages/compiler/src/backend/llvm/expr-records.ts +++ b/packages/compiler/src/backend/llvm/expr-records.ts @@ -147,7 +147,9 @@ function directRegexOperandMethod(method: string): boolean { case "match": case "search": case "matchAll": + case "matchAllCaptures": case "matchAllInto": + case "matchAllCapturesInto": case "replace": case "replaceAll": case "split": @@ -192,7 +194,7 @@ export function emitRegexIntrinsic( } else { values = preservesRegexInputs(method) ? emitBorrowedInputs(host, inputs) - : method === "matchAllInto" + : method === "matchAllInto" || method === "matchAllCapturesInto" ? inputs.map((a) => emitBorrowedInput(host, a)) : inputs.map((a) => host.emitExpr(a)); } @@ -216,6 +218,9 @@ export function emitRegexIntrinsic( // Every match drained eagerly into a fresh +1 string[][]; throws // Node's TypeError on a non-global regex (catchable). return fallible("scr_regex_match_all", `ptr ${r.name}, ptr ${args[0]!.name}`); + case "matchAllCaptures": + // matchAll whose rows carry no index, input or groups properties. + return fallible("scr_regex_match_all_captures", `ptr ${r.name}, ptr ${args[0]!.name}`); case "matchAllInto": // matchAll's companion-index form: args[1] (a number[]) also // receives each match's UTF-16 start index. @@ -223,6 +228,11 @@ export function emitRegexIntrinsic( "scr_regex_match_all_into", `ptr ${r.name}, ptr ${args[0]!.name}, ptr ${args[1]!.name}`, ); + case "matchAllCapturesInto": + return fallible( + "scr_regex_match_all_captures_into", + `ptr ${r.name}, ptr ${args[0]!.name}, ptr ${args[1]!.name}`, + ); case "replaceAll": return fallible( "scr_regex_replace_all", diff --git a/packages/compiler/src/backend/llvm/lib-dispatch.ts b/packages/compiler/src/backend/llvm/lib-dispatch.ts index f21ded106..034f0b6f1 100644 --- a/packages/compiler/src/backend/llvm/lib-dispatch.ts +++ b/packages/compiler/src/backend/llvm/lib-dispatch.ts @@ -526,7 +526,13 @@ export function emitGenericLibCall(host: LlvmEmitterContext, e: LibCallExpr): Ll if (MAY_THROW_LIB_FNS.has(e.fn) && LIB_FN_SYMS[e.fn] === undefined) { throw new LlvmUnsupportedError(`libCall:${e.fn}`, e.loc); } - const sym = mockTimerSym(host, e) ?? LIB_FN_SYMS[e.fn]; + // queueMicrotask's checked-dynamic form carries the AsyncLocalStorage + // context like the static one (lib-process.ts). + const sym = + mockTimerSym(host, e) ?? + (e.fn === "timers.queueMicrotaskDyn" && host.schedulesInContext + ? "scr_queue_microtask_dyn_als" + : LIB_FN_SYMS[e.fn]); if (sym === undefined) throw new LlvmUnsupportedError(`libCall:${e.fn}`, e.loc); const args = READ_ONLY_STRING_LIB_FNS.has(e.fn) ? emitBorrowedInputs(host, e.args) diff --git a/packages/compiler/src/backend/llvm/lib-process.ts b/packages/compiler/src/backend/llvm/lib-process.ts index 546c9a209..3c35ae6dc 100644 --- a/packages/compiler/src/backend/llvm/lib-process.ts +++ b/packages/compiler/src/backend/llvm/lib-process.ts @@ -374,16 +374,18 @@ export function emitAsyncContextLibCall(host: LlvmEmitterContext, e: LibCallExpr host.usesTimers = true; const cb = host.emitExpr(e.args[0]!); host.moveTemp(cb); + // Programs that enter AsyncLocalStorage stores schedule through the + // *_als entry points, which carry the active stores into the callback. const sym = - mockTimerSym(host, e) ?? - { - "timers.setTimeout": "scr_set_timeout", - "timers.setInterval": "scr_set_interval", - "timers.setTimeoutHandle": "scr_set_timeout_handle", - "timers.setImmediate": "scr_set_immediate", - "process.nextTick": "scr_next_tick", - "timers.queueMicrotask": "scr_queue_microtask", - }[e.fn]; + (mockTimerSym(host, e) ?? + { + "timers.setTimeout": "scr_set_timeout", + "timers.setInterval": "scr_set_interval", + "timers.setTimeoutHandle": "scr_set_timeout_handle", + "timers.setImmediate": "scr_set_immediate", + "process.nextTick": "scr_next_tick", + "timers.queueMicrotask": "scr_queue_microtask", + }[e.fn]!) + (host.schedulesInContext ? "_als" : ""); const rest = e.args.slice(1).map((a) => host.emitExpr(a)); const argList = [`ptr ${cb.name}`, ...rest.map((a) => `${host.llType(a.type)} ${a.name}`)].join( ", ", diff --git a/packages/compiler/src/backend/llvm/lib-shared.ts b/packages/compiler/src/backend/llvm/lib-shared.ts index 7b174c6f5..14148af0c 100644 --- a/packages/compiler/src/backend/llvm/lib-shared.ts +++ b/packages/compiler/src/backend/llvm/lib-shared.ts @@ -890,6 +890,7 @@ export const LIB_FN_SYMS: Record = { "dyn.iteratorCanStep": "scr_dyn_iterator_can_step", "dyn.iteratorStep": "scr_dyn_iterator_step", "dyn.iteratorStepDone": "scr_dyn_iterator_step_done", + "dyn.collectionIteratorIndex": "scr_dyn_collection_iterator_index", "dyn.mapSeedEntries": "scr_dyn_map_seed_entries", "dyn.mapSeedEntry": "scr_dyn_map_seed_entry", "dyn.arrLen": "scr_dyn_arr_len", @@ -1414,6 +1415,7 @@ export const LIB_FN_SYMS: Record = { "regex.groupEquals": "scr_regex_group_eq", "regex.groupNumber": "scr_regex_group_number", "regex.groupParseInt": "scr_regex_group_parse_int", + "regex.resultProp": "scr_regex_result_prop", // queueMicrotask's checked-dynamic form (borrowed dyn; a non-function // throws synchronously), the minted setImmediate value, and the // timers/promises immediate — all mark the loop live. diff --git a/packages/compiler/src/backend/llvm/reference-effects.ts b/packages/compiler/src/backend/llvm/reference-effects.ts index 88875fce4..e7b044187 100644 --- a/packages/compiler/src/backend/llvm/reference-effects.ts +++ b/packages/compiler/src/backend/llvm/reference-effects.ts @@ -29,6 +29,7 @@ export function preservesRegexInputs(method: string): boolean { case "match": case "search": case "matchAll": + case "matchAllCaptures": case "replace": case "replaceAll": case "split": diff --git a/packages/compiler/src/backend/llvm/reference-writes.ts b/packages/compiler/src/backend/llvm/reference-writes.ts index 9397b7aee..63e4993e8 100644 --- a/packages/compiler/src/backend/llvm/reference-writes.ts +++ b/packages/compiler/src/backend/llvm/reference-writes.ts @@ -104,6 +104,7 @@ const INERT_LIB_CALLS = new Set([ "regex.groupEquals", "regex.groupNumber", "regex.groupParseInt", + "regex.resultProp", "process.cwd", "process.argv", "process.execArgv", diff --git a/packages/compiler/src/backend/llvm/shapes.ts b/packages/compiler/src/backend/llvm/shapes.ts index 951ed61c9..d49df20b2 100644 --- a/packages/compiler/src/backend/llvm/shapes.ts +++ b/packages/compiler/src/backend/llvm/shapes.ts @@ -61,64 +61,64 @@ import { LlvmUnsupportedError } from "./unsupported.js"; /** What the tables need from the emitter: the extern-declaration ledger * and the module-wide cycle/shape indexes. */ export interface ShapeHost { - declare(decl: string): void; /** The hierarchy root of a class (a program class or a runtime base), * or undefined for classes outside the class graph. */ classRootOf?(className: string): string | undefined; - /** Request the shared OOM abort helper (@sc_oom) — emitted once. */ - needOom(): void; + /** Byte offset from an object pointer back to ScrCycHdr.buf. */ + readonly cycleBufOffset: number; + declare(decl: string): void; + /** Inline the small-object allocator's fast paths (llvm/alloc.ts). */ + readonly inlineAlloc?: boolean; + /** Record and class fields that store a scalar union's tag and bits + * inline (inline-unions.ts): they own nothing. */ + readonly inlineUnions: InlineUnionFields; /** The immutable ScrMapOps descriptor (scr_map.h) of a Map or Set type: * a module constant holding the LLVM operands { key retain, key release, * key trace, value retain, value release, value trace, union key kinds }, * or `null` when every operand is null. */ mapOps(operands: readonly string[]): string; - /** The target C ABI's `size_t` / refcount integer type. */ - readonly sizeType: "i32" | "i64"; - /** Byte offset from an object pointer back to ScrCycHdr.buf. */ - readonly cycleBufOffset: number; - readonly tracedShapes: Set; - readonly tracedUnions: Set; + /** Request the shared OOM abort helper (@sc_oom) — emitted once. */ + needOom(): void; + /** The next collector statistics index for an emitted ScrCycType + * descriptor (cycTypeDescriptor), counting from FIRST_EMITTED_CYC_KIND + * in emission order. + * Absent: every descriptor gets 0, which keeps no statistics. */ + nextCycKind?(): number; /** Unions represented as a nullable arm pointer (nullable-unions.ts): * their RC and trace entry points are the arm's NULL-tolerant ones. */ readonly nullableUnions: NullableUnions; - /** Record and class fields that store a scalar union's tag and bits - * inline (inline-unions.ts): they own nothing. */ - readonly inlineUnions: InlineUnionFields; - /** The zeroed `%ScrUnion` an inline store reads for a NULL dummy. */ - unionNoneRef(): string; - readonly recordsById: Map; - readonly recordCloneShapes: ReadonlySet; - /** The program constructs lifetime regions: the ProgramLifetime classes - * allocate in regions and count references in their region chunk instead - * of being empty (llvm/regions.ts). */ - regions?: RegionHooks | null; - /** The alias tag of a runtime word that lifetime-region code reads and - * writes outside every field: the chunk count, the chunk owner, the class - * cursors and scr_region_counting (", !tbaa !N", or "" in debug builds). */ - regionAliasTag?(word: "count" | "owner" | "cursor" | "counting"): string; /** Emit the live-object audit notes (`scr_obj_alloc_note` / * `scr_obj_free_note`) in emitted new/free helpers. Only runtimes built * with SCR_RC_AUDIT (the sanitized lane) count them; release and dev * runtime packs define them empty, so plain builds omit the calls. * Absent means emit. */ readonly objectAudit?: boolean; - /** Inline the small-object allocator's fast paths (llvm/alloc.ts). */ - readonly inlineAlloc?: boolean; - /** Worker executables: the inline allocator state is thread-local. */ - readonly threadLocalAlloc?: boolean; /** Inline RC fast-path helpers requested so far (":"); the * emitter defines each once through {@link emitInlineRcHelpers}. Null * keeps every retain/release a runtime call. */ readonly rcHelpers: Set | null; - /** Which fast paths are inlined (see {@link InlineRcMode}). */ - readonly rcMode?: InlineRcMode; /** Define the fast-path helpers `noinline` (see {@link inlineRcOption}). */ readonly rcHelpersNoinline?: boolean; - /** The next collector statistics index for an emitted ScrCycType - * descriptor (cycTypeDescriptor), counting from FIRST_EMITTED_CYC_KIND - * in emission order. - * Absent: every descriptor gets 0, which keeps no statistics. */ - nextCycKind?(): number; + /** Which fast paths are inlined (see {@link InlineRcMode}). */ + readonly rcMode?: InlineRcMode; + readonly recordCloneShapes: ReadonlySet; + readonly recordsById: Map; + /** The alias tag of a runtime word that lifetime-region code reads and + * writes outside every field: the chunk count, the chunk owner, the class + * cursors and scr_region_counting (", !tbaa !N", or "" in debug builds). */ + regionAliasTag?(word: "count" | "owner" | "cursor" | "counting"): string; + /** The program constructs lifetime regions: the ProgramLifetime classes + * allocate in regions and count references in their region chunk instead + * of being empty (llvm/regions.ts). */ + regions?: RegionHooks | null; + /** The target C ABI's `size_t` / refcount integer type. */ + readonly sizeType: "i32" | "i64"; + /** Worker executables: the inline allocator state is thread-local. */ + readonly threadLocalAlloc?: boolean; + readonly tracedShapes: Set; + readonly tracedUnions: Set; + /** The zeroed `%ScrUnion` an inline store reads for a NULL dummy. */ + unionNoneRef(): string; } /** What class emission asks of lifetime regions (LifetimeRegions in diff --git a/packages/compiler/src/backend/may-throw.ts b/packages/compiler/src/backend/may-throw.ts index f07bbfa78..b72dc545d 100644 --- a/packages/compiler/src/backend/may-throw.ts +++ b/packages/compiler/src/backend/may-throw.ts @@ -297,7 +297,9 @@ export function computeMayThrow( rec.method === "replaceAll" || rec.method === "split" || rec.method === "matchAll" || - rec.method === "matchAllInto" + rec.method === "matchAllCaptures" || + rec.method === "matchAllInto" || + rec.method === "matchAllCapturesInto" ) { f.throws = true; } diff --git a/packages/compiler/src/frontend/lowering/containers/bytes.ts b/packages/compiler/src/frontend/lowering/containers/bytes.ts index c41bf5163..91cf0b263 100644 --- a/packages/compiler/src/frontend/lowering/containers/bytes.ts +++ b/packages/compiler/src/frontend/lowering/containers/bytes.ts @@ -35,6 +35,7 @@ import { } from "../optional-arguments.js"; import { lowerCheckedArrayFrom } from "./array-construction.js"; import { lowerToNumberArgument } from "../lower-exprs.js"; +import { lowerBytesIteratorValue } from "../iterator-adapters.js"; /** TypedArray.prototype.sort and toSorted. The receiver and comparator * expressions are evaluated before entering the helper (buildBytesSortFn). @@ -462,6 +463,21 @@ export function lowerBytesMethodCall( if (name === "sort" || name === "toSorted") { return lowerBytesSortCall(lowerer, call, access, receiverIr); } + // values()/keys()/entries() as iterator objects (for...of heads and + // stored numeric iterators lower before this). + if ((name === "values" || name === "keys" || name === "entries") && nArgs === 0) { + const type = lowerer.mapTypeOf(lowerer.typeOf(call)); + if (type?.kind === "generator") { + const value = lowerBytesIteratorValue( + lowerer, + lowerer.lowerExpr(access.expression), + name, + type, + loc, + ); + if (value) return value; + } + } if (name === "toReversed") { if (nArgs !== 0) { lowerer.noLowering(`.toReversed with ${nArgs} arguments on typed arrays`, call); diff --git a/packages/compiler/src/frontend/lowering/containers/iterator-helpers.ts b/packages/compiler/src/frontend/lowering/containers/iterator-helpers.ts index 7cb3753ac..79b91af64 100644 --- a/packages/compiler/src/frontend/lowering/containers/iterator-helpers.ts +++ b/packages/compiler/src/frontend/lowering/containers/iterator-helpers.ts @@ -5,6 +5,7 @@ import type { Lowerer } from "../lowerer.js"; import { BOOL, CAUGHT, + DYN, F64, type IrMapIntrinsicMethod, type IrSetIntrinsicMethod, @@ -28,12 +29,19 @@ import { concatStrings, throwTypeError } from "./dynamic-receivers.js"; import { type GenType, generatorLoopBinding, + resultRecordOf, genStep, genValue, stepDrivable, } from "../lower-generators.js"; import { exitBeforeReturns } from "./for-of.js"; -import { iteratorSlotAdapter } from "../iterator-adapters.js"; +import { + iteratorSlotAdapter, + lowerArrayIteratorValue, + lowerBytesIteratorValue, + lowerCollectionIteratorValue, + lowerStringIteratorValue, +} from "../iterator-adapters.js"; import { lowerArrayCallback } from "./callback-arguments.js"; /* ── iterator helpers (ES2025) ─────────────────────────────────────────── */ @@ -150,15 +158,45 @@ export function lowerGeneratorHelperCall( if (call.arguments.length !== 1 || argNode === undefined || ts.isSpreadElement(argNode)) { lowerer.noLowering(`.${name} with ${call.arguments.length} arguments`, call); } + // A stage on m.keys()/values()/entries() (or a Set's) walks the + // collection inside the helper's own generator. + const collection = collectionIteratorCall(lowerer, access.expression); + if (collection !== null) { + const coll = lowerer.lowerExpr(collection.receiver); + if (coll.type.kind !== "map" && coll.type.kind !== "set") + lowerer.badType(collection.receiver, lowerer.typeOf(collection.receiver)); + const source: ChainSource = { kind: "collection", type: coll.type, proj: collection.proj }; + const elem = collectionSeed(lowerer, source); + const { stage, out } = lowerStage(lowerer, { name, argNode, access }, elem, loc); + return lowerHelperObject(lowerer, call, coll, source, elem, stage, out, loc); + } const src = lowerHelperSource(lowerer, access.expression); if (name !== "take" && name !== "drop") { const { stage, out } = lowerStage(lowerer, { name, argNode, access }, src.elem, loc); - return lowerHelperObject(lowerer, call, src.gen, src.elem, stage, out, loc); + return lowerHelperObject( + lowerer, + call, + src.gen, + { kind: "generator", type: src.gen.type, elem: src.elem }, + src.elem, + stage, + out, + loc, + ); } // The budget check closes the source, so the source is bound first. const bound = bindSource(lowerer, src.gen, loc); const { stage, out } = lowerStage(lowerer, { name, argNode, access }, src.elem, loc, bound.ref); - const helper = lowerHelperObject(lowerer, call, bound.ref, src.elem, stage, out, loc); + const helper = lowerHelperObject( + lowerer, + call, + bound.ref, + { kind: "generator", type: src.gen.type, elem: src.elem }, + src.elem, + stage, + out, + loc, + ); return { kind: "seqExpr", stmts: [bound.decl], result: helper, type: helper.type, loc }; } // The stages before the terminal, back to the first receiver that is @@ -189,6 +227,24 @@ export function lowerGeneratorHelperCall( collectionSeed(lowerer, source), ); } + // Iterator.from(arr) walks the array by index. + const arrayRoot = arrayIteratorCall(lowerer, cur); + if (arrayRoot !== null) { + const items = lowerer.lowerExpr(arrayRoot.receiver); + if (items.type.kind !== "array") + lowerer.badType(arrayRoot.receiver, lowerer.typeOf(arrayRoot.receiver)); + const elem = + arrayRoot.proj === "entries" ? entryPairType(lowerer, items.type.elem) : items.type.elem; + return lowerFusedChain( + lowerer, + call, + name, + stages, + { kind: "array", type: items.type, proj: arrayRoot.proj }, + items, + elem, + ); + } const src = lowerHelperSource(lowerer, cur); return lowerFusedChain( lowerer, @@ -201,6 +257,125 @@ export function lowerGeneratorHelperCall( ); } +/** `h.return!()` or `h.return?.()` on a helper object. The lib declares + * return() as optional on iterator objects, and a helper object always + * has one, so both spellings are the call: the helper completes and closes + * its source, and the result is `{ value: undefined, done: true }`. The + * receiver must be provably a helper object — a helper stage call, or a + * const binding initialized with one — since other iterator objects (the + * array, string, Map and Set iterators) have no return(). Null otherwise, + * and for the plain `h.return()` spelling (lowerGenMethodCall's). */ +export function lowerHelperReturnCall(lowerer: Lowerer, call: ts.CallExpression): IrExpr | null { + let callee: ts.Expression = call.expression; + const nonNull = ts.isNonNullExpression(callee); + if (nonNull) callee = (callee as ts.NonNullExpression).expression; + if (!nonNull && call.questionDotToken === undefined) return null; + if (!ts.isPropertyAccessExpression(callee) || callee.questionDotToken) return null; + if (callee.name.text !== "return" || call.arguments.length !== 0) return null; + if (!isHelperObject(lowerer, callee.expression) || !lowerer.isStdlibMember(callee)) return null; + const callT = lowerer.mapTypeOf(lowerer.typeOf(call)); + const gen = lowerer.lowerExpr(callee.expression); + if (gen.type.kind !== "generator" || gen.type.async) return null; + const loc = locOf(call); + const recT = resultRecordOf(lowerer, gen.type); + const resume: IrExpr = { kind: "genResume", mode: "return", gen, arg: null, type: recT, loc }; + // `?.` types the call `IteratorResult | undefined`, which has no mapping + // as a whole; the result is never undefined, so it is the record itself. + if (callT === null || typeEquals(callT, recT)) return resume; + const result = lowerer.coerceToExpected(resume, callT); + if (!typeEquals(result.type, callT)) lowerer.badType(call, lowerer.typeOf(call)); + return result; +} + +/** Whether `expr` is a helper stage call, or a const binding initialized + * with one. */ +function isHelperObject(lowerer: Lowerer, expr: ts.Expression): boolean { + while (ts.isParenthesizedExpression(expr)) expr = expr.expression; + if (isStageCall(lowerer, expr)) return true; + if (!ts.isIdentifier(expr)) return false; + const symbol = lowerer.resolveValueSymbol(expr); + const decl = symbol ? lowerer.checker.valueDeclarationOf(symbol) : undefined; + if ( + !decl || + !ts.isVariableDeclaration(decl) || + decl.initializer === undefined || + !ts.isVariableDeclarationList(decl.parent) || + (decl.parent.flags & ts.NodeFlags.Const) === 0 + ) + return false; + let init: ts.Expression = decl.initializer; + while (ts.isParenthesizedExpression(init)) init = init.expression; + return isStageCall(lowerer, init); +} + +/** `Iterator.from(value)` over a typed built-in iterable: the value's own + * iterator, which already inherits from Iterator.prototype, so Node returns + * it rather than a wrapper. A generator (including a helper object or an + * array iterator) is itself; an array, string or typed array gives its + * values iterator; a Set its values and a Map its entries, as a generator + * over the live collection. The result converts into the call's + * IteratorObject type. Null for other arguments (objects with their own + * next method, untyped values), which keep the Iterator fence. */ +export function lowerIteratorFromCall( + lowerer: Lowerer, + call: ts.CallExpression, + access: ts.PropertyAccessExpression, +): IrExpr | null { + if (access.name.text !== "from" || call.arguments.length !== 1) return null; + const argNode = call.arguments[0]!; + if (ts.isSpreadElement(argNode)) return null; + const resultT = lowerer.mapTypeOf(lowerer.typeOf(call)); + const argT = lowerer.mapTypeOf(lowerer.typeOf(argNode)); + if (resultT?.kind !== "generator" || resultT.async || argT === null) return null; + const iterableKinds = ["generator", "array", "string", "bytes", "set", "map"]; + if (!iterableKinds.includes(argT.kind) || (argT.kind === "generator" && argT.async)) return null; + const loc = locOf(call); + const value = lowerer.lowerExpr(argNode); + const own = (yieldT: IrType): GenType => ({ + kind: "generator", + yieldT, + retT: VOID, + nextT: UNDEFINED_T, + }); + let iterator: IrExpr | null = null; + const t = value.type; + if (t.kind === "generator") iterator = value; + else if (t.kind === "array") + iterator = lowerArrayIteratorValue(lowerer, value, "values", own(t.elem), loc); + else if (t.kind === "string") + iterator = lowerStringIteratorValue(lowerer, value, own(STRING), loc); + else if (t.kind === "bytes") + iterator = lowerBytesIteratorValue(lowerer, value, "values", own(F64), loc); + else if (t.kind === "set" || t.kind === "map") { + const elem = t.kind === "set" ? t.elem : entryPairTypeOf(lowerer, t.key, t.value); + iterator = lowerCollectionIteratorValue( + lowerer, + value, + t.kind === "set" ? "values" : "entries", + own(elem), + loc, + ); + } + if (iterator === null) lowerer.badType(argNode, lowerer.typeOf(argNode)); + const result = lowerer.coerceToExpected(iterator, resultT); + if (!typeEquals(result.type, resultT)) lowerer.badType(call, lowerer.typeOf(call)); + return result; +} + +/** The interned [K, V] pair a Map's entries yield. */ +function entryPairTypeOf(lowerer: Lowerer, keyT: IrType, valueT: IrType): IrType { + return { + kind: "record", + shapeId: lowerer.shapes.intern( + [ + { name: "0", type: keyT }, + { name: "1", type: valueT }, + ], + true, + ), + }; +} + const ITER_STAGES = new Set(["map", "filter", "take", "drop", "flatMap"]); const ITER_TERMINALS = new Set(["toArray", "forEach", "reduce", "some", "every", "find"]); /** Native iterators that ride checked runtime values and adapt into @@ -353,12 +528,30 @@ export function lowerForOfHelperChain( } } -/** `arr.values()` or `arr.entries()` on an array value, as the root of a - * helper chain. */ +/** The argument of an `Iterator.from(x)` call, or null. As a chain's root, + * the call is the argument's own iterator, so the chain can walk the + * argument directly. */ +function iteratorFromArgument(lowerer: Lowerer, expr: ts.Expression): ts.Expression | null { + if (!ts.isCallExpression(expr) || !ts.isPropertyAccessExpression(expr.expression)) return null; + if (expr.questionDotToken || expr.expression.questionDotToken || expr.arguments.length !== 1) + return null; + const arg = expr.arguments[0]!; + if (ts.isSpreadElement(arg) || expr.expression.name.text !== "from") return null; + if (!lowerer.isStdlibGlobal(expr.expression.expression, "Iterator")) return null; + return lowerer.isStdlibMember(expr.expression) ? arg : null; +} + +/** `arr.values()`, `arr.entries()` or `Iterator.from(arr)` on an array value, + * as the root of a helper chain. */ function arrayIteratorCall( lowerer: Lowerer, expr: ts.Expression, ): { receiver: ts.Expression; proj: "values" | "entries" } | null { + const fromArg = iteratorFromArgument(lowerer, expr); + if (fromArg !== null) + return lowerer.mapTypeOf(lowerer.typeOf(fromArg))?.kind === "array" + ? { receiver: fromArg, proj: "values" } + : null; if (!ts.isCallExpression(expr) || !ts.isPropertyAccessExpression(expr.expression)) return null; if (expr.questionDotToken || expr.expression.questionDotToken || expr.arguments.length !== 0) return null; @@ -401,6 +594,17 @@ function collectionIteratorCall( lowerer: Lowerer, expr: ts.Expression, ): { receiver: ts.Expression; proj: "keys" | "values" | "entries" } | null { + // Iterator.from(set) is the Set's values iterator, Iterator.from(map) the + // Map's entries iterator. + const fromArg = iteratorFromArgument(lowerer, expr); + if (fromArg !== null) { + const kind = lowerer.mapTypeOf(lowerer.typeOf(fromArg))?.kind; + return kind === "set" + ? { receiver: fromArg, proj: "values" } + : kind === "map" + ? { receiver: fromArg, proj: "entries" } + : null; + } if (!ts.isCallExpression(expr) || !ts.isPropertyAccessExpression(expr.expression)) return null; if (expr.questionDotToken || expr.expression.questionDotToken || expr.arguments.length !== 0) return null; @@ -480,6 +684,11 @@ function lowerHelperSource( lowerer: Lowerer, expr: ts.Expression, ): { gen: IrExpr & { type: GenType }; elem: IrType } { + // Iterator.from(gen) is the generator itself; the call's IteratorObject + // type would add an adapter generator over it. + const fromArg = iteratorFromArgument(lowerer, expr); + if (fromArg !== null && lowerer.mapTypeOf(lowerer.typeOf(fromArg))?.kind === "generator") + return lowerHelperSource(lowerer, fromArg); const tsType = lowerer.typeOf(expr); const mapped = lowerer.mapTypeOf(tsType); const arg = lowerer.checker.getTypeArguments(tsType as ts.TypeReference)[0]; @@ -505,6 +714,14 @@ function lowerHelperSource( ) lowerer.badType(expr, tsType); const target: GenType = { kind: "generator", yieldT, retT: VOID, nextT: UNDEFINED_T }; + // m.keys()/values()/entries() (or a Set's) on a typed collection: a + // generator over the collection with typed reads. + const collection = collectionIteratorCall(lowerer, expr); + if (collection !== null) { + const coll = lowerer.lowerExpr(collection.receiver); + const walk = lowerCollectionIteratorValue(lowerer, coll, collection.proj, target, locOf(expr)); + if (walk !== null) return { gen: walk as IrExpr & { type: GenType }, elem: yieldT }; + } const adapted = iteratorSlotAdapter(lowerer, lowerer.lowerExpr(expr), target); if (adapted === null || adapted.type.kind !== "generator") lowerer.badType(expr, tsType); return { gen: adapted as IrExpr & { type: GenType }, elem: yieldT }; @@ -577,10 +794,17 @@ function lowerStage( if (ret.kind === "array") return { stage: { kind: "flatMap", fn }, out: ret.elem }; if (ret.kind === "generator" && ret.async !== true && stepDrivable(ret)) return { stage: { kind: "flatMap", fn }, out: ret.yieldT }; + // A Set flattens its values and a Map its entries, walked live. A string + // is rejected at run time with Node's TypeError (strings are not + // iterator objects). + if (ret.kind === "set") return { stage: { kind: "flatMap", fn }, out: ret.elem }; + if (ret.kind === "map") + return { stage: { kind: "flatMap", fn }, out: entryPairTypeOf(lowerer, ret.key, ret.value) }; + if (ret.kind === "string") return { stage: { kind: "flatMap", fn }, out: STRING }; lowerer.noLowering( `.flatMap over a callback returning '${lowerer.fmt(ret)}'`, s.argNode, - "callbacks returning an array or a generator are the supported forms", + "callbacks returning an array, a generator, a Set or a Map are the supported forms", ); } @@ -749,7 +973,8 @@ function lowerFusedChain( function lowerHelperObject( lowerer: Lowerer, call: ts.CallExpression, - src: IrExpr & { type: GenType }, + src: IrExpr, + source: (ChainSource & { kind: "generator" }) | (ChainSource & { kind: "collection" }), srcElem: IrType, stage: StageIr, out: IrType, @@ -773,21 +998,23 @@ function lowerHelperObject( "the helper's element type must be representable — annotate the callback's types", ); } - if (!stepDrivable(src.type)) { + if (source.kind === "generator" && !stepDrivable(source.type)) { lowerer.noLowering( - `.${stage.kind} over an iterator yielding '${lowerer.fmt(src.type.yieldT)}'`, + `.${stage.kind} over an iterator yielding '${lowerer.fmt(source.type.yieldT)}'`, call, "helpers over iterators of numbers, booleans, strings, arrays, records and class instances are the supported forms", ); } const arg = "budget" in stage ? stage.budget : stage.fn; - const key = `iterHelper:${stage.kind}:${typeKey(src.type)}:${typeKey(srcElem)}:${typeKey(arg.type)}:${typeKey(outT)}`; + const srcKey = + source.kind === "collection" ? `${source.proj}:${typeKey(source.type)}` : typeKey(source.type); + const key = `iterHelper:${stage.kind}:${srcKey}:${typeKey(srcElem)}:${typeKey(arg.type)}:${typeKey(outT)}`; let name = lowerer.arrHofHelpers.get(key); if (!name) { name = `%iter.${stage.kind}.${lowerer.arrHofHelpers.size}`; lowerer.arrHofHelpers.set(key, name); lowerer.liftedFns.push( - buildHelperGenFn(lowerer, name, src.type, srcElem, stage, arg.type, outT, resultType, loc), + buildHelperGenFn(lowerer, name, source, srcElem, stage, arg.type, outT, resultType, loc), ); } return { kind: "call", callee: name, args: [src, arg], type: outT, loc }; @@ -1079,6 +1306,107 @@ function driveGenerator( ]; } +/** Statements that walk the Map or Set in local `collId` from inside a + * helper's generator: each pass runs `pre` (which may `break`), takes the + * next live entry index from the runtime's native iterator, which keeps + * the walk live and ends it when exhausted or released, and runs `body` + * over the projected element read with the typed intrinsics. */ +function driveCollectionNative( + lowerer: Lowerer, + frame: ChainFrame, + collId: string, + source: ChainSource & { kind: "collection" }, + pre: IrStmt[], + body: (value: IrExpr) => IrStmt[], + loc: SrcLoc, +): IrStmt[] { + const t = source.type; + const coll = (): IrExpr => varRef(collId, t, loc); + const nativeId = frame.local("native", DYN, false); + const iId = frame.local("i", F64, false); + const read = (method: "iterKey" | "iterValue", type: IrType): IrExpr => + t.kind === "map" + ? { kind: "mapIntrinsic", method, receiver: coll(), args: [varRef(iId, F64, loc)], type, loc } + : { + kind: "setIntrinsic", + method: "iterKey", + receiver: coll(), + args: [varRef(iId, F64, loc)], + type, + loc, + }; + const keyT = t.kind === "map" ? t.key : t.elem; + const valueT = t.kind === "map" ? t.value : t.elem; + const seedT = collectionSeed(lowerer, source); + const seed: IrExpr = + source.proj === "entries" + ? { + kind: "recordLit", + fields: [ + { name: "0", value: read("iterKey", keyT) }, + { name: "1", value: read("iterValue", valueT) }, + ], + type: seedT, + loc, + } + : source.proj === "values" && t.kind === "map" + ? read("iterValue", valueT) + : read("iterKey", keyT); + const vId = frame.local("v", seedT, false); + return [ + { + kind: "varDecl", + localId: nativeId, + init: { + kind: "dynInvoke", + recv: lowerer.coerceToExpected(coll(), DYN), + method: "values", + calleeName: t.kind === "map" ? "map.values" : "set.values", + args: [], + type: DYN, + loc, + }, + loc, + }, + { + kind: "while", + cond: boolLit(true, loc), + body: [ + ...pre, + { + kind: "varDecl", + localId: iId, + init: { + kind: "libCall", + fn: "dyn.collectionIteratorIndex", + args: [varRef(nativeId, DYN, loc)], + type: F64, + loc, + }, + loc, + }, + { + kind: "if", + cond: { + kind: "bin", + op: "<", + left: varRef(iId, F64, loc), + right: numLit(0, loc), + type: BOOL, + loc, + }, + then: [{ kind: "break", loc }], + else_: null, + loc, + }, + { kind: "varDecl", localId: vId, init: seed, loc }, + ...body(varRef(vId, seedT, loc)), + ], + loc, + }, + ]; +} + /** `for (j = 0; j < arr.length && !stop; j++) body(arr[j])` over a live * array local (the array iterator's per-step length read). */ function walkArray( @@ -1245,8 +1573,9 @@ function walkCollection( } /** A flatMap stage's inner walk over the callback's result in local - * `innerId`: an array by index, or a generator driven (and closed on - * early exit) like the source. `stop` ends the walk early. */ + * `innerId`: an array by index, a Set's values or a Map's entries walked + * live, or a generator driven (and closed on early exit) like the source. + * A string result throws Node's TypeError. `stop` ends the walk early. */ function walkInner( lowerer: Lowerer, frame: ChainFrame, @@ -1257,6 +1586,27 @@ function walkInner( loc: SrcLoc, ): IrStmt[] { if (innerT.kind === "array") return [walkArray(frame, innerId, innerT, stop, body, loc)]; + if (innerT.kind === "set" || innerT.kind === "map") { + const source: ChainSource = { + kind: "collection", + type: innerT, + proj: innerT.kind === "set" ? "values" : "entries", + }; + return walkCollection(lowerer, frame, innerId, source, stop ?? boolLit(false, loc), body, loc); + } + if (innerT.kind === "string") { + return [ + throwTypeError( + { + kind: "strLit", + value: "Iterator.prototype.flatMap called on non-object", + type: STRING, + loc, + }, + loc, + ), + ]; + } if (innerT.kind !== "generator") throw new InternalCompilerError("lowerer bug: flatMap over a non-iterable result"); const pre: IrStmt[] = @@ -1768,7 +2118,7 @@ function buildIterChainFn( function buildHelperGenFn( lowerer: Lowerer, name: string, - srcT: GenType, + source: (ChainSource & { kind: "generator" }) | (ChainSource & { kind: "collection" }), srcElem: IrType, stage: StageIr, argT: IrType, @@ -1777,8 +2127,12 @@ function buildHelperGenFn( loc: SrcLoc, ): IrFunction { const frame = new LiftedFrame(loc); - const srcId = frame.param("src", srcT); + const srcId = frame.param("src", source.type); const argId = frame.param(stage.kind === "take" || stage.kind === "drop" ? "n" : "f", argT); + const drive = (pre: IrStmt[], each: (value: IrExpr) => IrStmt[], at: SrcLoc): IrStmt[] => + source.kind === "generator" + ? driveGenerator(lowerer, frame, srcId, source.type, srcElem, pre, each, at) + : driveCollectionNative(lowerer, frame, srcId, source, pre, each, at); const yieldOf = (value: IrExpr): IrStmt => ({ kind: "exprStmt", expr: { @@ -1804,12 +2158,7 @@ function buildHelperGenFn( const n = (): IrExpr => varRef(argId, F64, loc); if (stage.kind === "map") { body.push( - ...driveGenerator( - lowerer, - frame, - srcId, - srcT, - srcElem, + ...drive( [], (value) => { const mId = frame.local("m", fnT!.ret, false); @@ -1824,12 +2173,7 @@ function buildHelperGenFn( ); } else if (stage.kind === "filter") { body.push( - ...driveGenerator( - lowerer, - frame, - srcId, - srcT, - srcElem, + ...drive( [], (value) => { const okId = frame.local("ok", BOOL, false); @@ -1860,28 +2204,12 @@ function buildHelperGenFn( loc, }, ]; - body.push( - ...driveGenerator( - lowerer, - frame, - srcId, - srcT, - srcElem, - pre, - (value) => [yieldOf(value)], - loc, - ), - ); + body.push(...drive(pre, (value) => [yieldOf(value)], loc)); } else if (stage.kind === "drop") { const droppedId = frame.local("dropped", F64, true); body.push({ kind: "varDecl", localId: droppedId, init: numLit(0, loc), loc }); body.push( - ...driveGenerator( - lowerer, - frame, - srcId, - srcT, - srcElem, + ...drive( [], (value) => [ { @@ -1905,12 +2233,7 @@ function buildHelperGenFn( ); } else { body.push( - ...driveGenerator( - lowerer, - frame, - srcId, - srcT, - srcElem, + ...drive( [], (value) => { const innerT = fnT!.ret; @@ -1932,7 +2255,14 @@ function buildHelperGenFn( returnType: VOID, locals: frame.locals, body, - generator: { yieldT: outT.yieldT, nextT: outT.nextT, resultType, closesSource: true }, + generator: { + yieldT: outT.yieldT, + nextT: outT.nextT, + resultType, + tag: "Iterator Helper", + // A native Map or Set iterator has no return() to call. + ...(source.kind === "generator" ? { closesSource: true as const } : {}), + }, loc, }; } diff --git a/packages/compiler/src/frontend/lowering/containers/match-row-uses.ts b/packages/compiler/src/frontend/lowering/containers/match-row-uses.ts new file mode 100644 index 000000000..387ff81d3 --- /dev/null +++ b/packages/compiler/src/frontend/lowering/containers/match-row-uses.ts @@ -0,0 +1,299 @@ +import * as ts from "../../ts7/adapter.js"; +import type { Lowerer } from "../lowerer.js"; +import { identifiersNamed } from "../identifier-uses.js"; +import { bindingNeverReassigned } from "../binding-analysis.js"; +import { matchCallbackParamGroupsOf, matchResultNamedGroupsOf } from "../lower-exprs.js"; + +/** Whether the rows a matchAll drain produces are read only through their + * elements: element reads and `in` tests with number keys, `.length`, the compile-time + * `.groups` projection, `{ groups }` destructuring and, on a for-of binding + * over the drain itself (`companion`), the companion-index `.index`. Rows + * reach these reads through for-of loops, spreads, `Array.from`, stored + * const bindings, `filter`/`slice`/`toReversed` copies, `find`/`findLast`/ + * `at` picks and inline callbacks of the array methods below. No such read + * can observe a row's `index`, `input` or `groups` properties, so the + * drain leaves them out. False for any other use. */ +export function matchAllRowsReadByElements( + lowerer: Lowerer, + drain: ts.Expression, + companion: boolean, +): boolean { + const parent = outerValue(drain).parent; + if ( + ts.isCallExpression(parent) && + parent.arguments[0] === outerValue(drain) && + ts.isPropertyAccessExpression(parent.expression) && + parent.expression.name.text === "from" && + lowerer.isStdlibGlobal(parent.expression.expression, "Array") + ) { + const mapper = parent.arguments[1]; + if (mapper === undefined) return rowsReadByElements(lowerer, parent, 0); + return parent.arguments.length === 2 && callbackReadsByElements(lowerer, mapper, 2); + } + if (ts.isForOfStatement(parent) && parent.expression === outerValue(drain)) + return forOfReadsByElements(lowerer, parent, companion); + return rowsReadByElements(lowerer, drain, 0); +} + +/** matchAllRowsReadByElements for a stored drain, `const rows = + * s.matchAll(re)`: a const-binding for-of loop over `rows` in the same + * function walks the companion indices, so its binding may also read + * `.index`. */ +export function storedMatchAllRowsReadByElements( + lowerer: Lowerer, + decl: ts.VariableDeclaration, +): boolean { + const scope = enclosingFunction(decl); + return constBindingReadsByElements(lowerer, decl, (use) => { + const parent = outerValue(use).parent; + if ( + ts.isForOfStatement(parent) && + parent.expression === outerValue(use) && + ts.isVariableDeclarationList(parent.initializer) && + (parent.initializer.flags & ts.NodeFlags.Const) !== 0 && + enclosingFunction(parent) === scope + ) + return forOfReadsByElements(lowerer, parent, true); + return rowsReadByElements(lowerer, use, 1); + }); +} + +function enclosingFunction(node: ts.Node): ts.Node | undefined { + let at = node.parent; + while (at !== undefined && !ts.isFunctionLike(at)) at = at.parent; + return at; +} + +/** Array methods whose first argument is a callback over the rows. */ +const ROW_CALLBACK_METHODS = new Set([ + "map", + "forEach", + "filter", + "some", + "every", + "find", + "findLast", + "findIndex", + "findLastIndex", + "flatMap", +]); + +/** Array methods whose result holds rows of the receiver. */ +const ROW_SUBSET_METHODS = new Set(["filter", "slice", "toReversed"]); + +/** Array methods that return one row of the receiver. */ +const ROW_PICK_METHODS = new Set(["find", "findLast", "at"]); + +/** Bounds the walk through stored bindings and copies. */ +const MAX_DEPTH = 8; + +function outerValue(node: ts.Node): ts.Node { + let ref = node; + while (ts.isParenthesizedExpression(ref.parent) || ts.isNonNullExpression(ref.parent)) + ref = ref.parent; + return ref; +} + +/** `node` evaluates to an array of rows. */ +function rowsReadByElements(lowerer: Lowerer, node: ts.Node, depth: number): boolean { + if (depth > MAX_DEPTH) return false; + const ref = outerValue(node); + const parent = ref.parent; + if (ts.isSpreadElement(parent)) { + const literal = parent.parent; + return ( + ts.isArrayLiteralExpression(literal) && + literal.elements.length === 1 && + rowsReadByElements(lowerer, literal, depth + 1) + ); + } + if (ts.isForOfStatement(parent) && parent.expression === ref) + return forOfReadsByElements(lowerer, parent, false); + if (ts.isVariableDeclaration(parent) && parent.initializer === ref) + return constBindingReadsByElements(lowerer, parent, (use) => + rowsReadByElements(lowerer, use, depth + 1), + ); + if (ts.isElementAccessExpression(parent) && parent.expression === ref) + return numberKeyRead(lowerer, parent) && rowReadByElements(lowerer, parent, depth + 1); + if (!ts.isPropertyAccessExpression(parent) || parent.expression !== ref) return false; + if (parent.questionDotToken !== undefined || !lowerer.isStdlibMember(parent)) return false; + const method = parent.name.text; + const call = parent.parent; + if (!ts.isCallExpression(call) || call.expression !== parent) + return method === "length" && isValueRead(parent); + if (ROW_CALLBACK_METHODS.has(method)) { + if (call.arguments.length !== 1 || !callbackReadsByElements(lowerer, call.arguments[0]!, 2)) + return false; + } else if (ROW_SUBSET_METHODS.has(method) || ROW_PICK_METHODS.has(method)) { + if (call.arguments.some((a) => ts.isSpreadElement(a))) return false; + } else { + return false; + } + if (ROW_SUBSET_METHODS.has(method)) return rowsReadByElements(lowerer, call, depth + 1); + if (ROW_PICK_METHODS.has(method)) return rowReadByElements(lowerer, call, depth + 1); + return true; +} + +/** `node` evaluates to one row. */ +function rowReadByElements(lowerer: Lowerer, node: ts.Node, depth: number): boolean { + if (depth > MAX_DEPTH) return false; + const ref = outerValue(node); + const parent = ref.parent; + if (ts.isExpressionStatement(parent)) return true; + if (ts.isElementAccessExpression(parent) && parent.expression === ref) + return numberKeyRead(lowerer, parent); + // `1 in m`: a number key tests an element. + if ( + ts.isBinaryExpression(parent) && + parent.operatorToken.kind === ts.SyntaxKind.InKeyword && + parent.right === ref + ) + return lowerer.mapTypeOf(lowerer.typeOf(parent.left))?.kind === "f64"; + if (ts.isPropertyAccessExpression(parent) && parent.expression === ref) { + if (parent.questionDotToken !== undefined || !isValueRead(parent)) return false; + if (parent.name.text === "length") return true; + return parent.name.text === "groups" && groupsProjected(lowerer, ref as ts.Expression); + } + if (ts.isVariableDeclaration(parent) && parent.initializer === ref) { + if (ts.isObjectBindingPattern(parent.name)) + return groupsPattern(parent.name) && groupsProjected(lowerer, ref as ts.Expression); + return constBindingReadsByElements(lowerer, parent, (use) => + rowReadByElements(lowerer, use, depth + 1), + ); + } + return false; +} + +/** A for-of loop over rows: its binding is read by elements, or by its + * companion `.index` when the loop walks the drain itself. */ +function forOfReadsByElements( + lowerer: Lowerer, + loop: ts.ForOfStatement, + companion: boolean, +): boolean { + const list = loop.initializer; + if (!ts.isVariableDeclarationList(list) || list.declarations.length !== 1) return false; + const decl = list.declarations[0]!; + if (!ts.isIdentifier(decl.name)) return false; + const symbol = lowerer.resolveValueSymbol(decl.name); + if (!symbol || !bindingNeverReassigned(lowerer, symbol, decl)) return false; + return usesOf(lowerer, decl.name, symbol).every((use) => { + const parent = outerValue(use).parent; + if ( + companion && + ts.isPropertyAccessExpression(parent) && + parent.name.text === "index" && + parent.questionDotToken === undefined && + isValueRead(parent) + ) + return true; + return rowReadByElements(lowerer, use, 1); + }); +} + +/** An inline callback whose first parameter (a row) is read by elements. + * The callback may name at most `maxParams` parameters, which keeps the + * array argument (the rows themselves) out of reach. */ +function callbackReadsByElements( + lowerer: Lowerer, + node: ts.Expression, + maxParams: number, +): boolean { + let fn: ts.Node = node; + while (ts.isParenthesizedExpression(fn)) fn = fn.expression; + if (!ts.isArrowFunction(fn) && !ts.isFunctionExpression(fn)) return false; + if (fn.parameters.length > maxParams) return false; + const param = fn.parameters[0]; + if (param === undefined) return true; + if (param.dotDotDotToken !== undefined || param.initializer !== undefined) return false; + if (ts.isObjectBindingPattern(param.name)) + return groupsPattern(param.name) && groupsProjectedParam(lowerer, param); + if (!ts.isIdentifier(param.name)) return false; + const symbol = lowerer.checker.getSymbolAtLocation(param.name); + if (!symbol || !bindingNeverReassigned(lowerer, symbol, param)) return false; + return usesOf(lowerer, param.name, symbol).every((use) => rowReadByElements(lowerer, use, 1)); +} + +/** Every use of a non-exported const binding satisfies `read`. */ +function constBindingReadsByElements( + lowerer: Lowerer, + decl: ts.VariableDeclaration, + read: (use: ts.Identifier) => boolean, +): boolean { + if ( + !ts.isIdentifier(decl.name) || + decl.type !== undefined || + !ts.isVariableDeclarationList(decl.parent) || + (decl.parent.flags & ts.NodeFlags.Const) === 0 + ) + return false; + const statement = decl.parent.parent; + if ( + ts.isVariableStatement(statement) && + statement.modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword) + ) + return false; + const symbol = lowerer.resolveValueSymbol(decl.name); + if (!symbol) return false; + return usesOf(lowerer, decl.name, symbol).every(read); +} + +function usesOf(lowerer: Lowerer, name: ts.Identifier, symbol: ts.Symbol): ts.Identifier[] { + return identifiersNamed(lowerer, name.getSourceFile(), name.text).filter( + (use) => use !== name && lowerer.resolveValueSymbol(use) === symbol, + ); +} + +/** `rows[i]` or `rows?.[i]` with a number key, read and not written. */ +function numberKeyRead(lowerer: Lowerer, access: ts.ElementAccessExpression): boolean { + if (!isValueRead(access)) return false; + const key = lowerer.mapTypeOf(lowerer.typeOf(access.argumentExpression)); + return key?.kind === "f64"; +} + +/** `m.groups` lowers to the compile-time projection over the row's + * elements (lowerMatchGroupsRead). */ +function groupsProjected(lowerer: Lowerer, row: ts.Expression): boolean { + const groups = matchResultNamedGroupsOf(lowerer, row); + return groups !== null && groups.length > 0; +} + +/** `({ groups }) => ...` lowers through the same projection. */ +function groupsProjectedParam(lowerer: Lowerer, param: ts.ParameterDeclaration): boolean { + const groups = matchCallbackParamGroupsOf(lowerer, param); + return groups !== null && groups.length > 0; +} + +/** An object pattern that binds only `groups`. */ +function groupsPattern(pattern: ts.ObjectBindingPattern): boolean { + return ( + pattern.elements.length > 0 && + pattern.elements.every((el) => { + if (el.dotDotDotToken !== undefined) return false; + const prop = el.propertyName; + if (prop === undefined) return ts.isIdentifier(el.name) && el.name.text === "groups"; + return (ts.isIdentifier(prop) || ts.isStringLiteral(prop)) && prop.text === "groups"; + }) + ); +} + +/** A read whose value is only consumed: not an assignment, update or + * delete target. */ +function isValueRead(access: ts.Expression): boolean { + const ref = outerValue(access); + const parent = ref.parent; + if ( + ts.isBinaryExpression(parent) && + parent.left === ref && + parent.operatorToken.kind >= ts.SyntaxKind.FirstAssignment && + parent.operatorToken.kind <= ts.SyntaxKind.LastAssignment + ) + return false; + return !( + (ts.isPrefixUnaryExpression(parent) && + (parent.operator === ts.SyntaxKind.PlusPlusToken || + parent.operator === ts.SyntaxKind.MinusMinusToken)) || + ts.isPostfixUnaryExpression(parent) || + ts.isDeleteExpression(parent) + ); +} diff --git a/packages/compiler/src/frontend/lowering/containers/string-and-regexp.ts b/packages/compiler/src/frontend/lowering/containers/string-and-regexp.ts index 361441636..dd70eb197 100644 --- a/packages/compiler/src/frontend/lowering/containers/string-and-regexp.ts +++ b/packages/compiler/src/frontend/lowering/containers/string-and-regexp.ts @@ -1,3 +1,4 @@ +import { matchAllRowsReadByElements } from "./match-row-uses.js"; import { dynUndefinedExpr, nodeThrowExpr, numLit, strLit, varRef } from "../../../ir/build.js"; import * as ts from "../../ts7/adapter.js"; import { @@ -869,7 +870,7 @@ export function lowerRegexMethodCall( const re = lowerer.lowerExprExpecting(arg0, { kind: "regex" }); return { kind: "regexIntrinsic", - method: "matchAll", + method: matchAllRowsReadByElements(lowerer, call, false) ? "matchAllCaptures" : "matchAll", receiver, args: [re], type: arrayOf(arrayOf(STRING)), diff --git a/packages/compiler/src/frontend/lowering/iterator-adapters.ts b/packages/compiler/src/frontend/lowering/iterator-adapters.ts index fb59c4cee..898c46f20 100644 --- a/packages/compiler/src/frontend/lowering/iterator-adapters.ts +++ b/packages/compiler/src/frontend/lowering/iterator-adapters.ts @@ -258,13 +258,350 @@ function dynIteratorAdapter( returnType: expected.retT, locals, body, - generator: { yieldT: expected.yieldT, nextT: expected.nextT, resultType }, + generator: { yieldT: expected.yieldT, nextT: expected.nextT, resultType, tag: "!" }, loc, }); } return { kind: "call", callee: name, args: [expr], type: expected, loc }; } +/** `m.keys()`, `m.values()` or `m.entries()` (or a Set's) as a source + * generator for an iterator helper object: an interned generator over the + * typed collection that steps the runtime's native iterator by entry + * index and reads each key or value with the typed intrinsics, so values + * keep their types without a checked conversion per step. The native + * iterator owns the live-walk bracket: it ends the iteration when the + * collection is exhausted or when the generator, done or abandoned, + * releases it. `type` yields the key, the value, or the interned [key, + * value] pair. Null when `type` does not match. */ +export function lowerCollectionIteratorValue( + lowerer: Lowerer, + receiver: IrExpr, + method: "keys" | "values" | "entries", + type: IrType, + loc: SrcLoc, +): IrExpr | null { + const collT = receiver.type; + if ((collT.kind !== "map" && collT.kind !== "set") || type.kind !== "generator") return null; + if (type.retT.kind !== "void" || type.nextT.kind !== "undefinedT") return null; + const keyT = collT.kind === "map" ? collT.key : collT.elem; + const valueT = collT.kind === "map" ? collT.value : collT.elem; + const resultType = genResultRecord(type.yieldT, type.retT, lowerer.shapes, lowerer.unions); + if (!resultType) return null; + const key = `collIter:${method}:${typeKey(collT)}:${typeKey(type)}`; + let name = lowerer.valueHelpers.get(key); + if (!name) { + const coll = varRef("coll.0", collT, loc); + const index = varRef("index.0", F64, loc); + const native = varRef("native.0", DYN, loc); + const read = (m: "iterKey" | "iterValue", t: IrType): IrExpr => + collT.kind === "map" + ? { kind: "mapIntrinsic", method: m, receiver: coll, args: [index], type: t, loc } + : { kind: "setIntrinsic", method: "iterKey", receiver: coll, args: [index], type: t, loc }; + let yielded: IrExpr; + if (method === "keys" || (collT.kind === "set" && method === "values")) + yielded = read("iterKey", keyT); + else if (method === "values") yielded = read("iterValue", valueT); + else + yielded = { + kind: "recordLit", + fields: [ + { name: "0", value: read("iterKey", keyT) }, + { name: "1", value: read("iterValue", valueT) }, + ], + type: type.yieldT, + loc, + }; + if (!typeEquals(yielded.type, type.yieldT)) return null; + name = `%coll.iter.${lowerer.valueHelpers.size}`; + lowerer.valueHelpers.set(key, name); + lowerer.liftedFns.push({ + name, + params: [{ localId: "coll.0", name: "coll", type: collT }], + returnType: VOID, + locals: [ + { id: "coll.0", name: "coll", type: collT, mutable: false }, + { id: "native.0", name: "native", type: DYN, mutable: false }, + { id: "index.0", name: "index", type: F64, mutable: true }, + ], + body: [ + { + kind: "varDecl", + localId: "native.0", + init: { + kind: "dynInvoke", + recv: lowerer.coerceToExpected(coll, DYN), + method: "values", + calleeName: collT.kind === "map" ? "map.values" : "set.values", + args: [], + type: DYN, + loc, + }, + loc, + }, + { + kind: "while", + cond: { kind: "boolLit", value: true, type: BOOL, loc }, + body: [ + { + kind: "varDecl", + localId: "index.0", + init: { + kind: "libCall", + fn: "dyn.collectionIteratorIndex", + args: [native], + type: F64, + loc, + }, + loc, + }, + { + kind: "if", + cond: { + kind: "bin", + op: "<", + left: index, + right: { kind: "numLit", value: 0, type: F64, loc }, + type: BOOL, + loc, + }, + then: [{ kind: "return", value: null, loc }], + else_: null, + loc, + }, + { kind: "exprStmt", expr: { kind: "yieldExpr", value: yielded, type: VOID, loc }, loc }, + ], + loc, + }, + ], + generator: { + yieldT: type.yieldT, + nextT: UNDEFINED_T, + resultType, + tag: "!", + }, + loc, + }); + } + return { kind: "call", callee: name, args: [receiver], type, loc }; +} + +/** `ta.values()`, `ta.keys()` or `ta.entries()` on a typed array as a + * value: an interned generator over the indices, reading the length and + * the element at each step, as the typed array iterator does. Null when + * `type` is not the matching number (or [number, number]) generator. */ +export function lowerBytesIteratorValue( + lowerer: Lowerer, + receiver: IrExpr, + method: "values" | "keys" | "entries", + type: IrType, + loc: SrcLoc, +): IrExpr | null { + const bytesT = receiver.type; + if (bytesT.kind !== "bytes" || type.kind !== "generator") return null; + if (type.retT.kind !== "void" || type.nextT.kind !== "undefinedT") return null; + const pair = type.yieldT.kind === "record" ? lowerer.shapes.get(type.yieldT.shapeId) : undefined; + const pairOk = + pair?.tuple === true && + pair.fields.length === 2 && + pair.fields.every((f) => (f.name === "0" || f.name === "1") && f.type.kind === "f64"); + if (method === "entries" ? !pairOk : type.yieldT.kind !== "f64") return null; + const resultType = genResultRecord(type.yieldT, type.retT, lowerer.shapes, lowerer.unions); + if (!resultType) return null; + const key = `bytesIter:${method}:${typeKey(bytesT)}:${typeKey(type)}`; + let name = lowerer.valueHelpers.get(key); + if (!name) { + name = `%bytes.iter.${lowerer.valueHelpers.size}`; + lowerer.valueHelpers.set(key, name); + const list = varRef("list.0", bytesT, loc); + const index = varRef("index.0", F64, loc); + const element: IrExpr = { + kind: "bytesIntrinsic", + method: "get", + receiver: list, + args: [index], + type: F64, + loc, + }; + const yielded: IrExpr = + method === "keys" + ? index + : method === "values" + ? element + : { + kind: "recordLit", + fields: [ + { name: "0", value: index }, + { name: "1", value: element }, + ], + type: type.yieldT, + loc, + }; + lowerer.liftedFns.push({ + name, + params: [{ localId: "list.0", name: "list", type: bytesT }], + returnType: VOID, + locals: [ + { id: "list.0", name: "list", type: bytesT, mutable: false }, + { id: "index.0", name: "index", type: F64, mutable: true }, + ], + body: [ + { + kind: "varDecl", + localId: "index.0", + init: { kind: "numLit", value: 0, type: F64, loc }, + loc, + }, + { + kind: "while", + cond: { + kind: "bin", + op: "<", + left: index, + right: { + kind: "bytesIntrinsic", + method: "length", + receiver: list, + args: [], + type: F64, + loc, + }, + type: BOOL, + loc, + }, + body: [ + { kind: "exprStmt", expr: { kind: "yieldExpr", value: yielded, type: VOID, loc }, loc }, + { + kind: "assign", + localId: "index.0", + value: { + kind: "bin", + op: "+", + left: index, + right: { kind: "numLit", value: 1, type: F64, loc }, + type: F64, + loc, + }, + loc, + }, + ], + loc, + }, + { kind: "return", value: null, loc }, + ], + generator: { yieldT: type.yieldT, nextT: UNDEFINED_T, resultType, tag: "Array Iterator" }, + loc, + }); + } + return { kind: "call", callee: name, args: [receiver], type, loc }; +} + +/** `s[Symbol.iterator]()` on a string value: an interned generator over + * its code points, the string iterator's steps (a lone surrogate is one + * step). Null when `type` is not the plain string-yielding generator. */ +export function lowerStringIteratorValue( + lowerer: Lowerer, + receiver: IrExpr, + type: IrType, + loc: SrcLoc, +): IrExpr | null { + if (receiver.type.kind !== "string" || type.kind !== "generator") return null; + if ( + type.yieldT.kind !== "string" || + type.retT.kind !== "void" || + type.nextT.kind !== "undefinedT" + ) + return null; + const resultType = genResultRecord(type.yieldT, type.retT, lowerer.shapes, lowerer.unions); + if (!resultType) return null; + const key = `strIter:${typeKey(type)}`; + let name = lowerer.valueHelpers.get(key); + if (!name) { + name = `%str.iter.${lowerer.valueHelpers.size}`; + lowerer.valueHelpers.set(key, name); + const text = varRef("text.0", STRING, loc); + const index = varRef("index.0", F64, loc); + const point = varRef("point.0", STRING, loc); + lowerer.liftedFns.push({ + name, + params: [{ localId: "text.0", name: "text", type: STRING }], + returnType: VOID, + locals: [ + { id: "text.0", name: "text", type: STRING, mutable: false }, + { id: "index.0", name: "index", type: F64, mutable: true }, + { id: "point.0", name: "point", type: STRING, mutable: false }, + ], + body: [ + { + kind: "varDecl", + localId: "index.0", + init: { kind: "numLit", value: 0, type: F64, loc }, + loc, + }, + { + kind: "while", + cond: { + kind: "bin", + op: "<", + left: index, + right: { + kind: "strIntrinsic", + method: "length", + receiver: text, + args: [], + type: F64, + loc, + }, + type: BOOL, + loc, + }, + body: [ + { + kind: "varDecl", + localId: "point.0", + init: { + kind: "strIntrinsic", + method: "cpAt", + receiver: text, + args: [index], + type: STRING, + loc, + }, + loc, + }, + { + kind: "assign", + localId: "index.0", + value: { + kind: "bin", + op: "+", + left: index, + right: { + kind: "strIntrinsic", + method: "length", + receiver: point, + args: [], + type: F64, + loc, + }, + type: F64, + loc, + }, + loc, + }, + { kind: "exprStmt", expr: { kind: "yieldExpr", value: point, type: VOID, loc }, loc }, + ], + loc, + }, + { kind: "return", value: null, loc }, + ], + generator: { yieldT: type.yieldT, nextT: UNDEFINED_T, resultType, tag: "String Iterator" }, + loc, + }); + } + return { kind: "call", callee: name, args: [receiver], type, loc }; +} + /** A generator flowing into a slot with other channel types (a * `Generator` returned through an * `IterableIterator` annotation). The interned adapter generator @@ -419,7 +756,12 @@ function generatorRetypeAdapter( loc, }, ], - generator: { yieldT: expected.yieldT, nextT: expected.nextT, resultType: toResult }, + generator: { + yieldT: expected.yieldT, + nextT: expected.nextT, + resultType: toResult, + inheritsTag: true, + }, loc, }); } @@ -690,7 +1032,7 @@ export function lowerArrayIteratorValue( }, { kind: "return", value: null, loc }, ], - generator: { yieldT: type.yieldT, nextT: UNDEFINED_T, resultType }, + generator: { yieldT: type.yieldT, nextT: UNDEFINED_T, resultType, tag: "Array Iterator" }, loc, }); } diff --git a/packages/compiler/src/frontend/lowering/lower-calls.ts b/packages/compiler/src/frontend/lowering/lower-calls.ts index 23b8b69d2..b3cd7e39b 100644 --- a/packages/compiler/src/frontend/lowering/lower-calls.ts +++ b/packages/compiler/src/frontend/lowering/lower-calls.ts @@ -94,6 +94,8 @@ import { lowerDynArrayFilterCall, lowerDynArrayFlatMapCall } from "./containers/ import { lowerGroupByStaticCall } from "./containers/collection-grouping.js"; import { lowerGeneratorHelperCall, + lowerHelperReturnCall, + lowerIteratorFromCall, lowerIteratorHelperCall, } from "./containers/iterator-helpers.js"; import { @@ -223,6 +225,7 @@ import { completeArgs, } from "./call-arguments.js"; import { isThisParameter, type ParamShape } from "./call-signatures.js"; +import { lowerStringIteratorValue } from "./iterator-adapters.js"; import { checkedArmExtraction } from "./coercions/unions.js"; import { maybePromiseReturnStmt, promiseArmedUnionOf } from "./maybe-promise.js"; import { @@ -365,6 +368,9 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { // machinery (the member always exists on a POSIX target, so the // optional call IS the call; `process.getuid` itself has no value // lowering for the chain to guard). + // `h.return!()` / `h.return?.()` on a helper object: the method exists. + const helperReturn = lowerHelperReturnCall(lowerer, expr); + if (helperReturn) return helperReturn; const processOptional = lowerer.lowerProcessOptionalMethodCall(expr); if (processOptional) return processOptional; const reqStreamOptional = lowerCompatReqStreamOptionalCall(lowerer, expr); @@ -1616,6 +1622,17 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { lowerer.unsupported("SC1090", expr, `method calls like '${expr.expression.getText()}'`); } + // `s[Symbol.iterator]()` on a string: the string iterator as a value. + if ( + ts.isElementAccessExpression(expr.expression) && + !expr.expression.questionDotToken && + !expr.questionDotToken && + expr.arguments.length === 0 + ) { + const iterator = lowerStringIteratorCall(lowerer, expr, expr.expression); + if (iterator) return iterator; + } + // The element spelling of an unsupported native Web method must fence // before lowering the callee into a checked-dynamic keyed read. if (ts.isElementAccessExpression(expr.expression)) { @@ -2783,7 +2800,8 @@ function lowerObjectPrototypeCall( const immediate = immediateObjectTagReceiver(lowerer, receiverNode); const receiver = immediate?.effect ?? lowerer.lowerExpr(receiverNode); const tag = immediate?.tag ?? objectToStringTag(lowerer, receiver.type); - if (tag === null && receiver.type.kind !== "dyn") { + // A generator object answers its tag through its checked capsule. + if (tag === null && receiver.type.kind !== "dyn" && receiver.type.kind !== "generator") { lowerer.noLowering( "Object.prototype.toString.call over a " + lowerer.fmt(receiver.type) + " receiver", receiverNode, @@ -2803,7 +2821,13 @@ function lowerObjectPrototypeCall( } const result: IrExpr = tag === null - ? { kind: "libCall", fn: "dyn.objectTag", args: [ref], type: STRING, loc } + ? { + kind: "libCall", + fn: "dyn.objectTag", + args: [ref.type.kind === "generator" ? lowerer.coerceToExpected(ref, DYN) : ref], + type: STRING, + loc, + } : { kind: "strLit", value: "[object " + tag + "]", type: STRING, loc }; return { kind: "seqExpr", stmts, result, type: STRING, loc }; } @@ -3157,10 +3181,32 @@ function lowerOptionalStringNumber( return { kind: "call", callee: helper, args: [arg], type: F64, loc }; } -/** Iterator types with checked runtime receivers: Map/Set iterators' - * helper calls lower in lowerGeneratorHelperCall; the string iterator's - * have no lowering. */ -const UNLOWERED_ITERATOR_TYPES = new Set(["MapIterator", "SetIterator", "StringIterator"]); +/** `s[Symbol.iterator]()` on a string-typed receiver: the generator over + * its code points. Null for other element calls. */ +function lowerStringIteratorCall( + lowerer: Lowerer, + call: ts.CallExpression, + access: ts.ElementAccessExpression, +): IrExpr | null { + let key = access.argumentExpression; + while (ts.isParenthesizedExpression(key)) key = key.expression; + if ( + !ts.isPropertyAccessExpression(key) || + lowerer.stdlibGlobalMember(key, "Symbol") !== "iterator" + ) + return null; + if (lowerer.mapTypeOf(lowerer.typeOf(access.expression))?.kind !== "string") return null; + const type = lowerer.mapTypeOf(lowerer.typeOf(call)); + if (type?.kind !== "generator") return null; + const receiver = lowerer.lowerExpr(access.expression); + const value = lowerStringIteratorValue(lowerer, receiver, type, locOf(call)); + if (!value) lowerer.unsupported("SC1090", call, "this string iterator type"); + return value; +} + +/** Iterator types with checked runtime receivers, whose helper calls lower + * in lowerGeneratorHelperCall. */ +const CHECKED_ITERATOR_TYPES = new Set(["MapIterator", "SetIterator"]); const ITERATOR_HELPER_METHODS = new Set([ "map", "filter", @@ -3220,25 +3266,17 @@ function lowerDynReceiverMethodCall( const recvSymbol = recvTs.getSymbol(); if ( recvSymbol && - UNLOWERED_ITERATOR_TYPES.has(recvSymbol.name) && + CHECKED_ITERATOR_TYPES.has(recvSymbol.name) && lowerer.isStdlibSymbol(recvSymbol) && ITERATOR_HELPER_METHODS.has(access.name.text) && lowerer.isStdlibMember(access) ) { - if (recvSymbol.name === "MapIterator" || recvSymbol.name === "SetIterator") { - const helper = lowerGeneratorHelperCall(lowerer, call, access); - if (helper) return helper; - lowerer.noLowering( - `collection iterator .${access.name.text}()`, - call, - "stored Map/Set iterators support .next() and iteration; iterator helper methods have no lowering", - ); - } - // The checked runtime's string iterator has no helper methods. + const helper = lowerGeneratorHelperCall(lowerer, call, access); + if (helper) return helper; lowerer.noLowering( - `${recvSymbol.name}.${access.name.text}`, + `collection iterator .${access.name.text}()`, call, - "iterator helpers lower on generators and on array, Map and Set iterators — spread the string first: [...s].values()", + "iterator helpers on Map/Set iterators are called directly on the iterator, without optional chaining", ); } const arrayReceiver = lowerer.checker.isArrayType(recvTs); @@ -6644,10 +6682,10 @@ function recordAssignHelper( return helper; } -/** The `Iterator` global's statics (ES2025 — Iterator.from, and the - * abstract constructor as a value) have no lowering: every member fences - * with the working spelling named instead of the generic-method fence's - * monomorphization wording. */ +/** The `Iterator` global's statics (ES2025): `Iterator.from` over a typed + * built-in iterable lowers (lowerIteratorFromCall); every other member, + * and other arguments, fence with the working spelling named instead of + * the generic-method fence's monomorphization wording. */ function lowerIteratorStaticFence( lowerer: Lowerer, call: ts.CallExpression, @@ -6655,11 +6693,15 @@ function lowerIteratorStaticFence( ): IrExpr | null { if (!lowerer.isStdlibGlobal(access.expression, "Iterator")) return null; if (!lowerer.isStdlibMember(access)) return null; + const from = lowerIteratorFromCall(lowerer, call, access); + if (from) return from; lowerer.noLowering( `Iterator.${access.name.text}`, call, - "iterator helpers apply directly to generators and to array, Map and Set iterators: " + - "arr.values().map(f).take(n).toArray()", + access.name.text === "from" + ? "Iterator.from accepts arrays, strings, typed arrays, Sets, Maps and iterator objects such as generators" + : "iterator helpers apply directly to generators and to array, Map and Set iterators: " + + "arr.values().map(f).take(n).toArray()", ); } diff --git a/packages/compiler/src/frontend/lowering/lower-classes.ts b/packages/compiler/src/frontend/lowering/lower-classes.ts index 55668c4b7..4445358d3 100644 --- a/packages/compiler/src/frontend/lowering/lower-classes.ts +++ b/packages/compiler/src/frontend/lowering/lower-classes.ts @@ -4598,8 +4598,7 @@ function lowerStaticFieldInitsInner(lowerer: Lowerer, info: ClassInfo): IrStmt[] continue; } const f = item.f; - lowerer.stats.statementsTotal++; - lowerer.bumpFileStat(locOf(f.initializer).file, "total"); + lowerer.countStatement(f.initializer, locOf(f.initializer).file); const diagsBefore = lowerer.diags.length; const sinkMark = lowerer.diagSinkMark(); try { @@ -4635,8 +4634,7 @@ function lowerStaticFieldInitsInner(lowerer: Lowerer, info: ClassInfo): IrStmt[] out.push({ kind: "assign", localId: f.globalId, value, loc: locOf(f.initializer) }); } catch (e) { if (!(e instanceof PoisonError)) throw e; - lowerer.stats.statementsFailed++; - lowerer.bumpFileStat(locOf(f.initializer).file, "failed"); + lowerer.countStatementFailed(f.initializer, locOf(f.initializer).file); if (isJsSourceFile(f.initializer.getSourceFile())) { const fence = lowerer.diagSink === null @@ -7764,8 +7762,7 @@ export function fieldInitStmts(lowerer: Lowerer, info: ClassInfo, thisLocal: IrL }); } if (!f.initializer) continue; - lowerer.stats.statementsTotal++; - lowerer.bumpFileStat(locOf(f.initializer).file, "total"); + lowerer.countStatement(f.initializer, locOf(f.initializer).file); const diagsBefore = lowerer.diags.length; const sinkMark = lowerer.diagSinkMark(); try { @@ -7780,8 +7777,7 @@ export function fieldInitStmts(lowerer: Lowerer, info: ClassInfo, thisLocal: IrL }); } catch (e) { if (!(e instanceof PoisonError)) throw e; - lowerer.stats.statementsFailed++; - lowerer.bumpFileStat(locOf(f.initializer).file, "failed"); + lowerer.countStatementFailed(f.initializer, locOf(f.initializer).file); if (isJsSourceFile(f.initializer.getSourceFile())) { const fence = lowerer.diagSink === null diff --git a/packages/compiler/src/frontend/lowering/lower-exprs.ts b/packages/compiler/src/frontend/lowering/lower-exprs.ts index 47da36a6e..e06466f6f 100644 --- a/packages/compiler/src/frontend/lowering/lower-exprs.ts +++ b/packages/compiler/src/frontend/lowering/lower-exprs.ts @@ -147,6 +147,7 @@ import { } from "../../diagnostics/diagnostic.js"; import { PoisonError, dynFallbackType, jsFuncNameOf, neverTaintedJsType, own } from "./lowerer.js"; import { lowerCollectionSpread } from "./containers/collection-methods.js"; +import { lowerHelperReturnCall } from "./containers/iterator-helpers.js"; import { arrayNumberRead, arrayValueRead, @@ -1912,6 +1913,10 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr { if (ts.isPostfixUnaryExpression(expr)) return lowerIncDec(lowerer, expr, false); if (ts.isBinaryExpression(expr)) return lowerer.lowerBinary(expr); if (ts.isCallExpression(expr)) { + // `h.return?.()` on a helper object: the method exists, so the + // optional call is the call. + const helperReturn = lowerHelperReturnCall(lowerer, expr); + if (helperReturn) return helperReturn; // The tail of an optional chain whose `?.` sits deeper // (`x?.trim().toLowerCase()`): the whole tail short-circuits together. if (isOptionalChainTail(lowerer, expr)) return lowerer.lowerOptionalChain(expr); @@ -2164,13 +2169,11 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr { (lowerer.isStdlibMember(expr) || isJsSourceFile(expr.getSourceFile())) ) { const recv = represented; - const read: IrExpr = { - kind: "dynKeyGet", - value: lowerer.coerceToExpected(recv, DYN), - key: { kind: "strLit", value: expr.name.text, type: STRING, loc }, - type: DYN, - loc, - }; + const key: IrExpr = { kind: "strLit", value: expr.name.text, type: STRING, loc }; + // A plain slice answers from its match source without a dynamic copy. + const read: IrExpr = typeEquals(recv.type, arrayOf(STRING)) + ? { kind: "libCall", fn: "regex.resultProp", args: [recv, key], type: DYN, loc } + : { kind: "dynKeyGet", value: lowerer.coerceToExpected(recv, DYN), key, type: DYN, loc }; return lowerer.maybeNarrow(read, expr); } } diff --git a/packages/compiler/src/frontend/lowering/lower-inspect.ts b/packages/compiler/src/frontend/lowering/lower-inspect.ts index 1d67cef92..2551031eb 100644 --- a/packages/compiler/src/frontend/lowering/lower-inspect.ts +++ b/packages/compiler/src/frontend/lowering/lower-inspect.ts @@ -253,6 +253,10 @@ function inspectSupport( visiting.delete(t.className); return null; } + // Generator objects print through their checked capsule: Node's + // `Object [Generator] {}`, or the tag of the iterator they implement. + case "generator": + return null; default: return `'${lowerer.fmt(t)}' values have no inspect lowering`; } @@ -343,6 +347,14 @@ export function inspectExpr( return { kind: "libCall", fn: "insp.buffer", args: [value], type: STRING, loc }; case "dyn": return { kind: "libCall", fn: "insp.dyn", args: [value, recurse, depth], type: STRING, loc }; + case "generator": + return { + kind: "libCall", + fn: "insp.dyn", + args: [lowerer.coerceToExpected(value, DYN), recurse, depth], + type: STRING, + loc, + }; case "object": if (eventTargetRootOf(lowerer.classes.get(t.className)) !== null) { return { diff --git a/packages/compiler/src/frontend/lowering/lower-stmts.ts b/packages/compiler/src/frontend/lowering/lower-stmts.ts index 7fb1f69a9..4ed68ab1f 100644 --- a/packages/compiler/src/frontend/lowering/lower-stmts.ts +++ b/packages/compiler/src/frontend/lowering/lower-stmts.ts @@ -1,3 +1,7 @@ +import { + matchAllRowsReadByElements, + storedMatchAllRowsReadByElements, +} from "./containers/match-row-uses.js"; import { dynUndefinedExpr, numLit, varRef } from "../../ir/build.js"; import { lowerCollectionSpread } from "./containers/collection-methods.js"; import { lowerEventObjectPropertyWrite } from "./lower-event-target.js"; @@ -690,10 +694,7 @@ export function lowerStmts(lowerer: Lowerer, stmts: readonly ts.Statement[]): Ir entry.index = i; if (unreachable && skipsWhenUnreachable(stmt)) continue; if (inFunction && isAbruptStatement(stmt)) unreachable = true; - if (!lowerer.suppressStats) { - lowerer.stats.statementsTotal++; - if (sf !== undefined) lowerer.bumpFileStat(sf.fileName, "total"); - } + if (!lowerer.suppressStats) lowerer.countStatement(stmt, sf?.fileName); const diagsBefore = lowerer.diags.length; const sinkMark = lowerer.diagSinkMark(); try { @@ -758,10 +759,7 @@ export function lowerStmts(lowerer: Lowerer, stmts: readonly ts.Statement[]): Ir } else if (!(e instanceof PoisonError)) { throw e; } - if (!lowerer.suppressStats) { - lowerer.stats.statementsFailed++; - if (sf !== undefined) lowerer.bumpFileStat(sf.fileName, "failed"); - } + if (!lowerer.suppressStats) lowerer.countStatementFailed(stmt, sf?.fileName); lowerer.noteBlockedBindings(stmt); // The runtime-fence conversion (JS sources, direct diagnostics // only — a capture wrapper in force means collection owns the @@ -1892,7 +1890,9 @@ function lowerMatchAllDrainDecl( localId: local.id, init: { kind: "regexIntrinsic", - method: "matchAllInto", + method: storedMatchAllRowsReadByElements(lowerer, decl) + ? "matchAllCapturesInto" + : "matchAllInto", receiver, args: [re, { kind: "varRef", localId: idxs.id, type: idxsT, loc }], type: rowsT, @@ -9568,7 +9568,9 @@ function lowerForOfMatchAll( }); rowsInit = { kind: "regexIntrinsic", - method: "matchAllInto", + method: matchAllRowsReadByElements(lowerer, call, true) + ? "matchAllCapturesInto" + : "matchAllInto", receiver, args: [re, { kind: "varRef", localId: idxs.id, type: idxsT, loc }], type: rowsT, diff --git a/packages/compiler/src/frontend/lowering/lower-test.ts b/packages/compiler/src/frontend/lowering/lower-test.ts index e36518559..c31e674af 100644 --- a/packages/compiler/src/frontend/lowering/lower-test.ts +++ b/packages/compiler/src/frontend/lowering/lower-test.ts @@ -90,12 +90,14 @@ function atStringOf(expr: ts.CallExpression): string { return `${sf.fileName}:${pos.line + 1}:${pos.character + 1}`; } -/** One parsed { skip?, todo?, only? } options literal. `mode` is the - * runtime literal (0 run / 1 skip / 2 todo), `msg` the directive message - * expression ("" = none). */ +/** One parsed { skip?, todo?, only? } options literal. `skip` and `todo` + * are whether each is set (a string or true), and `skipMsg` and `todoMsg` + * the string messages (directiveOf settles them). */ interface TestOptions { - mode: number; - msg: IrExpr | null; + skip: boolean; + skipMsg: IrExpr | null; + todo: boolean; + todoMsg: IrExpr | null; only: boolean; /** The { plan } count, -1 without one. */ plan: number; @@ -109,8 +111,10 @@ interface TestOptions { } const noOptions = (): TestOptions => ({ - mode: 0, - msg: null, + skip: false, + skipMsg: null, + todo: false, + todoMsg: null, only: false, plan: -1, timeout: null, @@ -118,6 +122,23 @@ const noOptions = (): TestOptions => ({ pre: [], }); +/** The runtime mode (0 run / 1 skip / 2 todo) and directive message of a + * test or suite, as Node's Test constructor settles them: the skip and todo + * method twins override the options of the same name, skip wins over + * todo, and the message is the skip string, else the todo string. */ +function directiveOf(opts: TestOptions, methodMode: number, loc: SrcLoc): [IrExpr, IrExpr] { + if (methodMode === 1) { + opts.skip = true; + opts.skipMsg = null; + } + if (methodMode === 2) { + opts.todo = true; + opts.todoMsg = null; + } + const mode = opts.skip ? 1 : opts.todo ? 2 : 0; + return [numLit(mode, loc), opts.skipMsg ?? opts.todoMsg ?? strLit("", loc)]; +} + /** Flags 8 and 16: a { timeout } and a { concurrency } are given. */ const optionFlags = (opts: TestOptions): number => (opts.only ? 4 : 0) | (opts.timeout ? 8 : 0) | (opts.concurrency ? 16 : 0); @@ -168,15 +189,33 @@ function lowerTestOptions( // A shorthand entry's value is the binding of the same name. const init: ts.Expression = ts.isPropertyAssignment(prop) ? prop.initializer : key; if (name === "skip" || name === "todo") { - const mode = name === "skip" ? 1 : 2; const t = lowerer.typeOf(init); if (t.isStringLiteralType() || lowerer.mapTypeOf(t)?.kind === "string") { - out.mode = mode; - out.msg = optionValue(lowerer, out, lowerer.lowerExprExpecting(init, STRING), locOf(init)); - } else if (init.kind === ts.SyntaxKind.TrueKeyword) { - out.mode = mode; - } else if (init.kind === ts.SyntaxKind.FalseKeyword) { - // explicit false: run normally + const msg = optionValue( + lowerer, + out, + lowerer.lowerExprExpecting(init, STRING), + locOf(init), + ); + if (name === "skip") { + out.skip = true; + out.skipMsg = msg; + } else { + out.todo = true; + out.todoMsg = msg; + } + } else if ( + init.kind === ts.SyntaxKind.TrueKeyword || + init.kind === ts.SyntaxKind.FalseKeyword + ) { + const on = init.kind === ts.SyntaxKind.TrueKeyword; + if (name === "skip") { + out.skip = on; + out.skipMsg = null; + } else { + out.todo = on; + out.todoMsg = null; + } } else { lowerer.noLowering( `${what} with a non-literal ${name} value`, @@ -284,6 +323,116 @@ function lowerBodyArg( /** The shared registration lowering behind test/it and their skip/todo/ * only method twins — and, with `sub`, behind t.test. Argument shapes: * (name), (name, fn), (name, options, fn). */ +/** The `name` a function value has in JavaScript, when the compiler can + * tell: "" for an anonymous function expression, the declared or binding + * name otherwise; null for other expressions. */ +function staticFunctionName(lowerer: Lowerer, node: ts.Expression): string | null { + while (ts.isParenthesizedExpression(node)) node = node.expression; + if (ts.isArrowFunction(node)) return ""; + if (ts.isFunctionExpression(node)) return node.name?.text ?? ""; + if (!ts.isIdentifier(node)) return null; + const symbol = lowerer.resolveValueSymbol(node); + if (!symbol) return null; + for (const decl of lowerer.checker.declarationsOf(symbol)) { + if (ts.isFunctionDeclaration(decl)) return decl.name?.text ?? "default"; + if (ts.isVariableDeclaration(decl) && ts.isIdentifier(decl.name) && decl.initializer) { + let init = decl.initializer; + while (ts.isParenthesizedExpression(init)) init = init.expression; + if (ts.isFunctionExpression(init) && init.name) return init.name.text; + if (ts.isArrowFunction(init) || ts.isFunctionExpression(init)) return decl.name.text; + } + } + return null; +} + +/** Node's (name?, options?, fn?) argument forms (`Test#createSubtest`): a + * leading function is the body, a leading object the options, otherwise + * the first argument is the name and a following object the options. */ +function splitTestArgs( + lowerer: Lowerer, + expr: ts.CallExpression, + what: string, +): { nameNode?: ts.Expression; optionsNode?: ts.Expression; fnNode?: ts.Expression } { + const args = expr.arguments; + const isFn = (a: ts.Expression): boolean => + lowerer.checker.getCallSignatures(lowerer.typeOf(a)).length > 0; + const isOptions = (a: ts.Expression): boolean => ts.isObjectLiteralExpression(a); + const unsupported = (): never => + lowerer.noLowering( + `${what} with these arguments`, + expr, + "the supported forms are (name?, options?, fn?) with a string name, an options object literal, and a function", + ); + if (args.length > 3 || args.some(ts.isSpreadElement)) unsupported(); + const out: { nameNode?: ts.Expression; optionsNode?: ts.Expression; fnNode?: ts.Expression } = {}; + let i = 0; + const next = args[i]; + if (next !== undefined && !isFn(next) && !isOptions(next)) { + out.nameNode = next; + i++; + } + const options = args[i]; + if (options !== undefined && isOptions(options)) { + out.optionsNode = options; + i++; + } + const fn = args[i]; + if (fn !== undefined && isFn(fn)) { + out.fnNode = fn; + i++; + } + if (i < args.length) unsupported(); + return out; +} + +/** The test's name, as Node's Test constructor completes it: a missing or + * empty name takes the function's name, or "". */ +function lowerTestName( + lowerer: Lowerer, + expr: ts.CallExpression, + what: string, + nameNode: ts.Expression | undefined, + fnNode: ts.Expression | undefined, + loc: SrcLoc, +): IrExpr { + const fallback = (): string => { + const fnName = fnNode ? staticFunctionName(lowerer, fnNode) : ""; + if (fnName === null) { + lowerer.noLowering( + `${what} without a name, with a function whose name is not known at compile time`, + fnNode ?? expr, + "give the test a name, or pass a function expression or a declared function", + ); + } + return fnName || ""; + }; + if (!nameNode) return strLit(fallback(), loc); + const name = lowerer.lowerExprExpecting(nameNode, STRING); + if (name.kind === "strLit") return name.value === "" ? strLit(fallback(), loc) : name; + // A name computed at run time: when the function's own name is not known + // at compile time (a function passed in as a parameter, say), the name + // is used as given, and an empty one stays empty (the limitations page + // notes it). Refusing here would refuse every helper that registers + // tests for its callers. + if (fnNode && staticFunctionName(lowerer, fnNode) === null) return name; + const slot = lowerer.declareHiddenLocal("%testName", STRING); + const value = varRef(slot.id, STRING, loc); + return { + kind: "seqExpr", + stmts: [{ kind: "varDecl", localId: slot.id, init: name, loc }], + result: { + kind: "ternary", + cond: { kind: "strEq", negated: false, left: value, right: strLit("", loc), type: BOOL, loc }, + then: strLit(fallback(), loc), + else_: value, + type: STRING, + loc, + }, + type: STRING, + loc, + }; +} + function lowerRegistration( lowerer: Lowerer, expr: ts.CallExpression, @@ -293,21 +442,11 @@ function lowerRegistration( sub: IrExpr | null, // the lowered TestContext receiver (t.test) ): IrExpr { if (sub === null) requireStatementPosition(lowerer, expr, what); - const args = expr.arguments; - if (args.length < 1 || args.length > 3) { - lowerer.noLowering( - `${what} with ${args.length} arguments`, - expr, - "the supported forms are (name), (name, fn), and (name, options, fn)", - ); - } - let name = lowerer.lowerExprExpecting(args[0]!, STRING); + const { nameNode, optionsNode, fnNode } = splitTestArgs(lowerer, expr, what); + let name = lowerTestName(lowerer, expr, what, nameNode, fnNode, loc); let opts = noOptions(); - let fnNode: ts.Expression | undefined; - if (args.length === 2) fnNode = args[1]; - if (args.length === 3) { - opts = lowerTestOptions(lowerer, args[1]!, what, false); - fnNode = args[2]; + if (optionsNode) { + opts = lowerTestOptions(lowerer, optionsNode, what, false); // The name runs before the option values. if (opts.pre.length > 0 && name.kind !== "strLit") { const slot = lowerer.declareHiddenLocal("%testName", STRING); @@ -315,11 +454,9 @@ function lowerRegistration( name = { kind: "varRef", localId: slot.id, type: STRING, loc }; } } - if (methodMode === 1 || methodMode === 2) opts.mode = methodMode; if (methodMode === 3) opts.only = true; const at = strLit(atStringOf(expr), loc); - const mode = numLit(opts.mode, loc); - const msg = opts.msg ?? strLit("", loc); + const [mode, msg] = directiveOf(opts, methodMode, loc); const flagBits = optionFlags(opts); const planTimeout = [ numLit(opts.plan, loc), @@ -387,27 +524,21 @@ function lowerSuite( methodMode: number, ): IrExpr { requireStatementPosition(lowerer, expr, what); - const args = expr.arguments; - if (args.length < 2 || args.length > 3) { - lowerer.noLowering( - `${what} with ${args.length} arguments`, - expr, - "the supported forms are (name, fn) and (name, options, fn)", - ); - } - let name = lowerer.lowerExprExpecting(args[0]!, STRING); + const split = splitTestArgs(lowerer, expr, what); + let name = lowerTestName(lowerer, expr, what, split.nameNode, split.fnNode, loc); let opts = noOptions(); - let fnNode = args[1]!; - if (args.length === 3) { - opts = lowerTestOptions(lowerer, args[1]!, what, true); - fnNode = args[2]!; + if (split.optionsNode) { + opts = lowerTestOptions(lowerer, split.optionsNode, what, true); if (opts.pre.length > 0 && name.kind !== "strLit") { const slot = lowerer.declareHiddenLocal("%testName", STRING); opts.pre.unshift({ kind: "varDecl", localId: slot.id, init: name, loc }); name = { kind: "varRef", localId: slot.id, type: STRING, loc }; } } - if (methodMode === 1 || methodMode === 2) opts.mode = methodMode; + const fnNode = split.fnNode; + if (!fnNode) { + lowerer.noLowering(`${what} without a body`, expr, "give the suite a body function"); + } const cb = lowerer.lowerExpr(fnNode); // The body may take the SuiteContext (flag 2, as test bodies take theirs). if ( @@ -429,8 +560,7 @@ function lowerSuite( ); } const at = strLit(atStringOf(expr), loc); - const mode = numLit(opts.mode, loc); - const msg = opts.msg ?? strLit("", loc); + const [mode, msg] = directiveOf(opts, methodMode, loc); // describe.only and { only: true } mark the suite (flag 4, as for tests). const flags = numLit( optionFlags(opts) | (methodMode === 3 ? 4 : 0) | (cb.type.params.length === 1 ? 2 : 0), diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index 6493bc5f3..2fb266be0 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -845,7 +845,25 @@ type RuntimeFenceTarget = /** The Lowerer's pass configuration (see lowerToIr). */ export interface LowererMode { + /** Symbols whose deferred diagnostics reachable emit already flushed — + * the remainder must not report them a second time. */ + alreadyFlushed?: ReadonlySet; + /** LowerOptions.externalTypes, threaded through every lowering pass. */ + externalTypes?: ReadonlyMap; + /** LowerOptions.externalTypeSpecifiersByFile, shared by every pass. */ + externalTypeSpecifiersByFile?: ReadonlyMap; + /** Program-validated ambient declaration symbols for each FFI name. + * Undefined in discovery's legacy call-local validation path. */ + ffiBindingSymbols?: ReadonlyMap>; + /** The build's outbound native FFI declarations. */ + ffiImports?: readonly IrFfiImport[]; + /** Statically resolved child_process.fork program roots, in stable target-id order. */ + forkTargets?: readonly ts.SourceFile[]; frontendServices?: FrontendServices | undefined; + /** LoadResult.inlineRequireCycleCalls. */ + inlineRequireCycleCalls?: ReadonlySet | undefined; + /** LowerOptions.libraryCallbacks (see there). */ + libraryCallbacks?: boolean; projectCheckJs?: boolean; /** Names of bodies a prior reachability pass reached; null lowers everything. */ reachable?: ReadonlySet | null; @@ -853,35 +871,17 @@ export interface LowererMode { * always-reachable init bodies and module building, and report deferred * collection diagnostics nothing flushed. */ remainder?: boolean; - /** Symbols whose deferred diagnostics reachable emit already flushed — - * the remainder must not report them a second time. */ - alreadyFlushed?: ReadonlySet; - /** The build's target platform (LowerOptions.targetPlatform — lowerToIr - * passes it to every pass). Defaults to the host. */ - targetPlatform?: string; /** Node's startup refusal (preflight's resolution walk / named-import * link checks): %main opens with exactly this throw, before any * module init — Node refuses the whole graph before anything evaluates, * so nothing runs. */ startupCrash?: StartupCrash | null; - /** LoadResult.inlineRequireCycleCalls. */ - inlineRequireCycleCalls?: ReadonlySet | undefined; - /** The build's outbound native FFI declarations. */ - ffiImports?: readonly IrFfiImport[]; - /** LowerOptions.libraryCallbacks (see there). */ - libraryCallbacks?: boolean; - /** Statically resolved child_process.fork program roots, in stable target-id order. */ - forkTargets?: readonly ts.SourceFile[]; - workerTargets?: readonly ts.SourceFile[]; - /** Program-validated ambient declaration symbols for each FFI name. - * Undefined in discovery's legacy call-local validation path. */ - ffiBindingSymbols?: ReadonlyMap>; - /** LowerOptions.externalTypes, threaded through every lowering pass. */ - externalTypes?: ReadonlyMap; - /** LowerOptions.externalTypeSpecifiersByFile, shared by every pass. */ - externalTypeSpecifiersByFile?: ReadonlyMap; + /** The build's target platform (LowerOptions.targetPlatform — lowerToIr + * passes it to every pass). Defaults to the host. */ + targetPlatform?: string; /** LowerOptions.tierMode, shared by every pass. */ tierMode?: TierMode; + workerTargets?: readonly ts.SourceFile[]; } function directExternalTypeSpecifiersByFile( @@ -2925,6 +2925,32 @@ export class Lowerer { * pass skips it (attribution describes the build). */ readonly statsByFile = new Map(); + /** Statements and field initializers already in the coverage totals, and + * those already counted as failed. Speculative lowering can lower one + * function body several times: each receiver probe of + * `(function () { ... }).call(this)` lowers it. A source statement counts + * once however many times it lowers. */ + private readonly countedStatements = new Set(); + private readonly failedStatements = new Set(); + + /** Adds `node` to the coverage totals unless it is already counted (the + * caller checks suppressStats). */ + countStatement(node: ts.Node, file: string | undefined): void { + if (this.countedStatements.has(node)) return; + this.countedStatements.add(node); + this.stats.statementsTotal++; + if (file !== undefined) this.bumpFileStat(file, "total"); + } + + /** Counts `node` as failed unless it already is (the caller checks + * suppressStats). */ + countStatementFailed(node: ts.Node, file: string | undefined): void { + if (this.failedStatements.has(node)) return; + this.failedStatements.add(node); + this.stats.statementsFailed++; + if (file !== undefined) this.bumpFileStat(file, "failed"); + } + /** Bumps the per-file attribution counter (no-op unless provenance is * active and this is the emit/discovery lane — mirror the CALLER's * suppressStats guard, this method only gates remainder). */ @@ -2942,28 +2968,29 @@ export class Lowerer { readonly moduleOrder: ts.SourceFile[], mode: LowererMode = {}, ) { + // The mode's options, sorted by name; the values derived from them follow. + this.alreadyFlushed = mode.alreadyFlushed ?? new Set(); + this.externalTypes = mode.externalTypes ?? new Map(); + this.ffiBindingSymbols = mode.ffiBindingSymbols ?? null; + this.ffiImports = mode.ffiImports ?? []; + this.forkTargets = mode.forkTargets ?? []; this.frontendServices = mode.frontendServices; + this.inlineRequireCycleCalls = mode.inlineRequireCycleCalls ?? new Set(); + this.libraryCallbacks = mode.libraryCallbacks ?? false; + this.projectCheckJs = mode.projectCheckJs ?? false; this.reachable = mode.reachable ?? null; this.remainder = mode.remainder ?? false; - this.tierMode = mode.tierMode ?? "auto"; - this.alreadyFlushed = mode.alreadyFlushed ?? new Set(); - this.targetPlatform = mode.targetPlatform ?? process.platform; this.startupCrash = mode.startupCrash ?? null; - this.inlineRequireCycleCalls = mode.inlineRequireCycleCalls ?? new Set(); - this.projectCheckJs = mode.projectCheckJs ?? false; - this.ffiImports = mode.ffiImports ?? []; - this.libraryCallbacks = mode.libraryCallbacks ?? false; + this.targetPlatform = mode.targetPlatform ?? process.platform; + this.tierMode = mode.tierMode ?? "auto"; this.workerTargets = mode.workerTargets ?? []; this.workerTargets.forEach((sf, id) => { this.workerTargetIdByPath.set(tsgoPath(resolve(sf.fileName)), id); }); - this.forkTargets = mode.forkTargets ?? []; this.forkTargets.forEach((sf, id) => { this.forkTargetIdByPath.set(tsgoPath(resolve(sf.fileName)), id); }); this.ffiImportsByName = new Map(this.ffiImports.map((entry) => [entry.name, entry])); - this.ffiBindingSymbols = mode.ffiBindingSymbols ?? null; - this.externalTypes = mode.externalTypes ?? new Map(); this.externalTypeSpecifiersByFile = mode.externalTypeSpecifiersByFile ?? directExternalTypeSpecifiersByFile(this.externalTypes); this.checker = program.getTypeChecker(); diff --git a/packages/compiler/src/frontend/type-mapper.ts b/packages/compiler/src/frontend/type-mapper.ts index d61464b07..41d18a6a2 100644 --- a/packages/compiler/src/frontend/type-mapper.ts +++ b/packages/compiler/src/frontend/type-mapper.ts @@ -3267,8 +3267,9 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // Native collection iterators use checked runtime iterator objects. if (isStdlibInterface("MapIterator") || isStdlibInterface("SetIterator")) return DYN; // Array iterators (`list.values()` and friends) are generators over the - // live array: each step reads the current length and element. - if (isStdlibInterface("ArrayIterator")) { + // live array: each step reads the current length and element. String + // iterators (`s[Symbol.iterator]()`) are generators over the code points. + if (isStdlibInterface("ArrayIterator") || isStdlibInterface("StringIterator")) { const arg = checker.getTypeArguments(widened as ts.TypeReference)[0]; const yieldT = arg ? mapType(arg, ctx) : null; if (!yieldT || yieldT.kind === "void" || isUnitType(yieldT)) return null; diff --git a/packages/compiler/src/ir/builtin-signatures.ts b/packages/compiler/src/ir/builtin-signatures.ts index c034f3a2e..13c1e8871 100644 --- a/packages/compiler/src/ir/builtin-signatures.ts +++ b/packages/compiler/src/ir/builtin-signatures.ts @@ -182,6 +182,7 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ "dyn.iteratorCanStep": { argTypes: [DYN, DYN], result: BOOL }, "dyn.iteratorStep": { argTypes: [DYN], result: DYN }, "dyn.iteratorStepDone": { argTypes: [DYN], result: BOOL }, + "dyn.collectionIteratorIndex": { argTypes: [DYN], result: F64 }, "dyn.mapSeedEntries": { argTypes: [DYN], result: DYN }, "dyn.mapSeedEntry": { argTypes: [DYN], result: DYN }, /** The for-of-over-dyn pack accessors — the emitted index loop drives @@ -3427,6 +3428,8 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ "regex.groupEquals": { argTypes: [arrayOf(STRING), F64, STRING], result: BOOL }, "regex.groupNumber": { argTypes: [arrayOf(STRING), F64], result: F64 }, "regex.groupParseInt": { argTypes: [arrayOf(STRING), F64, F64], result: F64 }, + // `.index` / `.input` (args[1]) of a match result. Borrowed; never throws. + "regex.resultProp": { argTypes: [arrayOf(STRING), STRING], result: DYN }, // node:events EventEmitter: receivers are emitter-hierarchy objects and // the chaining forms (on/off/removeAll/setMax) return the receiver's // own class — program-dependent object types, checked in the libCall diff --git a/packages/compiler/src/ir/function-references.test.ts b/packages/compiler/src/ir/function-references.test.ts index ab4b05c8b..117bba24a 100644 --- a/packages/compiler/src/ir/function-references.test.ts +++ b/packages/compiler/src/ir/function-references.test.ts @@ -143,6 +143,7 @@ const FUNCTION_FIELDS: Record>, Role> = { "captures[].localId": "other", "classCaptures[].name": "other", "classCaptures[].localId": "other", + "generator.tag": "other", }; const MODULE_FIELDS: Record>, Role> = { diff --git a/packages/compiler/src/ir/int32-slots.ts b/packages/compiler/src/ir/int32-slots.ts index 7ea3d6bde..c4180fd5b 100644 --- a/packages/compiler/src/ir/int32-slots.ts +++ b/packages/compiler/src/ir/int32-slots.ts @@ -373,6 +373,7 @@ const DYN_READ_ONLY = new Set([ "dyn.iteratorResult", "dyn.iteratorStep", "dyn.iteratorStepDone", + "dyn.collectionIteratorIndex", "dyn.mapSeedEntries", "dyn.mapSeedEntry", "dyn.numberConstructor", diff --git a/packages/compiler/src/ir/intrinsic-signatures.ts b/packages/compiler/src/ir/intrinsic-signatures.ts index 3c224f8bc..eda569883 100644 --- a/packages/compiler/src/ir/intrinsic-signatures.ts +++ b/packages/compiler/src/ir/intrinsic-signatures.ts @@ -53,11 +53,17 @@ export const REGEX_INTRINSIC_SIGS: Record< matchGroups: { receiver: STRING, argTypes: [REGEX, F64, F64], result: VOID }, lastIndex: { receiver: REGEX, argTypes: [], result: F64 }, matchAll: { receiver: STRING, argTypes: [REGEX], result: arrayOf(arrayOf(STRING)) }, + matchAllCaptures: { receiver: STRING, argTypes: [REGEX], result: arrayOf(arrayOf(STRING)) }, matchAllInto: { receiver: STRING, argTypes: [REGEX, arrayOf(F64)], result: arrayOf(arrayOf(STRING)), }, + matchAllCapturesInto: { + receiver: STRING, + argTypes: [REGEX, arrayOf(F64)], + result: arrayOf(arrayOf(STRING)), + }, search: { receiver: STRING, argTypes: [REGEX], result: F64 }, source: { receiver: REGEX, argTypes: [], result: STRING }, flags: { receiver: REGEX, argTypes: [], result: STRING }, diff --git a/packages/compiler/src/ir/ir.ts b/packages/compiler/src/ir/ir.ts index 343f52617..d2e6129cd 100644 --- a/packages/compiler/src/ir/ir.ts +++ b/packages/compiler/src/ir/ir.ts @@ -1712,6 +1712,15 @@ export interface IrFunction { * iterator helper's source): `.return()` before the first `.next()` * closes it. */ closesSource?: true; + /** The @@toStringTag the object prints with (util.inspect, + * Object.prototype.toString) when it is not a generator object: + * "Iterator Helper", "Array Iterator", "String Iterator". A tag that + * starts with '!' marks a generator standing in for an object of + * another class, which those refuse to print. */ + tag?: string; + /** The first parameter is a generator this one adapts: it prints with + * that generator's tag. */ + inheritsTag?: true; }; /** Source function name for native error stack frames. */ sourceName?: string; @@ -2431,13 +2440,21 @@ export type IrRegexIntrinsicMethod = * unobservable across the lowered surface (strings are immutable; the * spec clones the regex at the call, so lastIndex games can't reach the * drain either). Non-global regexes THROW Node's exact TypeError - * (catchable — replaceAll's stance). */ + * (catchable — replaceAll's stance). Each row carries its index, input + * and groups properties. */ | "matchAll" + /** matchAll whose rows are read only through their elements + * (matchAllRowsReadByElements): the rows carry no index, input or + * groups properties. Same result and throw contract as matchAll. */ + | "matchAllCaptures" /** matchAll's companion-index form (the for-of-over-matchAll desugar): * args[1] is a number[] the drain ALSO fills with each match's UTF-16 * start index — the row's `.index`, always present (every drained row * matched). Same result and throw contract as matchAll. */ | "matchAllInto" + /** matchAllInto whose rows are read only through their elements and the + * companion index: the rows carry no index, input or groups properties. */ + | "matchAllCapturesInto" /** `s.search(re)` — the first match's UTF-16 index, or -1. Symbol.search * neither reads nor writes lastIndex (a fresh exec from position 0), so * no g/y fence applies: /g is irrelevant and /y anchors at 0 — exactly diff --git a/packages/compiler/src/ir/runtime-features.ts b/packages/compiler/src/ir/runtime-features.ts index c33c83e8e..482dc7fae 100644 --- a/packages/compiler/src/ir/runtime-features.ts +++ b/packages/compiler/src/ir/runtime-features.ts @@ -24,6 +24,11 @@ export interface RuntimeFeatures { assert: boolean; dynInvoke: boolean; dynAsync: boolean; + /** The program enters AsyncLocalStorage stores (store.run, enterWith, + * a diagnostics_channel store binding): its timer, immediate, tick and + * queueMicrotask scheduling goes through the runtime's *_als entry + * points, which carry the active stores into each callback. */ + asyncContext: boolean; inspect: boolean; childProcess: boolean; net: boolean; @@ -108,6 +113,16 @@ const DYN_ASYNC_LIB_FNS: ReadonlySet = new Set([ "dc.chanRunStores", ]); +/** The libCalls that enter AsyncLocalStorage stores (RuntimeFeatures + * asyncContext). */ +const ASYNC_CONTEXT_LIB_FNS: ReadonlySet = new Set([ + "als.run", + "als.exitRun", + "als.enterWith", + "dc.chanBindStore", + "dc.chanRunStores", +]); + const LEGACY_URL_LIB_FNS: ReadonlySet = new Set([ "url.legacyParse", "url.legacyFormat", @@ -123,6 +138,178 @@ const UTIL_CALLABLE_LIB_FNS: ReadonlySet = new Set([ "util.debuglog", ]); +/** Entry points that link the regex unit: the regex and glob matchers (fs.glob's walk compiles its patterns with the glob matcher), and readline's terminal unit (scr_readline_tty.c), which measures with the regex unit's stripVTControlCharacters. */ +const REGEX_LIB_FNS: ReadonlySet = new Set([ + "dyn.nativeRegexIs", + "fs.callbackGlob", + "fs.globSync", + "fs.globTypesSync", + "fsp.globError", + "fsp.globStrings", + "fsp.globTypes", + "path.matchesGlob", + "path.win32MatchesGlob", + "regexp.escape", + "rl.cursorPos", + "rl.keypressStdin", + "rl.keypressStream", + "stdin.onKeypress", + "util.stripVTControlCharacters", +]); + +/** TextDecoder entry points over the legacy encodings. */ +const LEGACY_TEXT_DECODER_LIB_FNS: ReadonlySet = new Set([ + "text.decodeLegacy", + "text.decodeLegacyOptions", + "text.decodeStream", +]); + +/** Signal-taking readline, stream and timers/promises entry points, which register abort listeners. */ +const ABORT_SIGNAL_LIB_FNS: ReadonlySet = new Set([ + "http.clientSignal", + "rl.createSignal", + "rl.questionAsyncSignal", + "sp.finishedOptsSignal", + "sp.pipelineOptsSignal", + "stream.addAbortSignal", + "stream.addAbortSignalFn", + "stream.finishedOptsSignal", + "tp.schedulerWaitSignal", + "tp.setImmediateSignal", + "tp.setTimeoutSignal", +]); + +/** Entry points that link the stream unit (with STREAM_LIB_PREFIXES). */ +const STREAM_LIB_FNS: ReadonlySet = new Set([ + "console.bind", + "crypto.hashStream", + "crypto.hashStreamPiped", + "fileHandle.createReadStream", + "fileHandle.createWriteStream", + "fs.createReadStream", + "fs.createWriteStream", + "sp.finished", + "sp.pipeline", + "sp.pipelineStages", + "stream.addAbortSignal", + "stream.addAbortSignalFn", + "stream.destroy", + "stream.destroyErr", + "stream.errored", + "stream.prop", + "stream.workerStdio", +]); + +/** libCall name prefixes that link the stream unit. */ +const STREAM_LIB_PREFIXES: readonly string[] = [ + "duplex.", + "fs.fsStream", + "passthrough.", + "readable.", + "sc.", + "sp.finishedOpts", + "sp.pipelineOpts", + "stream.finishedOpts", + "stream.pipelineStages", + "stream.set", + "transform.", + "writable.", + "zlib.stream", +]; + +/** The Readable operators and Readable.from over a compiled generator or a checked-dynamic iterator (scr_stream_ops.c), which await through the dyn-async unit (with READABLE_DYN_ASYNC_PREFIXES). */ +const READABLE_DYN_ASYNC_LIB_FNS: ReadonlySet = new Set([ + "readable.fromAsyncGen", + "readable.fromAsyncIter", + "readable.fromDone", + "readable.fromDyn", + "readable.fromIter", +]); + +/** libCall name prefixes of the Readable entry points that await through the dyn-async unit. */ +const READABLE_DYN_ASYNC_PREFIXES: readonly string[] = ["readable.fromGen", "readable.op"]; + +/** Entry points that link the dyn-invoke unit. */ +const DYN_INVOKE_LIB_FNS: ReadonlySet = new Set([ + "dyn.arrayProtoCall", + "dyn.arrayPrototype", + "dyn.builtinFunction", + "dyn.builtinMethod", + "dyn.defineProperty", + "dyn.defineProps", + "dyn.definePrototypeProps", + "dyn.functionApply", + "dyn.globalConstructor", + "dyn.objCreateWithProperties", + "dyn.objectTag", +]); + +/** Member names whose constant-key dynamic reads answer the inherited Object.prototype member through the dyn-invoke unit. Mirrors expr-dynamic.ts INHERITED_OBJECT_MEMBERS (the IR layer does not import the backend). */ +const INHERITED_MEMBER_NAMES: ReadonlySet = new Set([ + "constructor", + "hasOwnProperty", + "propertyIsEnumerable", + "toString", + "valueOf", +]); + +/** Entry points that link the child-process unit (with CHILD_PROCESS_LIB_PREFIXES): the fork IPC channel on the process object. */ +const CHILD_PROCESS_LIB_FNS: ReadonlySet = new Set([ + "process.connected", + "process.disconnect", + "process.forkTarget", + "process.onDisconnect", + "process.onMessage", + "process.send", + "process.sendCb", +]); + +/** libCall name prefixes that link the child-process unit. */ +const CHILD_PROCESS_LIB_PREFIXES: readonly string[] = ["child.", "cp.", "spawnRes.", "writer."]; + +/** Entry points that link the symbol unit, besides the sym. and disposable. families. */ +const SYMBOL_LIB_FNS: ReadonlySet = new Set([ + "dyn.usingDispose", + "dyn.usingMethod", + "util.isDeepStrictEqual", +]); + +/** Entry points that link the querystring unit. */ +const QS_LIB_FNS: ReadonlySet = new Set(["qs.parse", "qs.stringify", "qs.unescape"]); + +/** Entry points that link the parseArgs unit. */ +const PARSE_ARGS_LIB_FNS: ReadonlySet = new Set([ + "util.isDeepStrictEqual", + "util.parseArgs", + "util.styleText", +]); + +/** String methods whose Unicode tables live in the regex unit's libunicode. */ +const CASE_MAPPING_STR_METHODS: ReadonlySet = new Set([ + "normalize", + "toLowerCase", + "toUpperCase", +]); + +/** Array methods that link the copying unit. */ +const COPYING_ARR_METHODS: ReadonlySet = new Set([ + "toReversed", + "toSpliced", + "with", + "withUndefined", +]); + +/** Typed-array methods that link the copying unit. */ +const COPYING_BYTES_METHODS: ReadonlySet = new Set([ + "join", + "toArray", + "toReversed", + "with", +]); + +const startsWithAny = (fn: string, prefixes: readonly string[]): boolean => + prefixes.some((prefix) => fn.startsWith(prefix)); + /** Inspect types and expressions together in one traversal. This is a fresh * snapshot: callers may mutate an IR module between compilation passes. */ /** The checked-dynamic undefined value (dynUndefinedExpr). */ @@ -208,6 +395,7 @@ function scanRuntimeFeatures(mod: IrModule, stopAt?: keyof RuntimeFeatures): Run mod.functions.some( (fn) => fn.async === true && fn.generator === undefined && fn.returnType.kind === "dyn", ), + asyncContext: false, inspect: false, childProcess: false, net: false, @@ -239,130 +427,33 @@ function scanRuntimeFeatures(mod: IrModule, stopAt?: keyof RuntimeFeatures): Run const expr = (node: IrExpr): boolean => { if (node.kind === "libCall") { const fn = node.fn; - if ( - fn === "regexp.escape" || - fn === "dyn.nativeRegexIs" || - fn === "util.stripVTControlCharacters" || - fn === "path.matchesGlob" || - fn === "path.win32MatchesGlob" || - // fs.glob's walk compiles its patterns with the glob matcher. - fn === "fs.globSync" || - fn === "fs.globTypesSync" || - fn === "fs.callbackGlob" || - fn === "fsp.globStrings" || - fn === "fsp.globTypes" || - fn === "fsp.globError" - ) - features.regex = true; - if ( - fn === "text.decodeLegacy" || - fn === "text.decodeLegacyOptions" || - fn === "text.decodeStream" - ) - features.legacyTextDecoder = true; + if (REGEX_LIB_FNS.has(fn)) features.regex = true; + if (LEGACY_TEXT_DECODER_LIB_FNS.has(fn)) features.legacyTextDecoder = true; if (fn.startsWith("fetch.")) features.fetch = true; if (fn.startsWith("event.") || fn === "events.addAbortListener") features.eventTarget = true; - // Signal-taking readline, stream and timers/promises entry points - // register abort listeners. - if ( - fn === "rl.createSignal" || - fn === "rl.questionAsyncSignal" || - fn === "http.clientSignal" || - fn === "tp.setTimeoutSignal" || - fn === "tp.schedulerWaitSignal" || - fn === "tp.setImmediateSignal" || - fn === "stream.addAbortSignal" || - fn === "stream.addAbortSignalFn" || - fn === "sp.finishedOptsSignal" || - fn === "sp.pipelineOptsSignal" || - fn === "stream.finishedOptsSignal" - ) { - features.eventTarget = true; - } + if (ABORT_SIGNAL_LIB_FNS.has(fn)) features.eventTarget = true; if (PROCESS_EVENT_LIB_FNS.has(fn)) features.processEvents = true; - // readline's terminal unit (scr_readline_tty.c) measures with the - // regex unit's stripVTControlCharacters; with a completer it prints - // completion errors through the inspect unit. - if ( - fn === "rl.cursorPos" || - fn === "rl.keypressStdin" || - fn === "rl.keypressStream" || - fn === "stdin.onKeypress" - ) - features.regex = true; + // With a completer, readline's terminal unit prints completion errors + // through the inspect unit. if (fn === "rl.terminal") { features.regex = true; if (!isDynUndefined(node.args[3]!)) features.inspect = true; } if (fn.startsWith("emitter.")) features.emitter = true; - if ( - fn.startsWith("readable.") || - fn.startsWith("writable.") || - fn.startsWith("duplex.") || - fn.startsWith("transform.") || - fn.startsWith("passthrough.") || - fn.startsWith("sc.") || - fn.startsWith("stream.set") || - fn === "stream.destroy" || - fn === "stream.destroyErr" || - fn === "stream.prop" || - fn === "stream.workerStdio" || - fn === "stream.errored" || - fn === "sp.finished" || - fn === "sp.pipeline" || - fn === "sp.pipelineStages" || - fn.startsWith("stream.pipelineStages") || - fn === "console.bind" || - fn === "crypto.hashStream" || - fn === "crypto.hashStreamPiped" || - fn === "stream.addAbortSignal" || - fn === "stream.addAbortSignalFn" || - fn.startsWith("sp.finishedOpts") || - fn.startsWith("sp.pipelineOpts") || - fn.startsWith("stream.finishedOpts") || - fn.startsWith("zlib.stream") || - fn === "fs.createReadStream" || - fn === "fs.createWriteStream" || - fn === "fileHandle.createReadStream" || - fn === "fileHandle.createWriteStream" || - fn.startsWith("fs.fsStream") - ) { + if (STREAM_LIB_FNS.has(fn) || startsWithAny(fn, STREAM_LIB_PREFIXES)) { // the stream unit emits through the emitter unit, also when the // program constructs no stream (a pipeline of functions) features.stream = true; features.emitter = true; } if (fn.startsWith("zlib.")) features.zlib = true; - // The Readable operators and Readable.from over a compiled generator - // or a checked-dynamic iterator (scr_stream_ops.c) await through the - // dyn-async unit. - if ( - fn.startsWith("readable.op") || - fn.startsWith("readable.fromGen") || - fn === "readable.fromDyn" || - fn === "readable.fromIter" || - fn === "readable.fromAsyncIter" || - fn === "readable.fromAsyncGen" || - fn === "readable.fromDone" - ) + if (READABLE_DYN_ASYNC_LIB_FNS.has(fn) || startsWithAny(fn, READABLE_DYN_ASYNC_PREFIXES)) features.dynAsync = true; if (fn.startsWith("dc.")) features.dc = true; if (fn.startsWith("assert.")) features.assert = true; - if ( - fn === "dyn.defineProps" || - fn === "dyn.definePrototypeProps" || - fn === "dyn.defineProperty" || - fn === "dyn.objCreateWithProperties" || - fn === "dyn.arrayProtoCall" || - fn === "dyn.arrayPrototype" || - fn === "dyn.functionApply" || - fn === "dyn.builtinMethod" || - fn === "dyn.globalConstructor" || - fn === "dyn.objectTag" || - fn === "dyn.builtinFunction" - ) - features.dynInvoke = true; + if (DYN_INVOKE_LIB_FNS.has(fn)) features.dynInvoke = true; if (DYN_ASYNC_LIB_FNS.has(fn)) features.dynAsync = true; + if (ASYNC_CONTEXT_LIB_FNS.has(fn)) features.asyncContext = true; if ( fn.startsWith("insp.") || fn.startsWith("console.") || @@ -370,19 +461,7 @@ function scanRuntimeFeatures(mod: IrModule, stopAt?: keyof RuntimeFeatures): Run fn === "util.styleText" ) features.inspect = true; - if ( - fn.startsWith("cp.") || - fn.startsWith("child.") || - fn.startsWith("writer.") || - fn.startsWith("spawnRes.") || - fn === "process.forkTarget" || - fn === "process.connected" || - fn === "process.send" || - fn === "process.sendCb" || - fn === "process.disconnect" || - fn === "process.onMessage" || - fn === "process.onDisconnect" - ) + if (CHILD_PROCESS_LIB_FNS.has(fn) || startsWithAny(fn, CHILD_PROCESS_LIB_PREFIXES)) features.childProcess = true; if (fn.startsWith("net.")) features.net = true; if (fn.startsWith("http.")) { @@ -395,18 +474,12 @@ function scanRuntimeFeatures(mod: IrModule, stopAt?: keyof RuntimeFeatures): Run features.tls = true; } if (fn.startsWith("http2.") && !HTTP2_LEGACY_FNS.has(fn)) features.http2 = true; - if ( - fn.startsWith("sym.") || - fn === "util.isDeepStrictEqual" || - fn === "dyn.usingMethod" || - fn === "dyn.usingDispose" || - fn.startsWith("disposable.") - ) + if (fn.startsWith("sym.") || fn.startsWith("disposable.") || SYMBOL_LIB_FNS.has(fn)) features.symbol = true; if (fn === "process.hrtimeValue") features.bigint = true; if (fn.startsWith("bigint.")) features.bigint = true; if (fn.startsWith("sp.") || fn === "url.searchParams") features.searchParams = true; - if (fn === "qs.parse" || fn === "qs.stringify" || fn === "qs.unescape") features.qs = true; + if (QS_LIB_FNS.has(fn)) features.qs = true; // scr_url_legacy.c lowercases and normalizes hostnames (the regex // unit's libunicode), parses and stringifies queries, and emits its // deprecation warnings through the dyn-async unit. @@ -415,8 +488,7 @@ function scanRuntimeFeatures(mod: IrModule, stopAt?: keyof RuntimeFeatures): Run features.qs = true; features.dynAsync = true; } - if (fn === "util.parseArgs" || fn === "util.isDeepStrictEqual" || fn === "util.styleText") - features.parseArgs = true; + if (PARSE_ARGS_LIB_FNS.has(fn)) features.parseArgs = true; if (fn === "util.styleText") features.processEvents = true; // scr_util_callable.c: dyn wrappers over the descriptor, inspect, // regex (NODE_DEBUG matching) and terminal-color units. @@ -453,30 +525,13 @@ function scanRuntimeFeatures(mod: IrModule, stopAt?: keyof RuntimeFeatures): Run features.regex = true; break; case "strIntrinsic": - if ( - node.method === "toLowerCase" || - node.method === "toUpperCase" || - node.method === "normalize" - ) - features.regex = true; + if (CASE_MAPPING_STR_METHODS.has(node.method)) features.regex = true; break; case "arrIntrinsic": - if ( - node.method === "toReversed" || - node.method === "toSpliced" || - node.method === "with" || - node.method === "withUndefined" - ) - features.copying = true; + if (COPYING_ARR_METHODS.has(node.method)) features.copying = true; break; case "bytesIntrinsic": - if ( - node.method === "toReversed" || - node.method === "with" || - node.method === "join" || - node.method === "toArray" - ) - features.copying = true; + if (COPYING_BYTES_METHODS.has(node.method)) features.copying = true; break; case "dynInvoke": features.dynInvoke = true; @@ -884,13 +939,3 @@ export function moduleUsesTls(mod: IrModule): boolean { export function moduleUsesTlsCa(mod: IrModule): boolean { return scanRuntimeFeatures(mod, "tlsCa").tlsCa; } - -/** Mirrors expr-dynamic.ts INHERITED_OBJECT_MEMBERS (the IR layer does not - * import the backend). */ -const INHERITED_MEMBER_NAMES: ReadonlySet = new Set([ - "constructor", - "valueOf", - "toString", - "hasOwnProperty", - "propertyIsEnumerable", -]); diff --git a/packages/compiler/test/match-row-emission.test.ts b/packages/compiler/test/match-row-emission.test.ts new file mode 100644 index 000000000..39e07bc1c --- /dev/null +++ b/packages/compiler/test/match-row-emission.test.ts @@ -0,0 +1,69 @@ +import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { afterEach, expect, test } from "vitest"; +import { compile } from "../src/index.js"; + +const directories: string[] = []; +afterEach(async () => { + await Promise.all(directories.splice(0).map((dir) => rm(dir, { recursive: true, force: true }))); +}); + +async function emit(source: string): Promise { + const outDir = await mkdtemp(join(tmpdir(), "scriptc-match-rows-")); + directories.push(outDir); + const entry = join(outDir, "main.ts"); + await writeFile(entry, source); + const outPath = join(outDir, "program.ll"); + const result = await compile(entry, { entry, outDir, outPath, outputKind: "llvm" }); + expect(result.ok, JSON.stringify(result)).toBe(true); + return readFile(outPath, "utf8"); +} + +const calls = (llvm: string, symbol: string): number => + llvm.split("\n").filter((line) => line.includes("call ") && line.includes(`@${symbol}(`)).length; + +test("rows read only through their elements carry no match metadata", async () => { + const llvm = await emit(` +const text = "k1=v1; k2=v2;"; +const re = /(?k\\d)=(?v\\d);/g; +for (const m of text.matchAll(re)) console.log(m.index, m[1], m.groups!.val, 1 in m); +console.log([...text.matchAll(re)].map((m) => m[2]).join(",")); +console.log([...text.matchAll(re)].filter((m) => m.length > 2).find((m) => m[1] === "k2")?.[2]); +console.log(Array.from(text.matchAll(re), ({ groups }) => groups!.key).join(",")); +const rows = [...text.matchAll(re)]; +for (const row of rows.slice(1)) console.log(row[0]); +`); + expect(calls(llvm, "scr_regex_match_all_captures_into")).toBe(1); + expect(calls(llvm, "scr_regex_match_all_captures")).toBe(4); + expect(calls(llvm, "scr_regex_match_all")).toBe(0); + expect(calls(llvm, "scr_regex_match_all_into")).toBe(0); +}); + +test("rows whose properties are read or that escape keep match metadata", async () => { + const llvm = await emit(` +const text = "k1=v1; k2=v2;"; +const re = /(?k\\d)=(?v\\d);/g; +for (const m of text.matchAll(re)) console.log(m.input.length); +console.log([...text.matchAll(re)].map((m) => m.index).join(",")); +function first(m: RegExpExecArray): string { + return m[1]!; +} +console.log([...text.matchAll(re)].map(first).join(",")); +console.log([...text.matchAll(re)].map((m) => m)[0]![1]); +`); + expect(calls(llvm, "scr_regex_match_all_into")).toBe(1); + expect(calls(llvm, "scr_regex_match_all")).toBe(3); + expect(calls(llvm, "scr_regex_match_all_captures")).toBe(0); + expect(calls(llvm, "scr_regex_match_all_captures_into")).toBe(0); +}); + +test(".index and .input on a match slice read its match source", async () => { + const llvm = await emit(` +const rows = [..."a1b2".matchAll(/[a-z](\\d)/g)]; +console.log(rows[1]!.index, rows[0]!.input); +const m = /b/.exec("ab")!; +console.log(m.index); +`); + expect(calls(llvm, "scr_regex_result_prop")).toBe(3); +}); diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5815-als-callback-context.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5815-als-callback-context.ts.json new file mode 100644 index 000000000..f192a6b22 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5815-als-callback-context.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/5815-als-callback-context.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6010-match-metadata.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6010-match-metadata.ts.json new file mode 100644 index 000000000..d2b199768 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6010-match-metadata.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6010-match-metadata.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6011-match-metadata-js.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6011-match-metadata-js.js.json new file mode 100644 index 000000000..437b28911 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6011-match-metadata-js.js.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6011-match-metadata-js.js" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6071-iterator-sources.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6071-iterator-sources.ts.json new file mode 100644 index 000000000..2745f7978 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6071-iterator-sources.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6071-iterator-sources.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6072-iterator-sources-js.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6072-iterator-sources-js.js.json new file mode 100644 index 000000000..e89c895e4 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6072-iterator-sources-js.js.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6072-iterator-sources-js.js" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6073-stored-collection-helpers.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6073-stored-collection-helpers.ts.json new file mode 100644 index 000000000..6d9eeff70 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6073-stored-collection-helpers.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6073-stored-collection-helpers.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6074-stored-collection-helpers-js.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6074-stored-collection-helpers-js.js.json new file mode 100644 index 000000000..fbbaf0df4 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6074-stored-collection-helpers-js.js.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6074-stored-collection-helpers-js.js" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6075-print-iterators.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6075-print-iterators.ts.json new file mode 100644 index 000000000..04adc830d --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6075-print-iterators.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6075-print-iterators.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6076-print-iterators-js.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6076-print-iterators-js.js.json new file mode 100644 index 000000000..255e32494 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6076-print-iterators-js.js.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6076-print-iterators-js.js" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6200-http-agent-free-socket-data.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6200-http-agent-free-socket-data.ts.json new file mode 100644 index 000000000..e8df3e384 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6200-http-agent-free-socket-data.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6200-http-agent-free-socket-data.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/string-iterator-helpers.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/string-iterator-helpers.ts.json deleted file mode 100644 index 0a716bec5..000000000 --- a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/string-iterator-helpers.ts.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "order": [ - "/tests/diagnostics/string-iterator-helpers.ts" - ], - "diags": [] -} diff --git a/packages/runtime/src/scr_alloc.c b/packages/runtime/src/scr_alloc.c index 2212a1a36..246d27f0a 100644 --- a/packages/runtime/src/scr_alloc.c +++ b/packages/runtime/src/scr_alloc.c @@ -638,6 +638,7 @@ static _Thread_local ScrSaKeep *scr_sa_keep; /* while the worker's memory is kep static void scr_sa_keep_begin(unsigned i); static void scr_sa_keep_drained(ScrSaKeep *k, ScrSaBlock *b, unsigned c); static bool scr_sa_drain(void); +static bool scr_sa_drain_some(bool one); #endif /* Moves every block other threads freed into this arena's free lists. */ @@ -672,8 +673,8 @@ static bool scr_sa_reserve(void) { if (claimed == UINT32_MAX) { #ifdef SCR_SA_TRIM /* Arenas held by discards that wait (see scr_sa_thread_park): this - * thread discards them and claims one. */ - if (scr_sa_drain()) { + * thread discards one of them and claims it. */ + if (scr_sa_drain_some(true)) { scr_sa_refused = false; claimed = atomic_load_explicit(&scr_sa_claimed, memory_order_relaxed); continue; @@ -934,6 +935,12 @@ static void scr_sa_trim(void) { * interrupts every running thread of the process to flush its TLB, and * glibc's trim locks the arenas other threads allocate from, so discards * wait for the last worker of a group, which hands them to one helper; + * - the helper waits SCR_SA_DISCARD_DELAY_MS (100 by default) and stops if + * a worker started meanwhile: a program that starts its next workers or + * exits within that time does not return pages it would fault back in or + * never touch again, and the last worker of the next group hands the + * discards to a new helper; a thread that finds every arena claimed + * discards one waiting arena and claims it; * - the helper then returns the pages * it carved (MADV_DONTNEED; the start of the first page, which it may * share with older blocks, is cleared), frees the recorded system blocks, @@ -1276,11 +1283,13 @@ static void scr_sa_defer_discard(unsigned i, ScrSaKeep *k) { pthread_mutex_unlock(&scr_sa_pending_lock); } -/* Runs the waiting discards on the calling thread, which ends with no arena; - * returns whether there were any. */ -static bool scr_sa_drain(void) { +/* Runs the waiting discards on the calling thread, which ends with no arena: + * all of them, or only the first one when `one` is set; returns whether + * there were any. */ +static bool scr_sa_drain_some(bool one) { bool any = false; for (;;) { + if (one && any) return true; pthread_mutex_lock(&scr_sa_pending_lock); uint32_t pending = scr_sa_pending; unsigned i = pending ? (unsigned)__builtin_ctz(pending) : 0; @@ -1298,9 +1307,30 @@ static bool scr_sa_drain(void) { } } +static bool scr_sa_drain(void) { return scr_sa_drain_some(false); } + +/* SCR_SA_DISCARD_DELAY_MS: how long the trim helper waits after the last + * worker parks before it returns their memory (default 100). */ +static long scr_sa_discard_delay_ms(void) { + const char *delay = getenv("SCR_SA_DISCARD_DELAY_MS"); + if (!delay) return 100; + long ms = strtol(delay, NULL, 10); + return ms < 0 ? 0 : ms; +} + +/* Waits out the delay first, and stops if a worker starts meanwhile: the + * discards then wait for the last worker of that group. A program that exits + * within the delay does not spend its last milliseconds returning pages, and + * one that starts its next workers within it does not return pages while + * they start. */ static void *scr_sa_drain_main(void *arg) { (void)arg; - scr_sa_drain(); + for (long left = scr_sa_discard_delay_ms(); left > 0; left -= 5) { + struct timespec pause = {0, (left < 5 ? left : 5) * 1000000L}; + nanosleep(&pause, NULL); + if (atomic_load_explicit(&scr_sa_workers_live, memory_order_acquire) != 0) return NULL; + } + if (atomic_load_explicit(&scr_sa_workers_live, memory_order_acquire) == 0) scr_sa_drain(); return NULL; } diff --git a/packages/runtime/src/scr_alloc.test.c b/packages/runtime/src/scr_alloc.test.c index f0efdc935..aa6b0b60b 100644 --- a/packages/runtime/src/scr_alloc.test.c +++ b/packages/runtime/src/scr_alloc.test.c @@ -627,7 +627,7 @@ static void *small_worker(void *arg) { /* Discards wait while another worker runs, and the last worker to park * starts them. When every arena is held by a discard that waits, a worker - * that needs one runs them itself. */ + * that needs one discards one of them and claims it. */ static void check_deferred_discard(void) { size_t page = (size_t)sysconf(_SC_PAGESIZE); atomic_store(&discard_step, 0); @@ -656,6 +656,55 @@ static void check_deferred_discard(void) { discard_in_old_arena = true; } +/* A plain thread that holds an arena until discard_step reaches 3. */ +static void *holding_thread(void *arg) { + (void)arg; + void *p = scr_mem_alloc(DISCARD_SIZE); + assert(p && owned(p)); + atomic_store(&discard_step, 1); + wait_step(3); + scr_mem_free(p); + scr_sa_thread_park(); + return NULL; +} + +/* The helper waits SCR_SA_DISCARD_DELAY_MS before it returns the pages, and + * stops if a worker starts meanwhile; the last worker of that group starts + * the discards again. A plain thread holds the arena whose slice the earlier + * checks filled, so the worker carves a slice of its own. */ +static void check_discard_delay(void) { + size_t page = (size_t)sysconf(_SC_PAGESIZE); + setenv("SCR_SA_DISCARD_DELAY_MS", "300", 1); + atomic_store(&discard_step, 0); + pthread_t plain; + assert(pthread_create(&plain, NULL, holding_thread, NULL) == 0); + wait_step(1); + discard_shares = false; + discard_in_old_arena = false; + pthread_t t; + assert(pthread_create(&t, NULL, discard_worker, NULL) == 0); + assert(pthread_join(t, NULL) == 0); + atomic_store(&discard_step, 0); + pthread_t holder; + assert(pthread_create(&holder, NULL, holding_worker, NULL) == 0); + wait_step(1); + /* Past the delay: the helper stopped when the holder started. */ + struct timespec pause = {0, 600000000L}; + nanosleep(&pause, NULL); + uintptr_t first = (discard_lo + page - 1) & ~(uintptr_t)(page - 1); + size_t total = (discard_hi - first) / page; + assert(resident_pages(first, discard_hi) > total / 2); + for (size_t i = 0; i < DISCARD_BLOCKS; i += 4099) verify(discard_blocks[i], block_size, (unsigned char)i); + setenv("SCR_SA_DISCARD_DELAY_MS", "0", 1); + atomic_store(&discard_step, 3); + assert(pthread_join(holder, NULL) == 0); + assert(pthread_join(plain, NULL) == 0); + scr_sa_wait_trims(); + unsetenv("SCR_SA_DISCARD_DELAY_MS"); + assert(resident_pages(first, discard_hi) < total / 2); + discard_in_old_arena = true; +} + static void check_discard(void) { discard_blocks = malloc(DISCARD_BLOCKS * sizeof *discard_blocks); assert(discard_blocks); @@ -750,6 +799,7 @@ static void check_discard(void) { free(trim_blocks); check_deferred_discard(); + check_discard_delay(); for (unsigned i = 0; i < DISCARD_OLD; i++) scr_mem_free(old_blocks[i]); free(discard_blocks); } diff --git a/packages/runtime/src/scr_array.c b/packages/runtime/src/scr_array.c index 4ef24e906..1f4efc321 100644 --- a/packages/runtime/src/scr_array.c +++ b/packages/runtime/src/scr_array.c @@ -40,6 +40,8 @@ static void scr_arr_oom(void) { /* Readers of the out-of-line storage see an all-empty one until the first * sparse entry, noncanonical property or metadata allocates it. */ static const ScrArrExt scr_arr_no_ext; + +SCR_TL bool (*scr_arr_match_copy)(ScrArr *a, ScrDyn *target) = NULL; #define SCR_AX(a) ((const ScrArrExt *)((a)->ext ? (a)->ext : &scr_arr_no_ext)) ScrArrExt *scr_arr_ext(ScrArr *a) { @@ -4423,6 +4425,10 @@ ScrStr *scr_str_raw(ScrArr *raw, ScrArr *subs) { } void scr_arr_copy_metadata(ScrArr *a, ScrDyn *target) { + const ScrDyn *metadata = SCR_AX(a)->metadata; + if (metadata && metadata->kind == SCR_DYN_HANDLE && metadata->v.handle.tag == SCR_DYNH_MATCH_SOURCE && + scr_arr_match_copy(a, target)) + return; if (!SCR_AX(a)->metadata) return; for (size_t i = 0; i < SCR_AX(a)->metadata->v.obj.len; i++) { ScrDynEntry *entry = &SCR_AX(a)->metadata->v.obj.entries[i]; diff --git a/packages/runtime/src/scr_array.h b/packages/runtime/src/scr_array.h index d02c2097e..5289f8c13 100644 --- a/packages/runtime/src/scr_array.h +++ b/packages/runtime/src/scr_array.h @@ -105,7 +105,12 @@ typedef struct ScrArrExt { struct ScrArrProp *props; /* noncanonical numeric properties */ size_t prop_len; size_t prop_cap; - struct ScrDyn *metadata; /* heterogeneous named properties of match results */ + /* Heterogeneous named properties of match results. Until a result's + * metadata is first needed, this is the match source handle that the + * results of one exec or matchAll call share (SCR_DYNH_MATCH_SOURCE, + * scr_regex.c), and match_index is the result's UTF-16 start index. */ + struct ScrDyn *metadata; + double match_index; } ScrArrExt; /* The LLVM backend mirrors ScrArr as %ScrArr: its inline retain/release read * `cyc` (field 4, i8) to decide mark-live / candidate buffering exactly like @@ -207,6 +212,11 @@ static inline ScrArr *scr_arr_retain(ScrArr *a) { * the candidate-root buffer like every headered type. */ void scr_arr_release(ScrArr *a); void scr_arr_copy_metadata(ScrArr *a, struct ScrDyn *target); +/* Installed by scr_regex.c when it records a pending match source; this + * unit links without the regex runtime. Writes the pending metadata of + * `a` into its dynamic copy or materializes it, answering whether it + * wrote. */ +extern SCR_TL bool (*scr_arr_match_copy)(ScrArr *a, struct ScrDyn *target); /* The array's out-of-line storage, allocated (zeroed) on first use. */ ScrArrExt *scr_arr_ext(ScrArr *a); /* Moves inline dense slots (kept in the header block of a small array) to diff --git a/packages/runtime/src/scr_async.c b/packages/runtime/src/scr_async.c index f630ee5f9..583b1f6d0 100644 --- a/packages/runtime/src/scr_async.c +++ b/packages/runtime/src/scr_async.c @@ -694,6 +694,26 @@ static inline void *scr_fiber_region_capture(void) { return regions != NULL ? regions->capture() : NULL; } +/* enterWith on the main stack lasts until that code returns to the loop, + * so the store does not reach callbacks scheduled without it: the loop + * clears the main slot when the main module's body returns, and an + * enterWith inside a callback the loop runs puts a tick that clears it at + * the front of the tick queue, so it runs as soon as that callback + * returns. */ +static SCR_TL bool scr_loop_started = false; + +static void scr_als_main_clear(void) { + scr_als_ctx_release(scr_als_main_slot); + scr_als_main_slot = NULL; +} + +static void scr_next_tick_raw_first(void (*fn)(void)); + +void scr_als_entered_with(void) { + if (scr_loop_started && SCR_ALS_SLOT() == &scr_als_main_slot) + scr_next_tick_raw_first(scr_als_main_clear); +} + static SCR_TL ScrCtx scr_loop_ctx; /* the main stack (scheduler home) */ static SCR_TL ScrFiber *scr_current = NULL; /* A finished fiber whose last switch went to the loop (scr_resume_fiber). */ @@ -1168,6 +1188,17 @@ void scr_next_tick_raw(void (*fn)(void)) { scr_nt_tail = t; } +/* A raw tick at the front of the queue: it runs before the ticks already + * queued. */ +static void scr_next_tick_raw_first(void (*fn)(void)) { + ScrNtick *t = calloc(1, sizeof *t); + if (!t) scr_oom(); + t->raw = fn; + t->next = scr_nt_head; + scr_nt_head = t; + if (scr_nt_tail == NULL) scr_nt_tail = t; +} + void scr_nticks_teardown(void) { while (scr_nt_head != NULL) { ScrNtick *t = scr_nt_head; @@ -3731,6 +3762,54 @@ void scr_queue_microtask(ScrClosure *cb) { scr_ready_push(f); } +/* ── scheduling in an AsyncLocalStorage context ─────────────────────── + * Programs that enter AsyncLocalStorage stores schedule their timer, + * interval, setImmediate, process.nextTick and queueMicrotask callbacks + * through these entry points (the emitter picks them when the program + * uses the store API), so each callback runs with the stores that were + * active when it was scheduled, as Node's async resources capture them. + * With a snapshot active, the callback is wrapped in a closure that + * installs it around the call; other programs keep the plain entry + * points. */ +static void *scr_als_ctx_retain_v(void *c) { return scr_als_ctx_retain(c); } +static void scr_als_ctx_release_v(void *c) { scr_als_ctx_release(c); } + +static void scr_als_bound_fire(ScrClosure *self) { + ScrClosure *cb = scr_box_get_ref(self->caps[0]); + ScrAlsCtx **slot = SCR_ALS_SLOT(); + ScrAlsCtx *prev = *slot; + *slot = scr_box_get_ref(self->caps[1]); /* the slot owns this reference */ + ((void (*)(ScrClosure *))cb->fn)(cb); + /* An enterWith inside the callback replaced the slot's snapshot; either + * way the slot's reference is released here. */ + slot = SCR_ALS_SLOT(); + scr_als_ctx_release(*slot); + *slot = prev; + scr_closure_release(cb); +} + +static ScrClosure *scr_als_bind(ScrClosure *cb /*moves*/) { + ScrAlsCtx *active = *SCR_ALS_SLOT(); + if (active == NULL) return cb; + ScrClosure *bound = scr_closure_new((void *)scr_als_bound_fire, 2); + bound->caps[0] = scr_box_new(SCR_BOX_FUNC); + scr_box_set_ref(bound->caps[0], cb); + bound->caps[1] = scr_box_new_obj(scr_als_ctx_retain_v, scr_als_ctx_release_v, NULL); + scr_box_set_ref(bound->caps[1], scr_als_ctx_retain(active)); + return bound; +} + +void scr_set_timeout_als(ScrClosure *cb, double ms) { scr_set_timeout(scr_als_bind(cb), ms); } +double scr_set_interval_als(ScrClosure *cb, double ms) { + return scr_set_interval(scr_als_bind(cb), ms); +} +double scr_set_timeout_handle_als(ScrClosure *cb, double ms) { + return scr_set_timeout_handle(scr_als_bind(cb), ms); +} +double scr_set_immediate_als(ScrClosure *cb) { return scr_set_immediate(scr_als_bind(cb)); } +void scr_next_tick_als(ScrClosure *cb) { scr_next_tick(scr_als_bind(cb)); } +void scr_queue_microtask_als(ScrClosure *cb) { scr_queue_microtask(scr_als_bind(cb)); } + /* The checked-dynamic argument form (JS files — common.mustCall wrappers * and the suite's invalid-input probes): a non-function throws Node's * ERR_INVALID_ARG_TYPE synchronously; a function value is called with @@ -3743,16 +3822,28 @@ static void scr_micro_dyn_fire(ScrClosure *c) { scr_dyn_release(fn); } -void scr_queue_microtask_dyn(const ScrDyn *cb) { +/* The closure that calls a dyn function value, or NULL with Node's + * ERR_INVALID_ARG_TYPE pending for a non-function. */ +static ScrClosure *scr_micro_dyn_closure(const ScrDyn *cb) { if (cb->kind != SCR_DYN_FUNC) { static const char msg[] = "The \"callback\" argument must be of type function."; scr_throw_error_msg_code(SCR_ERR_TYPE, msg, sizeof msg - 1, "ERR_INVALID_ARG_TYPE"); - return; + return NULL; } ScrClosure *c = scr_closure_new((void *)scr_micro_dyn_fire, 1); c->caps[0] = scr_box_new_obj(scr_dyn_retain_v, scr_dyn_release_v, NULL); scr_box_set_ref(c->caps[0], scr_dyn_retain((ScrDyn *)cb)); - scr_queue_microtask(c); + return c; +} + +void scr_queue_microtask_dyn(const ScrDyn *cb) { + ScrClosure *c = scr_micro_dyn_closure(cb); + if (c) scr_queue_microtask(c); +} + +void scr_queue_microtask_dyn_als(const ScrDyn *cb) { + ScrClosure *c = scr_micro_dyn_closure(cb); + if (c) scr_queue_microtask_als(c); } /* ── the event hooks (scr_events.c) ────────────────────────────────── @@ -4520,6 +4611,9 @@ static SCR_TL bool scr_loop_cjs_entry = false; void scr_loop_commonjs_entry(void) { scr_loop_cjs_entry = true; } bool scr_loop_run(ScrPromise *top_level) { + /* The main module's body has returned: an enterWith it made ends here. */ + scr_loop_started = true; + if (scr_als_main_slot != NULL) scr_als_main_clear(); bool first_checkpoint = !scr_loop_cjs_entry; bool rejection_failed; for (;;) { @@ -4892,6 +4986,7 @@ struct ScrGen { void (*drop_args)(void *); /* scr_gen_set_unstarted_return's hook; NULL for most generators. */ void (*unstarted_return)(void *); + const char *tag; /* scr_gen_set_tag's; NULL is the default */ bool is_async; void (*settle_async)(ScrGen *, ScrPromise *); ScrAsyncGenRequest *requests_head; @@ -5453,6 +5548,18 @@ void scr_gen_resume(ScrGen *g) { void scr_gen_set_unstarted_return(ScrGen *g, void (*hook)(void *)) { g->unstarted_return = hook; } +void scr_gen_set_tag(ScrGen *g, const char *tag) { g->tag = tag; } + +void scr_gen_copy_tag(ScrGen *g, const ScrGen *from) { g->tag = from->tag; } + +const char *scr_gen_tag(const ScrGen *g) { + return g->tag ? g->tag : g->is_async ? "AsyncGenerator" : "Generator"; +} + +__attribute__((constructor)) static void scr_gen_tag_reader_init(void) { + scr_gen_tag_reader = scr_gen_tag; +} + void scr_gen_close(ScrGen *g) { scr_gen_slot_reset(&g->ret); scr_gen_resume_return(g); diff --git a/packages/runtime/src/scr_async.h b/packages/runtime/src/scr_async.h index efa98e942..96421ca1c 100644 --- a/packages/runtime/src/scr_async.h +++ b/packages/runtime/src/scr_async.h @@ -158,6 +158,8 @@ ScrAlsCtx **scr_als_slot(void); #endif ScrAlsCtx *scr_als_ctx_retain(ScrAlsCtx *c); void scr_als_ctx_release(ScrAlsCtx *c); +/* After an enterWith: on the main stack, queues the tick that ends it. */ +void scr_als_entered_with(void); double scr_als_new(void); ScrDyn *scr_als_get(double id); /* +1; the undefined value when unset */ ScrAlsCtx *scr_als_enter(double id, ScrDyn *value); /* borrows value; prev out (owned) */ @@ -409,6 +411,7 @@ double scr_set_immediate(ScrClosure *cb); /* cb ownership moves in */ * synchronously on a non-function; borrowed. */ void scr_queue_microtask(ScrClosure *cb); void scr_queue_microtask_dyn(const ScrDyn *cb); +void scr_queue_microtask_dyn_als(const ScrDyn *cb); /* setImmediate AS A dyn VALUE (the traceCallback shape): a minted dyn * callable that schedules args[0](args[1..]) on the immediate queue and * answers undefined; a non-function first argument throws Node's @@ -428,6 +431,15 @@ bool scr_immediate_has_ref(double handle); * teardown (they must never run yet must not leak). cb ownership moves * in. */ void scr_next_tick(ScrClosure *cb); +/* The scheduling entry points of programs that enter AsyncLocalStorage + * stores: each callback runs with the stores active when it was scheduled + * (scr_async.c). */ +void scr_set_timeout_als(ScrClosure *cb, double ms); +double scr_set_interval_als(ScrClosure *cb, double ms); +double scr_set_timeout_handle_als(ScrClosure *cb, double ms); +double scr_set_immediate_als(ScrClosure *cb); +void scr_next_tick_als(ScrClosure *cb); +void scr_queue_microtask_als(ScrClosure *cb); /* A successful process.stdout/stderr.write completion on that same queue. * The callback and its program-shaped Error | null adapter both MOVE into * the entry; the adapter is invoked with NULL (Node's success argument). */ diff --git a/packages/runtime/src/scr_async_dyn.c b/packages/runtime/src/scr_async_dyn.c index ffcb85050..7611378fb 100644 --- a/packages/runtime/src/scr_async_dyn.c +++ b/packages/runtime/src/scr_async_dyn.c @@ -120,6 +120,7 @@ void scr_als_restore(ScrAlsCtx *prev) { void scr_als_enter_with(double id, ScrDyn *value) { ScrAlsCtx *prev = scr_als_enter(id, value); scr_als_ctx_release(prev); /* no restore point — Node's enterWith */ + scr_als_entered_with(); } void scr_als_disable(double id) { diff --git a/packages/runtime/src/scr_dyn.h b/packages/runtime/src/scr_dyn.h index 33dce0fe6..140b16d16 100644 --- a/packages/runtime/src/scr_dyn.h +++ b/packages/runtime/src/scr_dyn.h @@ -117,76 +117,82 @@ typedef enum { /* The handle-type tags the checked-dynamic tree can carry. The set is deliberately the * HANDLE kinds whose member surfaces already have complete static * lowerings (the http/net receiver surface); new kinds join by adding a - * tag + an ops registration in their owning unit. */ + * tag + an ops registration in their owning unit. + * + * Tags are sorted by name and every value is explicit and permanent: the + * LLVM backend hardcodes some of them (DYN_HANDLE_TAG_NUM in + * packages/compiler/src/backend/llvm/dyn.ts). A new tag goes in its sorted + * place with the next value after the largest one in use, below + * SCR_DYNH_CAPACITY. packages/compiler/src/backend/llvm/dyn-handle-tags.test.ts + * checks that values are unique and in range and that the backend's + * numbers match. */ typedef enum { - SCR_DYNH_HTTP_REQ, /* ScrHttpReq — IncomingMessage (server req & client res) */ - SCR_DYNH_HTTP_RES, /* ScrHttpRes — ServerResponse */ - SCR_DYNH_NET_SOCKET, /* ScrNetSocket — net.Socket */ - SCR_DYNH_NET_SERVER, /* ScrNetServer — net.Server (http.Server rides the same handle) */ - SCR_DYNH_H2_SESSION, /* ScrH2Session — Http2Session (client & server) */ - SCR_DYNH_H2_STREAM, /* ScrH2Stream — Http2Stream (client & server) */ - SCR_DYNH_HTTP_CLIENT, /* ScrHttpClientReq — http.ClientRequest */ - SCR_DYNH_HTTP_AGENT, /* ScrHttpAgent — http.Agent / https.Agent */ - SCR_DYNH_ABORT_SIGNAL, /* native static-fetch AbortSignal */ - SCR_DYNH_WEB_STREAM, /* native WHATWG ReadableStream */ - SCR_DYNH_WEB_READER, /* native ReadableStreamDefaultReader */ - SCR_DYNH_WEB_CONTROLLER, /* native ReadableStreamDefaultController */ - SCR_DYNH_FETCH_RESPONSE, /* native static-fetch Response */ - SCR_DYNH_FETCH_HEADERS, /* native static-fetch response Headers */ - SCR_DYNH_EVENT, /* ScrEvent — new Event() and AbortSignal's abort events */ - SCR_DYNH_ABORT_CONTROLLER, /* native static-fetch AbortController */ - SCR_DYNH_CHILD, /* child_process.ChildProcess */ - SCR_DYNH_FILE_HANDLE, /* fs/promises.FileHandle */ - SCR_DYNH_ARRAY_BUFFER, /* fixed-length ArrayBuffer storage */ - SCR_DYNH_SEGMENTER, /* default Unicode grapheme segmentation */ - SCR_DYNH_SEGMENTS, /* iterable immutable segmentation result */ - SCR_DYNH_BUILTIN_MODULE, /* native builtin export object */ - SCR_DYNH_GLOBAL, /* native global object identity */ - SCR_DYNH_WEAK_MAP, /* native weak-key metadata */ - SCR_DYNH_WEAK_SET, /* native weak membership */ - SCR_DYNH_SET, /* native Set, shared backing map */ - SCR_DYNH_REGEXP, /* native RegExp, shared compiled pattern */ - SCR_DYNH_STDIO, /* stable process stdin/stdout/stderr values */ - SCR_DYNH_URL, /* native WHATWG URL */ - SCR_DYNH_MAP, /* native Map, shared backing */ - SCR_DYNH_DATE, /* JavaScript Date with stable native identity */ - SCR_DYNH_ITERATOR, /* live native array/string/byte iterator */ - SCR_DYNH_CONSOLE, /* stored global console value */ - SCR_DYNH_SEARCH_PARAMS, /* live WHATWG URLSearchParams */ - SCR_DYNH_FETCH_REQUEST, - SCR_DYNH_WORKER, - SCR_DYNH_MESSAGE_PORT, - SCR_DYNH_SHARED_ARRAY_BUFFER, - SCR_DYNH_CONSOLE_INSTANCE, /* node:console Console instance */ - SCR_DYNH_EVENT_TARGET, /* ScrEventTarget — new EventTarget() */ - SCR_DYNH_CUSTOM_EVENT, /* ScrEvent with detail — new CustomEvent() */ - SCR_DYNH_BLOB, /* ScrBlob — new Blob() and Blob#slice() */ - SCR_DYNH_FILE, /* ScrBlob with a name — new File() */ - SCR_DYNH_KEY_OBJECT, /* node:crypto secret KeyObject (scr_lib.c) */ - SCR_DYNH_DIR, /* fs.Dir (scr_lib.c) */ - SCR_DYNH_CIPHER, /* node:crypto Cipheriv (scr_cipher.c) */ - SCR_DYNH_DECIPHER, /* node:crypto Decipheriv (scr_cipher.c) */ - SCR_DYNH_NET_SOCKET_ADDRESS, /* net.SocketAddress (scr_net.c) */ - SCR_DYNH_NET_BLOCK_LIST, /* net.BlockList (scr_net.c) */ - SCR_DYNH_DISPOSABLE_STACK, /* DisposableStack (scr_symbol.c) */ - SCR_DYNH_MESSAGE_EVENT, /* ScrEvent with data — BroadcastChannel's 'message' */ - SCR_DYNH_BROADCAST_CHANNEL, /* worker_threads BroadcastChannel (scr_worker_events.c) */ - SCR_DYNH_HASH, /* node:crypto Hash (scr_lib.c) */ - SCR_DYNH_HMAC, /* node:crypto Hmac (scr_lib.c) */ - SCR_DYNH_ITERATOR_HELPER, /* an iterator helper object over a checked iterator (scr_json.c) */ - SCR_DYNH_COUNT, + SCR_DYNH_ABORT_CONTROLLER = 15, /* native static-fetch AbortController */ + SCR_DYNH_ABORT_SIGNAL = 8, /* native static-fetch AbortSignal */ + SCR_DYNH_ARRAY_BUFFER = 18, /* fixed-length ArrayBuffer storage */ + SCR_DYNH_BLOB = 41, /* ScrBlob — new Blob() and Blob#slice() */ + SCR_DYNH_BROADCAST_CHANNEL = 51, /* worker_threads BroadcastChannel (scr_worker_events.c) */ + SCR_DYNH_BUILTIN_MODULE = 21, /* native builtin export object */ + SCR_DYNH_CHILD = 16, /* child_process.ChildProcess */ + SCR_DYNH_CIPHER = 45, /* node:crypto Cipheriv (scr_cipher.c) */ + SCR_DYNH_CONSOLE = 32, /* stored global console value */ + SCR_DYNH_CONSOLE_INSTANCE = 38, /* node:console Console instance */ + SCR_DYNH_CUSTOM_EVENT = 40, /* ScrEvent with detail — new CustomEvent() */ + SCR_DYNH_DATE = 30, /* JavaScript Date with stable native identity */ + SCR_DYNH_DECIPHER = 46, /* node:crypto Decipheriv (scr_cipher.c) */ + SCR_DYNH_DIR = 44, /* fs.Dir (scr_lib.c) */ + SCR_DYNH_DISPOSABLE_STACK = 49, /* DisposableStack (scr_symbol.c) */ + SCR_DYNH_EVENT = 14, /* ScrEvent — new Event() and AbortSignal's abort events */ + SCR_DYNH_EVENT_TARGET = 39, /* ScrEventTarget — new EventTarget() */ + SCR_DYNH_FETCH_HEADERS = 13, /* native static-fetch response Headers */ + SCR_DYNH_FETCH_REQUEST = 34, + SCR_DYNH_FETCH_RESPONSE = 12, /* native static-fetch Response */ + SCR_DYNH_FILE = 42, /* ScrBlob with a name — new File() */ + SCR_DYNH_FILE_HANDLE = 17, /* fs/promises.FileHandle */ + SCR_DYNH_GLOBAL = 22, /* native global object identity */ + SCR_DYNH_H2_SESSION = 4, /* ScrH2Session — Http2Session (client & server) */ + SCR_DYNH_H2_STREAM = 5, /* ScrH2Stream — Http2Stream (client & server) */ + SCR_DYNH_HASH = 52, /* node:crypto Hash (scr_lib.c) */ + SCR_DYNH_HMAC = 53, /* node:crypto Hmac (scr_lib.c) */ + SCR_DYNH_HTTP_AGENT = 7, /* ScrHttpAgent — http.Agent / https.Agent */ + SCR_DYNH_HTTP_CLIENT = 6, /* ScrHttpClientReq — http.ClientRequest */ + SCR_DYNH_HTTP_REQ = 0, /* ScrHttpReq — IncomingMessage (server req & client res) */ + SCR_DYNH_HTTP_RES = 1, /* ScrHttpRes — ServerResponse */ + SCR_DYNH_ITERATOR = 31, /* live native array/string/byte iterator */ + SCR_DYNH_ITERATOR_HELPER = 54, /* an iterator helper object over a checked iterator (scr_json.c) */ + SCR_DYNH_KEY_OBJECT = 43, /* node:crypto secret KeyObject (scr_lib.c) */ + SCR_DYNH_MAP = 29, /* native Map, shared backing */ + SCR_DYNH_MATCH_SOURCE = 55, /* a match result's pending metadata (scr_regex.c) */ + SCR_DYNH_MESSAGE_EVENT = 50, /* ScrEvent with data — BroadcastChannel's 'message' */ + SCR_DYNH_MESSAGE_PORT = 36, + SCR_DYNH_NET_BLOCK_LIST = 48, /* net.BlockList (scr_net.c) */ + SCR_DYNH_NET_SERVER = 3, /* ScrNetServer — net.Server (http.Server rides the same handle) */ + SCR_DYNH_NET_SOCKET = 2, /* ScrNetSocket — net.Socket */ + SCR_DYNH_NET_SOCKET_ADDRESS = 47, /* net.SocketAddress (scr_net.c) */ + SCR_DYNH_REGEXP = 26, /* native RegExp, shared compiled pattern */ + SCR_DYNH_SEARCH_PARAMS = 33, /* live WHATWG URLSearchParams */ + SCR_DYNH_SEGMENTER = 19, /* default Unicode grapheme segmentation */ + SCR_DYNH_SEGMENTS = 20, /* iterable immutable segmentation result */ + SCR_DYNH_SET = 25, /* native Set, shared backing map */ + SCR_DYNH_SHARED_ARRAY_BUFFER = 37, + SCR_DYNH_STDIO = 27, /* stable process stdin/stdout/stderr values */ + SCR_DYNH_URL = 28, /* native WHATWG URL */ + SCR_DYNH_WEAK_MAP = 23, /* native weak-key metadata */ + SCR_DYNH_WEAK_SET = 24, /* native weak membership */ + SCR_DYNH_WEB_CONTROLLER = 11, /* native ReadableStreamDefaultController */ + SCR_DYNH_WEB_READER = 10, /* native ReadableStreamDefaultReader */ + SCR_DYNH_WEB_STREAM = 9, /* native WHATWG ReadableStream */ + SCR_DYNH_WORKER = 35, } ScrDynHandleTag; /* Every per-tag table (scr_dynh_ops in scr_json.c, the message clone hooks - * in scr_message.c) is sized by this fixed capacity, never by - * SCR_DYNH_COUNT. Appending a tag for a link-gated unit therefore changes - * no always-linked storage or code: a program that never boxes the new - * handle links byte-identical objects. Raising the capacity is a - * program-wide layout change; do it in a change of its own, measured as - * neutral, and only when the enum outgrows it. */ + * in scr_message.c) is sized by this fixed capacity, never by the number of + * tags. Adding a tag for a link-gated unit therefore changes no + * always-linked storage or code: a program that never boxes the new handle + * links byte-identical objects. Raising the capacity is a program-wide + * layout change; do it in a change of its own, measured as neutral, and + * only when the tags outgrow it. */ #define SCR_DYNH_CAPACITY 128 -_Static_assert(SCR_DYNH_COUNT <= SCR_DYNH_CAPACITY, - "ScrDynHandleTag outgrew SCR_DYNH_CAPACITY: raise it in its own change"); typedef struct ScrDyn ScrDyn; typedef struct ScrBytes ScrBytes; /* full definition below (C11 repeat) */ @@ -713,6 +719,16 @@ ScrDyn *scr_dyn_iterator_result(ScrDyn *value); bool scr_dyn_iterator_can_step(const ScrDyn *iterator, const ScrDyn *next); ScrDyn *scr_dyn_iterator_step(const ScrDyn *iterator); bool scr_dyn_iterator_step_done(const ScrDyn *iterator); +/* A native Map or Set iterator stepped by the compiler's typed walk: the + * next live entry index of its collection, or -1 once the collection is + * exhausted (the iterator then ends its iteration). */ +double scr_dyn_collection_iterator_index(const ScrDyn *iterator); +/* For util.inspect: a native iterator's kind (0 array or typed array, + * 1 string, 2 Map, 3 Set, 4 a handle's), its selection (0 values, 1 keys, + * 2 entries), its collection (NULL once exhausted, and for kinds other + * than 2 and 3) and the index its next step reads from. */ +void scr_dyn_native_iterator_state(const ScrDyn *iterator, unsigned *kind, unsigned *selection, + ScrMap **collection, double *index); /* The ordinary object snapshot plus a retained typed source. source_access * releases that source when materialize=false and returns a fresh snapshot * when true. */ @@ -892,6 +908,9 @@ ScrDyn *scr_dyn_array_prototype_base(void); /* +1, shared native array identity * non-object kinds throw Node's catchable TypeErrors (strict-mode * wording). All three operands BORROWED (the value is retained in). */ void scr_dyn_key_set(ScrDyn *recv, ScrStr *key, ScrDyn *value); +/* A named (non-index) property of an extensible, unfrozen ARR, stored as + * scr_dyn_key_set stores it. Takes ownership of value. */ +void scr_dyn_arr_named_put(ScrDyn *arr, const char *key, size_t key_len, ScrDyn *value); void scr_dyn_key_set_computed(ScrDyn *recv, ScrDyn *key, ScrDyn *value); void scr_dyn_key_delete(ScrDyn *recv, const ScrStr *key, bool strict); ScrDyn *scr_dyn_global_symbol_get(ScrSym *key); /* borrowed key; +1 value */ diff --git a/packages/runtime/src/scr_generator.h b/packages/runtime/src/scr_generator.h index a92cd3222..c4397e021 100644 --- a/packages/runtime/src/scr_generator.h +++ b/packages/runtime/src/scr_generator.h @@ -63,6 +63,20 @@ ScrGen *scr_async_gen_new(void (*entry)(ScrFiber *, void *), void *argpack, /* Runs when return() reaches the generator before its first resume, with * the packed arguments still alive: an iterator helper closes its source. */ void scr_gen_set_unstarted_return(ScrGen *g, void (*hook)(void *)); +/* The @@toStringTag that util.inspect and Object.prototype.toString print + * for the generator object: "Generator" (or "AsyncGenerator") unless set, + * as the compiler does for iterator helpers ("Iterator Helper") and for + * the array and string iterators it builds as generators. A tag that + * starts with '!' marks a generator standing in for an object of another + * class, which these refuse to print. */ +void scr_gen_set_tag(ScrGen *g, const char *tag); +/* An adapter generator prints with the tag of the generator it adapts. */ +void scr_gen_copy_tag(ScrGen *g, const ScrGen *from); +const char *scr_gen_tag(const ScrGen *g); +/* scr_gen_tag for units that also link without the generator runtime (a + * library links none): the generator runtime sets it at startup, and only + * then can a generator capsule exist. */ +extern const char *(*scr_gen_tag_reader)(const ScrGen *g); ScrGen *scr_gen_retain(ScrGen *g); void scr_gen_release(ScrGen *g); /* NULL-tolerant */ void *scr_gen_retain_v(void *g); diff --git a/packages/runtime/src/scr_http.c b/packages/runtime/src/scr_http.c index f34cc18bb..83a1b3583 100644 --- a/packages/runtime/src/scr_http.c +++ b/packages/runtime/src/scr_http.c @@ -3323,6 +3323,13 @@ static void scr_http_conn_pump(ScrHttpConn *conn) { static void scr_http_conn_data(void *ctx, const char *buf, size_t n) { ScrHttpConn *conn = (ScrHttpConn *)ctx; + if (conn->pool_agent != NULL) { + /* Data on an idle pooled socket: the agent destroys the socket, as + * Node's free-socket read guard does, so the bytes are never read as + * the next request's response. */ + scr_net_sock_destroy(conn->sock); + return; + } if (conn->len + n > conn->cap) { size_t cap = conn->cap ? conn->cap : 8192; while (cap < conn->len + n) cap *= 2; diff --git a/packages/runtime/src/scr_inspect.c b/packages/runtime/src/scr_inspect.c index 4aa703711..f72d7aca8 100644 --- a/packages/runtime/src/scr_inspect.c +++ b/packages/runtime/src/scr_inspect.c @@ -1058,6 +1058,99 @@ static ScrStr *scr_insp_weak_collection(const char *label, double recurse, doubl return ib_take(&out); } +/* A native iterator: the array and string iterators print Node's empty + * `Object [Array Iterator] {}`; the Map and Set iterators print the + * entries their next steps would produce, under Node's labels + * (`[Set Iterator] { 1, 2 }`, `[Map Entries] { [ 'a', 1 ] }`), without + * advancing. NULL for a handle's iterator (kind 4), which keeps the + * unsupported answer. */ +static ScrStr *scr_insp_native_iterator(ScrDyn *d, double recurse, double depth) { + unsigned kind, selection; + ScrMap *map; + double index; + scr_dyn_native_iterator_state(d, &kind, &selection, &map, &index); + if (kind == 0) return scr_str_new("Object [Array Iterator] {}", 26); + if (kind == 1) return scr_str_new("Object [String Iterator] {}", 27); + if (kind != 2 && kind != 3) return NULL; + bool pairs = selection == 2; + const char *label = kind == 3 ? (pairs ? "[Set Entries]" : "[Set Iterator]") + : (pairs ? "[Map Entries]" : "[Map Iterator]"); + if (recurse > depth) { + InspBuf out = {0}; + ib_cstr(&out, "[Object "); + ib_cstr(&out, label); + ib_char(&out, ']'); + return ib_take(&out); + } + size_t remaining = 0; + if (map) { + for (double i = scr_map_iter_skip(map, index); i < scr_map_iter_count(map); i++) + if (scr_map_iter_live(map, i)) remaining++; + } + if (remaining == 0) { + InspBuf out = {0}; + ib_cstr(&out, label); + ib_cstr(&out, " { }"); + return ib_take(&out); + } + ScrStr *base = scr_str_new(label, strlen(label)); + scr_insp_begin(recurse + 1); + scr_map_iter_enter(map); + size_t shown = 0; + for (double i = scr_map_iter_skip(map, index); i < scr_map_iter_count(map) && shown < 100; i++) { + if (!scr_map_iter_live(map, i)) continue; + shown++; + ScrDyn *item; + if (pairs) { + item = scr_dyn_new_arr(); + scr_dyn_arr_push(item, scr_map_dyn_key(map, i)); + scr_dyn_arr_push(item, kind == 3 ? scr_map_dyn_key(map, i) : scr_map_dyn_value(map, i)); + } else { + item = kind == 2 && selection == 0 ? scr_map_dyn_value(map, i) : scr_map_dyn_key(map, i); + } + ScrStr *text = scr_insp_dyn(item, recurse + 1, depth); + scr_dyn_release(item); + scr_insp_entry(text, false); + scr_str_release(text); + } + scr_map_iter_exit(map); + if (remaining > shown) { + ScrStr *more = scr_insp_more_items((double)(remaining - shown)); + scr_insp_entry(more, false); + scr_str_release(more); + } + ScrStr *b0 = scr_str_new("{", 1); + ScrStr *b1 = scr_str_new("}", 1); + ScrStr *out = scr_insp_end(base, b0, b1, recurse + 1, false, false); + scr_str_release(base); + scr_str_release(b0); + scr_str_release(b1); + return out; +} + +/* A compiled generator object: Node's empty `Object [] {}`, with the + * tag the compiler set (scr_gen_set_tag). A generator standing in for an + * object of another class (a tag starting with '!') throws the + * "not supported yet" Error instead. */ +static bool scr_insp_is_generator(const ScrDyn *d) { + const char *key = d->v.typed_ref.type_key; + return !strncmp(key, "generator<", 10) || !strncmp(key, "async-generator<", 16); +} + +static ScrStr *scr_insp_generator(const ScrDyn *d) { + const char *tag = scr_gen_tag_reader(d->v.typed_ref.ptr); + if (tag[0] == '!') { + static const char message[] = "util.inspect of this iterator object is not supported yet"; + scr_throw_error_msg(SCR_ERR_ERROR, message, sizeof message - 1); + return scr_str_new("", 0); + } + InspBuf out = {0}; + ib_cstr(&out, "Object ["); + ib_cstr(&out, tag); + ib_cstr(&out, "] {}"); + return ib_take(&out); +} + static ScrStr *scr_insp_dyn_collection(ScrDyn *d, double recurse, double depth, bool is_map) { ScrMap *map = d->v.handle.ptr; const char *label = is_map ? "Map" : "Set"; @@ -1309,6 +1402,10 @@ ScrStr *scr_insp_dyn(ScrDyn *d, double recurse, double depth) { return scr_insp_weak_collection(d->v.handle.tag == SCR_DYNH_WEAK_MAP ? "WeakMap" : "WeakSet", recurse, depth); if (d->v.handle.tag == SCR_DYNH_DATE) return scr_insp_date(scr_dyn_native_date_value(d)); + if (d->v.handle.tag == SCR_DYNH_ITERATOR) { + ScrStr *iterator = scr_insp_native_iterator(d, recurse, depth); + if (iterator) return iterator; + } const ScrDynHandleOps *ops = scr_dyn_handle_ops_of(d); if (ops->inspect_view) { /* Node's custom inspectors answer the bare class name beyond the @@ -1349,6 +1446,7 @@ ScrStr *scr_insp_dyn(ScrDyn *d, double recurse, double depth) { } case SCR_DYN_TYPED_REF: { + if (scr_insp_is_generator(d)) return scr_insp_generator(d); const char *cls = NULL; ScrDyn *view = scr_event_ref_hooks ? scr_event_ref_hooks->inspect_view(d, &cls) : NULL; if (view) { diff --git a/packages/runtime/src/scr_json.c b/packages/runtime/src/scr_json.c index 0376ece2c..7accb818b 100644 --- a/packages/runtime/src/scr_json.c +++ b/packages/runtime/src/scr_json.c @@ -1678,6 +1678,8 @@ ScrDyn *scr_dyn_symbol_get(const ScrDyn *value, const ScrStr *key) { * later, and it neither grows nor owns heap references. */ static ScrDyn scr_dyn_small_ints[384]; +const char *(*scr_gen_tag_reader)(const ScrGen *g) = NULL; + __attribute__((constructor)) static void scr_dyn_small_ints_init(void) { for (int i = 0; i < 384; i++) { scr_dyn_small_ints[i].rc = SIZE_MAX; @@ -4673,6 +4675,31 @@ bool scr_dyn_iterator_step_done(const ScrDyn *value) { return ((ScrNativeIterator *)value->v.handle.ptr)->step_done; } +void scr_dyn_native_iterator_state(const ScrDyn *value, unsigned *kind, unsigned *selection, + ScrMap **collection, double *index) { + const ScrNativeIterator *iterator = value->v.handle.ptr; + *kind = iterator->kind; + *selection = iterator->selection; + *collection = (iterator->kind == 2 || iterator->kind == 3) && iterator->source + ? iterator->source->v.handle.ptr : NULL; + *index = iterator->index; +} + +double scr_dyn_collection_iterator_index(const ScrDyn *value) { + ScrNativeIterator *iterator = value->v.handle.ptr; + ScrDyn *source = iterator->source; + if (!source) return -1; + ScrMap *map = source->v.handle.ptr; + iterator->index = scr_map_iter_skip(map, iterator->index); + if (iterator->index >= scr_map_iter_count(map)) { + iterator->source = NULL; + scr_map_iter_exit(map); + scr_dyn_release(source); + return -1; + } + return iterator->index++; +} + static int scr_ih_method_index(const char *name, size_t len); static ScrDyn *scr_iterator_helper_method(ScrDyn *self, int index, ScrDyn *const *args, size_t argc); static ScrDyn *scr_ih_method_function(int index); @@ -6202,6 +6229,20 @@ ScrStr *scr_dyn_object_tag(const ScrDyn *d) { } if (d->kind == SCR_DYN_TYPED_REF) { if (scr_dyn_ref_is_error(d)) return scr_str_new("[object Error]", 14); + if (scr_dyn_generator(d)) { + const char *gen_tag = scr_gen_tag_reader(d->v.typed_ref.ptr); + if (gen_tag[0] == '!') { + static const char message[] = "Object.prototype.toString of this iterator is not supported yet"; + scr_throw_error_msg(SCR_ERR_ERROR, message, sizeof message - 1); + return NULL; + } + ScrJsonBuf b; + scr_jb_init(&b); + scr_jb_puts(&b, "[object "); + scr_jb_puts(&b, gen_tag); + scr_jb_putc(&b, ']'); + return scr_jb_finish(&b); + } ScrDyn *materialized = scr_dyn_typed_ref_materialize(d); ScrStr *out = scr_dyn_object_tag(materialized); scr_dyn_release(materialized); @@ -6234,6 +6275,13 @@ ScrStr *scr_dyn_object_tag(const ScrDyn *d) { scr_jb_putc(&b, ']'); return scr_jb_finish(&b); } + if (d->v.handle.tag == SCR_DYNH_ITERATOR && ((const ScrNativeIterator *)d->v.handle.ptr)->kind < 4) { + static const char *const tags[] = { + "[object Array Iterator]", "[object String Iterator]", "[object Map Iterator]", "[object Set Iterator]", + }; + const char *it_tag = tags[((const ScrNativeIterator *)d->v.handle.ptr)->kind]; + return scr_str_new(it_tag, strlen(it_tag)); + } if (scr_dyn_handle_ops_of(d)->to_string_tag) { ScrJsonBuf b; scr_jb_init(&b); @@ -7863,6 +7911,14 @@ static void scr_dyn_object_key_set(ScrDyn *recv, ScrStr *key, ScrDyn *value, con return; } +void scr_dyn_arr_named_put(ScrDyn *arr, const char *key, size_t key_len, ScrDyn *value) { + if (!arr->v.arr.properties) { + scr_dyn_region_store(arr, NULL); /* a table that later holds anything */ + arr->v.arr.properties = scr_dyn_new_obj(); + } + scr_dyn_obj_set(arr->v.arr.properties, key, key_len, value); +} + void scr_dyn_key_set(ScrDyn *recv, ScrStr *key, ScrDyn *value) { if (scr_dyn_class_reflection_fence(recv)) return; if (recv->kind == SCR_DYN_PROXY) { scr_dyn_proxy_set(recv, key, value); return; } diff --git a/packages/runtime/src/scr_message.test.c b/packages/runtime/src/scr_message.test.c index 0c6bd7ae3..a6c940783 100644 --- a/packages/runtime/src/scr_message.test.c +++ b/packages/runtime/src/scr_message.test.c @@ -175,7 +175,7 @@ static void check_documents(void) { /* A link-gated unit's handle crosses threads through the transfer hooks in * its ops: share on the sender, adopt on the receiver, drop with the * message. The test borrows the last capacity slot, which no unit uses, so - * it also proves that slots past SCR_DYNH_COUNT work. */ + * it also proves that slots past the tags in use work. */ #define TRANSFER_TAG ((ScrDynHandleTag)(SCR_DYNH_CAPACITY - 1)) typedef struct { _Atomic size_t rc; diff --git a/packages/runtime/src/scr_regex.c b/packages/runtime/src/scr_regex.c index a2e591b0e..44a91410b 100644 --- a/packages/runtime/src/scr_regex.c +++ b/packages/runtime/src/scr_regex.c @@ -610,6 +610,138 @@ static ScrStr *scr_regex_span(const ScrRegexInput *input, int start, int end) { return scr_str_new((const char *)input->data + start, (size_t)(end - start)); } +/* ── match metadata ───────────────────────────────────────────────────── */ + +/* Results of one exec or matchAll call share their subject and regex + * through one match source handle, which each result's metadata slot + * holds with its start index until the metadata object (index, input, + * groups) is first needed: when the result is converted to a dynamic + * value or its groups are read. Results read only through their elements + * never allocate it, and releasing a result releases the handle with the + * rest of its metadata. */ +typedef struct ScrRegexMatchSource { + size_t rc; + ScrStr *input; + ScrRegex *re; + ScrDyn *input_value; /* `input` as a dynamic value, built on first use */ +} ScrRegexMatchSource; + +static bool scr_regex_copy_pending_metadata(ScrArr *a, ScrDyn *target); + +static void *scr_regex_source_retain_v(void *p) { + ((ScrRegexMatchSource *)p)->rc++; + return p; +} + +static void scr_regex_source_release_v(void *p) { + ScrRegexMatchSource *src = p; + if (--src->rc != 0) return; + scr_str_release(src->input); + scr_regex_release(src->re); + scr_dyn_release(src->input_value); + free(src); +} + +/* The +1 match source handle of one exec or matchAll call. */ +static ScrDyn *scr_regex_source_new(ScrStr *s, ScrRegex *re) { + static const ScrDynHandleOps ops = { + "MatchSource", &scr_regex_source_retain_v, &scr_regex_source_release_v, NULL, NULL, NULL, NULL, + }; + scr_dyn_handle_install(SCR_DYNH_MATCH_SOURCE, &ops); + scr_arr_match_copy = &scr_regex_copy_pending_metadata; + ScrRegexMatchSource *src = malloc(sizeof *src); + if (!src) scr_regex_oom(); + src->rc = 0; + src->input = scr_str_retain(s); + src->re = scr_regex_retain(re); + src->input_value = NULL; + return scr_dyn_new_handle(src, SCR_DYNH_MATCH_SOURCE); +} + +/* Records the match at UTF-16 `index` as the row's pending metadata. */ +static void scr_regex_defer_metadata(ScrArr *row, ScrDyn *source, int index) { + ScrArrExt *x = scr_arr_ext(row); + x->metadata = scr_dyn_retain(source); + x->match_index = (double)index; +} + +/* The pending match source of a result, or NULL once its metadata exists. */ +static ScrRegexMatchSource *scr_regex_pending(const ScrArr *a) { + const ScrDyn *metadata = a->ext ? a->ext->metadata : NULL; + if (!metadata || metadata->kind != SCR_DYN_HANDLE || metadata->v.handle.tag != SCR_DYNH_MATCH_SOURCE) + return NULL; + return metadata->v.handle.ptr; +} + +/* The subject as a dynamic value, shared by the source's results. +1. */ +static ScrDyn *scr_regex_source_input(ScrRegexMatchSource *src) { + if (!src->input_value) src->input_value = scr_dyn_new_str(src->input); + return scr_dyn_retain(src->input_value); +} + +/* groups is a null-prototype object mapping each group name to its first + * participating capture (the row's own string, shared) or undefined, and + * undefined itself for a pattern without named groups. The row holds each + * capture as a string or a present undefined. +1. */ +static ScrDyn *scr_regex_groups_of(const ScrArr *a, uint8_t *bc) { + const char *groupnames = lre_get_groupnames(bc); + if (!groupnames) return scr_dyn_retain(scr_dyn_undefined()); + int capture_count = lre_get_capture_count(bc); + ScrDyn *groups = scr_dyn_new_obj_null_proto(); + const char *name = groupnames; + for (int k = 1; k < capture_count; k++) { + size_t length = strlen(name); + bool matched = (size_t)k < a->len && (size_t)k < a->cap && a->present[k] == SCR_ARR_VALUE; + /* ES2025 duplicates sit in distinct alternatives: at most one + * participates, and it wins over an earlier undefined. */ + if (length && (matched || !scr_dyn_obj_get(groups, name, length))) + scr_dyn_obj_set(groups, name, length, + matched ? scr_dyn_new_str((ScrStr *)(uintptr_t)a->data[k]) + : scr_dyn_retain(scr_dyn_undefined())); + name += length + LRE_GROUP_NAME_TRAILER_LEN; + } + return groups; +} + +static void scr_regex_materialize_metadata(ScrArr *a) { + ScrRegexMatchSource *src = scr_regex_pending(a); + if (!src) return; + ScrArrExt *x = a->ext; + ScrDyn *source = x->metadata; + ScrDyn *props = scr_dyn_new_obj(); + scr_dyn_obj_set(props, "index", 5, scr_dyn_new_num(x->match_index)); + scr_dyn_obj_set(props, "input", 5, scr_regex_source_input(src)); + scr_dyn_obj_set(props, "groups", 6, scr_regex_groups_of(a, scr_regex_bc(src->re))); + x->metadata = props; + scr_dyn_release(source); +} + +static bool scr_regex_copy_pending_metadata(ScrArr *a, ScrDyn *target) { + ScrRegexMatchSource *src = scr_regex_pending(a); + if (target->kind != SCR_DYN_ARR || target->v.arr.properties || target->non_extensible || + target->v.arr.frozen) { + scr_regex_materialize_metadata(a); + return false; + } + scr_dyn_arr_named_put(target, "index", 5, scr_dyn_new_num(a->ext->match_index)); + scr_dyn_arr_named_put(target, "input", 5, scr_regex_source_input(src)); + scr_dyn_arr_named_put(target, "groups", 6, scr_regex_groups_of(a, scr_regex_bc(src->re))); + return true; +} + +ScrDyn *scr_regex_result_prop(ScrArr *a, ScrStr *key) { + ScrRegexMatchSource *src = scr_regex_pending(a); + if (src) { + if (key->len == 5 && memcmp(key->data, "index", 5) == 0) + return scr_dyn_new_num(a->ext->match_index); + if (key->len == 5 && memcmp(key->data, "input", 5) == 0) return scr_regex_source_input(src); + scr_regex_materialize_metadata(a); + } + ScrDyn *metadata = a->ext ? a->ext->metadata : NULL; + ScrDyn *value = metadata ? scr_dyn_obj_get(metadata, key->data, key->len) : NULL; + return scr_dyn_retain(value ? value : scr_dyn_undefined()); +} + /* ECMA-262 AdvanceStringIndex: past a whole surrogate pair under /u, one * code unit otherwise. ASCII cannot contain a surrogate pair. */ static int scr_advance(const ScrRegexInput *input, int i, bool unicode) { @@ -713,12 +845,6 @@ static ScrArr *scr_regex_exec_core(ScrStr *s, ScrRegex *re, uint64_t mask, bool int count = lre_get_capture_count(bc); bool spans = lazy != 0 && !input.wide && (count >= 64 || ((mask | lazy) >> count) == 0); out = scr_arr_new_inline(SCR_ELEM_STR, (size_t)count + spans); - if (metadata) { - ScrDyn *props = scr_dyn_new_obj(); - scr_arr_ext(out)->metadata = props; - scr_dyn_obj_set(props, "index", 5, scr_dyn_new_num((double)scr_capture_index(&input, capture[0]))); - scr_dyn_obj_set(props, "input", 5, scr_dyn_new_str(s)); - } /* A fresh dense array: every slot is written directly, then the * hole-free prefix is the run of materialized captures from 0. Its * storage pointers are fixed (the inline block never moves here), so @@ -750,6 +876,11 @@ static ScrArr *scr_regex_exec_core(ScrStr *s, ScrRegex *re, uint64_t mask, bool while (filled < n && present[filled] == SCR_ARR_VALUE) filled++; out->filled = (uint32_t)filled; out->len = n; + if (metadata) { + ScrDyn *source = scr_regex_source_new(s, re); + scr_regex_defer_metadata(out, source, scr_capture_index(&input, capture[0])); + scr_dyn_release(source); + } } scr_regex_input_dispose(&input); return out; /* +1, or NULL (no match) */ @@ -883,8 +1014,11 @@ double scr_regex_search(ScrStr *s, ScrRegex *re) { * Node's exact TypeError (catchable — replaceAll's stance). Empty matches * advance one unit, unicode-aware (AdvanceStringIndex). `indices`, when * non-NULL, receives each match's UTF-16 start index (the row's .index — - * the companion array the for-of-over-matchAll desugar reads). */ -static ScrArr *scr_regex_match_all_core(ScrStr *s, ScrRegex *re, ScrArr *indices) { + * the companion array the for-of-over-matchAll desugar reads). With + * `metadata`, each row records the match source for its index, input and + * groups properties. */ +static ScrArr *scr_regex_match_all_core(ScrStr *s, ScrRegex *re, ScrArr *indices, + bool metadata) { uint8_t *bc = scr_regex_bc(re); int re_flags = lre_get_flags(bc); if (!(re_flags & LRE_FLAG_GLOBAL)) { @@ -895,6 +1029,7 @@ static ScrArr *scr_regex_match_all_core(ScrStr *s, ScrRegex *re, ScrArr *indices } bool unicode = (re_flags & (LRE_FLAG_UNICODE | LRE_FLAG_UNICODE_SETS)) != 0; int capture_count = lre_get_capture_count(bc); + ScrDyn *source = metadata ? scr_regex_source_new(s, re) : NULL; ScrRegexInput input; scr_regex_input_init(&input, re, bc, s); int len = input.len; @@ -906,30 +1041,51 @@ static ScrArr *scr_regex_match_all_core(ScrStr *s, ScrRegex *re, ScrArr *indices if (scr_exec(&input, bc, pos) != 1) break; int start = scr_capture_index(&input, capture[0]); int end = scr_capture_index(&input, capture[1]); - ScrArr *row = scr_arr_new(SCR_ELEM_STR, (size_t)capture_count); + /* One allocation per row, like an exec result: every slot is written + * directly, then the hole-free prefix is the run of captures from 0. */ + ScrArr *row = scr_arr_new_inline(SCR_ELEM_STR, (size_t)capture_count); + uint64_t *data = row->data; + uint8_t *present = row->present; for (int k = 0; k < capture_count; k++) { const uint8_t *cs = capture[2 * k], *ce = capture[2 * k + 1]; if (cs == NULL || ce == NULL) { - scr_arr_set_undefined(row, (double)row->len); + data[k] = 0; + present[k] = SCR_ARR_UNDEFINED; } else { - scr_arr_push_ref( - row, scr_regex_span(&input, scr_capture_index(&input, cs), scr_capture_index(&input, ce))); + ScrStr *text = + scr_regex_span(&input, scr_capture_index(&input, cs), scr_capture_index(&input, ce)); + data[k] = (uint64_t)(uintptr_t)text; + present[k] = SCR_ARR_VALUE; } } + size_t filled = 0; + while (filled < (size_t)capture_count && present[filled] == SCR_ARR_VALUE) filled++; + row->filled = (uint32_t)filled; + row->len = (size_t)capture_count; + if (source) scr_regex_defer_metadata(row, source, start); scr_arr_push_ref(out, row); if (indices != NULL) scr_arr_push_f64(indices, (double)start); pos = end == start ? scr_advance(&input, end, unicode) : end; } + scr_dyn_release(source); scr_regex_input_dispose(&input); return out; /* +1 (possibly empty — Node's no-match drain is []) */ } ScrArr *scr_regex_match_all(ScrStr *s, ScrRegex *re) { - return scr_regex_match_all_core(s, re, NULL); + return scr_regex_match_all_core(s, re, NULL, true); +} + +ScrArr *scr_regex_match_all_captures(ScrStr *s, ScrRegex *re) { + return scr_regex_match_all_core(s, re, NULL, false); } ScrArr *scr_regex_match_all_into(ScrStr *s, ScrRegex *re, ScrArr *indices) { - return scr_regex_match_all_core(s, re, indices); + return scr_regex_match_all_core(s, re, indices, true); +} + +ScrArr *scr_regex_match_all_captures_into(ScrStr *s, ScrRegex *re, ScrArr *indices) { + return scr_regex_match_all_core(s, re, indices, false); } /* ── replace / replaceAll ─────────────────────────────────────────────── */ diff --git a/packages/runtime/src/scr_regex.h b/packages/runtime/src/scr_regex.h index 014e5aa83..01c545bc5 100644 --- a/packages/runtime/src/scr_regex.h +++ b/packages/runtime/src/scr_regex.h @@ -98,11 +98,23 @@ ScrStr *scr_regex_replace_all(ScrStr *s, ScrRegex *re, ScrStr *rep); ScrArr *scr_regex_split(ScrStr *s, ScrRegex *re); ScrArr *scr_regex_split_limit(ScrStr *s, ScrRegex *re, double limit); /* matchAll drained eagerly: +1 string[][] of honest match slices; throws - * Node's TypeError on a non-global regex (catchable). */ + * Node's TypeError on a non-global regex (catchable). Each row carries its + * index, input and groups properties. */ ScrArr *scr_regex_match_all(ScrStr *s, ScrRegex *re); +/* scr_regex_match_all for rows read only through their elements: the rows + * carry no index, input or groups properties. */ +ScrArr *scr_regex_match_all_captures(ScrStr *s, ScrRegex *re); /* matchAll + the companion-index drain: also pushes each match's UTF-16 * start index (f64) onto `indices` — the .index the for-of-over-matchAll * desugar reads. Same throw/result contract as scr_regex_match_all. */ ScrArr *scr_regex_match_all_into(ScrStr *s, ScrRegex *re, ScrArr *indices); +/* scr_regex_match_all_into for rows read only through their elements and + * the companion index: the rows carry no index, input or groups + * properties. */ +ScrArr *scr_regex_match_all_captures_into(ScrStr *s, ScrRegex *re, ScrArr *indices); +/* `.index` / `.input` of a match result: its pending match source or its + * metadata answers, without building the metadata object; undefined when + * neither has the key. Borrows both; +1. */ +struct ScrDyn *scr_regex_result_prop(ScrArr *a, ScrStr *key); #endif /* SCR_REGEX_H */ diff --git a/packages/runtime/src/scr_test.c b/packages/runtime/src/scr_test.c index 00e609129..43affdd8a 100644 --- a/packages/runtime/src/scr_test.c +++ b/packages/runtime/src/scr_test.c @@ -2279,6 +2279,26 @@ double scr_test_mt_set_immediate(ScrClosure *cb /*moves*/) { ScrMockTimers *m = scr_mt_owner[SCR_MT_IMMEDIATE]; return m ? scr_mt_create(m, false, cb, -1)->handle : scr_set_immediate(cb); } +/* The same calls in a program that enters AsyncLocalStorage stores: a + * mocked timer runs in the context its tick() runs in, as in Node; a real + * one keeps the stores active when it was scheduled. */ +void scr_test_mt_set_timeout_als(ScrClosure *cb /*moves*/, double ms) { + ScrMockTimers *m = scr_mt_owner[SCR_MT_TIMEOUT]; + if (m) (void)scr_mt_create(m, false, cb, ms); + else scr_set_timeout_als(cb, ms); +} +double scr_test_mt_set_timeout_handle_als(ScrClosure *cb /*moves*/, double ms) { + ScrMockTimers *m = scr_mt_owner[SCR_MT_TIMEOUT]; + return m ? scr_mt_create(m, false, cb, ms)->handle : scr_set_timeout_handle_als(cb, ms); +} +double scr_test_mt_set_interval_als(ScrClosure *cb /*moves*/, double ms) { + ScrMockTimers *m = scr_mt_owner[SCR_MT_INTERVAL]; + return m ? scr_mt_create(m, true, cb, ms)->handle : scr_set_interval_als(cb, ms); +} +double scr_test_mt_set_immediate_als(ScrClosure *cb /*moves*/) { + ScrMockTimers *m = scr_mt_owner[SCR_MT_IMMEDIATE]; + return m ? scr_mt_create(m, false, cb, -1)->handle : scr_set_immediate_als(cb); +} void scr_test_mt_clear_immediate(double handle) { ScrMockTimers *m = scr_mt_owner[SCR_MT_IMMEDIATE]; if (m) scr_mt_clear(m, handle); diff --git a/packages/runtime/src/scr_test.h b/packages/runtime/src/scr_test.h index 64a280bf7..b29fca362 100644 --- a/packages/runtime/src/scr_test.h +++ b/packages/runtime/src/scr_test.h @@ -110,6 +110,12 @@ void scr_test_mt_clear_immediate(double handle); void scr_test_mt_immediate_unref(double handle); void scr_test_mt_immediate_ref(double handle); bool scr_test_mt_immediate_has_ref(double handle); +/* The setters of programs that also enter AsyncLocalStorage stores: an + * unmocked call uses the loop's *_als function. */ +void scr_test_mt_set_timeout_als(ScrClosure *cb, double ms); +double scr_test_mt_set_timeout_handle_als(ScrClosure *cb, double ms); +double scr_test_mt_set_interval_als(ScrClosure *cb, double ms); +double scr_test_mt_set_immediate_als(ScrClosure *cb); double scr_test_mt_date_now(void); /* timers/promises scheduler.wait(delay) (+1 promise; NULL after a throw). */ ScrPromise *scr_test_mt_scheduler_wait(double ms); diff --git a/scripts/zero-cost-gate.mjs b/scripts/zero-cost-gate.mjs index f4272ac6a..297e741e5 100644 --- a/scripts/zero-cost-gate.mjs +++ b/scripts/zero-cost-gate.mjs @@ -316,7 +316,9 @@ const definedFunctions = (binary) => * b14377864 (DYNAMIC_TIER_PREFIXES in zero-cost-compare.mjs): JSON.parse * trees, worker messages, regex exec metadata (two-tier §3.3 L5), and a * shared scr_dyn_call/scr_dyn_define_property core not yet traced to its - * typed root. Every other bench workload is fully typed and must link none. + * typed root. bench/thread-slots, added later, links them through worker + * messages: node:worker_threads values are dyn objects. Every other bench + * workload is fully typed and must link none. * Remove a workload here when a leak fix makes its binary clean. */ const DYNAMIC_WORKLOADS = new Set([ "bench/json-records", @@ -326,6 +328,7 @@ const DYNAMIC_WORKLOADS = new Set([ "bench/regex-logs", "bench/atomics-contended", "bench/intern-table", + "bench/thread-slots", "bench/typed-array-scan-workers", "bench/log-summary", "bench/inventory-report", diff --git a/tests/corpus/5815-als-callback-context.ts b/tests/corpus/5815-als-callback-context.ts new file mode 100644 index 000000000..461a2aa95 --- /dev/null +++ b/tests/corpus/5815-als-callback-context.ts @@ -0,0 +1,76 @@ +// AsyncLocalStorage context reaches timer, interval, setImmediate, +// process.nextTick and queueMicrotask callbacks: each runs in the store +// that was active when it was scheduled, including callbacks they schedule +// in turn, an interval on every tick, and a refreshed timer. A cleared +// timer or immediate never runs. enterWith inside a callback lasts until +// the callback returns, also in a callback scheduled outside any store, +// and callbacks scheduled outside any store, or inside exit(), see +// undefined. A store entered at the top level reaches only the callbacks +// scheduled after it. +import { AsyncLocalStorage } from "node:async_hooks"; + +const requests = new AsyncLocalStorage(); +const users = new AsyncLocalStorage(); +const seen: string[] = []; + +function record(where: string): void { + seen.push(`${where}: ${requests.getStore() ?? "-"} ${users.getStore() ?? "-"}`); +} + +requests.run("req-1", () => { + setTimeout(() => { + record("timeout"); + process.nextTick(() => record("tick from timeout")); + queueMicrotask(() => record("microtask from timeout")); + }, 1); + setImmediate(() => { + record("immediate"); + setTimeout(() => record("timeout from immediate"), 1); + }); + process.nextTick(() => record("tick")); + queueMicrotask(() => record("microtask")); + users.run("ada", () => { + setTimeout(() => record("nested timeout"), 2); + setImmediate(() => record("nested immediate")); + }); + let ticks = 0; + const interval = setInterval(() => { + ticks++; + record(`interval ${ticks}`); + if (ticks === 3) clearInterval(interval); + }, 1); + const refreshed = setTimeout(() => record("refreshed timeout"), 3); + refreshed.refresh(); + const cleared = setTimeout(() => record("cleared timeout"), 1); + clearTimeout(cleared); + const clearedImmediate = setImmediate(() => record("cleared immediate")); + clearImmediate(clearedImmediate); + requests.exit(() => { + setTimeout(() => record("timeout in exit"), 1); + queueMicrotask(() => record("microtask in exit")); + }); +}); + +requests.run("req-2", () => { + setTimeout(() => { + users.enterWith("grace"); + record("timeout after enterWith"); + setTimeout(() => record("timeout from enterWith"), 1); + }, 4); + setTimeout(() => record("timeout after the enterWith timeout"), 5); +}); + +setTimeout(() => { + record("timeout outside any store"); + requests.enterWith("entered outside"); +}, 1); +setTimeout(() => record("timeout after one that entered outside any store"), 8); +process.nextTick(() => record("tick outside any store")); + +setTimeout(() => { + record("final"); + for (const line of seen.sort()) console.log(line); +}, 30); + +users.enterWith("top level"); +setTimeout(() => record("timeout after a top-level enterWith"), 6); diff --git a/tests/corpus/6010-match-metadata.ts b/tests/corpus/6010-match-metadata.ts new file mode 100644 index 000000000..8de5f6c4a --- /dev/null +++ b/tests/corpus/6010-match-metadata.ts @@ -0,0 +1,40 @@ +// index and input on matchAll rows and exec results: for-of bindings that +// read only captures and the index, bindings that escape, spreads, +// callbacks, stored drains and closures. +const text = "k1=v10; k22=v3; k3=v400;"; +const re = /(?k\d+)=(?v\d+);/g; + +for (const m of text.matchAll(re)) console.log(m.index, m[1], m.groups!.val); +const kept: RegExpExecArray[] = []; +for (const m of text.matchAll(re)) { + kept.push(m); + console.log(m.index, m.input === text); +} +console.log(kept.map((m) => `${m.index}:${m[0]}`).join(" ")); + +const rows = [...text.matchAll(re)]; +console.log(rows.map((m) => m.index).join(","), rows[1]!.index, rows[2]!.input.length); +console.log(rows.filter((m) => m.index > 0).map((m) => m.groups!.key).join(",")); +console.log(rows.find((m) => m.index === 8)?.[2], rows.at(-1)!.index); +console.log(Array.from(text.matchAll(re), (m) => m.index + m[0].length).join(",")); + +const drained = text.matchAll(/v(\d+)/g); +for (const m of drained) console.log(m.index, m[1]); +const stored = text.matchAll(/k(\d)/g); +const later = (): string => [...stored].map((m) => m.index).join("|"); +console.log(later()); + +function startOf(m: RegExpExecArray): number { + return m.index; +} +console.log([...text.matchAll(/=/g)].map(startOf).join(",")); + +const e = /(?\d+);/.exec(text)!; +console.log(e.index, e.input === text, e[1], e.groups!.n); +const g = text.match(/k\d+/g)!; +console.log(g.join(","), g.length); +const plain = [..."aXbX".matchAll(/X/g)]; +console.log(plain.map((m) => m.index).join(","), plain.length); +console.log([...text.matchAll(re)].map((m) => m[1]).join(","), [...text.matchAll(re)].length); +console.log(Array.from(text.matchAll(re), (m) => m.groups!.key + m.length).join(",")); +for (const m of [...text.matchAll(re)].slice(1)) console.log(m[2], m.groups!.val); diff --git a/tests/corpus/6011-match-metadata-js.js b/tests/corpus/6011-match-metadata-js.js new file mode 100644 index 000000000..b67e11069 --- /dev/null +++ b/tests/corpus/6011-match-metadata-js.js @@ -0,0 +1,31 @@ +// Match metadata on matchAll rows and exec results in JavaScript: index, +// input and groups through callbacks, untyped functions, destructuring, +// Object.keys and spread copies. +const text = "k1=v10; k22=v3; k3=v400;"; +const re = /(?k\d+)=(?v\d+)(?!)?;/g; +const rows = [...text.matchAll(re)]; +console.log(rows.map((m) => m.index).join(","), rows.map((m) => m.input.length).join(",")); +console.log(rows.map(({ index, groups }) => `${index}=${groups.key}`).join(" ")); + +function describe(m) { + return [m.index, m.input === text, m.groups.key, m.groups.bang, Object.keys(m).join("|")]; +} +for (const m of text.matchAll(re)) console.log(describe(m)); +console.log(rows.map((m) => describe(m)[0]).join(",")); + +const first = rows[0]; +const copy = { ...first }; +console.log(Object.keys(copy).join(","), copy.index, copy.groups.val, Object.getPrototypeOf(first.groups) === null); +console.log(JSON.stringify(first), first.length); + +const dup = [..."a1-b".matchAll(/(?[a-z])|(?\d)|-/g)]; +console.log(dup.map((m) => describe2(m)).join(" ")); +function describe2(m) { + return `${m.index}:${m.groups.v}`; +} + +const pick = (m) => m && [m.index, m.groups]; +console.log(pick(/(?\d)(?x)?/.exec("ab3")), pick(/b/.exec("ab")), pick(/z/.exec("ab"))); +console.log(pick("a1".match(/(?\d)/)), Object.keys("a1a".match(/a/g)).join(",")); +const none = [..."ab".matchAll(/[a-z]/g)]; +console.log(none.map((m) => m.groups === undefined && m.index >= 0).join(",")); diff --git a/tests/corpus/6071-iterator-sources.ts b/tests/corpus/6071-iterator-sources.ts new file mode 100644 index 000000000..c63469022 --- /dev/null +++ b/tests/corpus/6071-iterator-sources.ts @@ -0,0 +1,70 @@ +// Iterator objects from strings and typed arrays, Iterator.from over the +// built-in iterables, return() on helper objects, and flatMap over Set, +// Map and string results. +const log: string[] = []; +function* count(label: string, n: number): Generator { + try { + for (let i = 1; i <= n; i++) { + log.push(`${label}${i}`); + yield i; + } + } finally { + log.push(`${label}:closed`); + } +} +function flush(what: unknown): void { + console.log(what, "|", log.join(" ")); + log.length = 0; +} + +// String iterators step by code point; a lone surrogate is one step. +const text = "a\u{1F600}b\uD800"; +const chars = text[Symbol.iterator](); +console.log(chars.next(), chars.next().value?.length, [...chars].length); +console.log(text[Symbol.iterator]().map((c) => c.codePointAt(0)!.toString(16)).toArray()); +console.log("naïve"[Symbol.iterator]().filter((c) => c !== "ï").toArray().join("")); +for (const c of "xyz"[Symbol.iterator]().drop(1)) console.log("char", c); +console.log("hello"[Symbol.iterator]().some((c) => c === "l"), "hello"[Symbol.iterator]().find((c) => c > "h")); + +// Typed array iterators. +const bytes = new Uint8Array([9, 8, 7]); +console.log(bytes.values().map((x) => x * 2).toArray(), bytes.keys().filter((i) => i > 0).toArray()); +console.log(bytes.entries().map(([i, v]) => i * v).toArray()); +const floats = new Float64Array([0.5, 1.5, 2.5]).values().map((x) => x + 1); +console.log(floats.next(), [...floats]); +for (const [i, v] of new Int16Array([-1, 2, -3, 4]).entries().filter(([, v]) => v > 0)) console.log(i, v); + +// Iterator.from returns each built-in iterable's own iterator. +const list = [1, 2]; +const fromList = Iterator.from(list); +list.push(3); +console.log([...fromList], Iterator.from("a\u{1F600}").toArray()); +console.log(Iterator.from(new Set(["p", "q"])).toArray(), Iterator.from(new Map([["k", 1]])).toArray()); +console.log(Iterator.from(new Uint8Array([4, 5])).reduce((a, b) => a + b, 0)); +const fromGen = Iterator.from(count("a", 3)); +flush([fromGen.next(), fromGen.take(1).toArray()]); +flush(Iterator.from(count("b", 9).map((x) => x * 3)).take(2).toArray()); + +// return() on helper objects. +const started = count("c", 3); +started.next(); +const doubled = started.map((x) => x * 2); +flush([doubled.return!(), started.next()]); +const fresh = count("d", 3); +const kept = fresh.filter(() => true); +flush([kept.return?.(), fresh.next(), kept.next()]); +const stepping = count("e", 5).drop(1); +flush([stepping.next(), stepping.return!(), stepping.next()]); +flush(count("f", 4).map((x) => x).take(2).return!()); + +// flatMap over Set and Map results, and a string result's TypeError. +console.log([1, 2].values().flatMap((x) => new Set([x, x * 10])).toArray()); +console.log([1, 2].values().flatMap((x) => new Map([[`k${x}`, x]])).toArray()); +const pairs = new Map([["m", 1], ["n", 2]]); +for (const [k, v] of [0].values().flatMap(() => pairs)) console.log(k, v); +try { + count("g", 3).flatMap((x) => `s${x}`).toArray(); +} catch (e) { + log.push(`${(e as Error).name}: ${(e as Error).message}`); +} +flush("string result"); diff --git a/tests/corpus/6072-iterator-sources-js.js b/tests/corpus/6072-iterator-sources-js.js new file mode 100644 index 000000000..84dd00067 --- /dev/null +++ b/tests/corpus/6072-iterator-sources-js.js @@ -0,0 +1,70 @@ +// The JavaScript form: iterator objects from strings and typed arrays, +// Iterator.from over the built-in iterables, return() on helper objects, +// and flatMap over Set, Map and string results. +const log = []; +function* count(label , n ) { + try { + for (let i = 1; i <= n; i++) { + log.push(`${label}${i}`); + yield i; + } + } finally { + log.push(`${label}:closed`); + } +} +function flush(what ) { + console.log(what, "|", log.join(" ")); + log.length = 0; +} + +// String iterators step by code point; a lone surrogate is one step. +const text = "a\u{1F600}b\uD800"; +const chars = text[Symbol.iterator](); +console.log(chars.next(), chars.next().value.length, [...chars].length); +console.log(text[Symbol.iterator]().map((c) => c.length).toArray()); +console.log("naïve"[Symbol.iterator]().filter((c) => c !== "ï").toArray().join("")); +for (const c of "xyz"[Symbol.iterator]().drop(1)) console.log("char", c); +console.log("hello"[Symbol.iterator]().some((c) => c === "l"), "hello"[Symbol.iterator]().find((c) => c > "h")); + +// Typed array iterators. +const bytes = new Uint8Array([9, 8, 7]); +console.log(bytes.values().map((x) => x * 2).toArray(), bytes.keys().filter((i) => i > 0).toArray()); +console.log(bytes.entries().map(([i, v]) => i * v).toArray()); +const floats = new Float64Array([0.5, 1.5, 2.5]).values().map((x) => x + 1); +console.log(floats.next(), [...floats]); +for (const [i, v] of new Int16Array([-1, 2, -3, 4]).entries().filter(([, v]) => v > 0)) console.log(i, v); + +// Iterator.from returns each built-in iterable's own iterator. +const list = [1, 2]; +const fromList = Iterator.from(list); +list.push(3); +console.log([...fromList], Iterator.from("a\u{1F600}").toArray()); +console.log(Iterator.from(new Set(["p", "q"])).toArray(), Iterator.from(new Map([["k", 1]])).toArray()); +console.log(Iterator.from(new Uint8Array([4, 5])).reduce((a, b) => a + b, 0)); +const fromGen = Iterator.from(count("a", 3)); +flush([fromGen.next(), fromGen.take(1).toArray()]); +flush(Iterator.from(count("b", 9).map((x) => x * 3)).take(2).toArray()); + +// return() on helper objects. +const started = count("c", 3); +started.next(); +const doubled = started.map((x) => x * 2); +flush([doubled.return(), started.next()]); +const fresh = count("d", 3); +const kept = fresh.filter(() => true); +flush([kept.return?.(), fresh.next(), kept.next()]); +const stepping = count("e", 5).drop(1); +flush([stepping.next(), stepping.return(), stepping.next()]); +flush(count("f", 4).map((x) => x).take(2).return()); + +// flatMap over Set and Map results, and a string result's TypeError. +console.log([1, 2].values().flatMap((x) => new Set([x, x * 10])).toArray()); +console.log([1, 2].values().flatMap((x) => new Map([[`k${x}`, x]])).toArray()); +const pairs = new Map ([["m", 1], ["n", 2]]); +for (const [k, v] of [0].values().flatMap(() => pairs)) console.log(k, v); +try { + count("g", 3).flatMap((x) => `s${x}`).toArray(); +} catch (e) { + log.push(`${e.name}: ${e.message}`); +} +flush("string result"); diff --git a/tests/corpus/6073-stored-collection-helpers.ts b/tests/corpus/6073-stored-collection-helpers.ts new file mode 100644 index 000000000..ed739eb85 --- /dev/null +++ b/tests/corpus/6073-stored-collection-helpers.ts @@ -0,0 +1,60 @@ +// Iterator helper objects over Map and Set iterators that are stored and +// consumed later: the helpers observe the collection live, and abandoned +// or returned helpers leave it usable. +const set = new Set([1, 2, 3]); +const doubled = set.values().map((x) => x * 2); +set.add(4); +set.delete(1); +console.log(doubled.next(), [...doubled]); + +const map = new Map([ + ["a", 1], + ["b", 2], + ["c", 3], +]); +const large = map.entries().filter(([, v]) => v > 1); +map.set("d", 4); +map.delete("b"); +console.log([...large].map(([k, v]) => `${k}=${v}`).join(" ")); +const keys = map.keys().map((k, i) => k.toUpperCase() + i); +console.log(keys.next(), keys.return!(), keys.next()); +const rest = map.values().drop(2); +for (const v of rest) console.log("value", v); +const firstTwo = map.keys().take(2); +console.log([...firstTwo], [...firstTwo]); + +// A stage on a stage, and Iterator.from over a Set and a Map. +const chained = set.values().filter((x) => x % 2 === 0).map((x) => `n${x}`); +console.log(chained.toArray()); +const fromSet = Iterator.from(set).map((x) => x + 0.5); +set.add(9); +console.log([...fromSet]); +console.log(Iterator.from(map).map(([k, v]) => k + v).toArray()); + +// A helper left mid-walk does not stop later changes or walks. +const abandoned = set.values().map((x) => x); +abandoned.next(); +for (let i = 0; i < 100; i++) { + set.add(100 + i); + set.delete(100 + i); +} +console.log(set.size, set.values().take(3).toArray(), [...set].length); +const returned = map.values().map((v) => v * 10); +returned.next(); +returned.return!(); +for (let i = 0; i < 50; i++) map.set(`t${i}`, i); +for (let i = 0; i < 50; i++) map.delete(`t${i}`); +console.log(map.size, [...map.keys()].join(",")); + +// Records and strings as elements. +interface Row { + id: number; + tag: string; +} +const rows = new Map([ + [1, { id: 1, tag: "x" }], + [2, { id: 2, tag: "y" }], +]); +const tags = rows.values().map((r) => r.tag.repeat(r.id)); +rows.set(3, { id: 3, tag: "z" }); +console.log([...tags], rows.entries().find(([, r]) => r.tag === "y")?.[0]); diff --git a/tests/corpus/6074-stored-collection-helpers-js.js b/tests/corpus/6074-stored-collection-helpers-js.js new file mode 100644 index 000000000..3a314a3c1 --- /dev/null +++ b/tests/corpus/6074-stored-collection-helpers-js.js @@ -0,0 +1,57 @@ +// The JavaScript form: iterator helper objects over Map and Set iterators +// that are stored and consumed later: the helpers observe the collection live, and abandoned +// or returned helpers leave it usable. +const set = new Set([1, 2, 3]); +const doubled = set.values().map((x) => x * 2); +set.add(4); +set.delete(1); +console.log(doubled.next(), [...doubled]); + +const map = new Map ([ + ["a", 1], + ["b", 2], + ["c", 3], +]); +const large = map.entries().filter(([, v]) => v > 1); +map.set("d", 4); +map.delete("b"); +console.log([...large].map(([k, v]) => `${k}=${v}`).join(" ")); +const keys = map.keys().map((k, i) => k.toUpperCase() + i); +console.log(keys.next(), keys.return(), keys.next()); +const rest = map.values().drop(2); +for (const v of rest) console.log("value", v); +const firstTwo = map.keys().take(2); +console.log([...firstTwo], [...firstTwo]); + +// A stage on a stage, and Iterator.from over a Set and a Map. +const chained = set.values().filter((x) => x % 2 === 0).map((x) => `n${x}`); +console.log(chained.toArray()); +const fromSet = Iterator.from(set).map((x) => x + 0.5); +set.add(9); +console.log([...fromSet]); +console.log(Iterator.from(map).map(([k, v]) => k + v).toArray()); + +// A helper left mid-walk does not stop later changes or walks. +const abandoned = set.values().map((x) => x); +abandoned.next(); +for (let i = 0; i < 100; i++) { + set.add(100 + i); + set.delete(100 + i); +} +console.log(set.size, set.values().take(3).toArray(), [...set].length); +const returned = map.values().map((v) => v * 10); +returned.next(); +returned.return(); +for (let i = 0; i < 50; i++) map.set(`t${i}`, i); +for (let i = 0; i < 50; i++) map.delete(`t${i}`); +console.log(map.size, [...map.keys()].join(",")); + +// Records and strings as elements. + +const rows = new Map ([ + [1, { id: 1, tag: "x" }], + [2, { id: 2, tag: "y" }], +]); +const tags = rows.values().map((r) => r.tag.repeat(r.id)); +rows.set(3, { id: 3, tag: "z" }); +console.log([...tags], rows.entries().find(([, r]) => r.tag === "y")?.[0]); diff --git a/tests/corpus/6075-print-iterators.ts b/tests/corpus/6075-print-iterators.ts new file mode 100644 index 000000000..8b4833264 --- /dev/null +++ b/tests/corpus/6075-print-iterators.ts @@ -0,0 +1,19 @@ +// util.inspect and Object.prototype.toString of iterator objects: generators, +// array, typed array and string iterators and iterator helpers print as +// Object [] {}, and Map and Set iterators print the entries they have +// left. +import { inspect } from "node:util"; +function* g(): Generator { yield 1; } +const s = new Set([1, 2, 3]); +const m = new Map([["a", 1], ["b", 2]]); +const si = s.values(); si.next(); +console.log(g(), [1, 2].values(), "ab"[Symbol.iterator](), new Uint8Array([1]).values()); +console.log(s.values(), s.entries(), m.keys(), m.values(), m.entries(), si); +console.log([1].values().map((x) => x), Iterator.from([1]), g().filter(() => true)); +console.log(inspect({ a: { b: { c: s.values(), d: g() } } })); +console.log(Object.prototype.toString.call(g()), Object.prototype.toString.call([1].values().map((x) => x)), Object.prototype.toString.call(m.keys())); +const done = s.values(); Array.from(done); +console.log(done, new Map().entries(), g()); +const keyed = new Map<{ x: number }, number[]>([[{ x: 1 }, [1, 2]]]); +console.log(keyed.entries(), keyed.keys()); +console.log(new Set(Array.from({ length: 120 }, (_, i) => i)).values()); diff --git a/tests/corpus/6076-print-iterators-js.js b/tests/corpus/6076-print-iterators-js.js new file mode 100644 index 000000000..ad4022b6b --- /dev/null +++ b/tests/corpus/6076-print-iterators-js.js @@ -0,0 +1,17 @@ +// The JavaScript form: util.inspect and Object.prototype.toString of +// iterator objects. +import { inspect } from "node:util"; +function* g() { yield 1; } +const s = new Set([1, 2, 3]); +const m = new Map ([["a", 1], ["b", 2]]); +const si = s.values(); si.next(); +console.log(g(), [1, 2].values(), "ab"[Symbol.iterator](), new Uint8Array([1]).values()); +console.log(s.values(), s.entries(), m.keys(), m.values(), m.entries(), si); +console.log([1].values().map((x) => x), Iterator.from([1]), g().filter(() => true)); +console.log(inspect({ a: { b: { c: s.values(), d: g() } } })); +console.log(Object.prototype.toString.call(g()), Object.prototype.toString.call([1].values().map((x) => x)), Object.prototype.toString.call(m.keys())); +const done = s.values(); Array.from(done); +console.log(done, new Map ().entries(), g()); +const keyed = new Map ([[{ x: 1 }, [1, 2]]]); +console.log(keyed.entries(), keyed.keys()); +console.log(new Set(Array.from({ length: 120 }, (_, i) => i)).values()); diff --git a/tests/corpus/6110-cjs-speculative-wrapper-fences.cjs b/tests/corpus/6110-cjs-speculative-wrapper-fences.cjs index fbb1a65fc..3ae9df3dc 100644 --- a/tests/corpus/6110-cjs-speculative-wrapper-fences.cjs +++ b/tests/corpus/6110-cjs-speculative-wrapper-fences.cjs @@ -5,8 +5,7 @@ // and `typeof module.exports` compile; a statement with no static lowering // (here `module.exports` passed as a value in the class base) compiles to // its runtime fence instead of vanishing. It never runs, so the binary -// matches Node. Coverage counts that one fenced statement once: the class -// base's body is lowered once. +// matches Node. const verbose = process.argv.length > 99; (function () { var root = this; diff --git a/tests/corpus/6200-http-agent-free-socket-data.ts b/tests/corpus/6200-http-agent-free-socket-data.ts new file mode 100644 index 000000000..69e89de49 --- /dev/null +++ b/tests/corpus/6200-http-agent-free-socket-data.ts @@ -0,0 +1,57 @@ +// Bytes that arrive on an idle pooled socket are not read as the next +// response: the agent destroys the socket, and the next request through +// the agent dials a new connection. +import * as http from "node:http"; +import * as net from "node:net"; + +let conns = 0; +const server = net.createServer((sock) => { + const id = ++conns; + let buf = ""; + sock.on("data", (d: Buffer) => { + buf += d.toString(); + while (buf.includes("\r\n\r\n")) { + const head = buf.slice(0, buf.indexOf("\r\n\r\n")); + buf = buf.slice(buf.indexOf("\r\n\r\n") + 4); + const path = head.split(" ")[1]!; + const body = `real ${path} conn${id}`; + sock.write(`HTTP/1.1 200 OK\r\nContent-Length: ${body.length}\r\nKeep-Alive: timeout=5\r\n\r\n${body}`); + if (path === "/poisoned") { + setTimeout(() => sock.write("HTTP/1.1 200 OK\r\nContent-Length: 6\r\n\r\nPOISON"), 50); + } + } + }); + sock.on("close", () => console.log(`server conn${id} closed`)); + sock.on("error", () => {}); +}); + +const agent = new http.Agent({ keepAlive: true }); +function get(port: number, path: string): Promise { + return new Promise((resolve) => { + const req = http.get({ host: "127.0.0.1", port, path, agent }, (res) => { + let body = ""; + res.on("data", (c: Buffer) => (body += c.toString())); + res.on("end", () => resolve(`${body} reused=${req.reusedSocket}`)); + }); + req.on("error", (e: Error) => resolve(`error ${e.message}`)); + }); +} + +function free(): number { + return Object.keys(agent.freeSockets).length; +} + +server.listen(0, "127.0.0.1", async () => { + const port = (server.address() as net.AddressInfo).port; + console.log(await get(port, "/quiet")); + await new Promise((r) => setTimeout(r, 20)); + console.log("free after /quiet", free()); + console.log(await get(port, "/again")); + console.log(await get(port, "/poisoned")); + await new Promise((r) => setTimeout(r, 200)); + console.log("free after stray bytes", free(), "total", agent.totalSocketCount); + console.log(await get(port, "/next")); + console.log("connections", conns); + agent.destroy(); + server.close(() => console.log("server closed")); +}); diff --git a/tests/diagnostics/collection-iterator-helpers.ts b/tests/diagnostics/collection-iterator-helpers.ts index 127b5c64b..d204e05c3 100644 --- a/tests/diagnostics/collection-iterator-helpers.ts +++ b/tests/diagnostics/collection-iterator-helpers.ts @@ -1,9 +1,13 @@ -// Iterator helpers lower over Map/Set iterators, generators and array -// iterators. These helper forms are refused explicitly. +// Iterator helpers lower over Map/Set iterators, generators and array, +// string and typed array iterators. These helper forms are refused +// explicitly. const set = new Set([1, 2]); -// flatMap callbacks return arrays or generators. -set.values().flatMap((value) => new Set([value, value + 1])); -// return() on a helper object. -const mapped = set.values().map((value) => value * 2); -mapped.return!(); +// return() on an iterator object that is not provably a helper object +// (array, string, Map and Set iterators have no return method). +function close(it: IteratorObject): void { + it.return!(); +} +close(set.values().map((value) => value * 2)); +// throw() on a helper object (helpers have no throw method). +set.values().map((value) => value + 1).throw!(new Error("stop")); // Each helper call must produce a diagnostic. diff --git a/tests/diagnostics/es2025-fences.ts b/tests/diagnostics/es2025-fences.ts index 067da192e..1d141eb84 100644 --- a/tests/diagnostics/es2025-fences.ts +++ b/tests/diagnostics/es2025-fences.ts @@ -1,6 +1,6 @@ // The es2025-lib surface WITHOUT a lowering fences by name: resizable // ArrayBuffers and transfer, SharedArrayBuffer.grow, Atomics.waitAsync, -// first-class iterator objects, and the unlowered forms of the +// Iterator.from over objects with their own next method, and the unlowered forms of the // implemented statics (Promise.try with fewer arguments than parameters, // groupBy's literal-union keys, a set-like non-Set argument). @@ -21,8 +21,8 @@ sab.grow(16); const ia = new Int32Array(4); const waited = Atomics.waitAsync(ia, 0, 0, 1); -// First-class iterator objects. -const it = Iterator.from([1, 2, 3].values()); +// Iterator.from over an object with its own next method. +const it = Iterator.from({ next: (): IteratorResult => ({ value: 1, done: false }) }); // Promise.try with fewer arguments than the callback's parameters. const tried = Promise.try((a: number, b?: number) => a + (b ?? 0), 1); diff --git a/tests/diagnostics/string-iterator-helpers.ts b/tests/diagnostics/string-iterator-helpers.ts deleted file mode 100644 index ed0e2315f..000000000 --- a/tests/diagnostics/string-iterator-helpers.ts +++ /dev/null @@ -1,3 +0,0 @@ -// The string iterator has no iterator-helper lowering; the call is -// refused instead of failing at run time. -const upper = "abc"[Symbol.iterator]().map((c) => c.toUpperCase()); diff --git a/tests/fixtures/node-test/cases/test-name-forms.ts b/tests/fixtures/node-test/cases/test-name-forms.ts new file mode 100644 index 000000000..b2f61dd89 --- /dev/null +++ b/tests/fixtures/node-test/cases/test-name-forms.ts @@ -0,0 +1,55 @@ +// The (name?, options?, fn?) forms of test, it, describe and t.test, and +// Node's default names: a missing or empty name takes the function's name, +// or "". A helper may register tests with computed names and +// functions it receives as parameters. With both skip and todo, skip wins, in either order and +// against the todo method twins. +import { describe, it, test } from "node:test"; + +function declaredTest(): void {} +const boundArrow = (): void => {}; +const emptyName = process.argv.length > 99 ? "never" : ""; + +// A helper that registers tests for its callers: the name is computed and +// the function arrives as a parameter. +function registerCase(label: string, body: () => void): void { + test(`case ${label}`, body); +} +registerCase("one", () => {}); +registerCase("two", declaredTest); + +test(function namedExpression() {}); +test(() => {}); +test(declaredTest); +test(boundArrow); +test("", boundArrow); +test(emptyName, declaredTest); +test({ skip: "options first" }, () => {}); +test({ todo: true }); +test(); +test("name and options", { skip: true }); + +describe(function namedSuite() { + it(() => {}); + it({ todo: "later" }, function insideTodo() {}); +}); +describe({ skip: "whole suite" }, () => { + it("not run", () => {}); +}); + +test("subtests", async (t) => { + await t.test(function namedSub() {}); + await t.test(() => {}); + await t.test({ skip: true }, () => {}); +}); + +test("skip then todo", { skip: "skip message", todo: "todo message" }, () => {}); +test("todo then skip", { todo: "todo message", skip: true }, () => {}); +test.todo("todo twin with skip", { skip: true }, () => {}); +it.skip("skip twin with todo", { todo: true }, () => {}); +// The twins override the option of the same name, and the message is the +// skip string, else the todo string. +test.skip("skip twin with a skip message", { skip: "option message" }, () => {}); +it.todo("todo twin with a todo message", { todo: "option message" }, () => {}); +it.skip("skip twin with a todo message", { todo: "todo message" }, () => {}); +test.todo("todo twin with a skip message", { skip: "skip message" }, () => {}); +test("skip false with a todo message", { skip: false, todo: "todo message" }, () => {}); diff --git a/tests/harness/__snapshots__/collection-iterator-helpers.ts.txt b/tests/harness/__snapshots__/collection-iterator-helpers.ts.txt index 87470b621..9c56aa958 100644 --- a/tests/harness/__snapshots__/collection-iterator-helpers.ts.txt +++ b/tests/harness/__snapshots__/collection-iterator-helpers.ts.txt @@ -1,17 +1,17 @@ -collection-iterator-helpers.ts:5:22 - error SC2020: '.flatMap over a callback returning 'Set'' is part of the standard library types but has no scriptc lowering yet +collection-iterator-helpers.ts:8:3 - error SC2020: 'IteratorObject.return' is part of the standard library types but has no scriptc lowering yet - 4 | // flatMap callbacks return arrays or generators. - 5 | set.values().flatMap((value) => new Set([value, value + 1])); - | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - 6 | // return() on a helper object. + 7 | function close(it: IteratorObject): void { + 8 | it.return!(); + | ^~~~~~~~~ + 9 | } - hint: callbacks returning an array or a generator are the supported forms + hint: the type checker sees the full standard library, but only the supported surface compiles (https://scriptc.dev/limitations) -collection-iterator-helpers.ts:8:1 - error SC2020: 'IteratorObject.return' is part of the standard library types but has no scriptc lowering yet +collection-iterator-helpers.ts:12:1 - error SC2020: 'IteratorObject.throw' is part of the standard library types but has no scriptc lowering yet - 7 | const mapped = set.values().map((value) => value * 2); - 8 | mapped.return!(); - | ^~~~~~~~~~~~~ - 9 | // Each helper call must produce a diagnostic. + 11 | // throw() on a helper object (helpers have no throw method). + 12 | set.values().map((value) => value + 1).throw!(new Error("stop")); + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 13 | // Each helper call must produce a diagnostic. hint: the type checker sees the full standard library, but only the supported surface compiles (https://scriptc.dev/limitations) \ No newline at end of file diff --git a/tests/harness/__snapshots__/es2025-fences.ts.txt b/tests/harness/__snapshots__/es2025-fences.ts.txt index d7eda27b6..b1ad939fe 100644 --- a/tests/harness/__snapshots__/es2025-fences.ts.txt +++ b/tests/harness/__snapshots__/es2025-fences.ts.txt @@ -45,12 +45,12 @@ es2025-fences.ts:22:16 - error SC2020: 'Atomics.waitAsync' is part of the standa es2025-fences.ts:25:12 - error SC2020: 'Iterator.from' is part of the standard library types but has no scriptc lowering yet - 24 | // First-class iterator objects. - 25 | const it = Iterator.from([1, 2, 3].values()); - | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 24 | // Iterator.from over an object with its own next method. + 25 | const it = Iterator.from({ next: (): IteratorResult => ({ value: 1, done: false }) }); + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 26 | - hint: iterator helpers apply directly to generators and to array, Map and Set iterators: arr.values().map(f).take(n).toArray() + hint: Iterator.from accepts arrays, strings, typed arrays, Sets, Maps and iterator objects such as generators es2025-fences.ts:28:27 - error SC2020: 'Promise.try over a callback of type '(a: number, b?: number | undefined) => number' with 1 argument' is part of the standard library types but has no scriptc lowering yet diff --git a/tests/harness/__snapshots__/string-iterator-helpers.ts.txt b/tests/harness/__snapshots__/string-iterator-helpers.ts.txt deleted file mode 100644 index ef768934e..000000000 --- a/tests/harness/__snapshots__/string-iterator-helpers.ts.txt +++ /dev/null @@ -1,8 +0,0 @@ -string-iterator-helpers.ts:3:15 - error SC2020: 'StringIterator.map' is part of the standard library types but has no scriptc lowering yet - - 2 | // refused instead of failing at run time. - 3 | const upper = "abc"[Symbol.iterator]().map((c) => c.toUpperCase()); - | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - 4 | - - hint: iterator helpers lower on generators and on array, Map and Set iterators — spread the string first: [...s].values() \ No newline at end of file diff --git a/tests/harness/corpus-costs.json b/tests/harness/corpus-costs.json index 1a4ad1180..8a3f3feb3 100644 --- a/tests/harness/corpus-costs.json +++ b/tests/harness/corpus-costs.json @@ -2092,6 +2092,7 @@ "5812-dyn-box-async-fields.ts": 3522, "5813-mock-timers-module.ts": 3766, "5814-mock-timers-date.ts": 3760, + "5815-als-callback-context.ts": 3110, "5830-timers-promises-signal.ts": 3875, "5831-util-types-brands.ts": 3347, "5832-util-types-brands-dynamic.js": 3250, @@ -2224,6 +2225,8 @@ "6000-json-stringify-class-instances.ts": 7567, "6001-json-stringify-js-class-instances.js": 4668, "601-closures-loops.ts": 1788, + "6010-match-metadata.ts": 3990, + "6011-match-metadata-js.js": 3660, "602-closures-identity-recursion.ts": 2372, "6020-class-extends-function-constructor.js": 4300, "6021-asserted-literal-union-arm.ts": 3400, @@ -2237,6 +2240,12 @@ "6053-class-to-primitive.ts": 4385, "6054-js-class-to-primitive.js": 2181, "6070-iterator-helpers-untyped.js": 4050, + "6071-iterator-sources.ts": 5150, + "6072-iterator-sources-js.js": 5350, + "6073-stored-collection-helpers.ts": 4000, + "6074-stored-collection-helpers-js.js": 4100, + "6075-print-iterators.ts": 3800, + "6076-print-iterators-js.js": 3500, "6100-record-get-or-create.ts": 3771, "6101-record-get-or-create-unchecked/main.ts": 2107, "6103-typeof-builtin-bindings.cjs": 2202, @@ -2257,6 +2266,14 @@ "6154-cjs-require-nested-destructure/main.js": 2800, "6155-cjs-export-table-methods/main.js": 2600, "6156-cjs-function-members-from-ts/main.ts": 3000, + "6200-http-agent-free-socket-data.ts": 3846, + "6230-date-parse-legacy.ts": 2490, + "6231-date-local-fields.ts": 2472, + "6232-date-fields-js.cjs": 2875, + "6233-object-identity-dynamic.cjs": 4636, + "6234-object-prototype-values.cjs": 3149, + "6235-object-identity-typed.ts": 2802, + "6236-builtin-function-values.cjs": 3572, "700-classes-basic.ts": 2530, "701-classes-composition.ts": 2357, "702-classes-this-capture.ts": 2140, @@ -5165,6 +5182,7 @@ "5812-dyn-box-async-fields.ts": 5278, "5813-mock-timers-module.ts": 4227, "5814-mock-timers-date.ts": 17708, + "5815-als-callback-context.ts": 4542, "5830-timers-promises-signal.ts": 4170, "5831-util-types-brands.ts": 4380, "5832-util-types-brands-dynamic.js": 3658, @@ -5297,6 +5315,8 @@ "6000-json-stringify-class-instances.ts": 12107, "6001-json-stringify-js-class-instances.js": 7469, "601-closures-loops.ts": 2297, + "6010-match-metadata.ts": 5450, + "6011-match-metadata-js.js": 4890, "602-closures-identity-recursion.ts": 2427, "6020-class-extends-function-constructor.js": 7500, "6021-asserted-literal-union-arm.ts": 7200, @@ -5310,6 +5330,12 @@ "6053-class-to-primitive.ts": 36270, "6054-js-class-to-primitive.js": 2958, "6070-iterator-helpers-untyped.js": 6900, + "6071-iterator-sources.ts": 5150, + "6072-iterator-sources-js.js": 5350, + "6073-stored-collection-helpers.ts": 4150, + "6074-stored-collection-helpers-js.js": 3600, + "6075-print-iterators.ts": 3450, + "6076-print-iterators-js.js": 3300, "6100-record-get-or-create.ts": 3173, "6101-record-get-or-create-unchecked/main.ts": 3228, "6103-typeof-builtin-bindings.cjs": 1796, @@ -5330,6 +5356,14 @@ "6154-cjs-require-nested-destructure/main.js": 6400, "6155-cjs-export-table-methods/main.js": 6000, "6156-cjs-function-members-from-ts/main.ts": 7000, + "6200-http-agent-free-socket-data.ts": 3452, + "6230-date-parse-legacy.ts": 3133, + "6231-date-local-fields.ts": 3365, + "6232-date-fields-js.cjs": 3637, + "6233-object-identity-dynamic.cjs": 4061, + "6234-object-prototype-values.cjs": 3979, + "6235-object-identity-typed.ts": 3441, + "6236-builtin-function-values.cjs": 4025, "700-classes-basic.ts": 2674, "701-classes-composition.ts": 2606, "702-classes-this-capture.ts": 2403, From a405f856bcebc68018f160f88afa6c905d543647 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Mon, 19 Oct 2026 09:06:15 -0500 Subject: [PATCH 014/122] Iterate class instances whose Symbol.iterator is a generator method A class with a parameterless *[Symbol.iterator]() generator method can now be iterated by for-of, array spread, Array.from, Math.max and Math.min spreads, new Set, new Map, Object.fromEntries and Iterator.from. Each site calls the method once and drives the generator it returns. obj[Symbol.iterator]() and the other well-known symbol methods called through element access now call the class's method, so iterator helpers apply to the result instead of failing on an untyped value. Writes that can replace a well-known symbol method (a symbol-keyed write, Object.defineProperty, a prototype write) now enter the method census, and those classes keep the existing checked or refused paths. --- .../src/frontend/lowering/class-iterables.ts | 35 ++++++++ .../lowering/class-method-mutations.ts | 27 +++++- .../lowering/containers/array-construction.ts | 6 +- .../lowering/containers/collection-methods.ts | 28 ++++++ .../lowering/containers/indexed-objects.ts | 36 +++++++- .../lowering/containers/iterator-helpers.ts | 14 ++- .../src/frontend/lowering/lower-exprs.ts | 4 + .../src/frontend/lowering/lower-stdlib.ts | 6 ++ .../src/frontend/lowering/lower-stmts.ts | 11 +++ .../compiler/src/frontend/lowering/lowerer.ts | 4 + .../src/frontend/lowering/symbol-methods.ts | 30 +++++-- .../test/class-method-mutations.test.ts | 21 +++++ .../6055-class-generator-iterables.ts.json | 6 ++ .../6056-class-symbol-method-calls.ts.json | 6 ++ .../corpus/6055-class-generator-iterables.ts | 86 +++++++++++++++++++ .../corpus/6056-class-symbol-method-calls.ts | 78 +++++++++++++++++ .../6055-class-generator-iterables.ts.json | 1 + .../6056-class-symbol-method-calls.ts.json | 1 + 18 files changed, 386 insertions(+), 14 deletions(-) create mode 100644 packages/compiler/src/frontend/lowering/class-iterables.ts create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6055-class-generator-iterables.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6056-class-symbol-method-calls.ts.json create mode 100644 tests/corpus/6055-class-generator-iterables.ts create mode 100644 tests/corpus/6056-class-symbol-method-calls.ts create mode 100644 tests/harness/corpus-costs/6055-class-generator-iterables.ts.json create mode 100644 tests/harness/corpus-costs/6056-class-symbol-method-calls.ts.json diff --git a/packages/compiler/src/frontend/lowering/class-iterables.ts b/packages/compiler/src/frontend/lowering/class-iterables.ts new file mode 100644 index 000000000..8c58fc0bd --- /dev/null +++ b/packages/compiler/src/frontend/lowering/class-iterables.ts @@ -0,0 +1,35 @@ +import type { IrExpr, IrType } from "../../ir/ir.js"; +import type { Lowerer } from "./lowerer.js"; +import { accessorCall } from "./lower-classes.js"; + +/** The generator a class instance's `[Symbol.iterator]` returns, when that + * method is a generator method taking no arguments: `obj[Symbol.iterator]()`, + * called once, as for-of, spread and Array.from call it. Null for other + * values, and for classes whose iterator the program can replace at run + * time (an instance property, a prototype write). */ +export function classGeneratorIterable(lowerer: Lowerer, value: IrExpr): IrExpr | null { + if (value.type.kind !== "object" || !iteratesThroughGenerator(lowerer, value.type)) return null; + const className = value.type.className; + const ret = lowerer.findMethodOn(lowerer.classes.get(className)!, "sym:iterator")!.sig.ret; + return accessorCall(lowerer, className, "sym:iterator", value, [], ret, value.loc); +} + +/** Whether values of `type` iterate through a generator method (the check + * classGeneratorIterable makes, from the type alone). */ +export function iteratesThroughGenerator(lowerer: Lowerer, type: IrType): boolean { + if (type.kind !== "object") return false; + const info = lowerer.classes.get(type.className); + if (!info || info.def.runtime || info.fields.has("sym:iterator")) return false; + if ( + lowerer.prototypeMethodAccesses.has("sym:iterator") || + lowerer.replacedSymbolMethods.has("sym:iterator") + ) + return false; + const found = lowerer.findMethodOn(info, "sym:iterator"); + return ( + !!found && + found.sig.params.length === 0 && + found.sig.ret.kind === "generator" && + !found.sig.ret.async + ); +} diff --git a/packages/compiler/src/frontend/lowering/class-method-mutations.ts b/packages/compiler/src/frontend/lowering/class-method-mutations.ts index 2a0e3a7eb..cef0bdf5f 100644 --- a/packages/compiler/src/frontend/lowering/class-method-mutations.ts +++ b/packages/compiler/src/frontend/lowering/class-method-mutations.ts @@ -43,9 +43,19 @@ export function collectClassMethodMutations( const jsAssignments: (ts.PropertyAccessExpression | ts.ElementAccessExpression)[] = []; const computedMethods: ts.Expression[] = []; const typedClasses: (ts.ClassDeclaration | ts.ClassExpression)[] = []; + const symbolMethods = new Set(); let unknownKey: ts.Node | undefined; let numericKey: ts.Node | undefined; let unknownOwnField = false; + // `sym:iterator` for `Symbol.iterator`: the name a class method declared + // with a well-known symbol key takes. + const wellKnownMethodName = (key: ts.Expression): string | null => { + while (ts.isParenthesizedExpression(key)) key = key.expression; + if (!ts.isPropertyAccessExpression(key)) return null; + const member = lowerer.stdlibGlobalMember(key, "Symbol"); + if (member === null || !(lowerer.typeOf(key).flags & ts.TypeFlags.ESSymbolLike)) return null; + return `sym:${member}`; + }; // JavaScript method inference and dynamic class factories keep their // existing dispatch contract. The native-call proof applies to typed // declarations (including JavaScript files TypeScript checks), while @@ -556,6 +566,19 @@ export function collectClassMethodMutations( lowerer.prototypeMethodAccesses.set(name, site); } for (const { key, receiver, site } of computedWrites) { + // A class method declared with a well-known symbol key is not a write; + // any other write with one can replace that method. + const symbolName = key ? wellKnownMethodName(unwrap(key)) : null; + if (symbolName) { + const declaration = + (ts.isMethodDeclaration(site) || + ts.isGetAccessorDeclaration(site) || + ts.isSetAccessorDeclaration(site)) && + (ts.isClassDeclaration(site.parent) || ts.isClassExpression(site.parent)); + if (declaration) symbolMethods.add(symbolName); + else lowerer.replacedSymbolMethods.add(symbolName); + continue; + } const type = key ? lowerer.typeOf(key) : undefined; const names = type && (literalValues(type) ?? (key ? literalLoopKeys(unwrap(key)) : null)); if (names) { @@ -586,8 +609,10 @@ export function collectClassMethodMutations( if (String(Number(name)) === name) markName(name, numericKey); } } - if (unknownKey) + if (unknownKey) { for (const method of methods) lowerer.prototypeMethodAccesses.set(method, unknownKey); + for (const method of symbolMethods) lowerer.replacedSymbolMethods.add(method); + } if (!unknownKey && !unknownOwnField) for (const field of arrowFields) { if (!receiverFields.has(field)) lowerer.receiverFreeCallbackFields.add(field); diff --git a/packages/compiler/src/frontend/lowering/containers/array-construction.ts b/packages/compiler/src/frontend/lowering/containers/array-construction.ts index 6072b58b7..5a61f53ce 100644 --- a/packages/compiler/src/frontend/lowering/containers/array-construction.ts +++ b/packages/compiler/src/frontend/lowering/containers/array-construction.ts @@ -44,6 +44,7 @@ import { lowerArrayCallback, callbackArrayElement, } from "./callback-arguments.js"; +import { classGeneratorIterable } from "../class-iterables.js"; /** Build the argument array for a mutating/copying operation while keeping * the receiver's scalar payload ABI. A missing read is represented as the @@ -913,7 +914,7 @@ export function lowerArrayFromCall( // and materializes holes while keeping element reference identity. if ((args.length === 1 || args.length === 2) && !ts.isObjectLiteralExpression(args[0]!)) { const source = lowerer.lowerExpr(args[0]!); - const src = + let src = source.type.kind === "string" && args.length === 2 ? strCharsCall(lowerer, source, loc) : source; @@ -1008,6 +1009,9 @@ export function lowerArrayFromCall( if (src.type.kind === "searchParams" && args.length === 1) { return lowerCheckedArrayFrom(lowerer, lowerer.coerceInto(args[0]!, src, DYN), loc); } + // A class whose [Symbol.iterator] is a generator method iterates the + // generator that method returns. + src = classGeneratorIterable(lowerer, src) ?? src; // A generator (or an iterator adapted into one) drains in order. if (src.type.kind === "generator" && args.length === 1) { const arrT = lowerer.mapTypeOf(lowerer.typeOf(call)); diff --git a/packages/compiler/src/frontend/lowering/containers/collection-methods.ts b/packages/compiler/src/frontend/lowering/containers/collection-methods.ts index a8b782b51..6a5290c0f 100644 --- a/packages/compiler/src/frontend/lowering/containers/collection-methods.ts +++ b/packages/compiler/src/frontend/lowering/containers/collection-methods.ts @@ -42,6 +42,8 @@ import { import { strCharsCall } from "./array-construction.js"; import { arrayElementRead } from "./array-iteration.js"; import { isSearchParamsIterable } from "../builtins/url.js"; +import { classGeneratorIterable } from "../class-iterables.js"; +import { generatorDrain } from "../iterator-adapters.js"; /** Ambient Map method calls. `get`/`set`/`has`/`delete`/`clear` lower to * mapIntrinsic; `forEach` desugars to a direct call of a synthetic loop @@ -1048,6 +1050,14 @@ function setFromSeedValue( } : null; } + // A generator, or a class whose [Symbol.iterator] is a generator method: + // the Set takes every value it yields, in order. + const generator = + classGeneratorIterable(lowerer, source) ?? (source.type.kind === "generator" ? source : null); + if (generator) { + const drained = generatorDrain(lowerer, generator, setT.elem, loc); + return drained ? { kind: "setNew", seed: drained, type: setT, loc } : null; + } let seed: IrExpr; if (source.type.kind === "set" && typeEquals(source.type, setT)) { return { kind: "setIntrinsic", method: "clone", receiver: source, args: [], type: setT, loc }; @@ -1246,6 +1256,24 @@ export function mapFromSeedValue( } : null; } + // A generator of entries, or a class whose [Symbol.iterator] is one: the + // Map takes every [key, value] pair it yields, in order. + const generator = + classGeneratorIterable(lowerer, seed) ?? (seed.type.kind === "generator" ? seed : null); + if (generator) { + const entry: IrType = { + kind: "record", + shapeId: lowerer.shapes.intern( + [ + { name: "0", type: mapT.key }, + { name: "1", type: mapT.value }, + ], + true, + ), + }; + const drained = generatorDrain(lowerer, generator, entry, loc); + return drained ? mapFromSeedValue(lowerer, drained, mapT) : null; + } if (seed.type.kind === "map") { if (!typeEquals(seed.type, mapT)) return null; return { kind: "mapIntrinsic", method: "clone", receiver: seed, args: [], type: mapT, loc }; diff --git a/packages/compiler/src/frontend/lowering/containers/indexed-objects.ts b/packages/compiler/src/frontend/lowering/containers/indexed-objects.ts index 38df368ac..4ba196136 100644 --- a/packages/compiler/src/frontend/lowering/containers/indexed-objects.ts +++ b/packages/compiler/src/frontend/lowering/containers/indexed-objects.ts @@ -28,6 +28,8 @@ import { tryLowerExpression } from "../expressions/try-lower-expression.js"; import { locOf } from "../../program.js"; import { typeKey } from "../../type-mapper.js"; import { type WidthLift } from "../lowerer.js"; +import { classGeneratorIterable, iteratesThroughGenerator } from "../class-iterables.js"; +import { generatorDrain } from "../iterator-adapters.js"; /** Object() and new Object() preserve known object inputs. Primitive * boxing and unproven checked-dynamic values retain their refusal. */ @@ -472,9 +474,37 @@ function lowerTypedObjectFromEntriesCall( if (call.arguments.nodes.length !== 1 || ts.isSpreadElement(call.arguments.nodes[0]!)) return null; const argNode = call.arguments.nodes[0]!; - const argIr = lowerer.mapTypeOf(lowerer.typeOf(argNode)); + let argIr = lowerer.mapTypeOf(lowerer.typeOf(argNode)); + // A generator of entries, or a class whose [Symbol.iterator] is one, + // drains into the entry array the object is built from. + let drained: IrExpr | null = null; + if (argIr?.kind === "generator" || (argIr && iteratesThroughGenerator(lowerer, argIr))) { + const value = lowerer.lowerExpr(argNode); + const generator = + classGeneratorIterable(lowerer, value) ?? (value.type.kind === "generator" ? value : null); + if (generator?.type.kind !== "generator") return null; + // The entries take the result's value type: `[string, V]` for an + // object `{ [k: string]: V }`. + const result = lowerer.mapTypeOf(lowerer.typeOf(call)); + const valueT = + result?.kind === "record" ? lowerer.shapes.get(result.shapeId)?.indexValue : undefined; + if (!valueT) return null; + const entry: IrType = { + kind: "record", + shapeId: lowerer.shapes.intern( + [ + { name: "0", type: STRING }, + { name: "1", type: valueT }, + ], + true, + ), + }; + drained = generatorDrain(lowerer, generator, entry, locOf(call)); + if (!drained) return null; + argIr = drained.type; + } if (argIr?.kind !== "array") return null; - const represented = tryLowerExpression(lowerer, argNode); + const represented = drained ?? tryLowerExpression(lowerer, argNode); if (represented && !typeEquals(represented.type, argIr)) return null; // `Object.fromEntries(rows)` over a `string[][]` VALUE — the env-line // idiom (`envArray.map((env) => env.split('='))`). The checker has no @@ -522,7 +552,7 @@ function lowerTypedObjectFromEntriesCall( // Enumeration/filtering may keep the array in checked-dynamic storage. // Validate its tuple layout before calling a helper with a native-array // ABI; opaque tuple values themselves retain their original references. - const receiver = lowerer.lowerExprExpecting(argNode, argIr); + const receiver = drained ?? lowerer.lowerExprExpecting(argNode, argIr); const key = `obj.fromEntries:${argIr.elem.shapeId}:${resultT.shapeId}`; let helper = lowerer.arrHofHelpers.get(key); if (!helper) { diff --git a/packages/compiler/src/frontend/lowering/containers/iterator-helpers.ts b/packages/compiler/src/frontend/lowering/containers/iterator-helpers.ts index aeefffe48..d439d956e 100644 --- a/packages/compiler/src/frontend/lowering/containers/iterator-helpers.ts +++ b/packages/compiler/src/frontend/lowering/containers/iterator-helpers.ts @@ -34,6 +34,7 @@ import { genValue, stepDrivable, } from "../lower-generators.js"; +import { classGeneratorIterable, iteratesThroughGenerator } from "../class-iterables.js"; import { exitBeforeReturns } from "./for-of.js"; import { iteratorSlotAdapter, @@ -317,9 +318,11 @@ function isHelperObject(lowerer: Lowerer, expr: ts.Expression): boolean { * it rather than a wrapper. A generator (including a helper object or an * array iterator) is itself; an array, string or typed array gives its * values iterator; a Set its values and a Map its entries, as a generator - * over the live collection. The result converts into the call's - * IteratorObject type. Null for other arguments (objects with their own - * next method, untyped values), which keep the Iterator fence. */ + * over the live collection; a class instance whose `[Symbol.iterator]` is + * a generator method gives the generator it returns. The result converts + * into the call's IteratorObject type. Null for other arguments (objects + * with their own next method, untyped values), which keep the Iterator + * fence. */ export function lowerIteratorFromCall( lowerer: Lowerer, call: ts.CallExpression, @@ -332,7 +335,9 @@ export function lowerIteratorFromCall( const argT = lowerer.mapTypeOf(lowerer.typeOf(argNode)); if (resultT?.kind !== "generator" || resultT.async || argT === null) return null; const iterableKinds = ["generator", "array", "string", "bytes", "set", "map"]; - if (!iterableKinds.includes(argT.kind) || (argT.kind === "generator" && argT.async)) return null; + const classIterable = iteratesThroughGenerator(lowerer, argT); + if (!classIterable && !iterableKinds.includes(argT.kind)) return null; + if (argT.kind === "generator" && argT.async) return null; const loc = locOf(call); const value = lowerer.lowerExpr(argNode); const own = (yieldT: IrType): GenType => ({ @@ -344,6 +349,7 @@ export function lowerIteratorFromCall( let iterator: IrExpr | null = null; const t = value.type; if (t.kind === "generator") iterator = value; + else if (classIterable) iterator = classGeneratorIterable(lowerer, value); else if (t.kind === "array") iterator = lowerArrayIteratorValue(lowerer, value, "values", own(t.elem), loc); else if (t.kind === "string") diff --git a/packages/compiler/src/frontend/lowering/lower-exprs.ts b/packages/compiler/src/frontend/lowering/lower-exprs.ts index 09407f71f..654133b5e 100644 --- a/packages/compiler/src/frontend/lowering/lower-exprs.ts +++ b/packages/compiler/src/frontend/lowering/lower-exprs.ts @@ -224,6 +224,7 @@ import { } from "./expressions/optional-chains.js"; import { foldedStringKeyOf } from "./expressions/object-literals.js"; import { tryLowerExpression } from "./expressions/try-lower-expression.js"; +import { classGeneratorIterable } from "./class-iterables.js"; import { classToNumberConversion, classToStringConversion, @@ -6244,6 +6245,9 @@ export function lowerArrayLiteral( loc: locOf(el), }; } + // `[...obj]` over a class whose [Symbol.iterator] is a generator + // method spreads the generator that method returns. + src = classGeneratorIterable(lowerer, src) ?? src; // `[...it]` over a generator (or an iterator adapted into one): // drain it once, in order, into a fresh element array. if (src.type.kind === "generator") { diff --git a/packages/compiler/src/frontend/lowering/lower-stdlib.ts b/packages/compiler/src/frontend/lowering/lower-stdlib.ts index 92d9f0690..01e02c8ff 100644 --- a/packages/compiler/src/frontend/lowering/lower-stdlib.ts +++ b/packages/compiler/src/frontend/lowering/lower-stdlib.ts @@ -42,6 +42,8 @@ import { STATIC_RESPONSE_CALLS, STATIC_RESPONSE_READS, } from "../../compat/fetch-profile.js"; +import { classGeneratorIterable } from "./class-iterables.js"; +import { generatorDrain } from "./iterator-adapters.js"; export function refuseUnsupportedApi( lowerer: Lowerer, @@ -2438,6 +2440,10 @@ export function lowerStdlibMethodCall( src = lowerer.coerceInto(spread.expression, src, inferred); } } + // A generator, or a class whose [Symbol.iterator] is a generator + // method, drains into the number[] the fold reads. + src = classGeneratorIterable(lowerer, src) ?? src; + if (src.type.kind === "generator") src = generatorDrain(lowerer, src, F64, loc) ?? src; if (src.type.kind !== "array" || src.type.elem.kind !== "f64") { lowerer.unsupported( "SC1090", diff --git a/packages/compiler/src/frontend/lowering/lower-stmts.ts b/packages/compiler/src/frontend/lowering/lower-stmts.ts index 36abb9130..766583917 100644 --- a/packages/compiler/src/frontend/lowering/lower-stmts.ts +++ b/packages/compiler/src/frontend/lowering/lower-stmts.ts @@ -217,6 +217,7 @@ import { constituentTypes } from "../ts7/checker.js"; import { npmStaticPackageOfPath } from "../npm-static.js"; import { fenceNodeModuleMutation } from "./lower-node-module.js"; import { fencePerfEntryKeyWalk } from "./builtins/performance.js"; +import { classGeneratorIterable } from "./class-iterables.js"; interface UsingRegistration { acquire: IrStmt[]; @@ -8767,6 +8768,16 @@ export function lowerForOf(lowerer: Lowerer, stmt: ts.ForOfStatement): IrStmt { ); } if (iterable.type.kind === "object") { + // A [Symbol.iterator] generator method: the loop drives the + // generator it returns. + const generator = classGeneratorIterable(lowerer, iterable); + if (generator) + return lowerForOfGenerator( + lowerer, + stmt, + generator as IrExpr & { type: IrType & { kind: "generator" } }, + labels, + ); // Class iterables ([Symbol.iterator]() + next() — classIteratorOf) // drive the same protocol with ordinary method calls. const cit = lowerer.classIteratorOf(iterable.type); diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index d252af250..a4fa362c5 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -2584,6 +2584,10 @@ export class Lowerer { /** Named method slots observed before class collection need a callable * ABI and shared prototype descriptors instead of call-site specialization. */ readonly prototypeMethodAccesses = new Map(); + /** Well-known symbol methods (`sym:iterator`) the program may replace + * through a symbol-keyed write: an instance or prototype property, or a + * reflection call. */ + readonly replacedSymbolMethods = new Set(); /** Typed calls through class-backed protocols, by signature; class * dynamic dispatch adds their native arms (class-protocol-dispatch.ts). */ readonly protocolDispatches = new Map(); diff --git a/packages/compiler/src/frontend/lowering/symbol-methods.ts b/packages/compiler/src/frontend/lowering/symbol-methods.ts index f6a8e68e4..b02e95641 100644 --- a/packages/compiler/src/frontend/lowering/symbol-methods.ts +++ b/packages/compiler/src/frontend/lowering/symbol-methods.ts @@ -2,10 +2,11 @@ import * as ts from "../ts7/adapter.js"; import { locOf } from "../program.js"; import type { IrExpr } from "../../ir/ir.js"; import type { Lowerer } from "./lowerer.js"; -import { accessorCall } from "./lower-classes.js"; +import { accessorCall, type ClassInfo } from "./lower-classes.js"; import { classSymbolKeyOf } from "./symbol-fields.js"; -/** A stable symbol method uses the same completed arguments and virtual +/** A stable symbol method (a module-scope symbol, or a well-known symbol + * such as `Symbol.iterator`) uses the same completed arguments and virtual * dispatch as a string-named class method. Runtime-selected keys stay on * the checked-property path. */ export function lowerClassSymbolMethodCall( @@ -15,12 +16,13 @@ export function lowerClassSymbolMethodCall( const access = call.expression; if (!ts.isElementAccessExpression(access) || access.questionDotToken || call.questionDotToken) return null; - const key = classSymbolKeyOf(lowerer, access.argumentExpression); - if (!key) return null; const type = lowerer.mapTypeOf(lowerer.typeOf(access.expression)); if (type?.kind !== "object") return null; const info = lowerer.classes.get(type.className); - const name = info?.symbolMethods?.get(key.identity); + const key = classSymbolKeyOf(lowerer, access.argumentExpression); + const name = key + ? info?.symbolMethods?.get(key.identity) + : wellKnownSymbolMethod(lowerer, info, access.argumentExpression); if (!name) return null; const method = lowerer.findMethodOn(info ?? null, name); if (!method) return null; @@ -29,3 +31,21 @@ export function lowerClassSymbolMethodCall( const args = lowerer.completeArgs(call.arguments.nodes, method.sig.params, loc, call); return accessorCall(lowerer, type.className, name, receiver, args, method.sig.ret, loc); } + +/** The `sym:` method a well-known symbol key such as `Symbol.iterator` + * names, when the class declares it as a method and the program cannot + * replace it at run time (an instance property of that name, a prototype + * write). */ +function wellKnownSymbolMethod( + lowerer: Lowerer, + info: ClassInfo | undefined, + key: ts.Expression, +): string | null { + while (ts.isParenthesizedExpression(key)) key = key.expression; + if (!info || info.def.runtime || !ts.isPropertyAccessExpression(key)) return null; + const member = lowerer.stdlibGlobalMember(key, "Symbol"); + if (member === null) return null; + const name = `sym:${member}`; + if (info.fields.has(name) || lowerer.replacedSymbolMethods.has(name)) return null; + return name; +} diff --git a/packages/compiler/test/class-method-mutations.test.ts b/packages/compiler/test/class-method-mutations.test.ts index 2258bd54a..b508a3949 100644 --- a/packages/compiler/test/class-method-mutations.test.ts +++ b/packages/compiler/test/class-method-mutations.test.ts @@ -114,3 +114,24 @@ test("a computed key in an Object.assign source keeps checked dispatch", async ( .replace("LITERAL", "Object.assign(target, { [key]: value })"); expect(await callersOf(source, "%dyn.class.properties")).not.toEqual([]); }); + +const range = ` +class Range { + hi: number; + constructor(hi: number) { this.hi = hi; } + *[Symbol.iterator](): Generator { for (let i = 0; i < this.hi; i++) yield i; } +} +const r = new Range(3); +console.log(r[Symbol.iterator]().next().value); +`; + +test("a well-known symbol method call lowers to the class's method", async () => { + expect(await callersOf(range, "%Range.sym:iterator")).not.toEqual([]); +}); + +test("a reflection call with a well-known symbol key keeps checked dispatch", async () => { + const write = "Object.defineProperty(r, Symbol.iterator, { value: function* () {} });\n"; + // The checked path reads the declared method through its value thunk. + const callers = await callersOf(`${range}${write}`, "%Range.sym:iterator"); + expect(callers.filter((name) => !name.startsWith("%method.value:"))).toEqual([]); +}); diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6055-class-generator-iterables.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6055-class-generator-iterables.ts.json new file mode 100644 index 000000000..661995e3f --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6055-class-generator-iterables.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6055-class-generator-iterables.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6056-class-symbol-method-calls.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6056-class-symbol-method-calls.ts.json new file mode 100644 index 000000000..445389b5e --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6056-class-symbol-method-calls.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6056-class-symbol-method-calls.ts" + ], + "diags": [] +} diff --git a/tests/corpus/6055-class-generator-iterables.ts b/tests/corpus/6055-class-generator-iterables.ts new file mode 100644 index 000000000..50641c261 --- /dev/null +++ b/tests/corpus/6055-class-generator-iterables.ts @@ -0,0 +1,86 @@ +// Classes whose [Symbol.iterator] is a generator method iterate the +// generator it returns, called once: in for-of (an early exit closes it), +// array and Math.max spreads, Array.from, new Set, new Map, +// Object.fromEntries and yield*. +class Counter implements Iterable { + n: number; + constructor(n: number) { + this.n = n; + } + *[Symbol.iterator](): Generator { + for (let i = 0; i < this.n; i++) yield i; + } +} +const c = new Counter(3); +for (const v of c) console.log("of", v); +console.log([...c], Array.from(c), Math.max(...c)); +for (const v of c) { + if (v === 1) break; + console.log("early", v); +} + +class Pairs implements Iterable<[string, number]> { + *[Symbol.iterator](): Generator<[string, number]> { + yield ["a", 1]; + yield ["b", 2]; + } +} +class Nums implements Iterable { + *[Symbol.iterator](): Generator { + yield 1; + yield 2; + yield 2; + } +} +const p = new Pairs(); +const n = new Nums(); +console.log(new Set(n).size); +console.log(new Map(p).get("b")); + +console.log(Object.fromEntries(p)); +function* outer(): Generator { + yield 0; + yield* n; +} +console.log([...outer()]); +function* plain(): Generator { + yield 3; + yield 3; + yield 4; +} +console.log(new Set(plain()).size, [...new Set(n)]); + +class Tracked implements Iterable { + log: string[] = []; + *[Symbol.iterator](): Generator { + try { + for (const word of ["a", "b", "c"]) { + this.log.push("yield " + word); + yield word; + } + } finally { + this.log.push("closed"); + } + } +} +const t = new Tracked(); +for (const w of t) { + if (w === "b") break; + t.log.push("saw " + w); +} +console.log(t.log.join(", ")); +class Tree implements Iterable { + value: number; + children: Tree[]; + constructor(value: number, children: Tree[] = []) { + this.value = value; + this.children = children; + } + *[Symbol.iterator](): Generator { + yield this.value; + for (const child of this.children) yield* child; + } +} +class Leafy extends Tree {} +const tree = new Tree(1, [new Tree(2, [new Tree(3)]), new Leafy(4)]); +console.log([...tree], Array.from(new Leafy(9)), Math.min(...tree)); diff --git a/tests/corpus/6056-class-symbol-method-calls.ts b/tests/corpus/6056-class-symbol-method-calls.ts new file mode 100644 index 000000000..2829dc067 --- /dev/null +++ b/tests/corpus/6056-class-symbol-method-calls.ts @@ -0,0 +1,78 @@ +// Well-known symbol methods called through element access, and +// Iterator.from over a class whose [Symbol.iterator] is a generator method: +// both give the class's own generator, so iterator helpers apply to it. +class Range { + lo: number; + hi: number; + constructor(lo: number, hi: number) { + this.lo = lo; + this.hi = hi; + } + *[Symbol.iterator](): Generator { + for (let i = this.lo; i < this.hi; i++) yield i; + } +} +class Labeled extends Range { + label = "l"; +} +class Words { + text: string; + constructor(text: string) { + this.text = text; + } + *[Symbol.iterator](): Generator { + for (const w of this.text.split(" ")) yield w; + } +} +class Money { + cents: number; + constructor(cents: number) { + this.cents = cents; + } + [Symbol.toPrimitive](hint: string): string | number { + return hint === "number" ? this.cents / 100 : `$${(this.cents / 100).toFixed(2)}`; + } +} +class Handle { + name: string; + constructor(name: string) { + this.name = name; + } + [Symbol.dispose](): void { + console.log("dispose", this.name); + } +} + +const r = new Range(1, 6); +const direct = r[Symbol.iterator](); +console.log(direct.next(), direct.next()); +console.log(r[Symbol.iterator]().map((x) => x + 1).toArray()); +const ranges: Range[] = [new Range(0, 3), new Labeled(5, 7)]; +for (const x of ranges) console.log(x[Symbol.iterator]().toArray()); +console.log(r[Symbol.iterator]().filter((x) => x > 3).toArray()); + +const it = Iterator.from(r); +console.log(it.next().value, it.next().value); +console.log([...it]); +console.log(Iterator.from(r).map((x) => x * 10).toArray()); +console.log(Iterator.from(r).filter((x) => x % 2 === 1).toArray()); +console.log(Iterator.from(r).take(2).toArray(), Iterator.from(r).drop(3).toArray()); +console.log(Iterator.from(r).reduce((a, b) => a + b, 0)); +console.log(Iterator.from(r).some((x) => x > 4), Iterator.from(r).every((x) => x > 0)); +console.log(Iterator.from(r).find((x) => x > 2)); +console.log(Iterator.from(new Words("a bb ccc")).map((w) => w.length).toArray()); +Iterator.from(r).forEach((x) => console.log("each", x)); +for (const x of Iterator.from(r).flatMap((x) => [x, -x]).take(4)) console.log(x); +const g = Iterator.from(r); +for (const x of g) { + if (x === 2) break; +} +console.log(g.next()); + +console.log(Iterator.from(r), r[Symbol.iterator](), Iterator.from(r).map((x) => x)); +console.log(Iterator.from(r).flatMap((x) => new Set([x, x + 10])).toArray()); +console.log(r[Symbol.iterator]().flatMap((x) => new Map([[x, String(x)]])).toArray()); + +const m = new Money(1234); +console.log(m[Symbol.toPrimitive]("number"), m[Symbol.toPrimitive]("default")); +new Handle("h")[Symbol.dispose](); diff --git a/tests/harness/corpus-costs/6055-class-generator-iterables.ts.json b/tests/harness/corpus-costs/6055-class-generator-iterables.ts.json new file mode 100644 index 000000000..68b6f9db4 --- /dev/null +++ b/tests/harness/corpus-costs/6055-class-generator-iterables.ts.json @@ -0,0 +1 @@ +{ "plain": 4921, "san": 6451 } diff --git a/tests/harness/corpus-costs/6056-class-symbol-method-calls.ts.json b/tests/harness/corpus-costs/6056-class-symbol-method-calls.ts.json new file mode 100644 index 000000000..4207a0ed0 --- /dev/null +++ b/tests/harness/corpus-costs/6056-class-symbol-method-calls.ts.json @@ -0,0 +1 @@ +{ "plain": 3468, "san": 5120 } From 6e68a811f10777e00fd9c61922ec0dba06cbecab Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Mon, 19 Oct 2026 12:27:45 -0700 Subject: [PATCH 015/122] WebAssembly W5: the Memory, Table and Global constructors, Module.imports/exports/customSections, and function references from tables new WebAssembly.Memory, Table and Global construct the runtime objects an instance exports, with Node 24's descriptor order, defaults, conversions and messages (V8's EnforceUint32 errors, the v128 and shared-memory errors; a valid shared memory is refused with a TypeError until threads), so either kind can be imported. Imports of memories, tables and globals now check limits, types and mutability with V8's LinkError messages, and exports keep V8's identity (imported objects re-export as themselves, one object per memory and table). The classes called without new throw Node's TypeError. WebAssembly.Module.imports, exports and customSections read the descriptor (ABI v2). Custom sections are carried only by programs that call customSections. Every function a module references reaches JavaScript as a callable function: the translator emits one call thunk per signature, so table.get and funcref globals return the module's unexported functions too, and exported functions can be stored in tables and globals from any instance. Discovery also finds .so WebAssembly shared libraries at resolved paths. Fixes to W3/W4 found on the way: externref Table.grow fills with undefined; Memory.grow/Table index errors and funcref messages match V8; an invalid funcref global import is a LinkError; global.valueOf() on a WebAssembly.Global-typed receiver answers the value; explicit-bounds memories keep the blocks grow replaced, so old ArrayBuffers never dangle. This unblocks quickjs-wasi's extension loader: quickjs-wasi with its url, encoding, headers and structured-clone extensions runs through its own glue identically to Node, and pi codemode's static coverage goes from 83% to 84% with no WebAssembly blocker left. Evidence: differential corpus programs for the API (webassembly-jsapi), a 261-test port of WPT wasm/jsapi (webassembly-wpt-jsapi), quickjs-wasi's dist/extensions.js run unmodified against dylink-shaped modules (webassembly-dylink), and the 2.0 spec suite's modules that import the spectest memory and table (new webassembly-spec-imports group plus the memory and table groups); all match Node in the plain and sanitized lanes. The zero-cost gate leaves every non-WebAssembly program byte-identical. Co-Authored-By: Claude Opus 5.5 --- .../limitations/webassembly-modules.mdx | 10 +- .../generated/node-v24-backlog.json | 6 +- .../generated/node-v24-internal.json | 10 +- internal/compatibility/static-support.json | 2 +- .../compiler/src/backend/llvm/lib-shared.ts | 8 + .../compiler/src/backend/llvm/wasm-modules.ts | 5 +- .../compiler/src/coverage/surface-manifest.ts | 2 +- .../compiler/src/diagnostics/diagnostic.ts | 15 +- .../frontend/lowering/builtins/webassembly.ts | 125 +- .../src/frontend/lowering/lower-calls.ts | 3 + .../compiler/src/frontend/lowering/lowerer.ts | 4 + packages/compiler/src/ir/builtin-effects.ts | 8 + .../compiler/src/ir/builtin-signatures.ts | 18 + packages/compiler/src/ir/ir.ts | 4 + packages/compiler/src/wasm/discover.ts | 24 +- packages/compiler/src/wasm/translate.test.ts | 31 + packages/compiler/src/wasm/translate.ts | 88 +- packages/compiler/surface-manifest.json | 2 +- .../corpus/webassembly-dylink/main.mjs.json | 7 + .../tests/corpus/webassembly-jsapi.mjs.json | 6 + .../webassembly-spec-imports/main.mjs.json | 8 + .../corpus/webassembly-wpt-jsapi.mjs.json | 6 + packages/runtime/src/scr_wasm.c | 511 +++++++- packages/runtime/src/scr_wasm_abi.h | 28 +- .../webassembly-dylink/LICENSE.quickjs-wasi | 21 + tests/corpus/webassembly-dylink/extensions.js | 372 ++++++ tests/corpus/webassembly-dylink/main.mjs | 101 ++ tests/corpus/webassembly-jsapi.mjs | 356 ++++++ .../corpus/webassembly-spec-imports/data.mjs | 137 ++ .../corpus/webassembly-spec-imports/main.mjs | 5 + tests/corpus/webassembly-spec-memory/data.mjs | 16 +- tests/corpus/webassembly-spec-table/data.mjs | 14 +- tests/corpus/webassembly-wpt-jsapi.mjs | 1117 +++++++++++++++++ tests/coverage-fixtures/wasm/ext.so | Bin 0 -> 36 bytes tests/coverage-fixtures/wasm/glue.js | 9 + tests/coverage-fixtures/wasm/main.ts | 13 +- tests/coverage-fixtures/wasm/native.so | Bin 0 -> 25 bytes tests/diagnostics/webassembly-fences.ts | 13 +- tests/harness/__snapshots__/coverage-wasm.txt | 9 +- .../__snapshots__/webassembly-fences.ts.txt | 83 +- tests/harness/coverage.test.ts | 8 +- tests/wasm-spec/corpus/dylink-ext.wat | 42 + tests/wasm-spec/corpus/dylink-main.wat | 33 + tests/wasm-spec/corpus/jsapi-own.wat | 11 + tests/wasm-spec/corpus/jsapi.wat | 37 + tests/wasm-spec/gen.mjs | 17 +- tests/wasm-spec/runner.mjs | 5 +- 47 files changed, 3162 insertions(+), 188 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-dylink/main.mjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-jsapi.mjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-spec-imports/main.mjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-wpt-jsapi.mjs.json create mode 100644 tests/corpus/webassembly-dylink/LICENSE.quickjs-wasi create mode 100644 tests/corpus/webassembly-dylink/extensions.js create mode 100644 tests/corpus/webassembly-dylink/main.mjs create mode 100644 tests/corpus/webassembly-jsapi.mjs create mode 100644 tests/corpus/webassembly-spec-imports/data.mjs create mode 100644 tests/corpus/webassembly-spec-imports/main.mjs create mode 100644 tests/corpus/webassembly-wpt-jsapi.mjs create mode 100644 tests/coverage-fixtures/wasm/ext.so create mode 100644 tests/coverage-fixtures/wasm/native.so create mode 100644 tests/wasm-spec/corpus/dylink-ext.wat create mode 100644 tests/wasm-spec/corpus/dylink-main.wat create mode 100644 tests/wasm-spec/corpus/jsapi-own.wat create mode 100644 tests/wasm-spec/corpus/jsapi.wat diff --git a/docs/content/limitations/webassembly-modules.mdx b/docs/content/limitations/webassembly-modules.mdx index e768b4ef0..8c8f8645c 100644 --- a/docs/content/limitations/webassembly-modules.mdx +++ b/docs/content/limitations/webassembly-modules.mdx @@ -1,16 +1,16 @@ -scriptc translates WebAssembly modules to native code when it builds the program; there is no interpreter or JIT in the executable. A build translates every module whose bytes it can see: files at paths the compiler resolves (`require.resolve`, `createRequire(...).resolve`, `import.meta.resolve`, `new URL("./x.wasm", import.meta.url)`, `path.join(__dirname, ...)`), modules inlined as base64 or byte-array literals, `.wasm` files shipped by packages whose code uses WebAssembly, and files listed in a `scriptc.wasm` array in `package.json`. At run time `WebAssembly.compile`, `instantiate`, `validate` and `new WebAssembly.Module` find the translated module by the SHA-256 of the bytes they are given. Bytes the build did not see, including bytes produced at run time, reject with a `CompileError` that says the module was not translated, and `WebAssembly.validate` answers `false` for them. `scriptc coverage` lists the modules a build finds, with their size, whether they validate, and the WebAssembly proposals they use. +scriptc translates WebAssembly modules to native code when it builds the program; there is no interpreter or JIT in the executable. A build translates every module whose bytes it can see: files at paths the compiler resolves (`require.resolve`, `createRequire(...).resolve`, `import.meta.resolve`, `new URL("./x.wasm", import.meta.url)`, `path.join(__dirname, ...)`), including `.so` WebAssembly shared libraries such as quickjs-wasi's extensions, modules inlined as base64 or byte-array literals, `.wasm` files shipped by packages whose code uses WebAssembly, and files listed in a `scriptc.wasm` array in `package.json`. At run time `WebAssembly.compile`, `instantiate`, `validate` and `new WebAssembly.Module` find the translated module by the SHA-256 of the bytes they are given. Bytes the build did not see, including bytes produced at run time, reject with a `CompileError` that says the module was not translated, and `WebAssembly.validate` answers `false` for them. `scriptc coverage` lists the modules a build finds, with their size, whether they validate, and the WebAssembly proposals they use. Translated modules can use the WebAssembly 2.0 feature set: the MVP plus sign-extension operators, non-trapping float-to-int conversions, bulk memory, multi-value, mutable globals, reference types, and extended constant expressions. Modules that use SIMD, threads, exception handling, tail calls, typed function references or GC, memory64, or multiple memories are not translated, so loading them rejects with the `CompileError` above; packages that fall back to a scalar build when that happens, such as undici and es-module-lexer, take their fallback. -Supported: `WebAssembly.compile`, `instantiate` (with bytes or a `Module`), `validate`, `new WebAssembly.Module`, `new WebAssembly.Instance`, and `instanceof` for every class; the error classes `CompileError`, `LinkError` and `RuntimeError`, constructed with or without `new`; `globalThis.WebAssembly` and its `compile`, `instantiate` and `validate` functions as values. Instances expose exported functions, memories (`buffer`, `grow`), tables (`get`, `set`, `grow`, `length`) and globals (`value`). Imports can be functions, numbers or BigInts for immutable globals, and memories, tables and globals exported by other instances. Traps throw `WebAssembly.RuntimeError` with Node.js's messages, unbounded recursion throws `RangeError`, and an exception thrown by an imported function propagates through the WebAssembly frames unchanged. A `WebAssembly.Module` can be sent to a worker through `workerData` or `postMessage`. +Supported: `WebAssembly.compile`, `instantiate` (with bytes or a `Module`), `validate`, `new WebAssembly.Module`, `new WebAssembly.Instance`, `new WebAssembly.Memory`, `new WebAssembly.Table`, `new WebAssembly.Global`, `WebAssembly.Module.imports`, `exports` and `customSections`, and `instanceof` for every class; the error classes `CompileError`, `LinkError` and `RuntimeError`, constructed with or without `new`; `globalThis.WebAssembly` and its `compile`, `instantiate` and `validate` functions as values. Instances expose exported functions, memories (`buffer`, `grow`), tables (`get`, `set`, `grow`, `length`) and globals (`value`, `valueOf`). Imports can be functions, numbers or BigInts for immutable globals, and memories, tables and globals that the program constructs or other instances export; their limits, types and mutability are checked with Node.js's `LinkError` messages. Function references from a table or global reach JavaScript as callable functions, and exported functions can be stored in tables and globals, which is what dynamic linkers such as quickjs-wasi's extension loader do. Traps throw `WebAssembly.RuntimeError` with Node.js's messages, unbounded recursion throws `RangeError`, and an exception thrown by an imported function propagates through the WebAssembly frames unchanged. A `WebAssembly.Module` can be sent to a worker through `workerData` or `postMessage`. -Not supported yet: the `Memory`, `Table`, `Global`, `Tag` and `Exception` constructors, `WebAssembly.Module.imports`, `exports` and `customSections`, `compileStreaming` and `instantiateStreaming`, and the classes used as values other than with `new` and `instanceof`. TypeScript sources fail to build with SC2020 at each such use, and JavaScript sources throw that diagnostic when the statement runs. Programs built for `wasm32-wasi` can't use the WebAssembly API. +Not supported yet: the `Tag` and `Exception` constructors, shared memories (`new WebAssembly.Memory({ shared: true, ... })` throws a `TypeError`), `compileStreaming` and `instantiateStreaming`, and the classes used as values other than with `new`, `instanceof` and `WebAssembly.Module`'s static functions. TypeScript sources fail to build with SC2020 at each such use, and JavaScript sources throw that diagnostic when the statement runs. Programs built for `wasm32-wasi` can't use the WebAssembly API. Differences from Node.js: -- `Memory.prototype.grow` does not detach the previous `ArrayBuffer`. The memory never moves, so views created before the growth stay valid but keep their old length. +- Growing a memory, from JavaScript or inside the module, does not detach the previous `ArrayBuffer`. Its contents stay readable, so views created before the growth stay valid but keep their old length; code that reads `memory.buffer` again after growing, as WebAssembly glue code does, behaves as in Node.js. +- `valueOf()` answers a `WebAssembly.Global`'s value only when the receiver's static type is `WebAssembly.Global`; through an untyped value it answers the object, and converting a `Global` to a primitive uses the object rather than its value. - Instances are never freed, together with their memories, imports and exports. -- A function reference (`funcref`) can reach JavaScript only as a function the module exports; other function references throw a `TypeError`. - A load whose result the module never uses may be removed instead of trapping when its address is out of bounds. Loads whose result is dropped explicitly still trap. - NaN payloads may differ, for example when a signaling NaN passes through arithmetic or a JavaScript number. - `WebAssembly.compile` and `instantiate` with bytes settle after the current microtasks, as in Node.js, but can settle after `setImmediate` callbacks that Node.js would run later. diff --git a/internal/compatibility/generated/node-v24-backlog.json b/internal/compatibility/generated/node-v24-backlog.json index 4df2c983f..236255be9 100644 --- a/internal/compatibility/generated/node-v24-backlog.json +++ b/internal/compatibility/generated/node-v24-backlog.json @@ -34215,7 +34215,11 @@ "tests/corpus/webassembly-spec-float/main.mjs", "tests/corpus/webassembly-spec-control/main.mjs", "tests/corpus/webassembly-spec-memory/main.mjs", - "tests/corpus/webassembly-spec-table/main.mjs" + "tests/corpus/webassembly-spec-table/main.mjs", + "tests/corpus/webassembly-spec-imports/main.mjs", + "tests/corpus/webassembly-jsapi.mjs", + "tests/corpus/webassembly-wpt-jsapi.mjs", + "tests/corpus/webassembly-dylink/main.mjs" ] } } diff --git a/internal/compatibility/generated/node-v24-internal.json b/internal/compatibility/generated/node-v24-internal.json index f036d0ac3..4f78842aa 100644 --- a/internal/compatibility/generated/node-v24-internal.json +++ b/internal/compatibility/generated/node-v24-internal.json @@ -52219,13 +52219,17 @@ "tests/corpus/5830-timers-promises-signal.ts", "tests/corpus/text-codec-values/main.ts", "tests/corpus/webassembly-api.mjs", + "tests/corpus/webassembly-dylink/main.mjs", + "tests/corpus/webassembly-jsapi.mjs", "tests/corpus/webassembly-spec-control/main.mjs", "tests/corpus/webassembly-spec-float/main.mjs", + "tests/corpus/webassembly-spec-imports/main.mjs", "tests/corpus/webassembly-spec-int/main.mjs", "tests/corpus/webassembly-spec-memory/main.mjs", "tests/corpus/webassembly-spec-table/main.mjs", "tests/corpus/webassembly-typed.ts", "tests/corpus/webassembly-worker.ts", + "tests/corpus/webassembly-wpt-jsapi.mjs", "tests/harness/fetch-conformance.test.ts" ] }, @@ -53571,7 +53575,11 @@ "tests/corpus/webassembly-spec-float/main.mjs", "tests/corpus/webassembly-spec-control/main.mjs", "tests/corpus/webassembly-spec-memory/main.mjs", - "tests/corpus/webassembly-spec-table/main.mjs" + "tests/corpus/webassembly-spec-table/main.mjs", + "tests/corpus/webassembly-spec-imports/main.mjs", + "tests/corpus/webassembly-jsapi.mjs", + "tests/corpus/webassembly-wpt-jsapi.mjs", + "tests/corpus/webassembly-dylink/main.mjs" ] }, "anchorSource": "exact" diff --git a/internal/compatibility/static-support.json b/internal/compatibility/static-support.json index 96c41bcd1..703be5f11 100644 --- a/internal/compatibility/static-support.json +++ b/internal/compatibility/static-support.json @@ -28,7 +28,7 @@ "webstreams": "unreviewed" }, "evidenceByManifestPrefix": { - "stdlib.webassembly": ["tests/corpus/webassembly-api.mjs", "tests/corpus/webassembly-typed.ts", "tests/corpus/webassembly-worker.ts", "tests/corpus/webassembly-spec-int/main.mjs", "tests/corpus/webassembly-spec-float/main.mjs", "tests/corpus/webassembly-spec-control/main.mjs", "tests/corpus/webassembly-spec-memory/main.mjs", "tests/corpus/webassembly-spec-table/main.mjs"], + "stdlib.webassembly": ["tests/corpus/webassembly-api.mjs", "tests/corpus/webassembly-typed.ts", "tests/corpus/webassembly-worker.ts", "tests/corpus/webassembly-spec-int/main.mjs", "tests/corpus/webassembly-spec-float/main.mjs", "tests/corpus/webassembly-spec-control/main.mjs", "tests/corpus/webassembly-spec-memory/main.mjs", "tests/corpus/webassembly-spec-table/main.mjs", "tests/corpus/webassembly-spec-imports/main.mjs", "tests/corpus/webassembly-jsapi.mjs", "tests/corpus/webassembly-wpt-jsapi.mjs", "tests/corpus/webassembly-dylink/main.mjs"], "node-builtin.fs.readFileSync": ["tests/corpus/1403-buffer-fs.ts", "tests/corpus/2595-fs-arg-ladders.cjs", "tests/corpus/2919-fs-function-values.ts", "tests/corpus/2920-fs-promises-function-values.ts", "tests/corpus/fs-read-encodings.ts"], "syntax.using-declarations": ["tests/corpus/2841-using-sync.ts"], "syntax.await-using-declarations": ["tests/corpus/2842-await-using.ts"], diff --git a/packages/compiler/src/backend/llvm/lib-shared.ts b/packages/compiler/src/backend/llvm/lib-shared.ts index 10e3ddd8d..2b0c70dee 100644 --- a/packages/compiler/src/backend/llvm/lib-shared.ts +++ b/packages/compiler/src/backend/llvm/lib-shared.ts @@ -843,6 +843,14 @@ export const LIB_FN_SYMS: Record = { "wasm.instanceNew": "scr_wasm_instance_new", "wasm.is": "scr_wasm_is", "wasm.errorNew": "scr_wasm_error_new", + "wasm.memoryNew": "scr_wasm_memory_construct", + "wasm.tableNew": "scr_wasm_table_construct", + "wasm.globalNew": "scr_wasm_global_construct", + "wasm.withoutNew": "scr_wasm_construct_without_new", + "wasm.moduleImports": "scr_wasm_module_imports", + "wasm.moduleExports": "scr_wasm_module_exports", + "wasm.moduleCustomSections": "scr_wasm_module_custom_sections", + "wasm.valueOf": "scr_wasm_value_of", "wasm.function": "scr_wasm_function", "wasm.namespace": "scr_wasm_namespace", "wasm.globalView": "scr_wasm_global_view", diff --git a/packages/compiler/src/backend/llvm/wasm-modules.ts b/packages/compiler/src/backend/llvm/wasm-modules.ts index c37dc36c4..8b8479bc8 100644 --- a/packages/compiler/src/backend/llvm/wasm-modules.ts +++ b/packages/compiler/src/backend/llvm/wasm-modules.ts @@ -48,7 +48,10 @@ export function emitWasmModules( if (bytes === null) continue; let translation; try { - translation = translateWasmModule(bytes, hexBytes(module.sha256), { boundsChecks }); + translation = translateWasmModule(bytes, hexBytes(module.sha256), { + boundsChecks, + customSections: mod.wasmCustomSections === true, + }); } catch (error) { if (error instanceof WasmTranslateError) continue; throw error; diff --git a/packages/compiler/src/coverage/surface-manifest.ts b/packages/compiler/src/coverage/surface-manifest.ts index 824790bd9..9867a5617 100644 --- a/packages/compiler/src/coverage/surface-manifest.ts +++ b/packages/compiler/src/coverage/surface-manifest.ts @@ -454,7 +454,7 @@ export function generateSurfaceManifest(compilerVersion: string): SurfaceManifes kind: "stdlib", name: "globalThis.WebAssembly", status: "static", - note: `compile, instantiate, validate, Module, Instance, instanceof, the error classes, exports, memories, tables, globals and Module transfer to workers, over the modules translated at build time; the rest refuses with ${refusal.code} (${refusal.hint ?? ""})`, + note: `compile, instantiate, validate, the Module, Instance, Memory, Table and Global constructors, Module.imports, exports and customSections, instanceof, the error classes, exports, imports and Module transfer to workers, over the modules translated at build time; the rest refuses with ${refusal.code} (${refusal.hint ?? ""})`, }); } diff --git a/packages/compiler/src/diagnostics/diagnostic.ts b/packages/compiler/src/diagnostics/diagnostic.ts index 0359dbf6b..56c288605 100644 --- a/packages/compiler/src/diagnostics/diagnostic.ts +++ b/packages/compiler/src/diagnostics/diagnostic.ts @@ -752,18 +752,19 @@ export function noLoweringDiag( } /** A REACHED use of a part of the WebAssembly API scriptc does not - * implement yet (SC2020): the Memory, Table and Global constructors, - * Module.imports/exports/customSections, the streaming functions, the - * classes as values. compile, instantiate, validate, Module, Instance and - * the error classes lower (builtins/webassembly.ts). `surface` names what - * was used ('new WebAssembly.Memory', 'WebAssembly.Module.imports'). */ + * implement yet (SC2020): the streaming functions, Tag and Exception, the + * classes as values. compile, instantiate, validate, the Module, Instance, + * Memory, Table and Global constructors, Module.imports/exports/ + * customSections and the error classes lower (builtins/webassembly.ts). + * `surface` names what was used ('WebAssembly.compileStreaming', + * 'WebAssembly.Memory'). */ export function webAssemblyDiag(surface: string, loc: SrcLoc): ScrDiagnostic { return { code: "SC2020", - message: `'${surface}' is not supported yet: scriptc implements WebAssembly.compile, instantiate, validate, Module, Instance and the error classes over modules translated at build time, but not this part of the API`, + message: `'${surface}' is not supported yet: scriptc implements WebAssembly.compile, instantiate, validate, the Module, Instance, Memory, Table and Global constructors, Module.imports, exports and customSections, and the error classes over modules translated at build time, but not this part of the API`, loc, milestone: "later", - hint: "the Memory, Table and Global constructors, Module.imports, exports and customSections, and the streaming functions are planned; 'scriptc coverage' lists the modules this program loads (https://scriptc.dev/limitations)", + hint: "the streaming functions, Tag and Exception, and the classes used as values other than with new and instanceof are planned; 'scriptc coverage' lists the modules this program loads (https://scriptc.dev/limitations)", }; } diff --git a/packages/compiler/src/frontend/lowering/builtins/webassembly.ts b/packages/compiler/src/frontend/lowering/builtins/webassembly.ts index 3a0a00508..2d5c5f17e 100644 --- a/packages/compiler/src/frontend/lowering/builtins/webassembly.ts +++ b/packages/compiler/src/frontend/lowering/builtins/webassembly.ts @@ -6,6 +6,7 @@ import { BOOL, DYN, F64, STRING, type IrExpr } from "../../../ir/ir.js"; import { dynUndefinedExpr } from "../../../ir/build.js"; import { lowerDynObjectLiteral } from "../expressions/object-literals.js"; import { stdlibGlobalNameOf } from "../surfaces.js"; +import { isWebAssemblyType } from "../../type-mapper.js"; /* The WebAssembly API. scriptc.d.ts declares its types (lib.dom's * namespace) and the type mapper gives them the checked-dynamic @@ -13,16 +14,18 @@ import { stdlibGlobalNameOf } from "../surfaces.js"; * native code (wasm/translate.ts) and found at run time by the SHA-256 of * their bytes (scr_wasm.c): * - WebAssembly.compile, instantiate and validate calls, new - * WebAssembly.Module and new WebAssembly.Instance lower to wasm.* - * libCalls; members of the values they produce (exports, memory + * WebAssembly.Module, Instance, Memory, Table and Global, and + * WebAssembly.Module.imports, exports and customSections lower to + * wasm.* libCalls; members of the values they produce (exports, memory * buffers, globals, tables) dispatch through the native handles; + * - the classes called without `new` throw Node's TypeError + * (wasm.withoutNew); * - `v instanceof WebAssembly.` lowers to wasm.is; * - the error classes construct with or without `new` (wasm.errorNew). - * Every other reached use of the API value (the bare namespace, the - * Memory, Table and Global constructors, Module.imports and friends, the - * streaming functions) still fences at its expression with - * webAssemblyDiag: a build error in TypeScript sources, a located runtime - * fence in JavaScript ones. typeof needs no value and folds to Node's + * Every other reached use of the API value (the classes as values, the + * streaming functions, Tag and Exception) still fences at its expression + * with webAssemblyDiag: a build error in TypeScript sources, a located + * runtime fence in JavaScript ones. typeof needs no value and folds to Node's * answer. A lowering marks the lowerer, so the build translates the * modules it discovers (lowerToIr). */ @@ -170,15 +173,38 @@ function lowerArg(lowerer: Lowerer, arg: ts.Expression): IrExpr { : lowerer.lowerExprExpecting(arg, DYN); } -/** The arguments, padded with undefined to `count`; null for spreads or - * extra arguments (they stay refused). */ +/** An argument whose evaluation has no effect: a literal, a plain + * identifier, or an empty object or array literal. */ +function isInert(arg: ts.Expression): boolean { + const node = bare(arg); + if (ts.isObjectLiteralExpression(node)) return node.properties.length === 0; + if (ts.isArrayLiteralExpression(node)) return node.elements.length === 0; + return ( + ts.isIdentifier(node) || + ts.isNumericLiteral(node) || + node.kind === ts.SyntaxKind.BigIntLiteral || + ts.isStringLiteral(node) || + ts.isNoSubstitutionTemplateLiteral(node) || + node.kind === ts.SyntaxKind.NullKeyword || + node.kind === ts.SyntaxKind.TrueKeyword || + node.kind === ts.SyntaxKind.FalseKeyword + ); +} + +/** The arguments, padded with undefined to `count`. Extra arguments are + * ignored, as the API ignores them, when evaluating them has no effect; + * null for spreads or other extra arguments (they stay refused). */ function lowerArgs( lowerer: Lowerer, args: readonly ts.Expression[], count: number, loc: IrExpr["loc"], ): IrExpr[] | null { - if (args.length > count || args.some(ts.isSpreadElement)) return null; + if (args.some(ts.isSpreadElement)) return null; + if (args.length > count) { + if (!args.slice(count).every(isInert)) return null; + args = args.slice(0, count); + } const out = args.map((arg) => lowerArg(lowerer, arg)); while (out.length < count) out.push(dynUndefinedExpr(loc)); return out; @@ -195,14 +221,57 @@ function errorNew(member: string, args: IrExpr[], loc: IrExpr["loc"]): IrExpr { }; } +/** The classes that require `new`, in wasm.withoutNew's numbering. */ +const NEW_ONLY = ["Module", "Instance", "Memory", "Table", "Global"]; + +/** `WebAssembly.Module.imports(module)`, `exports(module)` and + * `customSections(module, name)`. Their results are arrays of descriptors + * or ArrayBuffers built as checked-dynamic values, then taken into the + * call's own type. Null for anything else. */ +function lowerModuleStatic(lowerer: Lowerer, call: ts.CallExpression): IrExpr | null { + const surface = webAssemblySurface(lowerer, call.expression); + const name = surface?.startsWith("WebAssembly.Module.") + ? surface.slice("WebAssembly.Module.".length) + : null; + if (name !== "imports" && name !== "exports" && name !== "customSections") return null; + const loc = locOf(call); + const args = lowerArgs(lowerer, call.arguments, name === "customSections" ? 2 : 1, loc); + if (args === null) return null; + lowerer.usesWebAssembly = true; + if (name === "customSections") lowerer.usesWasmCustomSections = true; + const fn = + name === "imports" + ? "wasm.moduleImports" + : name === "exports" + ? "wasm.moduleExports" + : "wasm.moduleCustomSections"; + const result: IrExpr = { kind: "libCall", fn, args, type: DYN, loc }; + return lowerer.coerceInto(call, result, lowerer.irTypeOf(call)); +} + /** `WebAssembly.compile(bytes)`, `WebAssembly.instantiate(source, - * imports?)`, `WebAssembly.validate(bytes)`, and the error classes called - * without `new`. Null for anything else. */ + * imports?)`, `WebAssembly.validate(bytes)`, the error classes called + * without `new`, the other classes called without `new` (a TypeError), and + * Module's static functions. Null for anything else. */ export function lowerWebAssemblyCall(lowerer: Lowerer, call: ts.CallExpression): IrExpr | null { if (call.questionDotToken) return null; const member = webAssemblyMember(lowerer, call.expression); - if (member === null) return null; + if (member === null) return lowerModuleStatic(lowerer, call); const loc = locOf(call); + const newOnly = NEW_ONLY.indexOf(member); + if (newOnly >= 0) { + // The arguments evaluate, then the call throws. + const args = lowerArgs(lowerer, call.arguments, 2, loc); + if (args === null) return null; + lowerer.usesWebAssembly = true; + return { + kind: "libCall", + fn: "wasm.withoutNew", + args: [{ kind: "numLit", value: newOnly, type: F64, loc }, args[0]!, args[1]!], + type: DYN, + loc, + }; + } const isError = member === "CompileError" || member === "LinkError" || member === "RuntimeError"; if (member !== "compile" && member !== "instantiate" && member !== "validate" && !isError) return null; @@ -234,14 +303,17 @@ export function lowerWebAssemblyCall(lowerer: Lowerer, call: ts.CallExpression): } /** `new WebAssembly.Module(bytes)`, `new WebAssembly.Instance(module, - * imports?)` and the error classes. Null for anything else. */ + * imports?)`, `new WebAssembly.Memory(descriptor)`, `new + * WebAssembly.Table(descriptor, value?)`, `new + * WebAssembly.Global(descriptor, value?)` and the error classes. Null for + * anything else. */ export function lowerWebAssemblyNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr | null { const member = webAssemblyMember(lowerer, expr.expression); if (member === null) return null; const isError = member === "CompileError" || member === "LinkError" || member === "RuntimeError"; - if (member !== "Module" && member !== "Instance" && !isError) return null; + if (!NEW_ONLY.includes(member) && !isError) return null; const loc = locOf(expr); - const used = member === "Module" ? 1 : 2; + const used = member === "Module" || member === "Memory" ? 1 : 2; const args = lowerArgs(lowerer, expr.arguments ?? [], used, loc); if (args === null) return null; lowerer.usesWebAssembly = true; @@ -249,6 +321,12 @@ export function lowerWebAssemblyNew(lowerer: Lowerer, expr: ts.NewExpression): I if (isError) result = errorNew(member, args, loc); else if (member === "Module") result = { kind: "libCall", fn: "wasm.moduleNew", args: [args[0]!], type: DYN, loc }; + else if (member === "Memory") + result = { kind: "libCall", fn: "wasm.memoryNew", args: [args[0]!], type: DYN, loc }; + else if (member === "Table") + result = { kind: "libCall", fn: "wasm.tableNew", args: [args[0]!, args[1]!], type: DYN, loc }; + else if (member === "Global") + result = { kind: "libCall", fn: "wasm.globalNew", args: [args[0]!, args[1]!], type: DYN, loc }; else result = { kind: "libCall", @@ -309,3 +387,18 @@ export function lowerWebAssemblyValue(lowerer: Lowerer, expr: ts.Expression): Ir if (index < 0 || !ts.isPropertyAccessExpression(node)) return null; return fn("wasm.function", [{ kind: "numLit", value: index, type: F64, loc }]); } + +/** `global.valueOf()` on a receiver typed as a WebAssembly value: a + * Global answers its value (the generic dyn.valueOf answers the object + * itself for native handles other than Date). Null for other receivers. */ +export function lowerWebAssemblyValueOf( + lowerer: Lowerer, + receiver: ts.Expression, + recv: IrExpr, + loc: IrExpr["loc"], +): IrExpr | null { + const ctx = { checker: lowerer.checker, isStdlibFile: lowerer.isStdlibFile }; + if (!isWebAssemblyType(lowerer.typeOf(receiver), ctx)) return null; + lowerer.usesWebAssembly = true; + return { kind: "libCall", fn: "wasm.valueOf", args: [recv], type: DYN, loc }; +} diff --git a/packages/compiler/src/frontend/lowering/lower-calls.ts b/packages/compiler/src/frontend/lowering/lower-calls.ts index 303bdf517..37a96b1d8 100644 --- a/packages/compiler/src/frontend/lowering/lower-calls.ts +++ b/packages/compiler/src/frontend/lowering/lower-calls.ts @@ -1,6 +1,7 @@ import { guardNullProto, nullProtoRecordShape } from "./null-prototype.js"; import { lowerUnionEquality, tagEqualityMayMissAlias } from "./strict-equality.js"; import { lowerHashStreamCall } from "./builtins/hash-stream.js"; +import { lowerWebAssemblyValueOf } from "./builtins/webassembly.js"; import { lowerArrayCallbackCall } from "./containers/array-callback-call.js"; /** Dispatch source calls, builtin members and receiver-specific operations. * Signature collection, argument completion and function bodies have their @@ -3497,6 +3498,8 @@ function lowerDynReceiverMethodCall( // answers each kind's own: primitives and plain objects themselves, a // Date its time value, an own or inherited valueOf member its result. if (access.name.text === "valueOf" && call.arguments.length === 0) { + const webAssembly = lowerWebAssemblyValueOf(lowerer, access.expression, recv, locOf(call)); + if (webAssembly !== null) return webAssembly; return { kind: "libCall", fn: "dyn.valueOf", diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index 9cc8015e8..83fe9e4bd 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -1122,6 +1122,7 @@ export function lowerToIr( wasmModules.push({ sha256: report.sha256, bytes: encodeBase64(bytes) }); } result.module.wasmModules = wasmModules; + if (resultLowerer.usesWasmCustomSections) result.module.wasmCustomSections = true; timing("wasm-discovery", { modules: wasmModules.length }); } if (options.coverage !== true) return result; @@ -2681,6 +2682,9 @@ export class Lowerer { /** The program uses the WebAssembly API (builtins/webassembly.ts): the * build translates the modules it discovers. */ usesWebAssembly = false; + /** The program calls WebAssembly.Module.customSections: translated + * modules keep their custom sections. */ + usesWasmCustomSections = false; /** The program constructs a LifetimeRegion (lifetime-regions.ts). */ usesLifetimeRegions = false; /** Per-directory answers of experimental-import.ts's declaration-file test. */ diff --git a/packages/compiler/src/ir/builtin-effects.ts b/packages/compiler/src/ir/builtin-effects.ts index c6e93c268..e1ccf7dfd 100644 --- a/packages/compiler/src/ir/builtin-effects.ts +++ b/packages/compiler/src/ir/builtin-effects.ts @@ -138,6 +138,14 @@ export const MAY_THROW_LIB_FNS: ReadonlySet = new Set([ "wasm.moduleNew", "wasm.instanceNew", "wasm.errorNew", + "wasm.memoryNew", + "wasm.tableNew", + "wasm.globalNew", + "wasm.withoutNew", + "wasm.moduleImports", + "wasm.moduleExports", + "wasm.moduleCustomSections", + "wasm.valueOf", "events.addAbortListener", "fetch.streamNew", "fetch.streamFrom", diff --git a/packages/compiler/src/ir/builtin-signatures.ts b/packages/compiler/src/ir/builtin-signatures.ts index 1ec915409..fb6e264d4 100644 --- a/packages/compiler/src/ir/builtin-signatures.ts +++ b/packages/compiler/src/ir/builtin-signatures.ts @@ -120,6 +120,24 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ "wasm.instanceNew": { argTypes: [DYN, DYN], result: DYN }, "wasm.is": { argTypes: [DYN, F64], result: BOOL }, "wasm.errorNew": { argTypes: [F64, DYN, DYN], result: DYN }, + /** new WebAssembly.Memory(descriptor), Table(descriptor, value) and + * Global(descriptor, value) (an undefined value is the type's default); + * `wasm.withoutNew` throws the TypeError for Module, Instance, Memory, + * Table or Global (kinds 0-4) called without new, after its arguments + * evaluate. */ + "wasm.memoryNew": { argTypes: [DYN], result: DYN }, + "wasm.tableNew": { argTypes: [DYN, DYN], result: DYN }, + "wasm.globalNew": { argTypes: [DYN, DYN], result: DYN }, + "wasm.withoutNew": { argTypes: [F64, DYN, DYN], result: DYN }, + /** WebAssembly.Module.imports(module), exports(module) and + * customSections(module, name): checked-dynamic arrays of descriptor + * objects and ArrayBuffers. */ + "wasm.moduleImports": { argTypes: [DYN], result: DYN }, + "wasm.moduleExports": { argTypes: [DYN], result: DYN }, + "wasm.moduleCustomSections": { argTypes: [DYN, DYN], result: DYN }, + /** `v.valueOf()` on a WebAssembly-typed receiver: a Global's value, + * dyn.valueOf's answer for anything else. */ + "wasm.valueOf": { argTypes: [DYN], result: DYN }, /** WebAssembly.compile (0), instantiate (1) and validate (2) as function * values, and the namespace object holding them. */ "wasm.function": { argTypes: [F64], result: DYN }, diff --git a/packages/compiler/src/ir/ir.ts b/packages/compiler/src/ir/ir.ts index 145038748..0135c8f73 100644 --- a/packages/compiler/src/ir/ir.ts +++ b/packages/compiler/src/ir/ir.ts @@ -1003,6 +1003,10 @@ export interface IrModule { * API (possibly empty). The backend appends each module's translation * and emits the registry the runtime looks modules up in. */ wasmModules?: IrWasmModule[]; + /** The program calls WebAssembly.Module.customSections, so translated + * modules keep their custom sections (otherwise they carry none, and + * debug sections cost nothing). */ + wasmCustomSections?: boolean; } /** A WebAssembly module whose bytes the build saw, validated within the diff --git a/packages/compiler/src/wasm/discover.ts b/packages/compiler/src/wasm/discover.ts index 856f0752f..cd6cb1ff9 100644 --- a/packages/compiler/src/wasm/discover.ts +++ b/packages/compiler/src/wasm/discover.ts @@ -6,7 +6,9 @@ * 1. path: a `.wasm` path the compiler resolves from the source text * (`require.resolve`, `createRequire(...).resolve`, `import.meta.resolve`, * `new URL("./x.wasm", import.meta.url)`, and `path.join(__dirname, ...)` - * or `__dirname + "/x.wasm"`); + * or `__dirname + "/x.wasm"`), or a `.so` path spelled the same ways + * whose file is a module (WebAssembly shared libraries, such as + * quickjs-wasi's extensions); * 2. inline: a string literal that decodes as base64 to a module, or an * array literal of its bytes; * 3. package: every `.wasm` file shipped by a reached npm package whose @@ -78,6 +80,9 @@ const BASE64_MAGIC = "AGFzbQ"; const BYTES_MAGIC = /\[\s*(?:0|0x0+)\s*,\s*(?:97|0x61)\s*,\s*(?:115|0x73)\s*,\s*(?:109|0x6d)\s*,/i; /** Package directories deeper than this are not searched for modules. */ const PACKAGE_SCAN_DEPTH = 6; +/** A `.so` path in a string literal: the cheap test before a file with no + * `.wasm` mention is walked for shared-library paths. */ +const SHARED_LIBRARY_PATH = /\.so["'`]/; /** Larger files are reported by size but not read. */ const MAX_MODULE_BYTES = 256 * 1024 * 1024; @@ -139,6 +144,8 @@ class Discovery { return; } if (bytes.length > MAX_MODULE_BYTES) return; + // A shared-library path names a module only when the file is one. + if (source.kind === "path" && !path.endsWith(".wasm") && !isWasmHeader(bytes)) return; this.byPath.set(path, this.addBytes(bytes, path, source)); } @@ -146,7 +153,7 @@ class Discovery { scanFile(sf: ts.SourceFile): void { const text = sf.text; - const paths = text.includes(".wasm"); + const paths = text.includes(".wasm") || SHARED_LIBRARY_PATH.test(text); const base64 = text.includes(BASE64_MAGIC); const bytes = BYTES_MAGIC.test(text); if (!paths && !base64 && !bytes) return; @@ -184,7 +191,8 @@ class Discovery { ); } - /** The module path a resolution expression names, when it ends in .wasm. */ + /** The module path a resolution expression names, when it ends in .wasm + * (or .so). */ resolvedPath(sf: ts.SourceFile, node: ts.Node): string | null { const dir = dirname(sf.fileName); if (ts.isCallExpression(node)) { @@ -245,7 +253,7 @@ class Discovery { if (!ts.isStringLiteral(arg) && !ts.isNoSubstitutionTemplateLiteral(arg)) return null; parts.push(arg.text); } - if (!parts[parts.length - 1]!.endsWith(".wasm")) return null; + if (!isModulePath(parts[parts.length - 1]!)) return null; let path = parts[0]!; for (let i = 1; i < parts.length; i++) path = resolve(path, parts[i]!); return path; @@ -314,9 +322,15 @@ function addSource(report: WasmModuleReport, source: WasmSource): void { report.sources.push(source); } +/** A path that may name a module: `.wasm`, or `.so` for WebAssembly shared + * libraries (addFile keeps those only when the file is a module). */ +function isModulePath(text: string): boolean { + return text.endsWith(".wasm") || text.endsWith(".so"); +} + function wasmLiteral(node: ts.Expression): string | null { if (!ts.isStringLiteral(node) && !ts.isNoSubstitutionTemplateLiteral(node)) return null; - return node.text.endsWith(".wasm") ? node.text : null; + return isModulePath(node.text) ? node.text : null; } function isDirname(node: ts.Expression): boolean { diff --git a/packages/compiler/src/wasm/translate.test.ts b/packages/compiler/src/wasm/translate.test.ts index a9ef58248..07b0cbb9c 100644 --- a/packages/compiler/src/wasm/translate.test.ts +++ b/packages/compiler/src/wasm/translate.test.ts @@ -60,6 +60,37 @@ describe("translateWasmModule", () => { for (const decl of out.decls) expect(decl).toMatch(/^declare /); }); + test("keeps custom sections only when asked, and gives unexported table functions call thunks", () => { + // (module (type $t (func (param i32) (result i32))) (table 2 funcref) + // (elem (i32.const 0) $hidden $shown) (func $hidden (type $t) local.get 0) + // (func $shown (export "shown") (type $t) local.get 0)), plus a custom + // section "meta" with "hi". + const refs = new Uint8Array([ + 0, 97, 115, 109, 1, 0, 0, 0, 1, 6, 1, 96, 1, 127, 1, 127, 3, 3, 2, 0, 0, 4, 4, 1, 112, 0, 2, 7, 9, + 1, 5, 115, 104, 111, 119, 110, 0, 1, 9, 8, 1, 0, 65, 0, 11, 2, 0, 1, 10, 11, 2, 4, 0, 32, 0, 11, 4, + 0, 32, 0, 11, 0, 7, 4, 109, 101, 116, 97, 104, 105, + ]); + const prefix = wasmSymbolPrefix(SHA); + const plain = translateWasmModule(refs, SHA, { boundsChecks: false }).text.join("\n"); + expect(plain).toContain(`@${prefix}_customs = private constant [0 x { ptr, i64, ptr, i64 }] []`); + expect(plain).not.toContain(`c"hi"`); + const kept = translateWasmModule(refs, SHA, { boundsChecks: false, customSections: true }); + const text = kept.text.join("\n"); + expect(text).toContain(`@${prefix}_c0 = private unnamed_addr constant [2 x i8] c"hi", align 1`); + expect(text).toContain(`@${prefix}_customs = private constant [1 x { ptr, i64, ptr, i64 }]`); + // One call thunk for the signature, used by the unexported function + // only (the exported one has its export thunk). + expect(text).toContain(`define internal void @${prefix}_k0(ptr %code, ptr %inst, ptr %a, ptr %r)`); + expect(text).toContain(`%res = call i32 %code(ptr %inst, i32 `); + expect(text).toContain(`@${prefix}_calls = private constant [2 x ptr] [ptr @${prefix}_k0, ptr null]`); + for (const line of topLevelLines(kept.text)) { + expect( + line === "" || /^(define internal |@[\w.$-]+ = (private|internal) )/.test(line), + line, + ).toBe(true); + } + }); + test("checks memory accesses explicitly only when asked", () => { const guarded = translateWasmModule(ADD, SHA, { boundsChecks: false }).text.join("\n"); const checked = translateWasmModule(ADD, SHA, { boundsChecks: true }).text.join("\n"); diff --git a/packages/compiler/src/wasm/translate.ts b/packages/compiler/src/wasm/translate.ts index e0ce27be5..ddfbe1225 100644 --- a/packages/compiler/src/wasm/translate.ts +++ b/packages/compiler/src/wasm/translate.ts @@ -68,7 +68,7 @@ export const TRAP_NULL = 9; export const TRAP_EXHAUSTION = 100; /** The descriptor ABI version both sides agree on. */ -export const WASM_ABI_VERSION = 1; +export const WASM_ABI_VERSION = 2; /** Instance slot offsets (bytes). */ const OFF_MEMBASE = 8; @@ -81,6 +81,8 @@ const FUNCREF_SIZE = 24; export interface WasmTranslateOptions { /** Explicit bounds checks instead of guard pages. */ boundsChecks: boolean; + /** Keep the custom sections, for WebAssembly.Module.customSections. */ + customSections?: boolean; } export interface WasmTranslation { @@ -387,6 +389,44 @@ class ModuleTranslator { return name; } + /** A call thunk for signature `type`: `(code, inst, args, results)` + * calls `code` (a function of that signature) with the arguments in + * 64-bit slots. The runtime calls a function reference that reaches + * JavaScript through it when the module does not export the function. */ + callThunk(type: number): string { + const name = `@${this.p}_k${type}`; + const ft = this.m.types[type]!; + const out: string[] = []; + out.push(`define internal void ${name}(ptr %code, ptr %inst, ptr %a, ptr %r) nounwind {`); + out.push(`entry:`); + const args: string[] = ["ptr %inst"]; + for (let i = 0; i < ft.params.length; i++) { + out.push(` %ap${i} = getelementptr i64, ptr %a, i64 ${i}`); + out.push(` %as${i} = load i64, ptr %ap${i}`); + args.push(`${llType(ft.params[i]!)} ${fromSlot(out, ft.params[i]!, `%as${i}`, `a${i}`)}`); + } + const ret = retType(ft.results); + if (ft.results.length === 0) { + out.push(` call void %code(${args.join(", ")})`); + } else { + out.push(` %res = call ${ret} %code(${args.join(", ")})`); + for (let i = 0; i < ft.results.length; i++) { + let v = "%res"; + if (ft.results.length > 1) { + out.push(` %rv${i} = extractvalue ${ret} %res, ${i}`); + v = `%rv${i}`; + } + const slot = toSlot(out, ft.results[i]!, v, `r${i}`); + out.push(` %rp${i} = getelementptr i64, ptr %r, i64 ${i}`); + out.push(` store i64 ${slot}, ptr %rp${i}`); + } + } + out.push(` ret void`); + out.push(`}`); + this.text.push(out.join("\n")); + return name; + } + /** Emits the init function: globals, table initializers and segments, * in the specification's order. The runtime calls the start function * (which takes and returns nothing) separately. */ @@ -537,12 +577,27 @@ class ModuleTranslator { // every function a reference can name. const nf = m.funcTypes.length; const codes: string[] = []; + const callThunks: string[] = []; + const callThunkSyms = new Map(); const types: string[] = []; const sigs: string[] = []; const sigSyms = new Map(); for (let f = 0; f < nf; f++) { const referenced = this.refs.has(f); codes.push(referenced ? `ptr ${this.fn(f)}` : "ptr null"); + // Referenced functions the module does not export reach JavaScript + // through their signature's call thunk. + if (referenced && !thunks.has(f)) { + const type = m.funcTypes[f]!; + let thunk = callThunkSyms.get(type); + if (thunk === undefined) { + thunk = this.callThunk(type); + callThunkSyms.set(type, thunk); + } + callThunks.push(`ptr ${thunk}`); + } else { + callThunks.push("ptr null"); + } types.push(`i64 ${this.typeHashes[m.funcTypes[f]!]!}`); const sig = sigString(this.funcType(f)); let sym = sigSyms.get(sig); @@ -555,7 +610,9 @@ class ModuleTranslator { const codesSym = `@${p}_codes`; const typesSym = `@${p}_types`; const sigsSym = `@${p}_sigs`; + const callThunksSym = `@${p}_calls`; lines.push(`${codesSym} = private constant [${nf} x ptr] [${codes.join(", ")}]`); + lines.push(`${callThunksSym} = private constant [${nf} x ptr] [${callThunks.join(", ")}]`); lines.push(`${typesSym} = private constant [${nf} x i64] [${types.join(", ")}]`); lines.push(`${sigsSym} = private constant [${nf} x ptr] [${sigs.join(", ")}]`); // Tables. @@ -623,6 +680,30 @@ class ModuleTranslator { lines.push( `${elemsSym} = private constant [${elemRows.length} x { i64, i64, ptr }] [${elemRows.join(", ")}]`, ); + // Custom sections, when the program reads them. + const customRows: string[] = []; + if (this.opts.customSections === true) { + for (let s = 0; s < m.customs.length; s++) { + const custom = m.customs[s]!; + const name = textStr(custom.name); + const len = custom.payload.end - custom.payload.start; + let data = "ptr null"; + if (len > 0) { + const sym = `@${p}_c${s}`; + lines.push( + `${sym} = private unnamed_addr constant [${len} x i8] c"${cBytes(m.bytes, custom.payload.start, custom.payload.end)}", align 1`, + ); + data = `ptr ${sym}`; + } + customRows.push( + `{ ptr, i64, ptr, i64 } { ptr ${name.sym}, i64 ${name.len}, ${data}, i64 ${len} }`, + ); + } + } + const customsSym = `@${p}_customs`; + lines.push( + `${customsSym} = private constant [${customRows.length} x { ptr, i64, ptr, i64 }] [${customRows.join(", ")}]`, + ); const memory = m.memories[0]; const sha: string[] = []; for (let i = 0; i < 32; i++) sha.push(`i8 ${sha256[i]!}`); @@ -654,9 +735,12 @@ class ModuleTranslator { `ptr ${elemsSym}`, `ptr ${init}`, `ptr ${m.start === null ? "null" : this.fn(m.start)}`, + `i64 ${customRows.length}`, + `ptr ${customsSym}`, + `ptr ${callThunksSym}`, ]; const descType = - "{ [32 x i8], i64, i64, i64, i64, ptr, i64, ptr, i64, i64, ptr, ptr, ptr, i64, ptr, i64, i64, i64, i64, ptr, i64, ptr, i64, ptr, ptr, ptr }"; + "{ [32 x i8], i64, i64, i64, i64, ptr, i64, ptr, i64, i64, ptr, ptr, ptr, i64, ptr, i64, i64, i64, i64, ptr, i64, ptr, i64, ptr, ptr, ptr, i64, ptr, ptr }"; lines.push(`${desc} = internal constant ${descType} { ${fields.join(", ")} }`); this.text.push(lines.join("\n")); return desc; diff --git a/packages/compiler/surface-manifest.json b/packages/compiler/surface-manifest.json index 5d82b6719..571234953 100644 --- a/packages/compiler/surface-manifest.json +++ b/packages/compiler/surface-manifest.json @@ -5635,7 +5635,7 @@ "kind": "stdlib", "name": "globalThis.WebAssembly", "status": "static", - "note": "compile, instantiate, validate, Module, Instance, instanceof, the error classes, exports, memories, tables, globals and Module transfer to workers, over the modules translated at build time; the rest refuses with SC2020 (the Memory, Table and Global constructors, Module.imports, exports and customSections, and the streaming functions are planned; 'scriptc coverage' lists the modules this program loads (https://scriptc.dev/limitations))" + "note": "compile, instantiate, validate, the Module, Instance, Memory, Table and Global constructors, Module.imports, exports and customSections, instanceof, the error classes, exports, imports and Module transfer to workers, over the modules translated at build time; the rest refuses with SC2020 (the streaming functions, Tag and Exception, and the classes used as values other than with new and instanceof are planned; 'scriptc coverage' lists the modules this program loads (https://scriptc.dev/limitations))" }, { "id": "syntax.async-generators", diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-dylink/main.mjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-dylink/main.mjs.json new file mode 100644 index 000000000..14b064864 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-dylink/main.mjs.json @@ -0,0 +1,7 @@ +{ + "order": [ + "/tests/corpus/webassembly-dylink/extensions.js", + "/tests/corpus/webassembly-dylink/main.mjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-jsapi.mjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-jsapi.mjs.json new file mode 100644 index 000000000..4f8ee17cf --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-jsapi.mjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/webassembly-jsapi.mjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-spec-imports/main.mjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-spec-imports/main.mjs.json new file mode 100644 index 000000000..a23646c06 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-spec-imports/main.mjs.json @@ -0,0 +1,8 @@ +{ + "order": [ + "/tests/wasm-spec/runner.mjs", + "/tests/corpus/webassembly-spec-imports/data.mjs", + "/tests/corpus/webassembly-spec-imports/main.mjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-wpt-jsapi.mjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-wpt-jsapi.mjs.json new file mode 100644 index 000000000..dd80857d8 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-wpt-jsapi.mjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/webassembly-wpt-jsapi.mjs" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_wasm.c b/packages/runtime/src/scr_wasm.c index 03d59604f..3110a9aa4 100644 --- a/packages/runtime/src/scr_wasm.c +++ b/packages/runtime/src/scr_wasm.c @@ -36,12 +36,16 @@ * LinkError or RuntimeError (not an own property, as in Node), which is * the brand instanceof tests. * - * Gaps (results/opt-wasm-design/DESIGN.md, stages W5-W7): no Memory, - * Table or Global constructors; Memory.grow does not detach the previous - * ArrayBuffer (the memory never moves, so old views stay valid with their - * old length); instances, their imports and exports are never freed (the - * cycle collector does not trace them yet); funcref values cross to - * JavaScript only as exported functions. */ + * The Memory, Table and Global constructors build the same objects an + * instance exports, so either kind can be imported; WebAssembly.Module. + * imports, exports and customSections read the descriptor. + * + * Gaps (results/opt-wasm-design/DESIGN.md, stages W6-W9): Memory.grow + * does not detach the previous ArrayBuffer (the memory never moves with + * guard pages, and explicit-bounds memories keep their previous blocks, so + * old views stay valid with their old length); shared memories are not + * constructed; instances, their imports and exports are never freed (the + * cycle collector does not trace them yet). */ #if !defined(_GNU_SOURCE) && defined(__linux__) #define _GNU_SOURCE /* REG_RIP and friends in */ #endif @@ -86,6 +90,14 @@ ScrDyn *scr_wasm_error_new(double kind, ScrDyn *message, ScrDyn *options); ScrDyn *scr_wasm_function(double kind); ScrDyn *scr_wasm_namespace(void); ScrDyn *scr_wasm_global_view(void); +ScrDyn *scr_wasm_memory_construct(ScrDyn *descriptor); +ScrDyn *scr_wasm_table_construct(ScrDyn *descriptor, ScrDyn *value); +ScrDyn *scr_wasm_global_construct(ScrDyn *descriptor, ScrDyn *value); +ScrDyn *scr_wasm_construct_without_new(double kind, ScrDyn *arg0, ScrDyn *arg1); +ScrDyn *scr_wasm_module_imports(ScrDyn *module); +ScrDyn *scr_wasm_module_exports(ScrDyn *module); +ScrDyn *scr_wasm_module_custom_sections(ScrDyn *module, ScrDyn *name); +ScrDyn *scr_wasm_value_of(ScrDyn *value); /* Entry points of translated code. */ /* Sanitized builds do not declare it noreturn: AddressSanitizer would @@ -135,6 +147,12 @@ typedef struct ScrWasmInst { bool *data_dropped; bool *elem_dropped; ScrDyn *exports; + /* The Memory, Table and Global objects of the instance, by index: the + * imported object itself, or the one created when the module exports it, + * so every export of the same index is the same object, as in V8. */ + ScrDyn *memory_obj; + ScrDyn **table_objs; + ScrDyn **global_objs; } ScrWasmInst; static ScrWasmInst *scr_wasm_host_of(void *inst) { return ((ScrWasmInst **)inst)[0]; } @@ -388,9 +406,10 @@ static void scr_wasm_set_stack_limit(void) { #endif } -/* Calls a thunk with traps caught; answers a SCR_WASM_TRAP_* code. */ -static __attribute__((noinline)) int scr_wasm_enter(ScrWasmThunk fn, void *inst, const uint64_t *args, - uint64_t *results) { +/* Calls a thunk (or `call` on `code`, a function reference's code) with + * traps caught; answers a SCR_WASM_TRAP_* code. */ +static __attribute__((noinline)) int scr_wasm_enter_ref(ScrWasmThunk fn, ScrWasmCallThunk call, void *ref_code, + void *inst, const uint64_t *args, uint64_t *results) { #if SCR_WASM_SIGNALS scr_wasm_thread_init(); #endif @@ -408,7 +427,8 @@ static __attribute__((noinline)) int scr_wasm_enter(ScrWasmThunk fn, void *inst, int code = 0; if (SCR_WASM_SETJMP(entry.jmp) == 0) { scr_wasm_running = true; - fn(inst, args, results); + if (call) call(ref_code, inst, args, results); + else fn(inst, args, results); } else { code = scr_wasm_trap_code; } @@ -418,6 +438,10 @@ static __attribute__((noinline)) int scr_wasm_enter(ScrWasmThunk fn, void *inst, return code; } +static int scr_wasm_enter(ScrWasmThunk fn, void *inst, const uint64_t *args, uint64_t *results) { + return scr_wasm_enter_ref(fn, NULL, NULL, inst, args, results); +} + static void scr_wasm_init_thunk(void *inst, const uint64_t *args, uint64_t *results) { (void)args; (void)results; @@ -644,6 +668,8 @@ static ScrWasmMemory *scr_wasm_memory_retain(ScrWasmMemory *m) { static void scr_wasm_memory_release(ScrWasmMemory *m) { if (!m || --m->rc) return; scr_dyn_release(m->buffer); + for (size_t i = 0; i < m->retired_count; i++) free(m->retired[i]); + free(m->retired); #if SCR_WASM_SIGNALS if (m->guarded) { scr_wasm_range_remove(m->base); @@ -654,14 +680,15 @@ static void scr_wasm_memory_release(ScrWasmMemory *m) { free(m); } -/* A memory of `min` pages growing to at most `max` pages; NULL with - * Node's RangeError pending when the address space or memory is short. */ +/* A memory of `min` pages growing to at most `max` pages (UINT64_MAX: + * no declared maximum); NULL with Node's RangeError pending when the + * address space or memory is short. */ static ScrWasmMemory *scr_wasm_memory_new(uint64_t min, uint64_t max, bool guarded, const char *what) { - if (max > SCR_WASM_MAX_PAGES) max = SCR_WASM_MAX_PAGES; ScrWasmMemory *m = calloc(1, sizeof *m); if (!m) scr_trap("scriptc: out of memory\n"); m->rc = 1; - m->max_pages = max; + m->declared_max = max; + m->max_pages = max > SCR_WASM_MAX_PAGES ? SCR_WASM_MAX_PAGES : max; m->size = min * SCR_WASM_PAGE; #if SCR_WASM_SIGNALS if (guarded) { @@ -712,9 +739,20 @@ static int64_t scr_wasm_memory_grow_by(ScrWasmMemory *m, uint64_t delta) { return (int64_t)old; } #endif - uint8_t *grown = realloc(m->base, size); + /* A new block, not realloc: ArrayBuffers created before the growth keep + * pointing into the old one, which stays alive with the memory. */ + uint8_t *grown = calloc(1, size); if (!grown) return -1; - memset(grown + m->size, 0, size - m->size); + if (m->base) { + uint8_t **retired = realloc(m->retired, (m->retired_count + 1) * sizeof *retired); + if (!retired) { + free(grown); + return -1; + } + memcpy(grown, m->base, m->size); + m->retired = retired; + m->retired[m->retired_count++] = m->base; + } m->base = grown; m->reserved = size; m->size = size; @@ -783,6 +821,7 @@ static ScrWasmTable *scr_wasm_table_new(char elem, uint64_t min, uint64_t max) { t->rc = 1; t->elem = elem; t->size = (uint32_t)min; + t->declared_max = max; t->max = max > SCR_WASM_MAX_TABLE ? SCR_WASM_MAX_TABLE : (uint32_t)max; t->elems = calloc(min ? min : 1, sizeof *t->elems); if (!t->elems) scr_trap("scriptc: out of memory\n"); @@ -1023,9 +1062,15 @@ static uint32_t scr_wasm_to_int32(double n) { static ScrWasmFuncRef *scr_wasm_funcref_of(const ScrDyn *v); +/* V8's message for a value that is neither null nor an exported + * WebAssembly function where a funcref is expected (behind a prefix that + * names the operation). */ +#define SCR_WASM_FUNCREF_EXPECTED "function-typed object must be null (if nullable) or a Wasm function object" + /* ToWebAssemblyValue: false with an exception pending. References are - * borrowed from `v`. */ -static bool scr_wasm_from_js(char t, const ScrDyn *v, uint64_t *out) { + * borrowed from `v`. A funcref mismatch throws a TypeError with + * `funcref_error` (the export-call message when NULL). */ +static bool scr_wasm_from_js_as(char t, const ScrDyn *v, uint64_t *out, const char *funcref_error) { if (!v) v = scr_dyn_undefined(); switch (t) { case 'i': { @@ -1063,7 +1108,7 @@ static bool scr_wasm_from_js(char t, const ScrDyn *v, uint64_t *out) { } ScrWasmFuncRef *ref = scr_wasm_funcref_of(v); if (!ref) { - scr_wasm_type_error("type incompatibility when transforming from/to JS"); + scr_wasm_type_error(funcref_error ? funcref_error : "type incompatibility when transforming from/to JS"); return false; } *out = (uint64_t)(uintptr_t)ref; @@ -1079,6 +1124,14 @@ static bool scr_wasm_from_js(char t, const ScrDyn *v, uint64_t *out) { } } +static bool scr_wasm_from_js(char t, const ScrDyn *v, uint64_t *out) { return scr_wasm_from_js_as(t, v, out, NULL); } + +/* DefaultValue(type): what a missing initial value of a constructed + * global or table, or a missing table.grow/set value, stands for. */ +static uint64_t scr_wasm_default_value(char t) { + return t == 'x' ? (uint64_t)(uintptr_t)scr_dyn_undefined() : 0; +} + static size_t scr_wasm_params(const char *sig) { size_t n = 0; while (sig[n] && sig[n] != ':') n++; @@ -1116,7 +1169,11 @@ static ScrDyn *scr_wasm_export_call(ScrClosure *clo, ScrDyn *const *args, size_t bool ok = true; for (size_t i = 0; i < np && ok; i++) ok = scr_wasm_from_js(sig[i], i < argc ? args[i] : NULL, &a[i]); if (ok) { - int trap = scr_wasm_enter(thunk, host->inst, a, r); + /* A function the module does not export runs through its signature's + * call thunk, on the code its reference record names. */ + int trap = thunk ? scr_wasm_enter(thunk, host->inst, a, r) + : scr_wasm_enter_ref(NULL, host->desc->func_call_thunks[func], host->funcrefs[func].code, + host->inst, a, r); if (trap) { scr_wasm_throw_trap(trap); } else if (nr == 0) { @@ -1134,18 +1191,20 @@ static ScrDyn *scr_wasm_export_call(ScrClosure *clo, ScrDyn *const *args, size_t return result; } -/* The function value of exported function `func` (+1), created once per - * instance and function; NULL with a TypeError pending for a function the - * module does not export (funcref values of other functions are stage W5). */ +/* The function value of function `func` (+1), created once per instance + * and function: exported functions call through their export thunk, other + * referenced functions through their signature's call thunk. */ static ScrDyn *scr_wasm_function_value(ScrWasmInst *host, uint32_t func) { if (host->export_fns[func]) return scr_dyn_retain(host->export_fns[func]); - bool exported = false; - for (uint64_t i = 0; i < host->desc->export_count; i++) { + bool callable = host->desc->func_call_thunks[func] != NULL && host->funcrefs[func].code != NULL; + for (uint64_t i = 0; i < host->desc->export_count && !callable; i++) { const ScrWasmExportDesc *ex = &host->desc->exports[i]; - if (ex->kind == SCR_WASM_FUNC && ex->index == func) exported = true; + if (ex->kind == SCR_WASM_FUNC && ex->index == func) callable = true; } - if (!exported) { - scr_wasm_type_error("WebAssembly function references to functions a module does not export are not supported yet"); + if (!callable) { + /* Unreachable for valid modules: every function a reference can name + * is exported or has a call thunk. */ + scr_wasm_type_error("type incompatibility when transforming from/to JS"); return NULL; } char name[24]; @@ -1268,7 +1327,8 @@ static bool scr_wasm_global_assign(ScrWasmGlobal *g, const ScrDyn *value) { return false; } uint64_t v; - if (!scr_wasm_from_js(g->type, value, &v)) return false; + if (!scr_wasm_from_js_as(g->type, value, &v, "set WebAssembly.Global.value): " SCR_WASM_FUNCREF_EXPECTED)) + return false; if (g->type == 'x') scr_wasm_global_set_ref(g, (void *)(uintptr_t)v); else g->value = v; return true; @@ -1283,12 +1343,20 @@ static bool scr_wasm_set(void *h, const char *key, size_t len, const ScrDyn *val return false; } +/* V8's EnforceUint32: ToNumber (its own TypeError for symbols and + * BigInts), then finite, non-negative and at most 2^32-1, truncated. + * `what` names the value ("WebAssembly.Memory.grow(): Argument 0", + * "WebAssembly.Memory(): Property 'initial'"). */ static bool scr_wasm_u32_arg(const ScrDyn *v, const char *what, uint32_t *out) { double n; if (!scr_dyn_number_coerce_js(v ? v : scr_dyn_undefined(), &n)) return false; - if (!isfinite(n) || n < 0 || trunc(n) > 4294967295.0) { + const char *problem = !isfinite(n) ? "must be convertible to a valid number" + : n < 0 ? "must be non-negative" + : trunc(n) > 4294967295.0 ? "must be in the unsigned long range" + : NULL; + if (problem) { char msg[200]; - snprintf(msg, sizeof msg, "%s must be convertible to a valid number", what); + snprintf(msg, sizeof msg, "%s %s", what, problem); scr_wasm_type_error(msg); return false; } @@ -1309,7 +1377,7 @@ static ScrDyn *scr_wasm_invoke(void *h, ScrDyn *self, const char *method, ScrDyn return NULL; } if (o->inst && o->inst->memory == o->memory) o->inst->inst[1] = o->memory->base; - /* The previous buffer keeps its length (no detachment yet, W5). */ + /* The previous buffer keeps its length (no detachment). */ scr_dyn_release(o->memory->buffer); o->memory->buffer = NULL; return scr_dyn_new_num((double)old); @@ -1338,15 +1406,26 @@ static ScrDyn *scr_wasm_invoke(void *h, ScrDyn *self, const char *method, ScrDyn scr_wasm_range_error(msg); return NULL; } - const ScrDyn *v = argc > 1 ? args[1] : (t->elem == 'x' ? scr_dyn_undefined() : scr_dyn_new_null()); - if (!scr_wasm_from_js(t->elem, v, &value)) return NULL; + if (argc > 1) { + if (!scr_wasm_from_js_as(t->elem, args[1], &value, + "WebAssembly.Table.set(): Argument 1 is invalid for table: " SCR_WASM_FUNCREF_EXPECTED)) + return NULL; + } else { + value = scr_wasm_default_value(t->elem); + } scr_wasm_table_store(t, index, (void *)(uintptr_t)value); return scr_dyn_retain(scr_dyn_undefined()); } if (!strcmp(method, "grow")) { uint32_t delta; if (!scr_wasm_u32_arg(argc ? args[0] : NULL, "WebAssembly.Table.grow(): Argument 0", &delta)) return NULL; - if (argc > 1 && !scr_wasm_from_js(t->elem, args[1], &value)) return NULL; + if (argc > 1) { + if (!scr_wasm_from_js_as(t->elem, args[1], &value, + "WebAssembly.Table.grow(): Argument 1 is invalid: " SCR_WASM_FUNCREF_EXPECTED)) + return NULL; + } else { + value = scr_wasm_default_value(t->elem); + } int64_t old = scr_wasm_table_grow_by(t, delta, (void *)(uintptr_t)value); if (old < 0) { char msg[160]; @@ -1569,10 +1648,30 @@ static ScrDyn *scr_wasm_instance_from(const ScrWasmModuleDesc *desc, ScrDyn *imp } for (uint64_t i = 0; ok && i < n; i++) { const ScrWasmImportDesc *im = &desc->imports[i]; - if (im->kind == SCR_WASM_MEMORY && !scr_wasm_unbox(values[i], SCR_WASM_K_MEMORY)) { + if (im->kind != SCR_WASM_MEMORY) continue; + ScrWasmObj *mo = scr_wasm_unbox(values[i], SCR_WASM_K_MEMORY); + if (!mo) { scr_wasm_link_error(what, i, im, "memory import must be a WebAssembly.Memory object"); ok = false; + break; } + /* V8's limit checks, with its messages (a missing maximum prints as + * the unsigned -1). */ + char reason[200]; + uint64_t pages = mo->memory->size / SCR_WASM_PAGE; + if (pages < desc->memory_min) { + snprintf(reason, sizeof reason, "memory import has %llu pages which is smaller than the declared initial of %llu", + (unsigned long long)pages, (unsigned long long)desc->memory_min); + } else if (desc->memory_max != UINT64_MAX && mo->memory->declared_max == UINT64_MAX) { + snprintf(reason, sizeof reason, "memory import has no maximum limit, expected at most 4294967295"); + } else if (desc->memory_max != UINT64_MAX && mo->memory->declared_max > desc->memory_max) { + snprintf(reason, sizeof reason, "memory import has a larger maximum size %llu than the module's declared maximum %llu", + (unsigned long long)mo->memory->declared_max, (unsigned long long)desc->memory_max); + } else { + continue; + } + scr_wasm_link_error(what, i, im, reason); + ok = false; } for (uint64_t i = 0; ok && i < n; i++) { const ScrWasmImportDesc *im = &desc->imports[i]; @@ -1587,9 +1686,26 @@ static ScrDyn *scr_wasm_instance_from(const ScrWasmModuleDesc *desc, ScrDyn *imp ok = false; } else if (im->kind == SCR_WASM_TABLE) { ScrWasmObj *t = scr_wasm_unbox(v, SCR_WASM_K_TABLE); + const ScrWasmTableDesc *td = &desc->tables[im->index]; if (!t) { scr_wasm_link_error(what, i, im, "table import requires a WebAssembly.Table"); ok = false; + } else if (t->table->size < td->min || (td->max != UINT64_MAX && t->table->declared_max > td->max)) { + /* V8's size checks name the import by number only; the imported + * maximum prints in hexadecimal. */ + char msg[300]; + if (t->table->size < td->min) + snprintf(msg, sizeof msg, "%s: table import %llu is smaller than initial %llu, got %u", what, + (unsigned long long)i, (unsigned long long)td->min, t->table->size); + else if (t->table->declared_max == UINT64_MAX) + snprintf(msg, sizeof msg, "%s: table import %llu has no maximum length; required: %llu", what, + (unsigned long long)i, (unsigned long long)td->max); + else + snprintf(msg, sizeof msg, "%s: table import %llu has a larger maximum size %llx than the module's declared maximum %llu", + what, (unsigned long long)i, (unsigned long long)t->table->declared_max, + (unsigned long long)td->max); + scr_wasm_throw_kind(SCR_WASM_LINK_ERROR, msg); + ok = false; } else if (t->table->elem != im->type[0]) { scr_wasm_link_error(what, i, im, "imported table does not match the expected type"); ok = false; @@ -1599,16 +1715,24 @@ static ScrDyn *scr_wasm_instance_from(const ScrWasmModuleDesc *desc, ScrDyn *imp bool mutable_ = desc->globals[im->index].mutable_ != 0; char type = im->type[0]; if (g) { - if (g->global->type != type || g->global->mutable_ != mutable_) { + if (g->global->mutable_ != mutable_) { + scr_wasm_link_error(what, i, im, "imported global does not match the expected mutability"); + ok = false; + } else if (g->global->type != type) { scr_wasm_link_error(what, i, im, "imported global does not match the expected type"); ok = false; } } else if (mutable_) { scr_wasm_link_error(what, i, im, "imported mutable global must be a WebAssembly.Global object"); ok = false; + } else if (type == 'r') { + if (!v || (v->kind != SCR_DYN_NULL && !scr_wasm_funcref_of(v))) { + scr_wasm_link_error(what, i, im, SCR_WASM_FUNCREF_EXPECTED); + ok = false; + } } else if (type == 'I' ? !(v && v->kind == SCR_DYN_BIGINT) - : type == 'x' || type == 'r' ? false - : !(v && v->kind == SCR_DYN_NUM)) { + : type == 'x' ? false + : !(v && v->kind == SCR_DYN_NUM)) { scr_wasm_link_error(what, i, im, "global import must be a number, valid Wasm reference, or WebAssembly.Global object"); ok = false; @@ -1636,8 +1760,11 @@ static ScrDyn *scr_wasm_instance_from(const ScrWasmModuleDesc *desc, ScrDyn *imp host->globals = calloc(desc->global_count ? desc->global_count : 1, sizeof *host->globals); host->data_dropped = calloc(desc->data_count ? desc->data_count : 1, 1); host->elem_dropped = calloc(desc->elem_count ? desc->elem_count : 1, 1); + host->table_objs = calloc(desc->table_count ? desc->table_count : 1, sizeof *host->table_objs); + host->global_objs = calloc(desc->global_count ? desc->global_count : 1, sizeof *host->global_objs); if (!host->inst || !host->funcs || !host->funcrefs || !host->export_fns || !host->export_names || - !host->tables || !host->globals || !host->data_dropped || !host->elem_dropped) + !host->tables || !host->globals || !host->data_dropped || !host->elem_dropped || !host->table_objs || + !host->global_objs) scr_trap("scriptc: out of memory\n"); void **inst = host->inst; inst[0] = host; @@ -1660,14 +1787,20 @@ static ScrDyn *scr_wasm_instance_from(const ScrWasmModuleDesc *desc, ScrDyn *imp break; case SCR_WASM_MEMORY: host->memory = scr_wasm_memory_retain(scr_wasm_unbox(v, SCR_WASM_K_MEMORY)->memory); + host->memory_obj = v; + values[i] = NULL; break; case SCR_WASM_TABLE: host->tables[im->index] = scr_wasm_table_retain(scr_wasm_unbox(v, SCR_WASM_K_TABLE)->table); + host->table_objs[im->index] = v; + values[i] = NULL; break; default: { ScrWasmObj *g = scr_wasm_unbox(v, SCR_WASM_K_GLOBAL); if (g) { host->globals[im->index] = scr_wasm_global_retain(g->global); + host->global_objs[im->index] = v; + values[i] = NULL; } else { char type = im->type[0]; ScrWasmGlobal *cell = scr_wasm_global_new(type, false); @@ -1684,9 +1817,7 @@ static ScrDyn *scr_wasm_instance_from(const ScrWasmModuleDesc *desc, ScrDyn *imp } free(values); if (desc->memory_count && !host->memory) { - host->memory = scr_wasm_memory_new(desc->memory_min, desc->memory_max == UINT64_MAX ? SCR_WASM_MAX_PAGES - : desc->memory_max, - scr_wasm_guarded(desc), what); + host->memory = scr_wasm_memory_new(desc->memory_min, desc->memory_max, scr_wasm_guarded(desc), what); if (!host->memory) return NULL; } if (host->memory) { @@ -1743,12 +1874,22 @@ static ScrDyn *scr_wasm_instance_from(const ScrWasmModuleDesc *desc, ScrDyn *imp int kind = ex->kind == SCR_WASM_MEMORY ? SCR_WASM_K_MEMORY : ex->kind == SCR_WASM_TABLE ? SCR_WASM_K_TABLE : SCR_WASM_K_GLOBAL; - ScrWasmObj *o = scr_wasm_obj_new(kind); - o->inst = scr_wasm_inst_retain(host); - if (kind == SCR_WASM_K_MEMORY) o->memory = scr_wasm_memory_retain(host->memory); - else if (kind == SCR_WASM_K_TABLE) o->table = scr_wasm_table_retain(host->tables[ex->index]); - else o->global = scr_wasm_global_retain(host->globals[ex->index]); - value = scr_wasm_box(o); + /* V8 exports an imported object itself, and one object per memory + * and table; a defined global gets a new object per export. */ + ScrDyn **slot = kind == SCR_WASM_K_MEMORY ? &host->memory_obj + : kind == SCR_WASM_K_TABLE ? &host->table_objs[ex->index] + : &host->global_objs[ex->index]; + if (*slot) { + value = scr_dyn_retain(*slot); + } else { + ScrWasmObj *o = scr_wasm_obj_new(kind); + o->inst = scr_wasm_inst_retain(host); + if (kind == SCR_WASM_K_MEMORY) o->memory = scr_wasm_memory_retain(host->memory); + else if (kind == SCR_WASM_K_TABLE) o->table = scr_wasm_table_retain(host->tables[ex->index]); + else o->global = scr_wasm_global_retain(host->globals[ex->index]); + value = scr_wasm_box(o); + if (kind != SCR_WASM_K_GLOBAL) *slot = scr_dyn_retain(value); + } } scr_dyn_obj_set(exports, ex->name, (size_t)ex->name_len, value); } @@ -1770,6 +1911,273 @@ ScrDyn *scr_wasm_instance_new(ScrDyn *module, ScrDyn *imports) { return scr_wasm_instance_from(m->desc, imports, "WebAssembly.Instance()"); } +/* ── constructors and Module's static functions ────────────────────── */ + +/* Memories the program constructs are guarded exactly when the translated + * modules are (every module of a build shares the mode), so any of them + * can import one. */ +static bool scr_wasm_build_guarded(void) { +#if SCR_WASM_SIGNALS + return scr_wasm_registry.count > 0 && !(scr_wasm_registry.modules[0]->flags & SCR_WASM_DESC_BOUNDS_CHECKS); +#else + return false; +#endif +} + +/* V8's GetOptionalIntegerProperty: the descriptor's `name` property as an + * unsigned 32-bit integer within [lower, upper]. An absent or undefined + * property leaves *out unchanged, or throws "is required" when + * `required`. False with an exception pending. */ +static bool scr_wasm_limit(const ScrDyn *descriptor, const char *name, bool required, const char *what, + uint64_t lower, uint64_t upper, uint64_t *out) { + ScrDyn *v = scr_wasm_member(descriptor, name, strlen(name)); + if (scr_exc_pending()) return false; + char msg[240]; + if (!v) { + if (!required) return true; + snprintf(msg, sizeof msg, "%s: Property '%s' is required", what, name); + scr_wasm_type_error(msg); + return false; + } + char label[120]; + snprintf(label, sizeof label, "%s: Property '%s'", what, name); + uint32_t n; + bool ok = scr_wasm_u32_arg(v, label, &n); + scr_dyn_release(v); + if (!ok) return false; + /* V8 prints the lower bound in hexadecimal. */ + if (n < lower) { + snprintf(msg, sizeof msg, "%s: Property '%s': value %u is below the lower bound %llx", what, name, n, + (unsigned long long)lower); + scr_wasm_range_error(msg); + return false; + } + if (n > upper) { + snprintf(msg, sizeof msg, "%s: Property '%s': value %u is above the upper bound %llu", what, name, n, + (unsigned long long)upper); + scr_wasm_range_error(msg); + return false; + } + *out = n; + return true; +} + +/* ToString of a descriptor's type property (+1; NULL with an exception + * pending). An absent property reads as "undefined". */ +static ScrStr *scr_wasm_type_name(const ScrDyn *descriptor, const char *name) { + ScrDyn *v = scr_wasm_member(descriptor, name, strlen(name)); + if (scr_exc_pending()) return NULL; + if (!v) return scr_str_new("undefined", 9); + ScrStr *s = scr_dyn_string_coerce_js(v); + scr_dyn_release(v); + return s; +} + +static bool scr_wasm_str_is(const ScrStr *s, const char *text) { + size_t n = strlen(text); + return s->len == n && !memcmp(s->data, text, n); +} + +/* A descriptor's boolean property (ToBoolean; false when absent). */ +static bool scr_wasm_flag(const ScrDyn *descriptor, const char *name, bool *out) { + ScrDyn *v = scr_wasm_member(descriptor, name, strlen(name)); + if (scr_exc_pending()) return false; + *out = v && scr_dyn_truthy(v); + scr_dyn_release(v); + return true; +} + +static bool scr_wasm_absent(const ScrDyn *v) { return !v || v->kind == SCR_DYN_UNDEF; } + +ScrDyn *scr_wasm_memory_construct(ScrDyn *descriptor) { + scr_wasm_install(); + const char *what = "WebAssembly.Memory()"; + if (!scr_wasm_is_object(descriptor)) { + scr_wasm_type_error("WebAssembly.Memory(): Argument 0 must be a memory descriptor"); + return NULL; + } + uint64_t initial = 0, maximum = UINT64_MAX; + bool shared; + if (!scr_wasm_limit(descriptor, "initial", true, what, 0, SCR_WASM_MAX_PAGES, &initial) || + !scr_wasm_limit(descriptor, "maximum", false, what, initial, SCR_WASM_MAX_PAGES, &maximum) || + !scr_wasm_flag(descriptor, "shared", &shared)) + return NULL; + if (shared && maximum == UINT64_MAX) { + scr_wasm_type_error("WebAssembly.Memory(): If shared is true, maximum property should be defined."); + return NULL; + } + if (shared) { + scr_wasm_type_error("WebAssembly.Memory(): shared memories are not supported yet (scriptc does not translate " + "WebAssembly threads; see https://scriptc.dev/limitations)"); + return NULL; + } + ScrWasmMemory *m = scr_wasm_memory_new(initial, maximum, scr_wasm_build_guarded(), what); + if (!m) return NULL; + ScrWasmObj *o = scr_wasm_obj_new(SCR_WASM_K_MEMORY); + o->memory = m; + return scr_wasm_box(o); +} + +ScrDyn *scr_wasm_table_construct(ScrDyn *descriptor, ScrDyn *value) { + scr_wasm_install(); + const char *what = "WebAssembly.Table()"; + if (!scr_wasm_is_object(descriptor)) { + scr_wasm_type_error("WebAssembly.Table(): Argument 0 must be a table descriptor"); + return NULL; + } + ScrStr *type = scr_wasm_type_name(descriptor, "element"); + if (!type) return NULL; + char elem = scr_wasm_str_is(type, "anyfunc") ? 'r' : scr_wasm_str_is(type, "externref") ? 'x' : 0; + scr_str_release(type); + if (!elem) { + scr_wasm_type_error("WebAssembly.Table(): Descriptor property 'element' must be a WebAssembly reference type"); + return NULL; + } + uint64_t initial = 0, maximum = UINT64_MAX; + if (!scr_wasm_limit(descriptor, "initial", true, what, 0, SCR_WASM_MAX_TABLE, &initial) || + !scr_wasm_limit(descriptor, "maximum", false, what, initial, 4294967295u, &maximum)) + return NULL; + uint64_t init = scr_wasm_default_value(elem); + if (!scr_wasm_absent(value) && + !scr_wasm_from_js_as(elem, value, &init, + "WebAssembly.Table(): Argument 2 must be undefined or a value of type compatible with the " + "type of the new table: " SCR_WASM_FUNCREF_EXPECTED ".")) + return NULL; + ScrWasmTable *t = scr_wasm_table_new(elem, initial, maximum); + for (uint32_t i = 0; i < t->size; i++) t->elems[i] = scr_wasm_ref_retain(elem, (void *)(uintptr_t)init); + ScrWasmObj *o = scr_wasm_obj_new(SCR_WASM_K_TABLE); + o->table = t; + return scr_wasm_box(o); +} + +ScrDyn *scr_wasm_global_construct(ScrDyn *descriptor, ScrDyn *value) { + scr_wasm_install(); + if (!scr_wasm_is_object(descriptor)) { + scr_wasm_type_error("WebAssembly.Global(): Argument 0 must be a global descriptor"); + return NULL; + } + bool mutable_; + if (!scr_wasm_flag(descriptor, "mutable", &mutable_)) return NULL; + ScrStr *name = scr_wasm_type_name(descriptor, "value"); + if (!name) return NULL; + static const char *const names[] = {"i32", "i64", "f32", "f64", "externref", "anyfunc"}; + static const char letters[] = {'i', 'I', 'f', 'F', 'x', 'r'}; + char type = 0; + for (size_t i = 0; i < sizeof letters; i++) + if (scr_wasm_str_is(name, names[i])) type = letters[i]; + bool v128 = scr_wasm_str_is(name, "v128"); + scr_str_release(name); + if (v128) { + scr_wasm_type_error("WebAssembly.Global(): A global of type 'v128' cannot be created in JavaScript"); + return NULL; + } + if (!type) { + scr_wasm_type_error("WebAssembly.Global(): Descriptor property 'value' must be a WebAssembly type"); + return NULL; + } + uint64_t v = scr_wasm_default_value(type); + if (!scr_wasm_absent(value) && + !scr_wasm_from_js_as(type, value, &v, "WebAssembly.Global(): " SCR_WASM_FUNCREF_EXPECTED)) + return NULL; + ScrWasmGlobal *g = scr_wasm_global_new(type, mutable_); + if (type == 'x') scr_wasm_global_set_ref(g, (void *)(uintptr_t)v); + else g->value = v; + ScrWasmObj *o = scr_wasm_obj_new(SCR_WASM_K_GLOBAL); + o->global = g; + return scr_wasm_box(o); +} + +/* `WebAssembly.Module(...)` and the other classes called without new. */ +ScrDyn *scr_wasm_construct_without_new(double kind, ScrDyn *arg0, ScrDyn *arg1) { + (void)arg0; + (void)arg1; + static const char *const names[] = {"Module", "Instance", "Memory", "Table", "Global"}; + const char *name = names[(int)kind]; + char msg[160]; + snprintf(msg, sizeof msg, "WebAssembly.%s(): WebAssembly.%s must be invoked with 'new'", name, name); + scr_wasm_type_error(msg); + return NULL; +} + +static const ScrWasmModuleDesc *scr_wasm_module_arg(const ScrDyn *v, const char *what) { + ScrWasmObj *m = scr_wasm_unbox(v, SCR_WASM_K_MODULE); + if (m) return m->desc; + char msg[160]; + snprintf(msg, sizeof msg, "%s: Argument 0 must be a WebAssembly.Module", what); + scr_wasm_type_error(msg); + return NULL; +} + +static void scr_wasm_set_text(ScrDyn *obj, const char *key, const char *text, size_t len) { + ScrStr *s = scr_str_new(text, len); + scr_dyn_obj_set(obj, key, strlen(key), scr_dyn_new_str(s)); + scr_str_release(s); +} + +static const char *const scr_wasm_kind_names[] = {"function", "table", "memory", "global"}; + +ScrDyn *scr_wasm_module_imports(ScrDyn *module) { + const ScrWasmModuleDesc *desc = scr_wasm_module_arg(module, "WebAssembly.Module.imports()"); + if (!desc) return NULL; + ScrDyn *out = scr_dyn_new_arr(); + for (uint64_t i = 0; i < desc->import_count; i++) { + const ScrWasmImportDesc *im = &desc->imports[i]; + ScrDyn *row = scr_dyn_new_obj(); + scr_wasm_set_text(row, "module", im->module, (size_t)im->module_len); + scr_wasm_set_text(row, "name", im->name, (size_t)im->name_len); + scr_wasm_set_text(row, "kind", scr_wasm_kind_names[im->kind], strlen(scr_wasm_kind_names[im->kind])); + scr_dyn_arr_push(out, row); + } + return out; +} + +ScrDyn *scr_wasm_module_exports(ScrDyn *module) { + const ScrWasmModuleDesc *desc = scr_wasm_module_arg(module, "WebAssembly.Module.exports()"); + if (!desc) return NULL; + ScrDyn *out = scr_dyn_new_arr(); + for (uint64_t i = 0; i < desc->export_count; i++) { + const ScrWasmExportDesc *ex = &desc->exports[i]; + ScrDyn *row = scr_dyn_new_obj(); + scr_wasm_set_text(row, "name", ex->name, (size_t)ex->name_len); + scr_wasm_set_text(row, "kind", scr_wasm_kind_names[ex->kind], strlen(scr_wasm_kind_names[ex->kind])); + scr_dyn_arr_push(out, row); + } + return out; +} + +/* A fresh ArrayBuffer per matching custom section, in module order. */ +ScrDyn *scr_wasm_module_custom_sections(ScrDyn *module, ScrDyn *name) { + const ScrWasmModuleDesc *desc = scr_wasm_module_arg(module, "WebAssembly.Module.customSections()"); + if (!desc) return NULL; + if (scr_wasm_absent(name)) { + scr_wasm_type_error("WebAssembly.Module.customSections(): Argument 1 is required"); + return NULL; + } + ScrStr *wanted = scr_dyn_string_coerce_js(name); + if (!wanted) return NULL; + ScrDyn *out = scr_dyn_new_arr(); + for (uint64_t i = 0; i < desc->custom_count; i++) { + const ScrWasmCustomDesc *c = &desc->customs[i]; + if (c->name_len != wanted->len || memcmp(c->name, wanted->data, wanted->len) != 0) continue; + ScrBytes *bytes = scr_bytes_from_data(c->data, (size_t)c->len); + scr_dyn_arr_push(out, scr_array_buffer_from_bytes(bytes)); + scr_bytes_release(bytes); + } + scr_str_release(wanted); + return out; +} + +/* `v.valueOf()` on a WebAssembly-typed receiver: a Global's value; any + * other value answers what the generic valueOf answers. */ +ScrDyn *scr_wasm_value_of(ScrDyn *value) { + ScrWasmObj *g = scr_wasm_unbox(value, SCR_WASM_K_GLOBAL); + if (g) return scr_wasm_global_value(g->global); + ScrStr *what = scr_str_new("valueOf", 7); + ScrDyn *out = scr_dyn_value_of(value, what); + scr_str_release(what); + return out; +} + /* ── promises ────────────────────────────────────────────────────────── */ /* A promise settled now: fulfilled with value, or rejected with the @@ -1958,6 +2366,11 @@ static void scr_wasm_teardown(void) { ScrWasmInst *host = scr_wasm_hosts[i]; const ScrWasmModuleDesc *desc = host->desc; scr_dyn_release(host->exports); + scr_dyn_release(host->memory_obj); + for (uint64_t t = 0; t < desc->table_count; t++) scr_dyn_release(host->table_objs[t]); + for (uint64_t g = 0; g < desc->global_count; g++) scr_dyn_release(host->global_objs[g]); + free(host->table_objs); + free(host->global_objs); for (uint64_t f = 0; f < desc->func_count; f++) { scr_dyn_release(host->export_fns[f]); scr_dyn_release(host->funcs[f]); diff --git a/packages/runtime/src/scr_wasm_abi.h b/packages/runtime/src/scr_wasm_abi.h index bd2a1e3bd..140fa7648 100644 --- a/packages/runtime/src/scr_wasm_abi.h +++ b/packages/runtime/src/scr_wasm_abi.h @@ -33,7 +33,7 @@ #include #include -#define SCR_WASM_ABI_VERSION 1 +#define SCR_WASM_ABI_VERSION 2 enum { SCR_WASM_FUNC = 0, SCR_WASM_TABLE = 1, SCR_WASM_MEMORY = 2, SCR_WASM_GLOBAL = 3 }; @@ -61,6 +61,9 @@ enum { #define SCR_WASM_DESC_BOUNDS_CHECKS 1u typedef void (*ScrWasmThunk)(void *inst, const uint64_t *args, uint64_t *results); +/* A signature's call thunk: calls `code` (a function of that signature, + * `(inst, params...) -> results`) with the arguments in slots. */ +typedef void (*ScrWasmCallThunk)(void *code, void *inst, const uint64_t *args, uint64_t *results); typedef struct { const char *module; @@ -111,6 +114,16 @@ typedef struct { const int64_t *items; } ScrWasmElemDesc; +/* A custom section: its name and payload, in module order. Only programs + * that call WebAssembly.Module.customSections carry them (the translator + * emits none otherwise, so debug sections cost nothing). */ +typedef struct { + const char *name; + uint64_t name_len; + const uint8_t *data; + uint64_t len; +} ScrWasmCustomDesc; + typedef struct ScrWasmModuleDesc { uint8_t sha256[32]; uint64_t byte_length; @@ -143,6 +156,12 @@ typedef struct ScrWasmModuleDesc { * guard; then the start function `void (inst)`, or NULL. */ void (*init)(void *inst); void (*start)(void *inst); + uint64_t custom_count; + const ScrWasmCustomDesc *customs; + /* Per function: the call thunk of its signature for referenced + * functions the module does not export (NULL otherwise), so any + * function reference can be called from JavaScript. */ + const ScrWasmCallThunk *func_call_thunks; } ScrWasmModuleDesc; /* The registry the compiler emits in the program module of every program @@ -163,6 +182,12 @@ typedef struct ScrWasmMemory { bool guarded; struct ScrDyn *buffer; /* the cached ArrayBuffer */ uint64_t buffer_size; /* the size it was created at */ + uint64_t declared_max; /* pages; UINT64_MAX when the memory has no maximum */ + /* Explicit-bounds memories (not guarded): the blocks grow replaced. + * ArrayBuffers created before a growth still point into them, so they + * live as long as the memory. */ + uint8_t **retired; + size_t retired_count; } ScrWasmMemory; typedef struct ScrWasmFuncRef { @@ -178,6 +203,7 @@ typedef struct ScrWasmTable { uint32_t max; size_t rc; char elem; /* 'r' or 'x' */ + uint64_t declared_max; /* UINT64_MAX when the table has no maximum */ } ScrWasmTable; /* A global's cell: the value in the low bytes of `value`. */ diff --git a/tests/corpus/webassembly-dylink/LICENSE.quickjs-wasi b/tests/corpus/webassembly-dylink/LICENSE.quickjs-wasi new file mode 100644 index 000000000..5a2e99d85 --- /dev/null +++ b/tests/corpus/webassembly-dylink/LICENSE.quickjs-wasi @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Vercel, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/tests/corpus/webassembly-dylink/extensions.js b/tests/corpus/webassembly-dylink/extensions.js new file mode 100644 index 000000000..8c6b26feb --- /dev/null +++ b/tests/corpus/webassembly-dylink/extensions.js @@ -0,0 +1,372 @@ +/** + * WASM Dynamic Linking - Extension Loader + * + * Loads WASM shared libraries (.so) compiled with wasi-sdk as extensions + * for the QuickJS WASM runtime. Extensions can call QuickJS C API functions + * directly through dynamic linking (shared memory + symbol resolution). + * + * Key concepts: + * - Extensions are WASM shared libraries with a `dylink.0` custom section + * - They share linear memory and the indirect function table with the main module + * - Symbol imports (env.*) are resolved against the main module's exports + * - Each extension gets a unique __memory_base and __table_base + * - Extensions export an init function (e.g., `qjs_ext_url_init`) + */ +const decoder = new TextDecoder(); +/** + * Parse a null-terminated "key=value\nkey=value" string from WASM memory + * into a Record. Used to read version info from extensions. + */ +function parseVersionString(memory, ptr) { + const mem = new Uint8Array(memory.buffer); + let end = ptr; + while (mem[end] !== 0) + end++; + if (end === ptr) + return {}; + const str = decoder.decode(mem.slice(ptr, end)); + const result = {}; + for (const line of str.split('\n')) { + const eq = line.indexOf('='); + if (eq > 0) { + result[line.slice(0, eq)] = line.slice(eq + 1); + } + } + return result; +} +/** + * Check if an extension exports a versions function and parse the result. + */ +function extractExtensionVersions(ext, memory) { + const versionsFnName = `qjs_ext_${ext.name.replace(/-/g, '_')}_versions`; + const versionsFn = ext.instance.exports[versionsFnName]; + if (typeof versionsFn !== 'function') + return undefined; + const ptr = versionsFn(); + if (ptr === 0) + return undefined; + return parseVersionString(memory, ptr); +} +// ---- dylink.0 parser ---- +/** Read a ULEB128-encoded integer from a byte array */ +function readULEB128(bytes, offset) { + let result = 0; + let shift = 0; + while (offset.value < bytes.length) { + const byte = bytes[offset.value++]; + result |= (byte & 0x7f) << shift; + if ((byte & 0x80) === 0) + break; + shift += 7; + } + return result; +} +/** Parse the dylink.0 custom section from a WASM module */ +export function parseDylink(module) { + const sections = WebAssembly.Module.customSections(module, 'dylink.0'); + if (sections.length === 0) + return null; + const bytes = new Uint8Array(sections[0]); + const offset = { value: 0 }; + const info = { + memorySize: 0, + memoryAlignment: 0, + tableSize: 0, + tableAlignment: 0, + needed: [], + }; + while (offset.value < bytes.length) { + const subsectionType = readULEB128(bytes, offset); + const subsectionSize = readULEB128(bytes, offset); + const subsectionEnd = offset.value + subsectionSize; + if (subsectionType === 1) { + // WASM_DYLINK_MEM_INFO + info.memorySize = readULEB128(bytes, offset); + info.memoryAlignment = readULEB128(bytes, offset); + info.tableSize = readULEB128(bytes, offset); + info.tableAlignment = readULEB128(bytes, offset); + } + else if (subsectionType === 2) { + // WASM_DYLINK_NEEDED + const count = readULEB128(bytes, offset); + for (let i = 0; i < count; i++) { + const len = readULEB128(bytes, offset); + const name = new TextDecoder().decode(bytes.slice(offset.value, offset.value + len)); + offset.value += len; + info.needed.push(name); + } + } + offset.value = subsectionEnd; + } + return info; +} +// ---- Alignment helper ---- +/** Align a value up to 2^align */ +function alignUp(value, align) { + if (align <= 0) + return value; + const mask = (1 << align) - 1; + return (value + mask) & ~mask; +} +// ---- Extension Loader ---- +/** + * Load a WASM shared library extension and link it against the main module. + * + * @param descriptor - Extension description (name, wasm bytes, init function) + * @param mainExports - The main QuickJS WASM module's exports + * @param wasiBuiltins - Built-in WASI implementations (lowest priority) + * @param wasiUserOverrides - User-provided WASI overrides (highest priority) + * @param memoryProxy - A memory proxy for extension WASI factories + * @param allocBase - Optional fixed memory/table bases (for snapshot restore) + */ +export async function loadExtension(descriptor, mainExports, wasiBuiltins, wasiUserOverrides, memoryProxy, allocBase) { + // Compile the extension module + let module; + if (descriptor.wasm instanceof WebAssembly.Module) { + module = descriptor.wasm; + } + else { + module = await WebAssembly.compile(descriptor.wasm); + } + // Parse dylink.0 section + const dylink = parseDylink(module); + if (!dylink) { + throw new Error(`Extension "${descriptor.name}" is not a WASM shared library (missing dylink.0 section)`); + } + const memory = mainExports.memory; + const table = mainExports.__indirect_function_table; + const stackPointer = mainExports.__stack_pointer; + const mallocFn = mainExports.malloc; + if (!memory) + throw new Error('Main module does not export memory'); + if (!table) + throw new Error('Main module does not export __indirect_function_table'); + if (!mallocFn) + throw new Error('Main module does not export malloc'); + // Allocate memory region for the extension's static data + let memoryBase; + let tableBase; + if (allocBase) { + // Restore mode: use the exact same bases as before + memoryBase = allocBase.memoryBase; + tableBase = allocBase.tableBase; + } + else { + // Fresh allocation + if (dylink.memorySize > 0) { + // Use malloc from the main module to allocate in the shared heap + memoryBase = mallocFn(dylink.memorySize); + if (memoryBase === 0) { + throw new Error(`Failed to allocate ${dylink.memorySize} bytes for extension "${descriptor.name}"`); + } + // Zero-initialize the region + new Uint8Array(memory.buffer, memoryBase, dylink.memorySize).fill(0); + } + else { + memoryBase = 0; + } + } + // Grow the function table if needed + if (allocBase) { + tableBase = allocBase.tableBase; + // Ensure table is large enough + if (tableBase + dylink.tableSize > table.length) { + table.grow(tableBase + dylink.tableSize - table.length); + } + } + else { + tableBase = table.length; + if (dylink.tableSize > 0) { + table.grow(dylink.tableSize); + } + } + // Build the import object + const extImports = WebAssembly.Module.imports(module); + const importObj = { + env: {}, + 'GOT.mem': {}, + 'GOT.func': {}, + }; + // Provide wasi_snapshot_preview1 imports to extensions that need them. + // Layered merge: builtins → extension-provided → user overrides. + const needsWasi = extImports.some((imp) => imp.module === 'wasi_snapshot_preview1'); + if (needsWasi) { + const extWasi = (descriptor.wasi && memoryProxy) ? descriptor.wasi(memoryProxy) : undefined; + importObj['wasi_snapshot_preview1'] = { + ...wasiBuiltins, // 1. Built-in defaults (lowest priority) + ...extWasi, // 2. Extension-provided + ...wasiUserOverrides // 3. User overrides (highest priority) + }; + } + // Collect names of functions the extension exports (for self-resolution) + const extExportNames = new Set(WebAssembly.Module.exports(module) + .filter((e) => e.kind === 'function') + .map((e) => e.name)); + const unresolvedFuncs = new Set(); + for (const imp of extImports) { + if (imp.module === 'env') { + if (imp.name === 'memory' && imp.kind === 'memory') { + importObj.env.memory = memory; + } + else if (imp.name === '__indirect_function_table' && + imp.kind === 'table') { + importObj.env.__indirect_function_table = table; + } + else if (imp.name === '__memory_base' && imp.kind === 'global') { + importObj.env.__memory_base = new WebAssembly.Global({ value: 'i32', mutable: false }, memoryBase); + } + else if (imp.name === '__table_base' && imp.kind === 'global') { + importObj.env.__table_base = new WebAssembly.Global({ value: 'i32', mutable: false }, tableBase); + } + else if (imp.name === '__stack_pointer' && imp.kind === 'global') { + importObj.env.__stack_pointer = stackPointer; + } + else if (imp.kind === 'function') { + // Resolve function imports from the main module's exports + const resolved = mainExports[imp.name]; + if (resolved && typeof resolved === 'function') { + importObj.env[imp.name] = resolved; + } + else { + // Provide a trap stub for unresolved symbols (e.g., C++ runtime + // functions like wstring methods that are imported but never called). + // If actually called at runtime, this will throw. + unresolvedFuncs.add(imp.name); + importObj.env[imp.name] = () => { + throw new Error(`Extension "${descriptor.name}" called unresolved symbol: env.${imp.name}`); + }; + } + } + else if (imp.kind === 'global') { + // Other globals - try to resolve from main exports + const resolved = mainExports[imp.name]; + if (resolved instanceof WebAssembly.Global) { + importObj.env[imp.name] = resolved; + } + else { + // Create a zero-initialized mutable global as fallback + importObj.env[imp.name] = new WebAssembly.Global({ value: 'i32', mutable: true }, 0); + } + } + } + else if (imp.module === 'GOT.mem' && imp.kind === 'global') { + // GOT.mem entries are mutable globals containing memory addresses of symbols + // Try to resolve the symbol's address - for now, create mutable globals + // that can be patched by __wasm_apply_data_relocs + importObj['GOT.mem'][imp.name] = new WebAssembly.Global({ value: 'i32', mutable: true }, 0); + } + else if (imp.module === 'GOT.func' && imp.kind === 'global') { + // GOT.func entries are mutable globals containing table indices of functions. + // We resolve these by finding the function in the main module's exports, + // adding it to the indirect function table, and providing the table index. + const resolved = mainExports[imp.name]; + if (resolved && typeof resolved === 'function') { + const idx = table.length; + table.grow(1); + table.set(idx, resolved); + importObj['GOT.func'][imp.name] = new WebAssembly.Global({ value: 'i32', mutable: true }, idx); + } + else { + importObj['GOT.func'][imp.name] = new WebAssembly.Global({ value: 'i32', mutable: true }, 0); + } + } + } + // Self-resolve: in WASM shared libraries, the linker with --allow-undefined + // may leave symbols as both imports and exports (e.g., C++ weak symbols from + // libc++ string.cpp.o that satisfy references from ada.o). We use indirection + // through mutable wrapper functions: first create wrappers that initially trap, + // then instantiate, then patch the wrappers to forward to the instance's own exports. + // This way all internal calls go through the wrappers which point to the final exports. + const selfResolvable = [...unresolvedFuncs].filter((name) => extExportNames.has(name)); + // For self-resolvable symbols, create mutable wrappers + const wrappers = {}; + for (const name of selfResolvable) { + const wrapper = { target: null }; + wrappers[name] = wrapper; + // Replace the trap stub with a wrapper that forwards to the target + importObj.env[name] = (...args) => { + if (!wrapper.target) { + throw new Error(`Extension "${descriptor.name}" called unresolved symbol during init: env.${name}`); + } + return wrapper.target(...args); + }; + } + const instance = await WebAssembly.instantiate(module, importObj); + const extExports = instance.exports; + // Patch wrappers to point to the instance's own exports + for (const name of selfResolvable) { + const selfExport = extExports[name]; + if (typeof selfExport === 'function') { + wrappers[name].target = selfExport; + } + } + // Apply data relocations if the extension has them + if (typeof extExports.__wasm_apply_data_relocs === 'function') { + extExports.__wasm_apply_data_relocs(); + } + // Call constructors if present + if (typeof extExports.__wasm_call_ctors === 'function') { + extExports.__wasm_call_ctors(); + } + const initFn = descriptor.initFn ?? `qjs_ext_${descriptor.name.replace(/-/g, '_')}_init`; + const ext = { + name: descriptor.name, + module, + instance, + dylink, + memoryBase, + tableBase, + initFn, + }; + // Check for version reporting + ext.versions = extractExtensionVersions(ext, memory); + return ext; +} +/** + * Call the extension's init function, passing the JSContext and JSRuntime + * pointers from the main module. + */ +export function initExtension(ext, mainExports) { + const initFunc = ext.instance.exports[ext.initFn]; + if (typeof initFunc !== 'function') { + throw new Error(`Extension "${ext.name}" does not export init function "${ext.initFn}"`); + } + // Get the JSContext and JSRuntime pointers from the main module + const ctxPtr = mainExports.qjs_get_context_ptr(); + const rtPtr = mainExports.qjs_get_runtime_ptr(); + // Call the extension's init function: qjs_ext_xxx_init(ctx, rt) + const result = initFunc(ctxPtr, rtPtr); + if (result !== 0) { + throw new Error(`Extension "${ext.name}" init function returned error code ${result}`); + } +} +/** + * Re-instantiate extensions for snapshot restore. + * + * During restore, we need to: + * 1. Instantiate extension modules with the same memory/table bases + * 2. Let them populate the function table (via elem segments and __wasm_apply_data_relocs) + * 3. Do NOT call the init function (the state is already in the snapshot memory) + * + * This reconstructs the function table entries that extensions need, without + * modifying linear memory (which will be overwritten by the snapshot). + */ +export async function restoreExtensions(descriptors, extensionMeta, mainExports, wasiBuiltins, wasiUserOverrides, memoryProxy) { + const loaded = []; + for (const meta of extensionMeta) { + // Find the descriptor for this extension + const descriptor = descriptors.find((d) => d.name === meta.name); + if (!descriptor) { + throw new Error(`Extension "${meta.name}" required by snapshot but not provided`); + } + // Load with fixed bases + const ext = await loadExtension(descriptor, mainExports, wasiBuiltins, wasiUserOverrides, memoryProxy, { + memoryBase: meta.memoryBase, + tableBase: meta.tableBase, + }); + ext.initFn = meta.initFn; + loaded.push(ext); + } + return loaded; +} +//# sourceMappingURL=extensions.js.map \ No newline at end of file diff --git a/tests/corpus/webassembly-dylink/main.mjs b/tests/corpus/webassembly-dylink/main.mjs new file mode 100644 index 000000000..42237b1f1 --- /dev/null +++ b/tests/corpus/webassembly-dylink/main.mjs @@ -0,0 +1,101 @@ +// quickjs-wasi's WebAssembly extension loader, run through its own code: +// extensions.js is dist/extensions.js of quickjs-wasi 3.6.2, unmodified +// (MIT, LICENSE.quickjs-wasi). It parses the dylink.0 custom section +// (WebAssembly.Module.customSections), reads the extension's imports and +// exports (Module.imports, Module.exports), allocates the extension's data +// in the main module's memory, grows the shared function table and stores +// main-module functions in it (table.grow, table.set), creates +// __memory_base, __table_base and GOT globals (new WebAssembly.Global), and +// instantiates the extension against the main module's memory, table and +// stack pointer. The two modules stand in for quickjs.wasm and an +// extension .so; their text is in tests/wasm-spec/corpus/dylink-*.wat. +import { initExtension, loadExtension, parseDylink } from "./extensions.js"; + +const mainBytes = Buffer.from( + "AGFzbQEAAAABEANgAX8Bf2AAAX9gAn9/AX8DBwYAAQECAgAEBAFwAAQFAwEAAgYTA38BQYCABAt/AUGACAt/AUEACweOAQkGbWVtb3J5AgAZX19pbmRpcmVjdF9mdW5jdGlvbl90YWJsZQEAD19fc3RhY2tfcG9pbnRlcgMABm1hbGxvYwAAE3Fqc19nZXRfY29udGV4dF9wdHIAARNxanNfZ2V0X3J1bnRpbWVfcHRyAAILanNfcmVnaXN0ZXIAAwdqc19jYWxsAAQGaGVscGVyAAUKXAYXAQF/IwEhASMBIABqQQ9qQXBxJAEgAQsFAEHkAAsFAEHIAQsZAEGABCMCQQRsaiABNgIAIwJBAWokAiMCCxMAIAFBgAQgAEEEbGooAgARAAALCAAgAEHoB2oL", + "base64", +); +const extBytes = Buffer.from( + "AGFzbQEAAAABEwRgAX8Bf2ACf38Bf2AAAGAAAX8C0AEKA2VudgZtZW1vcnkCAAEDZW52GV9faW5kaXJlY3RfZnVuY3Rpb25fdGFibGUBcAAAA2Vudg9fX3N0YWNrX3BvaW50ZXIDfwEDZW52DV9fbWVtb3J5X2Jhc2UDfwADZW52DF9fdGFibGVfYmFzZQN/AAdHT1QubWVtB2NvdW50ZXIDfwEIR09ULmZ1bmMGaGVscGVyA38BA2Vudgtqc19yZWdpc3RlcgABA2Vudg5taXNzaW5nX3N5bWJvbAAAA2VudgtzZWxmX3N5bWJvbAAAAwwLAAACAgEDAAAAAwMHogEJGF9fd2FzbV9hcHBseV9kYXRhX3JlbG9jcwAFEV9fd2FzbV9jYWxsX2N0b3JzAAYRcWpzX2V4dF9kZW1vX2luaXQABxVxanNfZXh0X2RlbW9fdmVyc2lvbnMACAtzZWxmX3N5bWJvbAAJCHVzZV9zZWxmAAoLdXNlX21pc3NpbmcACw1zdGFja19wb2ludGVyAAwMYnVtcF9jb3VudGVyAA0JCAEAIwILAgMECnYLBwAgAEEDbAsJACAAIwQRAAALCQAjAUEgaiQDCwkAIwNBBTYCAAsZACAAIwIQABogACMCQQFqEAAaIAFByAFrCwQAIwELBwAgAEEBagsGACAAEAILBgAgABABCwQAIwALFAAjAyMDKAIAQQFqNgIAIwMoAgALCxwBACMBCxZuYW1lPWRlbW8KdmVyc2lvbj0xLjIAABoIZHlsaW5rLjABBDACAgACCQEHbGliYy5zbw==", + "base64", +); +// A module without a dylink.0 section (tests/wasm-spec/corpus/jsapi-own.wat). +const plainBytes = Buffer.from( + "AGFzbQEAAAABBgFgAX8BfwMCAQAEBQFwAQIIBQQBAQEEBhICfwFBBwt8AEQAAAAAAAD4PwsHKgcDbWVtAgAEbWVtMgIAA3RhYgEAAWcDAAJnMgMABWZpeGVkAwECaWQAAAkHAQBBAQsBAAoGAQQAIAAL", + "base64", +); + +const failure = (e) => (e instanceof Error ? `${e.name}: ${e.message}` : `threw ${String(e)}`); + +const { instance } = await WebAssembly.instantiate(mainBytes, {}); +const mainExports = instance.exports; +const table = mainExports.__indirect_function_table; + +console.log("dylink", JSON.stringify(parseDylink(new WebAssembly.Module(extBytes)))); +console.log("plain dylink", parseDylink(new WebAssembly.Module(plainBytes))); + +const ext = await loadExtension({ name: "demo", wasm: extBytes }, mainExports, {}, {}, null); +console.log("loaded", ext.name, ext.memoryBase, ext.tableBase, ext.initFn); +console.log("versions", JSON.stringify(ext.versions)); +console.log("dylink", JSON.stringify(ext.dylink)); +console.log("table length", table.length, table.get(6) === mainExports.helper); +// The extension's own functions in the shared table, called from here. +const triple = table.get(4); +console.log("table functions", triple(14), table.get(5) === table.get(5), triple.name, triple.length); +console.log("module", ext.module instanceof WebAssembly.Module, ext.instance instanceof WebAssembly.Instance); + +initExtension(ext, mainExports); +console.log("js_call", mainExports.js_call(0, 7), mainExports.js_call(1, 5)); +const x = ext.instance.exports; +console.log("self symbol", x.use_self(41)); +try { + x.use_missing(1); +} catch (e) { + console.log("missing symbol", failure(e)); +} +console.log("stack pointer", x.stack_pointer(), mainExports.__stack_pointer.value); +mainExports.__stack_pointer.value = 60000; +console.log("shared stack pointer", x.stack_pointer()); +console.log("counter", x.bump_counter(), x.bump_counter()); +console.log("memory", new Uint32Array(mainExports.memory.buffer, ext.memoryBase + 32, 1)[0]); + +// Snapshot restore (what restoreExtensions does per extension): the same +// bases, from a compiled Module, without init. +const restored = await loadExtension( + { name: "demo", wasm: new WebAssembly.Module(extBytes) }, + mainExports, + {}, + {}, + null, + { memoryBase: ext.memoryBase, tableBase: ext.tableBase }, +); +console.log("restored", restored.memoryBase, restored.tableBase, table.length); +console.log("restored counter", restored.instance.exports.bump_counter()); + +// A second extension instance gets its own bases. +const second = await loadExtension({ name: "demo", wasm: extBytes }, mainExports, {}, {}, null); +console.log("second", second.memoryBase, second.tableBase, table.length); +initExtension(second, mainExports); +console.log("js_call", mainExports.js_call(2, 10), mainExports.js_call(3, 10)); + +async function tryLoad(label, wasm, exports) { + try { + await loadExtension({ name: label, wasm }, exports, {}, {}, null); + console.log(label, "loaded"); + } catch (e) { + console.log(label, failure(e)); + } +} +await tryLoad("plain", plainBytes, mainExports); +await tryLoad("no memory", extBytes, { ...mainExports, memory: undefined }); +await tryLoad("no table", extBytes, { ...mainExports, __indirect_function_table: undefined }); +try { + initExtension({ ...ext, initFn: "qjs_ext_none_init" }, mainExports); +} catch (e) { + console.log("init", failure(e)); +} +try { + initExtension(ext, { ...mainExports, qjs_get_runtime_ptr: () => 201 }); +} catch (e) { + console.log("init", failure(e)); +} diff --git a/tests/corpus/webassembly-jsapi.mjs b/tests/corpus/webassembly-jsapi.mjs new file mode 100644 index 000000000..ad9739972 --- /dev/null +++ b/tests/corpus/webassembly-jsapi.mjs @@ -0,0 +1,356 @@ +// The WebAssembly JavaScript API's constructors and reflection: new +// WebAssembly.Memory, Table and Global (descriptors, defaults, conversions +// and every error), using them as imports (limits, types and mutability +// checked with Node's LinkError messages), the identity of exported +// objects, WebAssembly.Module.imports, exports and customSections, and the +// classes called without new. The modules are inline base64 literals +// (their text is in tests/wasm-spec/corpus/jsapi.wat and jsapi-own.wat). +const jsapi = Buffer.from("AGFzbQEAAAABKglgAX8Bf2ABfwBgAAF/YAF8AXxgAX4AYAABb2ABbwBgAn9/AX9gAn9/AAJbBwNlbnYDbG9nAAEDZW52Bm1lbW9yeQIBAQQDZW52BXRhYmxlAXABAggDZW52B2NvdW50ZXIDfwEDZW52BXNjYWxlA3wAA2VudgNiaWcDfgEDZW52A3JlZgNvAQMPDgAAAgMEBQYHCAACAAIBB5UBEQRidW1wAAMGc2NhbGVkAAQGYmlnQWRkAAUGZ2V0UmVmAAYGc2V0UmVmAAcFYXBwbHkACAVzdG9yZQAJBGxvYWQACgRzaXplAAsEZ3JvdwAMCXRhYmxlU2l6ZQANA3NheQAOBm1lbW9yeQIABXRhYmxlAQAHY291bnRlcgMAC2Fsc29Db3VudGVyAwAGZG91YmxlAAEJCAEAQQALAgECCnAOBwAgAEECbAsHACAAIABsCwsAIwBBAWokACMACwcAIAAjAaILCQAjAiAAfCQCCwQAIwMLBgAgACQDCwkAIAEgABEAAAsJACAAIAE6AAALBwAgAC0AAAsEAD8ACwYAIABAAAsFAPwQAAsGACAAEAALAAwIZHlsaW5rLjABAgMACgRtZXRhaGVsbG8ACwhkeWxpbmsuMAQFAAEA", "base64"); +const own = Buffer.from("AGFzbQEAAAABBgFgAX8BfwMCAQAEBQFwAQIIBQQBAQEEBhICfwFBBwt8AEQAAAAAAAD4PwsHKgcDbWVtAgAEbWVtMgIAA3RhYgEAAWcDAAJnMgMABWZpeGVkAwECaWQAAAkHAQBBAQsBAAoGAQQAIAAL", "base64"); + +const show = (v) => { + if (typeof v === "bigint") return `${v}n`; + if (typeof v === "number") return Object.is(v, -0) ? "-0" : String(v); + if (typeof v === "function") return "function"; + if (Array.isArray(v)) return `[${v.map(show).join(", ")}]`; + if (v instanceof WebAssembly.Memory) return "Memory"; + if (v instanceof WebAssembly.Table) return "Table"; + if (v instanceof WebAssembly.Global) return "Global"; + if (v instanceof ArrayBuffer) return `ArrayBuffer(${v.byteLength})`; + if (v !== null && typeof v === "object") return JSON.stringify(v); + return String(v); +}; +const attempt = (label, f) => { + try { + console.log(label, show(f())); + } catch (e) { + console.log(label, e instanceof Error ? `${e.name}: ${e.message}` : `threw ${show(e)}`); + } +}; + +// ── WebAssembly.Global ──────────────────────────────────────────────────── +console.log("Global"); +attempt("i32 default", () => new WebAssembly.Global({ value: "i32" }).value); +attempt("i64 default", () => new WebAssembly.Global({ value: "i64" }).value); +attempt("f32", () => new WebAssembly.Global({ value: "f32" }, 1.1).value); +attempt("f64", () => new WebAssembly.Global({ value: "f64" }, 1.1).value); +attempt("f64 undefined", () => new WebAssembly.Global({ value: "f64" }, undefined).value); +attempt("f64 null", () => new WebAssembly.Global({ value: "f64" }, null).value); +attempt("i32 string", () => new WebAssembly.Global({ value: "i32" }, "7").value); +attempt("i32 wraps", () => new WebAssembly.Global({ value: "i32" }, 2 ** 32 + 3).value); +attempt("i32 object", () => new WebAssembly.Global({ value: "i32" }, { valueOf: () => 9 }).value); +attempt("i64 bigint", () => new WebAssembly.Global({ value: "i64" }, 2n ** 63n).value); +attempt("i64 string", () => new WebAssembly.Global({ value: "i64" }, "12").value); +attempt("i64 number", () => new WebAssembly.Global({ value: "i64" }, 5).value); +attempt("i64 null", () => new WebAssembly.Global({ value: "i64" }, null).value); +attempt("i32 bigint", () => new WebAssembly.Global({ value: "i32" }, 5n).value); +attempt("externref default", () => new WebAssembly.Global({ value: "externref" }).value); +attempt("externref object", () => new WebAssembly.Global({ value: "externref" }, { a: 1 }).value); +attempt("anyfunc default", () => new WebAssembly.Global({ value: "anyfunc" }).value); +attempt("anyfunc arrow", () => new WebAssembly.Global({ value: "anyfunc" }, () => 1)); +attempt("anyfunc number", () => new WebAssembly.Global({ value: "anyfunc" }, 1)); +attempt("funcref name", () => new WebAssembly.Global({ value: "funcref" })); +attempt("v128", () => new WebAssembly.Global({ value: "v128" })); +attempt("bad type", () => new WebAssembly.Global({ value: "i16" })); +attempt("no value", () => new WebAssembly.Global({})); +attempt("no descriptor", () => new WebAssembly.Global()); +attempt("number descriptor", () => new WebAssembly.Global(1)); +attempt("toString type", () => new WebAssembly.Global({ value: { toString: () => "f64" } }, 2.5).value); +const order = []; +new WebAssembly.Global( + { + get value() { + order.push("value"); + return { toString: () => (order.push("value toString"), "f64") }; + }, + get mutable() { + order.push("mutable"); + return false; + }, + }, + { valueOf: () => (order.push("valueOf"), 1) }, +); +console.log("order", order.join(",")); +const immutable = new WebAssembly.Global({ value: "i32" }, 4); +attempt("immutable set", () => { + immutable.value = 5; +}); +console.log("immutable after", immutable.value, immutable.valueOf()); +const mutable = new WebAssembly.Global({ value: "i32", mutable: 1 }, 4); +mutable.value = 2.9; +console.log("mutable", mutable.value, mutable.valueOf()); +mutable.value = "x"; +console.log("mutable NaN", mutable.value); +const mutable64 = new WebAssembly.Global({ value: "i64", mutable: true }); +mutable64.value = true; +console.log("mutable64", show(mutable64.value)); +attempt("i64 set number", () => { + mutable64.value = 3; +}); +const mutableF32 = new WebAssembly.Global({ value: "f32", mutable: true }, 1); +mutableF32.value = undefined; +console.log("f32 set undefined", mutableF32.value); +const mutableRef = new WebAssembly.Global({ value: "externref", mutable: true }, "start"); +mutableRef.value = undefined; +console.log("externref set undefined", mutableRef.value); +const mutableFunc = new WebAssembly.Global({ value: "anyfunc", mutable: true }); +attempt("anyfunc set arrow", () => { + mutableFunc.value = () => 2; +}); +console.log(String(immutable), Object.prototype.toString.call(immutable), immutable instanceof WebAssembly.Global); +console.log(immutable); +// @ts-ignore: called without new, as Node reports +attempt("Global()", () => WebAssembly.Global({ value: "i32" })); + +// ── WebAssembly.Memory ──────────────────────────────────────────────────── +console.log("Memory"); +attempt("bytes", () => new WebAssembly.Memory({ initial: 1 }).buffer.byteLength); +attempt("zero", () => new WebAssembly.Memory({ initial: 0 }).buffer.byteLength); +attempt("string initial", () => new WebAssembly.Memory({ initial: "2" }).buffer.byteLength); +attempt("fractional", () => new WebAssembly.Memory({ initial: 1.9 }).buffer.byteLength); +attempt("null initial", () => new WebAssembly.Memory({ initial: null }).buffer.byteLength); +attempt("missing", () => new WebAssembly.Memory({})); +attempt("minimum only", () => new WebAssembly.Memory({ minimum: 1 })); +attempt("undefined initial", () => new WebAssembly.Memory({ initial: undefined })); +attempt("negative", () => new WebAssembly.Memory({ initial: -1 })); +attempt("negative fraction", () => new WebAssembly.Memory({ initial: -0.5 })); +attempt("NaN", () => new WebAssembly.Memory({ initial: NaN })); +attempt("Infinity", () => new WebAssembly.Memory({ initial: Infinity })); +attempt("2^32", () => new WebAssembly.Memory({ initial: 2 ** 32 })); +attempt("too many pages", () => new WebAssembly.Memory({ initial: 65537 })); +attempt("max below initial", () => new WebAssembly.Memory({ initial: 20, maximum: 1 })); +attempt("max too large", () => new WebAssembly.Memory({ initial: 1, maximum: 65537 })); +attempt("max 2^32", () => new WebAssembly.Memory({ initial: 1, maximum: 2 ** 32 })); +attempt("max null", () => new WebAssembly.Memory({ initial: 1, maximum: null })); +attempt("bigint initial", () => new WebAssembly.Memory({ initial: 1n })); +attempt("throwing initial", () => + new WebAssembly.Memory({ + initial: { + valueOf() { + throw new Error("boom"); + }, + }, + }), +); +attempt("shared without maximum", () => new WebAssembly.Memory({ initial: 1, shared: true })); +attempt("no descriptor", () => new WebAssembly.Memory()); +attempt("string descriptor", () => new WebAssembly.Memory("x")); +const memoryOrder = []; +new WebAssembly.Memory({ + get initial() { + memoryOrder.push("initial"); + return { valueOf: () => (memoryOrder.push("initial valueOf"), 1) }; + }, + get maximum() { + memoryOrder.push("maximum"); + return { valueOf: () => (memoryOrder.push("maximum valueOf"), 3) }; + }, + get shared() { + memoryOrder.push("shared"); + return false; + }, +}); +console.log("order", memoryOrder.join(",")); +const grown = new WebAssembly.Memory({ initial: 1, maximum: 2 }); +const before = grown.buffer; +new Uint8Array(before)[5] = 7; +console.log("same buffer", grown.buffer === grown.buffer); +attempt("grow", () => grown.grow(1)); +console.log("after grow", grown.buffer.byteLength, new Uint8Array(grown.buffer)[5], before === grown.buffer); +attempt("grow past maximum", () => grown.grow(1)); +attempt("grow negative", () => grown.grow(-1)); +attempt("grow missing", () => grown.grow()); +attempt("grow NaN", () => grown.grow(NaN)); +attempt("grow 2^32", () => grown.grow(2 ** 32)); +attempt("grow zero", () => grown.grow(0)); +attempt("unbounded grow", () => new WebAssembly.Memory({ initial: 1, maximum: undefined }).grow(3)); +console.log(String(grown), grown instanceof WebAssembly.Memory); +console.log(grown); +// @ts-ignore: called without new, as Node reports +attempt("Memory()", () => WebAssembly.Memory({ initial: 1 })); + +// ── WebAssembly.Table ───────────────────────────────────────────────────── +console.log("Table"); +attempt("anyfunc", () => new WebAssembly.Table({ initial: 2, element: "anyfunc" }).get(1)); +attempt("externref", () => new WebAssembly.Table({ initial: 1, element: "externref" }).get(0)); +attempt("externref init", () => new WebAssembly.Table({ initial: 2, element: "externref" }, "hi").get(1)); +attempt("externref undefined", () => new WebAssembly.Table({ initial: 2, element: "externref" }, undefined).get(1)); +attempt("anyfunc null", () => new WebAssembly.Table({ initial: 2, element: "anyfunc" }, null).get(1)); +attempt("anyfunc arrow", () => new WebAssembly.Table({ initial: 2, element: "anyfunc" }, () => 1)); +attempt("anyfunc number", () => new WebAssembly.Table({ initial: 2, element: "anyfunc" }, 5)); +attempt("funcref name", () => new WebAssembly.Table({ initial: 1, element: "funcref" })); +attempt("i32 element", () => new WebAssembly.Table({ initial: 1, element: "i32" })); +attempt("no element", () => new WebAssembly.Table({ initial: 1 })); +attempt("no initial", () => new WebAssembly.Table({ element: "anyfunc" })); +// @ts-ignore: an incomplete descriptor +attempt("empty", () => new WebAssembly.Table({})); +attempt("no descriptor", () => new WebAssembly.Table()); +attempt("max below initial", () => new WebAssembly.Table({ initial: 2, maximum: 1, element: "anyfunc" })); +attempt("bad init and descriptor", () => new WebAssembly.Table({ initial: 3, maximum: 2, element: "anyfunc" }, 37)); +attempt("too large", () => new WebAssembly.Table({ initial: 10000001, element: "anyfunc" })); +attempt("large maximum", () => new WebAssembly.Table({ initial: 1, maximum: 10000001, element: "anyfunc" }).length); +attempt("max 2^32", () => new WebAssembly.Table({ initial: 1, maximum: 2 ** 32, element: "anyfunc" })); +attempt("toString element", () => new WebAssembly.Table({ element: { toString: () => "anyfunc" }, initial: 1 }).length); +const tableOrder = []; +new WebAssembly.Table({ + get element() { + tableOrder.push("element"); + return { toString: () => (tableOrder.push("element toString"), "anyfunc") }; + }, + get initial() { + tableOrder.push("initial"); + return { valueOf: () => (tableOrder.push("initial valueOf"), 1) }; + }, + get maximum() { + tableOrder.push("maximum"); + return { valueOf: () => (tableOrder.push("maximum valueOf"), 3) }; + }, +}); +console.log("order", tableOrder.join(",")); +const refs = new WebAssembly.Table({ initial: 1, maximum: 4, element: "externref" }, 3); +attempt("set default", () => { + refs.set(0); + return refs.get(0); +}); +attempt("grow default", () => [refs.grow(1), refs.get(1), refs.length]); +attempt("grow value", () => [refs.grow(1, "z"), refs.get(2), refs.length]); +attempt("grow past maximum", () => refs.grow(2)); +attempt("get negative", () => refs.get(-1)); +attempt("get missing", () => refs.get()); +attempt("get out of range", () => refs.get(5)); +attempt("set out of range", () => refs.set(9, 1)); +const funcs = new WebAssembly.Table({ initial: 1, element: "anyfunc" }); +attempt("set arrow", () => funcs.set(0, () => 1)); +attempt("set number", () => funcs.set(0, 7)); +attempt("grow arrow", () => funcs.grow(1, () => 1)); +attempt("grow negative", () => funcs.grow(-1)); +console.log(String(funcs), funcs instanceof WebAssembly.Table); +console.log(funcs); +// @ts-ignore: called without new, as Node reports +attempt("Table()", () => WebAssembly.Table({ initial: 1, element: "anyfunc" })); + +// ── WebAssembly.Module's static functions ──────────────────────────────── +console.log("Module"); +const module = new WebAssembly.Module(jsapi); +console.log(JSON.stringify(WebAssembly.Module.imports(module))); +console.log(JSON.stringify(WebAssembly.Module.exports(module))); +const firstImport = WebAssembly.Module.imports(module)[0]; +console.log(Object.keys(firstImport).join(","), Array.isArray(WebAssembly.Module.imports(module))); +firstImport.module = "changed"; +console.log("fresh", WebAssembly.Module.imports(module)[0].module); +const dylink = WebAssembly.Module.customSections(module, "dylink.0"); +console.log( + "dylink.0", + dylink.length, + dylink.map((b) => `${show(b)} ${[...new Uint8Array(b)].join(" ")}`).join(" | "), +); +console.log("meta", new TextDecoder().decode(WebAssembly.Module.customSections(module, "meta")[0])); +console.log("empty name", WebAssembly.Module.customSections(module, "").length); +console.log("missing", WebAssembly.Module.customSections(module, "nope").length); +console.log("toString", WebAssembly.Module.customSections(module, { toString: () => "meta" }).length); +console.log("null", WebAssembly.Module.customSections(module, null).length); +const copy = WebAssembly.Module.customSections(module, "meta")[0]; +new Uint8Array(copy)[0] = 0; +console.log( + "copies", + new Uint8Array(WebAssembly.Module.customSections(module, "meta")[0])[0], + copy === WebAssembly.Module.customSections(module, "meta")[0], +); +attempt("customSections missing name", () => WebAssembly.Module.customSections(module)); +attempt("customSections symbol", () => WebAssembly.Module.customSections(module, Symbol("x"))); +attempt("customSections number", () => WebAssembly.Module.customSections(1, "x")); +attempt("imports object", () => WebAssembly.Module.imports({})); +attempt("exports missing", () => WebAssembly.Module.exports()); +attempt("imports stray", () => WebAssembly.Module.imports(module, 1).length); +console.log("own", JSON.stringify(WebAssembly.Module.imports(new WebAssembly.Module(own)))); +console.log("own", JSON.stringify(WebAssembly.Module.exports(new WebAssembly.Module(own)))); +// @ts-ignore: called without new, as Node reports +attempt("Module()", () => WebAssembly.Module(jsapi)); +// @ts-ignore: called without new, as Node reports +attempt("Instance()", () => WebAssembly.Instance(module, {})); + +// ── constructed objects as imports ──────────────────────────────────────── +console.log("imports"); +const logged = []; +const memory = new WebAssembly.Memory({ initial: 1, maximum: 4 }); +const table = new WebAssembly.Table({ initial: 2, maximum: 8, element: "anyfunc" }); +const counter = new WebAssembly.Global({ value: "i32", mutable: true }, 10); +const big = new WebAssembly.Global({ value: "i64", mutable: true }, 1n); +const ref = new WebAssembly.Global({ value: "externref", mutable: true }, "first"); +const env = { log: (v) => logged.push(v), memory, table, counter, scale: 2.5, big, ref }; +const instance = new WebAssembly.Instance(module, { env }); +const x = instance.exports; +console.log( + "identity", + x.memory === memory, + x.table === table, + x.counter === counter, + x.alsoCounter === counter, +); +attempt("bump", () => [x.bump(), x.bump(), counter.value]); +counter.value = 100; +attempt("bump after set", () => x.bump()); +attempt("scaled", () => x.scaled(4)); +x.bigAdd(41n); +console.log("big", show(big.value)); +attempt("getRef", () => x.getRef()); +x.setRef({ hello: "world" }); +console.log("ref", JSON.stringify(ref.value)); +ref.value = 42; +attempt("getRef after set", () => x.getRef()); +attempt("apply double", () => x.apply(0, 21)); +attempt("apply square", () => x.apply(1, 12)); +console.log("table get", table.get(0) === x.double); +// A function the module does not export, reached through the table. +const square = table.get(1); +console.log("unexported", square(5), square === table.get(1), square.name, square.length, typeof square); +x.store(3, 99); +console.log("memory", new Uint8Array(memory.buffer)[3], x.load(3)); +new Uint8Array(memory.buffer)[4] = 55; +console.log("memory from js", x.load(4)); +attempt("grow in wasm", () => [x.grow(1), x.size(), memory.buffer.byteLength]); +attempt("grow from js", () => [memory.grow(1), x.size(), memory.buffer.byteLength]); +attempt("table grow", () => [table.grow(2), x.tableSize(), table.length]); +const global = new WebAssembly.Global({ value: "anyfunc", mutable: true }, x.double); +console.log("funcref global", global.value === x.double); +global.value = null; +console.log("funcref null", global.value); +table.set(1, x.double); +attempt("apply after set", () => x.apply(1, 8)); +x.say(5); +console.log("logged", logged.join(",")); + +// Another instance's exports as imports, and the same objects again. +const provider = new WebAssembly.Instance(new WebAssembly.Module(own)).exports; +console.log("own identity", provider.mem === provider.mem2, provider.g === provider.g2, provider.mem instanceof WebAssembly.Memory); +const second = new WebAssembly.Instance(module, { + env: { ...env, memory: provider.mem, table: provider.tab, counter: provider.g }, +}).exports; +console.log("re-exported", second.memory === provider.mem, second.table === provider.tab, second.counter === provider.g); +attempt("bump provider global", () => [second.bump(), provider.g.value]); +attempt("apply provider table", () => second.apply(1, 3)); + +// ── link errors ─────────────────────────────────────────────────────────── +console.log("link errors"); +const link = (label, overrides) => + attempt(label, () => new WebAssembly.Instance(module, { env: { ...env, ...overrides } }) && "linked"); +link("memory too small", { memory: new WebAssembly.Memory({ initial: 0, maximum: 4 }) }); +link("memory without maximum", { memory: new WebAssembly.Memory({ initial: 1 }) }); +link("memory maximum too large", { memory: new WebAssembly.Memory({ initial: 1, maximum: 5 }) }); +link("memory larger initial", { memory: new WebAssembly.Memory({ initial: 3, maximum: 4 }) }); +link("memory number", { memory: 5 }); +link("table too small", { table: new WebAssembly.Table({ initial: 1, maximum: 8, element: "anyfunc" }) }); +link("table without maximum", { table: new WebAssembly.Table({ initial: 2, element: "anyfunc" }) }); +link("table maximum too large", { table: new WebAssembly.Table({ initial: 2, maximum: 12, element: "anyfunc" }) }); +link("table externref", { table: new WebAssembly.Table({ initial: 2, maximum: 8, element: "externref" }) }); +link("table small externref", { table: new WebAssembly.Table({ initial: 1, maximum: 8, element: "externref" }) }); +link("table number", { table: 3 }); +link("global immutable", { counter: new WebAssembly.Global({ value: "i32" }, 1) }); +link("global type", { counter: new WebAssembly.Global({ value: "i64", mutable: true }, 1n) }); +link("global both", { counter: new WebAssembly.Global({ value: "f64" }, 1) }); +link("global number", { counter: 1 }); +link("immutable global object", { scale: new WebAssembly.Global({ value: "f64" }, 4) }); +link("immutable global mutable", { scale: new WebAssembly.Global({ value: "f64", mutable: true }, 4) }); +link("immutable global bigint", { scale: 4n }); +link("i64 number", { big: 4 }); +link("externref anything", { ref: new WebAssembly.Global({ value: "externref", mutable: true }, null) }); diff --git a/tests/corpus/webassembly-spec-imports/data.mjs b/tests/corpus/webassembly-spec-imports/data.mjs new file mode 100644 index 000000000..c082547b8 --- /dev/null +++ b/tests/corpus/webassembly-spec-imports/data.mjs @@ -0,0 +1,137 @@ +/* Generated by tests/wasm-spec/gen.mjs from the WebAssembly 2.0 core test + * suite (WebAssembly/spec wg-2.0, Apache-2.0) with wabt 1.0.37 wast2json: + * imports.wast, table.wast, names.wast. Do not edit. */ +export const modules = [ + "AGFzbQEAAAABHgdgAABgAX8AYAF9AGAAAX9gAAF9YAF/AX9gAX4BfgMIBwABAgMEBQYECAJwAApwAQoUBQMBAAIGFAN/AEE3C30AQwAAMEILfgFCwgALB60BDQRmdW5jAAAIZnVuYy1pMzIAAQhmdW5jLWYzMgACCWZ1bmMtPmkzMgADCWZ1bmMtPmYzMgAEDWZ1bmMtaTMyLT5pMzIABQ1mdW5jLWk2NC0+aTY0AAYKZ2xvYmFsLWkzMgMACmdsb2JhbC1mMzIDAQ5nbG9iYWwtbXV0LWk2NAMCDHRhYmxlLTEwLWluZgEAC3RhYmxlLTEwLTIwAQEMbWVtb3J5LTItaW5mAgAKIQcCAAsCAAsCAAsEAEEWCwcAQwAAMEELBAAgAAsEACAACw==", + "AGFzbQEAAAABJAhgAX8AYAF+AGABfQBgAXwAYAF/AGACf30AYAJ8fABgAX4BfgKDAxIIc3BlY3Rlc3QJcHJpbnRfaTMyAAAIc3BlY3Rlc3QJcHJpbnRfaTY0AAEIc3BlY3Rlc3QJcHJpbnRfaTMyAAAIc3BlY3Rlc3QJcHJpbnRfaTY0AAEIc3BlY3Rlc3QJcHJpbnRfZjMyAAIIc3BlY3Rlc3QJcHJpbnRfZjY0AAMIc3BlY3Rlc3QNcHJpbnRfaTMyX2YzMgAFCHNwZWN0ZXN0DXByaW50X2Y2NF9mNjQABghzcGVjdGVzdAlwcmludF9pMzIAAAhzcGVjdGVzdAlwcmludF9mNjQAAwR0ZXN0DWZ1bmMtaTY0LT5pNjQABwhzcGVjdGVzdAlwcmludF9pMzIAAAhzcGVjdGVzdAlwcmludF9pMzIAAAhzcGVjdGVzdAlwcmludF9pMzIAAAhzcGVjdGVzdAlwcmludF9pMzIAAAhzcGVjdGVzdAlwcmludF9pMzIAAAhzcGVjdGVzdAlwcmludF9pMzIABAhzcGVjdGVzdAlwcmludF9pMzIABAMDAgABBAUBcAECAgczCAJwMQALAnAyAAwCcDMADQJwNAANAnA1AA4CcDYADwdwcmludDMyABIHcHJpbnQ2NAATCQgBAEEACwICBQpoAiwBAX0gALIhASAAEAAgAEEBakMAAChCEAYgABACIAAQCCABEAQgAEEAEQAACzkBAXwgABAKuSEBIAAQASABRAAAAAAAAPA/oEQAAAAAAIBKQBAHIAAQAyABEAUgARAJIAFBAREDAAs=", + "AGFzbQEAAAABBQFgAX8AAhYBCHNwZWN0ZXN0CXByaW50X2kzMgAAAwIBAAcNAQlwcmludF9pMzIAAQoIAQYAIAAQAAs=", + "AGFzbQEAAAABCwJgAX8AYAJ/fwF/AhYBCHNwZWN0ZXN0CXByaW50X2kzMgAAAwIBAQcNAQlwcmludF9pMzIAAQoJAQcAIAAgAWoL", + "AGFzbQEAAAABBAFgAAACDQEEdGVzdARmdW5jAAA=", + "AGFzbQEAAAABBQFgAX8AAhEBBHRlc3QIZnVuYy1pMzIAAA==", + "AGFzbQEAAAABBQFgAX0AAhEBBHRlc3QIZnVuYy1mMzIAAA==", + "AGFzbQEAAAABBQFgAAF/AhIBBHRlc3QJZnVuYy0+aTMyAAA=", + "AGFzbQEAAAABBQFgAAF9AhIBBHRlc3QJZnVuYy0+ZjMyAAA=", + "AGFzbQEAAAABBgFgAX8BfwIWAQR0ZXN0DWZ1bmMtaTMyLT5pMzIAAA==", + "AGFzbQEAAAABBgFgAX4BfgIWAQR0ZXN0DWZ1bmMtaTY0LT5pNjQAAA==", + "AGFzbQEAAAABBAFgAAACEAEEdGVzdAd1bmtub3duAAA=", + "AGFzbQEAAAABBAFgAAACFAEIc3BlY3Rlc3QHdW5rbm93bgAA", + "AGFzbQEAAAABBQFgAX8AAg0BBHRlc3QEZnVuYwAA", + "AGFzbQEAAAABBQFgAAF/Ag0BBHRlc3QEZnVuYwAA", + "AGFzbQEAAAABBgFgAX8BfwINAQR0ZXN0BGZ1bmMAAA==", + "AGFzbQEAAAABBAFgAAACEQEEdGVzdAhmdW5jLWkzMgAA", + "AGFzbQEAAAABBQFgAAF/AhEBBHRlc3QIZnVuYy1pMzIAAA==", + "AGFzbQEAAAABBQFgAX0AAhEBBHRlc3QIZnVuYy1pMzIAAA==", + "AGFzbQEAAAABBQFgAX4AAhEBBHRlc3QIZnVuYy1pMzIAAA==", + "AGFzbQEAAAABBgFgAX8BfwIRAQR0ZXN0CGZ1bmMtaTMyAAA=", + "AGFzbQEAAAABBAFgAAACEgEEdGVzdAlmdW5jLT5pMzIAAA==", + "AGFzbQEAAAABBQFgAX8AAhIBBHRlc3QJZnVuYy0+aTMyAAA=", + "AGFzbQEAAAABBQFgAAF9AhIBBHRlc3QJZnVuYy0+aTMyAAA=", + "AGFzbQEAAAABBQFgAAF+AhIBBHRlc3QJZnVuYy0+aTMyAAA=", + "AGFzbQEAAAABBgFgAX8BfwISAQR0ZXN0CWZ1bmMtPmkzMgAA", + "AGFzbQEAAAABBAFgAAACFgEEdGVzdA1mdW5jLWkzMi0+aTMyAAA=", + "AGFzbQEAAAABBQFgAX8AAhYBBHRlc3QNZnVuYy1pMzItPmkzMgAA", + "AGFzbQEAAAABBQFgAAF/AhYBBHRlc3QNZnVuYy1pMzItPmkzMgAA", + "AGFzbQEAAAABBQFgAAF/AhMBBHRlc3QKZ2xvYmFsLWkzMgAA", + "AGFzbQEAAAABBAFgAAACFQEEdGVzdAx0YWJsZS0xMC1pbmYAAA==", + "AGFzbQEAAAABBAFgAAACFQEEdGVzdAxtZW1vcnktMi1pbmYAAA==", + "AGFzbQEAAAABBAFgAAACFwEIc3BlY3Rlc3QKZ2xvYmFsX2kzMgAA", + "AGFzbQEAAAABBAFgAAACEgEIc3BlY3Rlc3QFdGFibGUAAA==", + "AGFzbQEAAAABBAFgAAACEwEIc3BlY3Rlc3QGbWVtb3J5AAA=", + "AGFzbQEAAAABEQRgAAF/YAABfmAAAX1gAAF8AqIBBwhzcGVjdGVzdApnbG9iYWxfaTMyA38ACHNwZWN0ZXN0Cmdsb2JhbF9pMzIDfwAIc3BlY3Rlc3QKZ2xvYmFsX2kzMgN/AAhzcGVjdGVzdApnbG9iYWxfaTMyA38ACHNwZWN0ZXN0Cmdsb2JhbF9pNjQDfgAIc3BlY3Rlc3QKZ2xvYmFsX2YzMgN9AAhzcGVjdGVzdApnbG9iYWxfZjY0A3wAAwgHAAAAAAECAwc5BwVnZXQtMAAABWdldC0xAAEFZ2V0LXgAAgVnZXQteQADBWdldC00AAQFZ2V0LTUABQVnZXQtNgAGCiQHBAAjAAsEACMBCwQAIwILBAAjAwsEACMECwQAIwULBAAjBgs=", + "AGFzbQEAAAACFAEEdGVzdApnbG9iYWwtaTMyA38A", + "AGFzbQEAAAACFAEEdGVzdApnbG9iYWwtZjMyA30A", + "AGFzbQEAAAACGAEEdGVzdA5nbG9iYWwtbXV0LWk2NAN+AQ==", + "AGFzbQEAAAACEQEEdGVzdAd1bmtub3duA38A", + "AGFzbQEAAAACFQEIc3BlY3Rlc3QHdW5rbm93bgN/AA==", + "AGFzbQEAAAACFAEEdGVzdApnbG9iYWwtaTMyA34A", + "AGFzbQEAAAACFAEEdGVzdApnbG9iYWwtaTMyA30A", + "AGFzbQEAAAACFAEEdGVzdApnbG9iYWwtaTMyA3wA", + "AGFzbQEAAAACFAEEdGVzdApnbG9iYWwtaTMyA38B", + "AGFzbQEAAAACFAEEdGVzdApnbG9iYWwtZjMyA38A", + "AGFzbQEAAAACFAEEdGVzdApnbG9iYWwtZjMyA34A", + "AGFzbQEAAAACFAEEdGVzdApnbG9iYWwtZjMyA3wA", + "AGFzbQEAAAACFAEEdGVzdApnbG9iYWwtZjMyA30B", + "AGFzbQEAAAACGAEEdGVzdA5nbG9iYWwtbXV0LWk2NAN/AQ==", + "AGFzbQEAAAACGAEEdGVzdA5nbG9iYWwtbXV0LWk2NAN9AQ==", + "AGFzbQEAAAACGAEEdGVzdA5nbG9iYWwtbXV0LWk2NAN8AQ==", + "AGFzbQEAAAACGAEEdGVzdA5nbG9iYWwtbXV0LWk2NAN+AA==", + "AGFzbQEAAAACDgEEdGVzdARmdW5jA38A", + "AGFzbQEAAAACFgEEdGVzdAx0YWJsZS0xMC1pbmYDfwA=", + "AGFzbQEAAAACFgEEdGVzdAxtZW1vcnktMi1pbmYDfwA=", + "AGFzbQEAAAACFwEIc3BlY3Rlc3QJcHJpbnRfaTMyA38A", + "AGFzbQEAAAACEwEIc3BlY3Rlc3QFdGFibGUDfwA=", + "AGFzbQEAAAACFAEIc3BlY3Rlc3QGbWVtb3J5A38A", + "AGFzbQEAAAABCgJgAAF/YAF/AX8CFQEIc3BlY3Rlc3QFdGFibGUBcAEKFAMEAwEAAAcIAQRjYWxsAAAJCAEAQQELAgECChMDBwAgABEAAAsEAEELCwQAQRYL", + "AGFzbQEAAAACJwIIc3BlY3Rlc3QFdGFibGUBcAAACHNwZWN0ZXN0BXRhYmxlAXAAAAQHAnAACnAACg==", + "AGFzbQEAAAACFwEEdGVzdAx0YWJsZS0xMC1pbmYBcAAK", + "AGFzbQEAAAACFwEEdGVzdAx0YWJsZS0xMC1pbmYBcAAF", + "AGFzbQEAAAACFwEEdGVzdAx0YWJsZS0xMC1pbmYBcAAA", + "AGFzbQEAAAACFgEEdGVzdAt0YWJsZS0xMC0yMAFwAAo=", + "AGFzbQEAAAACFgEEdGVzdAt0YWJsZS0xMC0yMAFwAAU=", + "AGFzbQEAAAACFgEEdGVzdAt0YWJsZS0xMC0yMAFwAAA=", + "AGFzbQEAAAACFwEEdGVzdAt0YWJsZS0xMC0yMAFwAQoU", + "AGFzbQEAAAACFwEEdGVzdAt0YWJsZS0xMC0yMAFwAQUU", + "AGFzbQEAAAACFwEEdGVzdAt0YWJsZS0xMC0yMAFwAQAU", + "AGFzbQEAAAACFwEEdGVzdAt0YWJsZS0xMC0yMAFwAQoZ", + "AGFzbQEAAAACFwEEdGVzdAt0YWJsZS0xMC0yMAFwAQUZ", + "AGFzbQEAAAACFwEEdGVzdAt0YWJsZS0xMC0yMAFwAQAZ", + "AGFzbQEAAAACFAEIc3BlY3Rlc3QFdGFibGUBcAAK", + "AGFzbQEAAAACFAEIc3BlY3Rlc3QFdGFibGUBcAAF", + "AGFzbQEAAAACFAEIc3BlY3Rlc3QFdGFibGUBcAAA", + "AGFzbQEAAAACFQEIc3BlY3Rlc3QFdGFibGUBcAEKFA==", + "AGFzbQEAAAACFQEIc3BlY3Rlc3QFdGFibGUBcAEFFA==", + "AGFzbQEAAAACFQEIc3BlY3Rlc3QFdGFibGUBcAEAFA==", + "AGFzbQEAAAACFQEIc3BlY3Rlc3QFdGFibGUBcAEKGQ==", + "AGFzbQEAAAACFQEIc3BlY3Rlc3QFdGFibGUBcAEFGQ==", + "AGFzbQEAAAACEgEEdGVzdAd1bmtub3duAXAACg==", + "AGFzbQEAAAACFgEIc3BlY3Rlc3QHdW5rbm93bgFwAAo=", + "AGFzbQEAAAACFwEEdGVzdAx0YWJsZS0xMC1pbmYBcAAM", + "AGFzbQEAAAACGAEEdGVzdAx0YWJsZS0xMC1pbmYBcAEKFA==", + "AGFzbQEAAAACFwEEdGVzdAt0YWJsZS0xMC0yMAFwAQwU", + "AGFzbQEAAAACFwEEdGVzdAt0YWJsZS0xMC0yMAFwAQoS", + "AGFzbQEAAAACFAEIc3BlY3Rlc3QFdGFibGUBcAAM", + "AGFzbQEAAAACFQEIc3BlY3Rlc3QFdGFibGUBcAEKDw==", + "AGFzbQEAAAACDwEEdGVzdARmdW5jAXAACg==", + "AGFzbQEAAAACFQEEdGVzdApnbG9iYWwtaTMyAXAACg==", + "AGFzbQEAAAACFwEEdGVzdAxtZW1vcnktMi1pbmYBcAAK", + "AGFzbQEAAAACGAEIc3BlY3Rlc3QJcHJpbnRfaTMyAXAACg==", + "AGFzbQEAAAABBgFgAX8BfwIVAQhzcGVjdGVzdAZtZW1vcnkCAQECAwIBAAcIAQRsb2FkAAAKCQEHACAAKAIACwsHAQBBCgsBEA==", + "AGFzbQEAAAACFgEEdGVzdAxtZW1vcnktMi1pbmYCAAI=", + "AGFzbQEAAAACFgEEdGVzdAxtZW1vcnktMi1pbmYCAAE=", + "AGFzbQEAAAACFgEEdGVzdAxtZW1vcnktMi1pbmYCAAA=", + "AGFzbQEAAAACFAEIc3BlY3Rlc3QGbWVtb3J5AgAB", + "AGFzbQEAAAACFAEIc3BlY3Rlc3QGbWVtb3J5AgAA", + "AGFzbQEAAAACFQEIc3BlY3Rlc3QGbWVtb3J5AgEBAg==", + "AGFzbQEAAAACFQEIc3BlY3Rlc3QGbWVtb3J5AgEAAg==", + "AGFzbQEAAAACFQEIc3BlY3Rlc3QGbWVtb3J5AgEBAw==", + "AGFzbQEAAAACFQEIc3BlY3Rlc3QGbWVtb3J5AgEAAw==", + "AGFzbQEAAAACEQEEdGVzdAd1bmtub3duAgAB", + "AGFzbQEAAAACFQEIc3BlY3Rlc3QHdW5rbm93bgIAAQ==", + "AGFzbQEAAAACFgEEdGVzdAxtZW1vcnktMi1pbmYCAAM=", + "AGFzbQEAAAACFwEEdGVzdAxtZW1vcnktMi1pbmYCAQID", + "AGFzbQEAAAACFAEIc3BlY3Rlc3QGbWVtb3J5AgAC", + "AGFzbQEAAAACFQEIc3BlY3Rlc3QGbWVtb3J5AgEBAQ==", + "AGFzbQEAAAACEgEEdGVzdAhmdW5jLWkzMgIAAQ==", + "AGFzbQEAAAACFAEEdGVzdApnbG9iYWwtaTMyAgAB", + "AGFzbQEAAAACFgEEdGVzdAx0YWJsZS0xMC1pbmYCAAE=", + "AGFzbQEAAAACFwEIc3BlY3Rlc3QJcHJpbnRfaTMyAgAB", + "AGFzbQEAAAACGAEIc3BlY3Rlc3QKZ2xvYmFsX2kzMgIAAQ==", + "AGFzbQEAAAACEwEIc3BlY3Rlc3QFdGFibGUCAAE=", + "AGFzbQEAAAABBgFgAX8BfwIVAQhzcGVjdGVzdAZtZW1vcnkCAQADAwIBAAcIAQRncm93AAAKCAEGACAAQAAL", + "AGFzbQEAAAA=", + "AGFzbQEAAAABKQpgAABgAX8AYAJ/fwBgAX4AYAF9AGABfABgAAF/YAABfmAAAX1gAAF8AugCEAhub3Qgd2FzbQpvdmVybG9hZGVkAAAIbm90IHdhc20Kb3ZlcmxvYWRlZAABCG5vdCB3YXNtCm92ZXJsb2FkZWQAAghub3Qgd2FzbQpvdmVybG9hZGVkAAMIbm90IHdhc20Kb3ZlcmxvYWRlZAAECG5vdCB3YXNtCm92ZXJsb2FkZWQABQhub3Qgd2FzbQpvdmVybG9hZGVkAAYIbm90IHdhc20Kb3ZlcmxvYWRlZAAHCG5vdCB3YXNtCm92ZXJsb2FkZWQACAhub3Qgd2FzbQpvdmVybG9hZGVkAAkIbm90IHdhc20Kb3ZlcmxvYWRlZAN/AAhub3Qgd2FzbQpvdmVybG9hZGVkA34ACG5vdCB3YXNtCm92ZXJsb2FkZWQDfQAIbm90IHdhc20Kb3ZlcmxvYWRlZAN8AAhub3Qgd2FzbQpvdmVybG9hZGVkAXAAAAhub3Qgd2FzbQpvdmVybG9hZGVkAgAA", + "AGFzbQEAAAAEBAFwAAA=", + "AGFzbQEAAAAEBAFwAAE=", + "AGFzbQEAAAAEBQFwAQAA", + "AGFzbQEAAAAEBQFwAQAB", + "AGFzbQEAAAAEBgFwAQGAAg==", + "AGFzbQEAAAAEBwFwAQCAgAQ=", + "AGFzbQEAAAAECQFwAQD/////Dw==", + "AGFzbQEAAAAEBwJwAABwAAA=", + "AGFzbQEAAAACFAEIc3BlY3Rlc3QFdGFibGUBcAAABAQBcAAA", + "AGFzbQEAAAABBQFgAAF/AwIBAAcHAQNmb28AAAoGAQQAQQAL", + "AGFzbQEAAAABBQFgAAF/AwIBAAcHAQNmb28AAAoGAQQAQQEL", + "AGFzbQEAAAABBQFgAAF/A+ED3wMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAerId8DAAAAATAAAQItMAACAV8AAwEkAAQBQAAFIX4hQCMkJV4mKigpXytgLT17fXxbXVw6IjsnPD4/LC4vIAAGA05hTgAHCEluZmluaXR5AAgCaWYACQZtYWxsb2MACgdfbWFsbG9jAAsIX19tYWxsb2MADAFhAA0BQQAOA++7vwAPAsOFABADQcyKABED4oSrABID76yDABMEZu+sgQAUA2ZmaQAVEAABAgMEBQYHCAkKCwwNDg8AFhAQERITFBUWFxgZGhscHR4fABcCIH8AGCDCgMKBwoLCg8KEwoXChsKHwojCicKKwovCjMKNwo7CjwAZIMKQwpHCksKTwpTClcKWwpfCmMKZwprCm8Kcwp3CnsKfABoY77+w77+x77+y77+z77+077+177+277+3ABsY77+477+577+677+777+877+977++77+/ABww4pCA4pCB4pCC4pCD4pCE4pCF4pCG4pCH4pCI4pCJ4pCK4pCL4pCM4pCN4pCO4pCPAB0w4pCQ4pCR4pCS4pCT4pCU4pCV4pCW4pCX4pCY4pCZ4pCa4pCb4pCc4pCd4pCe4pCfAB4G4pCg4pChAB8q77+w77+x77+y77+z77+077+177+277+377+477+577+677+777+877+9ACAD4oCNACED4oCMACICzY8AIwPigaAAJAPitb8AJQTwkYG/ACYD4aCOACcW77+v4oCLwqDCreKBoOGagOKAruKArQAoJ+KAjuKAj+KAkeKAqOKAqeKAquKAq+KArOKAr+KBpuKBp+KBqOKBqQApEuKBquKBq+KBrOKBreKBruKBrwAqDOKBoeKBouKBo+KBpAArDPCQgIDzn7+/9I+/vwAsgQJazI/NhsyGzZvNjMy0zZjNns2HzKvMpcyqzZPNiM2UzY7Ml8yezLrMr8yxzJ7MmcyxzJzMlsygYc2XzajMjsyEzIbNl8y/zaHNn82AzLbNgcylzLDMs8ytzZnMssyxzLnMnc2OzLxszYTNisyazZfNps2EzavMh82BzLbMt82JzKnMucyrzJ3Nls2FzJnMssy8zYfNms2NzK7NjsylzYXMnmfNg8yQzIXNrsyUzJDMjsyCzI/Mvs2KzI3Ni82KzafMgcyGzabNnsy2zZXNlM2azKlvzYvMlM2QzarNqcyhzY/MosynzYHMq8yZzKTMrs2WzZnNk8y6zJzMqcy8zJjMoAAtDOGFn+GFoOOFpO++oAAuA++4gAAvA++4hAAwBPOghIAAMQTzoIevADICzIgAMwEKADQD4pCkADUD4oCoADYBDQA3Ag0KADgCCg0AOQEeADoBCwA7AQwAPALChQA9A+KAqQA+A+KApgA/A+KPjgBAAsKLAEECwowAQgLCjQBDA+KGtQBEA+KGqQBFA+KMpABGA+KktgBHA+KGsgBIA+KuqABJA+KusABKA++/vQBLA++3kABMA++3kQBNA++3kgBOA++3kwBPA++3lABQA++3lQBRA++3lgBSA++3lwBTA++3mABUA++3mQBVA++3mgBWA++3mwBXA++3nABYA++3nQBZA++3ngBaA++3nwBbA++3oABcA++3oQBdA++3ogBeA++3owBfA++3pABgA++3pQBhA++3pgBiA++3pwBjA++3qABkA++3qQBlA++3qgBmA++3qwBnA++3rABoA++3rQBpA++3rgBqA++3rwBrA++/vgBsA++/vwBtBPCfv74AbgTwn7+/AG8E8K+/vgBwBPCvv78AcQTwv7++AHIE8L+/vwBzBPGPv74AdATxj7+/AHUE8Z+/vgB2BPGfv78AdwTxr7++AHgE8a+/vwB5BPG/v74AegTxv7+/AHsE8o+/vgB8BPKPv78AfQTyn7++AH4E8p+/vwB/BPKvv74AgAEE8q+/vwCBAQTyv7++AIIBBPK/v78AgwEE84+/vgCEAQTzj7+/AIUBBPOfv74AhgEE85+/vwCHAQTzr7++AIgBBPOvv78AiQEE87+/vgCKAQTzv7+/AIsBBPSPv74AjAEE9I+/vwCNAQnMiOKAvcyIzIkAjgEDYWJjAI8BBuKArWFiYwCQAQbigK5jYmEAkQEJ4oCtYWJj4oCuAJIBCeKArmNiYeKArQCTAQTwnZGoAJQBBPCdkLQAlQEE8J2YiACWAQTwnZi8AJcBBPCdkIAAmAEE8J2TkACZAQTwnZWsAJoBBPCdl5QAmwEE8J2SnACcAQTwnZSEAJ0BBPCdlLgAngEE8J2WoACfAQTwnZmwAKABA+G0gAChAQPhtKwAogED4pK2AKMBA++8oQCkAQTwn4SQAKUBBPCfhLAApgEE86CBgQCnAQZVKzAwNDEAqAEEQeKAiwCpAQLQkACqAQPqmZYAqwED4re8AKwBA+K3tgCtAQPisa8ArgEE8J+FkACvAQTwn4WwALABA+KwrQCxAQTwkJCCALIBBPCQkIgAswEE8JCSsAC0AQLDgAC1AQLDgQC2AQLDggC3AQLDgwC4AQLDhAC5AQLEgAC6AQLEggC7AQLEhAC8AQLHjQC9AQLHngC+AQLHoAC/AQLHugDAAQLIgADBAQLIggDCAQLIpgDDAQLIugDEAQLTkADFAQLTkgDGAQLfigDHAQPgoKEAyAED4KCiAMkBA+CgowDKAQPgoKQAywED4KClAMwBA+CkhADNAQPgpIUAzgED4KWyAM8BA+CmhQDQAQPgqIUA0QED4KqFANIBA+CshQDTAQPgroUA1AED4LCFANUBA+CyhQDWAQPgtIUA1wED4LiwANgBA+C6sADZAQPgvIEA2gED4L2oANsBA+C+uADcAQPhgKEA3QED4YCiAN4BA+GCnADfAQPhhaEA4AED4YqgAOEBA+GLkADiAQPhjqAA4wED4ZCKAOQBA+GWswDlAQPhmqgA5gED4ZqqAOcBA+GbhgDoAQPhnIAA6QED4ZygAOoBA+GdgADrAQPhnaAA7AED4aCgAO0BA+GihwDuAQPhpKAA7wED4aWjAPABA+GolQDxAQPhqYsA8gED4amhAPMBA+GugwD0AQPhr4AA9QED4a+BAPYBA+GwowD3AQPhuIAA+AED4bqgAPkBA+G6ogD6AQPhuqQA+wED4bqmAPwBA+G6qAD9AQPhuqoA/gED4bqsAP8BA+G6rgCAAgPhurAAgQID4bqyAIICA+G6tACDAgPhurYAhAID44GCAIUCA+OCogCGAgPjhJoAhwID44WPAIgCA+OIjgCJAgPjiI8AigID44iQAIsCA+OIkQCMAgPjiJIAjQID44iTAI4CA+OIlACPAgPjiJUAkAID44iWAJECA+OIlwCSAgPjiJgAkwID44iZAJQCA+OImgCVAgPjiJsAlgID44muAJcCA+OJrwCYAgPjibAAmQID44mxAJoCA+OJsgCbAgPjibMAnAID44m0AJ0CA+OJtQCeAgPjibYAnwID44m3AKACA+OJuAChAgPjibkAogID44m6AKMCA+OJuwCkAgPji5AApQID6oCKAKYCA+qTrgCnAgPqlYkAqAID6pqgAKkCA+qggACqAgPqoKMAqwID6qGdAKwCA+qiggCtAgPqo6oArgID6qSiAK8CA+qlhgCwAgPqpoQAsQID6qiAALICA++9sQCzAgPvv4IAtAIE8JCAgAC1AgTwkIqAALYCBPCQiqAAtwIE8JCMgAC4AgTwkI6gALkCBPCQkpYAugIE8JCUgAC7AgTwkJ2AALwCBPCQoIAAvQIE8JCkoAC+AgTwkKaAAL8CBPCQpqAAwAIE8JCogADBAgTwkKyAAMICBPCQsIAAwwIE8JCwgQDEAgTwkLKAAMUCBPCRgIUAxgIE8JGCgwDHAgTwkYSnAMgCBPCRhZAAyQIE8JGGgwDKAgTwkYiAAMsCBPCRioAAzAIE8JGKsADNAgTwkYyFAM4CBPCRjbAAzwIE8JGQgADQAgTwkZKBANECBPCRloAA0gIE8JGYgADTAgTwkZqAANQCBPCRnJIA1QIE8JGcoADWAgTwkaKhANcCBPCRq5UA2AIE8JGwgADZAgTwkbKPANoCBPCRsq8A2wIE8JKAgADcAgTwlqeVAN0CBPCWqYYA3gIE8JarpwDfAgTwlr2UAOACBPCbsYEA4QIE8JuxpADiAgTwnqCjAOMCBPCfh6YA5AID4rGtAOUCAs6bAOYCA+KxsADnAgLCqgDoAgPiiIAA6QID4oKzAOoCBPCQpIAA6wID4rKAAOwCBPCQjLAA7QICzoYA7gICzpEA7wID4byIAPACA+G8iQDxAgPhvIoA8gID4byLAPMCA+G8jAD0AgPhvI0A9QID4byOAPYCA+G8jwD3AgPhvogA+AID4b6JAPkCA+G+igD6AgPhvosA+wID4b6MAPwCA+G+jQD9AgPhvo4A/gID4b6PAP8CA+G+uACAAwPhvrkAgQMD4b66AIIDA+G+uwCDAwPhvrwAhAME8J2aqACFAwTwnZuiAIYDBPCdnJwAhwME8J2dlgCIAwTwnZ6QAIkDA+KNtgCKAwPijboAiwMD4qmcAIwDA+GXhQCNAwPhjqoAjgOcASnLusu88JSXj/CdhbTwnYW28J2FuPCdhbrigb7igo7inaninavin6/vtL/vuLbvuZrvvInvvaDzoICp4p2z4p214p+n4p+p4p+r4p+t4qaI4qaK4qaW4rij4ril77iY77i477i677i877i+77mA77mC77mE77mI77mc77me77y9772d772j86CBnfOggb3Cu+KAmeKAneKAuuKdrwCPA5wBKMu5y7vwlJeO8J2Fs/CdhbXwnYW38J2FueKBveKCjeKdqOKdquKfru+0vu+4te+5me+8iO+9n/OggKjinbLinbTin6bin6jin6rin6zipofiponippXiuKLiuKTvuJfvuLfvuLnvuLvvuL3vuL/vuYHvuYPvuYfvuZvvuZ3vvLvvvZvvvaLzoIGb86CBu8Kr4oCY4oCc4oC54p2uAJADCPCdqovwnaqkAJEDBPCdqosAkgMCwr0AkwMFMeKBhDIAlAMDMS8yAJUDA+CtswCWAwPgtbQAlwMD4rO9AJgDA+qgsQCZAwTwkIWBAJoDBPCQhbUAmwME8JCFtgCcAwTwkKa9AJ0DBPCQubsAngMD77yCAJ8DAX8AoAMBCAChAwPijKsAogMD4oymAKMDA+KQiACkAwPikKEApQMD4be7AKYDAQ8ApwMD4oaQAKgDA+KMpwCpAwPijZIAqgMD4o2UAKsDA+KNogCsAwPijasArQMBGgCuAwPikKYArwMD4pCaALADA++/vACxAwE/ALIDAsK/ALMDA+GlhQC0AwLNvgC1AwLVngC2AwLYnwC3AwPhjacAuAMD4oGHALkDA+KNsAC6AwPinZMAuwMD4p2UALwDA+KzugC9AwPis7sAvgMD4riuAL8DA+OJhADAAwPqmI8AwQMD6pu3AMIDA++4lgDDAwPvuZYAxAMD77yfAMUDBPCRhYMAxgME8J6lnwDHAwTzoIC/AMgDBPCWoYQAyQMD4q+RAMoDAsK2AMsDA+KBiwDMAwLcgADNAwPhg7sAzgMD4Y2oAM8DA+OAtwDQAwPinaEA0QMD4riPANIDA+K4kADTAwPiuJEA1AMD4riOANUDARQA1gMD4piZANcDA+K4vwDYAwPjgIcA2QMD4LmbANoDA+qZrgDbAwLPkwDcAwLPlADdAwPhupsA3gMK/BXfAwQAQQALBABBAQsEAEECCwQAQQMLBABBBAsEAEEFCwQAQQYLBABBBwsEAEEICwQAQQkLBABBCgsEAEELCwQAQQwLBABBDQsEAEEOCwQAQQ8LBABBEAsEAEERCwQAQRILBABBEwsEAEEUCwQAQRULBABBFgsEAEEXCwQAQRgLBABBGQsEAEEaCwQAQRsLBABBHAsEAEEdCwQAQR4LBABBHwsEAEEgCwQAQSELBABBIgsEAEEjCwQAQSQLBABBJQsEAEEmCwQAQScLBABBKAsEAEEpCwQAQSoLBABBKwsEAEEsCwQAQS0LBABBLgsEAEEvCwQAQTALBABBMQsEAEEyCwQAQTMLBABBNAsEAEE1CwQAQTYLBABBNwsEAEE4CwQAQTkLBABBOgsEAEE7CwQAQTwLBABBPQsEAEE+CwQAQT8LBQBBwAALBQBBwQALBQBBwgALBQBBwwALBQBBxAALBQBBxQALBQBBxgALBQBBxwALBQBByAALBQBByQALBQBBygALBQBBywALBQBBzAALBQBBzQALBQBBzgALBQBBzwALBQBB0AALBQBB0QALBQBB0gALBQBB0wALBQBB1AALBQBB1QALBQBB1gALBQBB1wALBQBB2AALBQBB2QALBQBB2gALBQBB2wALBQBB3AALBQBB3QALBQBB3gALBQBB3wALBQBB4AALBQBB4QALBQBB4gALBQBB4wALBQBB5AALBQBB5QALBQBB5gALBQBB5wALBQBB6AALBQBB6QALBQBB6gALBQBB6wALBQBB7AALBQBB7QALBQBB7gALBQBB7wALBQBB8AALBQBB8QALBQBB8gALBQBB8wALBQBB9AALBQBB9QALBQBB9gALBQBB9wALBQBB+AALBQBB+QALBQBB+gALBQBB+wALBQBB/AALBQBB/QALBQBB/gALBQBB/wALBQBBgAELBQBBgQELBQBBggELBQBBgwELBQBBhAELBQBBhQELBQBBhgELBQBBhwELBQBBiAELBQBBiQELBQBBigELBQBBiwELBQBBjAELBQBBjQELBQBBjgELBQBBjwELBQBBkAELBQBBkQELBQBBkgELBQBBkwELBQBBlAELBQBBlQELBQBBlgELBQBBlwELBQBBmAELBQBBmQELBQBBmgELBQBBmwELBQBBnAELBQBBnQELBQBBngELBQBBnwELBQBBoAELBQBBoQELBQBBogELBQBBowELBQBBpAELBQBBpQELBQBBpgELBQBBpwELBQBBqAELBQBBqQELBQBBqgELBQBBqwELBQBBrAELBQBBrQELBQBBrgELBQBBrwELBQBBsAELBQBBsQELBQBBsgELBQBBswELBQBBtAELBQBBtQELBQBBtgELBQBBtwELBQBBuAELBQBBuQELBQBBugELBQBBuwELBQBBvAELBQBBvQELBQBBvgELBQBBvwELBQBBwAELBQBBwQELBQBBwgELBQBBwwELBQBBxAELBQBBxQELBQBBxgELBQBBxwELBQBByAELBQBByQELBQBBygELBQBBywELBQBBzAELBQBBzQELBQBBzgELBQBBzwELBQBB0AELBQBB0QELBQBB0gELBQBB0wELBQBB1AELBQBB1QELBQBB1gELBQBB1wELBQBB2AELBQBB2QELBQBB2gELBQBB2wELBQBB3AELBQBB3QELBQBB3gELBQBB3wELBQBB4AELBQBB4QELBQBB4gELBQBB4wELBQBB5AELBQBB5QELBQBB5gELBQBB5wELBQBB6AELBQBB6QELBQBB6gELBQBB6wELBQBB7AELBQBB7QELBQBB7gELBQBB7wELBQBB8AELBQBB8QELBQBB8gELBQBB8wELBQBB9AELBQBB9QELBQBB9gELBQBB9wELBQBB+AELBQBB+QELBQBB+gELBQBB+wELBQBB/AELBQBB/QELBQBB/gELBQBB/wELBQBBgAILBQBBgQILBQBBggILBQBBgwILBQBBhAILBQBBhQILBQBBhgILBQBBhwILBQBBiAILBQBBiQILBQBBigILBQBBiwILBQBBjAILBQBBjQILBQBBjgILBQBBjwILBQBBkAILBQBBkQILBQBBkgILBQBBkwILBQBBlAILBQBBlQILBQBBlgILBQBBlwILBQBBmAILBQBBmQILBQBBmgILBQBBmwILBQBBnAILBQBBnQILBQBBngILBQBBnwILBQBBoAILBQBBoQILBQBBogILBQBBowILBQBBpAILBQBBpQILBQBBpgILBQBBpwILBQBBqAILBQBBqQILBQBBqgILBQBBqwILBQBBrAILBQBBrQILBQBBrgILBQBBrwILBQBBsAILBQBBsQILBQBBsgILBQBBswILBQBBtAILBQBBtQILBQBBtgILBQBBtwILBQBBuAILBQBBuQILBQBBugILBQBBuwILBQBBvAILBQBBvQILBQBBvgILBQBBvwILBQBBwAILBQBBwQILBQBBwgILBQBBwwILBQBBxAILBQBBxQILBQBBxgILBQBBxwILBQBByAILBQBByQILBQBBygILBQBBywILBQBBzAILBQBBzQILBQBBzgILBQBBzwILBQBB0AILBQBB0QILBQBB0gILBQBB0wILBQBB1AILBQBB1QILBQBB1gILBQBB1wILBQBB2AILBQBB2QILBQBB2gILBQBB2wILBQBB3AILBQBB3QILBQBB3gILBQBB3wILBQBB4AILBQBB4QILBQBB4gILBQBB4wILBQBB5AILBQBB5QILBQBB5gILBQBB5wILBQBB6AILBQBB6QILBQBB6gILBQBB6wILBQBB7AILBQBB7QILBQBB7gILBQBB7wILBQBB8AILBQBB8QILBQBB8gILBQBB8wILBQBB9AILBQBB9QILBQBB9gILBQBB9wILBQBB+AILBQBB+QILBQBB+gILBQBB+wILBQBB/AILBQBB/QILBQBB/gILBQBB/wILBQBBgAMLBQBBgQMLBQBBggMLBQBBgwMLBQBBhAMLBQBBhQMLBQBBhgMLBQBBhwMLBQBBiAMLBQBBiQMLBQBBigMLBQBBiwMLBQBBjAMLBQBBjQMLBQBBjgMLBQBBjwMLBQBBkAMLBQBBkQMLBQBBkgMLBQBBkwMLBQBBlAMLBQBBlQMLBQBBlgMLBQBBlwMLBQBBmAMLBQBBmQMLBQBBmgMLBQBBmwMLBQBBnAMLBQBBnQMLBQBBngMLBQBBnwMLBQBBoAMLBQBBoQMLBQBBogMLBQBBowMLBQBBpAMLBQBBpQMLBQBBpgMLBQBBpwMLBQBBqAMLBQBBqQMLBQBBqgMLBQBBqwMLBQBBrAMLBQBBrQMLBQBBrgMLBQBBrwMLBQBBsAMLBQBBsQMLBQBBsgMLBQBBswMLBQBBtAMLBQBBtQMLBQBBtgMLBQBBtwMLBQBBuAMLBQBBuQMLBQBBugMLBQBBuwMLBQBBvAMLBQBBvQMLBQBBvgMLBQBBvwMLBQBBwAMLBQBBwQMLBQBBwgMLBQBBwwMLBQBBxAMLBQBBxQMLBQBBxgMLBQBBxwMLBQBByAMLBQBByQMLBQBBygMLBQBBywMLBQBBzAMLBQBBzQMLBQBBzgMLBQBBzwMLBQBB0AMLBQBB0QMLBQBB0gMLBQBB0wMLBQBB1AMLBQBB1QMLBQBB1gMLBQBB1wMLBQBB2AMLBQBB2QMLBQBB2gMLBQBB2wMLBQBB3AMLBQBB3QMLBQBB3gML", + "AGFzbQEAAAABCgJgAX8AYAJ/fwACKwIIc3BlY3Rlc3QJcHJpbnRfaTMyAAAIc3BlY3Rlc3QJcHJpbnRfaTMyAAADAgEBBwsBB3ByaW50MzIAAgoMAQoAIAAQACABEAEL", +]; +export const script = "[[\"file\",\"imports.wast\"],[\"module\",3,0],[\"register\",\"test\"],[\"module\",26,1],[\"invoke\",85,0,\"print32\",[\"i32:13\"]],[\"invoke\",86,0,\"print64\",[\"i64:24\"]],[\"module\",97,2],[\"invoke\",104,0,\"print_i32\",[\"i32:13\"]],[\"module\",107,3],[\"invoke\",114,0,\"print_i32\",[\"i32:5\",\"i32:11\"]],[\"module\",116,4],[\"module\",117,5],[\"module\",118,6],[\"module\",119,7],[\"module\",120,8],[\"module\",121,9],[\"module\",122,10],[\"instantiate\",125,11],[\"instantiate\",129,12],[\"instantiate\",134,13],[\"instantiate\",138,14],[\"instantiate\",142,15],[\"instantiate\",146,16],[\"instantiate\",150,17],[\"instantiate\",154,18],[\"instantiate\",158,19],[\"instantiate\",162,20],[\"instantiate\",166,21],[\"instantiate\",170,22],[\"instantiate\",174,23],[\"instantiate\",178,24],[\"instantiate\",182,25],[\"instantiate\",186,26],[\"instantiate\",190,27],[\"instantiate\",194,28],[\"instantiate\",199,29],[\"instantiate\",203,30],[\"instantiate\",207,31],[\"instantiate\",211,32],[\"instantiate\",215,33],[\"instantiate\",219,34],[\"module\",226,35],[\"invoke\",246,0,\"get-0\",[]],[\"invoke\",247,0,\"get-1\",[]],[\"invoke\",248,0,\"get-x\",[]],[\"invoke\",249,0,\"get-y\",[]],[\"invoke\",250,0,\"get-4\",[]],[\"invoke\",251,0,\"get-5\",[]],[\"invoke\",252,0,\"get-6\",[]],[\"module\",254,36],[\"module\",255,37],[\"module\",256,38],[\"instantiate\",259,39],[\"instantiate\",263,40],[\"instantiate\",268,41],[\"instantiate\",272,42],[\"instantiate\",276,43],[\"instantiate\",280,44],[\"instantiate\",284,45],[\"instantiate\",288,46],[\"instantiate\",292,47],[\"instantiate\",296,48],[\"instantiate\",300,49],[\"instantiate\",304,50],[\"instantiate\",308,51],[\"instantiate\",312,52],[\"instantiate\",317,53],[\"instantiate\",321,54],[\"instantiate\",325,55],[\"instantiate\",329,56],[\"instantiate\",333,57],[\"instantiate\",337,58],[\"module\",344,59],[\"invoke\",356,0,\"call\",[\"i32:0\"]],[\"invoke\",357,0,\"call\",[\"i32:1\"]],[\"invoke\",358,0,\"call\",[\"i32:2\"]],[\"invoke\",359,0,\"call\",[\"i32:3\"]],[\"invoke\",360,0,\"call\",[\"i32:100\"]],[\"module\",363,59],[\"invoke\",375,0,\"call\",[\"i32:0\"]],[\"invoke\",376,0,\"call\",[\"i32:1\"]],[\"invoke\",377,0,\"call\",[\"i32:2\"]],[\"invoke\",378,0,\"call\",[\"i32:3\"]],[\"invoke\",379,0,\"call\",[\"i32:100\"]],[\"module\",381,60],[\"module\",388,61],[\"module\",389,62],[\"module\",390,63],[\"module\",391,64],[\"module\",392,65],[\"module\",393,66],[\"module\",394,67],[\"module\",395,68],[\"module\",396,69],[\"module\",397,70],[\"module\",398,71],[\"module\",399,72],[\"module\",400,73],[\"module\",401,74],[\"module\",402,75],[\"module\",403,76],[\"module\",404,77],[\"module\",405,78],[\"module\",406,79],[\"module\",407,80],[\"instantiate\",410,81],[\"instantiate\",414,82],[\"instantiate\",419,83],[\"instantiate\",423,84],[\"instantiate\",427,85],[\"instantiate\",431,86],[\"instantiate\",435,87],[\"instantiate\",439,88],[\"instantiate\",444,89],[\"instantiate\",448,90],[\"instantiate\",452,91],[\"instantiate\",456,92],[\"module\",464,93],[\"invoke\",471,0,\"load\",[\"i32:0\"]],[\"invoke\",472,0,\"load\",[\"i32:10\"]],[\"invoke\",473,0,\"load\",[\"i32:8\"]],[\"invoke\",474,0,\"load\",[\"i32:1000000\"]],[\"module\",476,93],[\"invoke\",482,0,\"load\",[\"i32:0\"]],[\"invoke\",483,0,\"load\",[\"i32:10\"]],[\"invoke\",484,0,\"load\",[\"i32:8\"]],[\"invoke\",485,0,\"load\",[\"i32:1000000\"]],[\"module\",500,94],[\"module\",501,95],[\"module\",502,96],[\"module\",503,97],[\"module\",504,98],[\"module\",505,99],[\"module\",506,100],[\"module\",507,101],[\"module\",508,102],[\"instantiate\",511,103],[\"instantiate\",515,104],[\"instantiate\",520,105],[\"instantiate\",524,106],[\"instantiate\",528,107],[\"instantiate\",532,108],[\"instantiate\",537,109],[\"instantiate\",541,110],[\"instantiate\",545,111],[\"instantiate\",549,112],[\"instantiate\",553,113],[\"instantiate\",557,114],[\"instantiate\",562,107],[\"instantiate\",566,108],[\"module\",570,115],[\"invoke\",574,0,\"grow\",[\"i32:0\"]],[\"invoke\",575,0,\"grow\",[\"i32:1\"]],[\"invoke\",576,0,\"grow\",[\"i32:0\"]],[\"invoke\",577,0,\"grow\",[\"i32:1\"]],[\"invoke\",578,0,\"grow\",[\"i32:0\"]],[\"module\",654,116],[\"register\",\"not wasm\"],[\"instantiate\",657,117],[\"file\",\"table.wast\"],[\"module\",3,118],[\"module\",4,119],[\"module\",5,120],[\"module\",6,121],[\"module\",7,122],[\"module\",8,123],[\"module\",9,124],[\"module\",11,125],[\"module\",12,126],[\"file\",\"names.wast\"],[\"module\",4,127],[\"invoke\",8,0,\"foo\",[]],[\"module\",12,128],[\"module\",19,129],[\"invoke\",615,0,\"\",[]],[\"invoke\",616,0,\"0\",[]],[\"invoke\",617,0,\"-0\",[]],[\"invoke\",618,0,\"_\",[]],[\"invoke\",620,0,\"@\",[]],[\"invoke\",621,0,\"~!@#$%^&*()_+`-={}|[]\\\\:\\\";'<>?,./ \",[]],[\"invoke\",622,0,\"NaN\",[]],[\"invoke\",623,0,\"Infinity\",[]],[\"invoke\",624,0,\"if\",[]],[\"invoke\",626,0,\"_malloc\",[]],[\"invoke\",627,0,\"__malloc\",[]],[\"invoke\",628,0,\"a\",[]],[\"invoke\",629,0,\"A\",[]],[\"invoke\",630,0,\"\",[]],[\"invoke\",632,0,\"Å\",[]],[\"invoke\",633,0,\"Å\",[]],[\"invoke\",634,0,\"ffi\",[]],[\"invoke\",635,0,\"ffi\",[]],[\"invoke\",636,0,\"ffi\",[]],[\"invoke\",638,0,\"\\u0010\\u0011\\u0012\\u0013\\u0014\\u0015\\u0016\\u0017\\u0018\\u0019\\u001a\\u001b\\u001c\\u001d\\u001e\\u001f\",[]],[\"invoke\",639,0,\" \",[]],[\"invoke\",640,0,\"€‚ƒ„…†‡ˆ‰Š‹ŒŽ\",[]],[\"invoke\",641,0,\"‘’“”•–—˜™š›œžŸ\",[]],[\"invoke\",642,0,\"￰￱￲￳￴￵￶￷\",[]],[\"invoke\",644,0,\"␀␁␂␃␄␅␆␇␈␉␊␋␌␍␎␏\",[]],[\"invoke\",645,0,\"␐␑␒␓␔␕␖␗␘␙␚␛␜␝␞␟\",[]],[\"invoke\",646,0,\"␠␡\",[]],[\"invoke\",647,0,\"￰￱￲￳￴￵￶￷￸�\",[]],[\"invoke\",648,0,\"‍\",[]],[\"invoke\",650,0,\"͏\",[]],[\"invoke\",651,0,\"⁠\",[]],[\"invoke\",652,0,\"⵿\",[]],[\"invoke\",653,0,\"𑁿\",[]],[\"invoke\",654,0,\"᠎\",[]],[\"invoke\",656,0,\"‎‏‑

‪‫‬ ⁦⁧⁨⁩\",[]],[\"invoke\",657,0,\"\",[]],[\"invoke\",658,0,\"⁡⁢⁣⁤\",[]],[\"invoke\",659,0,\"𐀀󟿿􏿿\",[]],[\"invoke\",660,0,\"Z̴͇̫̥̪͓͈͔͎̗̞̺̯̱̞̙̱̜̖̠̏͆̆͛͌͘͞ḁ̶̰̳̭͙̲̱̹̝͎̼͗ͨ̎̄̆͗̿̀́͟͡l̶̷͉̩̹̫̝͖̙̲̼͇͚͍̮͎̥̞̈́͊͗ͦ̈́ͫ̇́̚ͅͅg̶͕͔͚̩̓̐̅ͮ̔̐̎̂̏̾͊̍͋͊ͧ́̆ͦ͞o̡͋̔͐ͪͩ͏̢̧̫̙̤̮͖͙͓̺̜̩̼̘̠́\",[]],[\"invoke\",662,0,\"︀\",[]],[\"invoke\",663,0,\"︄\",[]],[\"invoke\",664,0,\"󠄀\",[]],[\"invoke\",665,0,\"󠇯\",[]],[\"invoke\",667,0,\"\\n\",[]],[\"invoke\",668,0,\"␤\",[]],[\"invoke\",669,0,\"
\",[]],[\"invoke\",670,0,\"\\r\",[]],[\"invoke\",671,0,\"\\r\\n\",[]],[\"invoke\",673,0,\"\\u001e\",[]],[\"invoke\",674,0,\"\\u000b\",[]],[\"invoke\",675,0,\"\\f\",[]],[\"invoke\",676,0,\"…\",[]],[\"invoke\",677,0,\"
\",[]],[\"invoke\",679,0,\"⏎\",[]],[\"invoke\",680,0,\"‹\",[]],[\"invoke\",681,0,\"Œ\",[]],[\"invoke\",682,0,\"\",[]],[\"invoke\",683,0,\"↵\",[]],[\"invoke\",685,0,\"⌤\",[]],[\"invoke\",686,0,\"⤶\",[]],[\"invoke\",687,0,\"↲\",[]],[\"invoke\",688,0,\"⮨\",[]],[\"invoke\",689,0,\"⮰\",[]],[\"invoke\",691,0,\"﷐\",[]],[\"invoke\",692,0,\"﷑\",[]],[\"invoke\",693,0,\"﷒\",[]],[\"invoke\",694,0,\"﷓\",[]],[\"invoke\",695,0,\"﷔\",[]],[\"invoke\",697,0,\"﷖\",[]],[\"invoke\",698,0,\"﷗\",[]],[\"invoke\",699,0,\"﷘\",[]],[\"invoke\",700,0,\"﷙\",[]],[\"invoke\",701,0,\"﷚\",[]],[\"invoke\",703,0,\"﷜\",[]],[\"invoke\",704,0,\"﷝\",[]],[\"invoke\",705,0,\"﷞\",[]],[\"invoke\",706,0,\"﷟\",[]],[\"invoke\",707,0,\"﷠\",[]],[\"invoke\",709,0,\"﷢\",[]],[\"invoke\",710,0,\"﷣\",[]],[\"invoke\",711,0,\"﷤\",[]],[\"invoke\",712,0,\"﷥\",[]],[\"invoke\",714,0,\"﷧\",[]],[\"invoke\",715,0,\"﷨\",[]],[\"invoke\",716,0,\"﷩\",[]],[\"invoke\",717,0,\"﷪\",[]],[\"invoke\",718,0,\"﷫\",[]],[\"invoke\",720,0,\"﷭\",[]],[\"invoke\",721,0,\"﷮\",[]],[\"invoke\",722,0,\"﷯\",[]],[\"invoke\",723,0,\"￾\",[]],[\"invoke\",724,0,\"￿\",[]],[\"invoke\",726,0,\"🿿\",[]],[\"invoke\",727,0,\"𯿾\",[]],[\"invoke\",728,0,\"𯿿\",[]],[\"invoke\",729,0,\"𿿾\",[]],[\"invoke\",730,0,\"𿿿\",[]],[\"invoke\",732,0,\"񏿿\",[]],[\"invoke\",733,0,\"񟿾\",[]],[\"invoke\",734,0,\"񟿿\",[]],[\"invoke\",735,0,\"񯿾\",[]],[\"invoke\",736,0,\"񯿿\",[]],[\"invoke\",738,0,\"񿿿\",[]],[\"invoke\",739,0,\"򏿾\",[]],[\"invoke\",740,0,\"򏿿\",[]],[\"invoke\",741,0,\"򟿾\",[]],[\"invoke\",742,0,\"򟿿\",[]],[\"invoke\",744,0,\"򯿿\",[]],[\"invoke\",745,0,\"򿿾\",[]],[\"invoke\",746,0,\"򿿿\",[]],[\"invoke\",747,0,\"󏿾\",[]],[\"invoke\",748,0,\"󏿿\",[]],[\"invoke\",750,0,\"󟿿\",[]],[\"invoke\",751,0,\"󯿾\",[]],[\"invoke\",752,0,\"󯿿\",[]],[\"invoke\",753,0,\"󿿾\",[]],[\"invoke\",754,0,\"󿿿\",[]],[\"invoke\",756,0,\"􏿿\",[]],[\"invoke\",757,0,\"̈‽̈̉\",[]],[\"invoke\",758,0,\"abc\",[]],[\"invoke\",759,0,\"‭abc\",[]],[\"invoke\",761,0,\"‭abc‮\",[]],[\"invoke\",762,0,\"‮cba‭\",[]],[\"invoke\",763,0,\"𝑨\",[]],[\"invoke\",764,0,\"𝐴\",[]],[\"invoke\",765,0,\"𝘈\",[]],[\"invoke\",767,0,\"𝐀\",[]],[\"invoke\",768,0,\"𝓐\",[]],[\"invoke\",769,0,\"𝕬\",[]],[\"invoke\",770,0,\"𝗔\",[]],[\"invoke\",771,0,\"𝒜\",[]],[\"invoke\",773,0,\"𝔸\",[]],[\"invoke\",774,0,\"𝖠\",[]],[\"invoke\",775,0,\"𝙰\",[]],[\"invoke\",776,0,\"ᴀ\",[]],[\"invoke\",777,0,\"ᴬ\",[]],[\"invoke\",779,0,\"A\",[]],[\"invoke\",780,0,\"🄐\",[]],[\"invoke\",781,0,\"🄰\",[]],[\"invoke\",782,0,\"󠁁\",[]],[\"invoke\",783,0,\"U+0041\",[]],[\"invoke\",785,0,\"А\",[]],[\"invoke\",786,0,\"Ꙗ\",[]],[\"invoke\",787,0,\"ⷼ\",[]],[\"invoke\",788,0,\"ⷶ\",[]],[\"invoke\",789,0,\"Ɐ\",[]],[\"invoke\",791,0,\"🅰\",[]],[\"invoke\",792,0,\"Ⱝ\",[]],[\"invoke\",793,0,\"𐐂\",[]],[\"invoke\",794,0,\"𐐈\",[]],[\"invoke\",795,0,\"𐒰\",[]],[\"invoke\",797,0,\"Á\",[]],[\"invoke\",798,0,\"Â\",[]],[\"invoke\",799,0,\"Ã\",[]],[\"invoke\",800,0,\"Ä\",[]],[\"invoke\",801,0,\"Ā\",[]],[\"invoke\",803,0,\"Ą\",[]],[\"invoke\",804,0,\"Ǎ\",[]],[\"invoke\",805,0,\"Ǟ\",[]],[\"invoke\",806,0,\"Ǡ\",[]],[\"invoke\",808,0,\"Ȁ\",[]],[\"invoke\",809,0,\"Ȃ\",[]],[\"invoke\",810,0,\"Ȧ\",[]],[\"invoke\",811,0,\"Ⱥ\",[]],[\"invoke\",812,0,\"Ӑ\",[]],[\"invoke\",814,0,\"ߊ\",[]],[\"invoke\",815,0,\"ࠡ\",[]],[\"invoke\",816,0,\"ࠢ\",[]],[\"invoke\",817,0,\"ࠣ\",[]],[\"invoke\",818,0,\"ࠤ\",[]],[\"invoke\",820,0,\"ऄ\",[]],[\"invoke\",821,0,\"अ\",[]],[\"invoke\",822,0,\"ॲ\",[]],[\"invoke\",823,0,\"অ\",[]],[\"invoke\",824,0,\"ਅ\",[]],[\"invoke\",826,0,\"ଅ\",[]],[\"invoke\",827,0,\"அ\",[]],[\"invoke\",828,0,\"అ\",[]],[\"invoke\",829,0,\"ಅ\",[]],[\"invoke\",830,0,\"അ\",[]],[\"invoke\",832,0,\"ະ\",[]],[\"invoke\",833,0,\"༁\",[]],[\"invoke\",834,0,\"ཨ\",[]],[\"invoke\",835,0,\"ྸ\",[]],[\"invoke\",836,0,\"အ\",[]],[\"invoke\",838,0,\"ႜ\",[]],[\"invoke\",839,0,\"ᅡ\",[]],[\"invoke\",840,0,\"አ\",[]],[\"invoke\",841,0,\"ዐ\",[]],[\"invoke\",842,0,\"Ꭰ\",[]],[\"invoke\",844,0,\"ᖳ\",[]],[\"invoke\",845,0,\"ᚨ\",[]],[\"invoke\",846,0,\"ᚪ\",[]],[\"invoke\",847,0,\"ᛆ\",[]],[\"invoke\",848,0,\"ᜀ\",[]],[\"invoke\",850,0,\"ᝀ\",[]],[\"invoke\",851,0,\"ᝠ\",[]],[\"invoke\",852,0,\"ᠠ\",[]],[\"invoke\",853,0,\"ᢇ\",[]],[\"invoke\",855,0,\"ᥣ\",[]],[\"invoke\",856,0,\"ᨕ\",[]],[\"invoke\",857,0,\"ᩋ\",[]],[\"invoke\",858,0,\"ᩡ\",[]],[\"invoke\",859,0,\"ᮃ\",[]],[\"invoke\",861,0,\"ᯁ\",[]],[\"invoke\",862,0,\"ᰣ\",[]],[\"invoke\",863,0,\"Ḁ\",[]],[\"invoke\",864,0,\"Ạ\",[]],[\"invoke\",865,0,\"Ả\",[]],[\"invoke\",867,0,\"Ầ\",[]],[\"invoke\",868,0,\"Ẩ\",[]],[\"invoke\",869,0,\"Ẫ\",[]],[\"invoke\",870,0,\"Ậ\",[]],[\"invoke\",871,0,\"Ắ\",[]],[\"invoke\",873,0,\"Ẳ\",[]],[\"invoke\",874,0,\"Ẵ\",[]],[\"invoke\",875,0,\"Ặ\",[]],[\"invoke\",876,0,\"あ\",[]],[\"invoke\",877,0,\"ア\",[]],[\"invoke\",879,0,\"ㅏ\",[]],[\"invoke\",880,0,\"㈎\",[]],[\"invoke\",881,0,\"㈏\",[]],[\"invoke\",882,0,\"㈐\",[]],[\"invoke\",883,0,\"㈑\",[]],[\"invoke\",885,0,\"㈓\",[]],[\"invoke\",886,0,\"㈔\",[]],[\"invoke\",887,0,\"㈕\",[]],[\"invoke\",888,0,\"㈖\",[]],[\"invoke\",889,0,\"㈗\",[]],[\"invoke\",891,0,\"㈙\",[]],[\"invoke\",892,0,\"㈚\",[]],[\"invoke\",893,0,\"㈛\",[]],[\"invoke\",894,0,\"㉮\",[]],[\"invoke\",895,0,\"㉯\",[]],[\"invoke\",897,0,\"㉱\",[]],[\"invoke\",898,0,\"㉲\",[]],[\"invoke\",899,0,\"㉳\",[]],[\"invoke\",900,0,\"㉴\",[]],[\"invoke\",901,0,\"㉵\",[]],[\"invoke\",903,0,\"㉷\",[]],[\"invoke\",904,0,\"㉸\",[]],[\"invoke\",905,0,\"㉹\",[]],[\"invoke\",906,0,\"㉺\",[]],[\"invoke\",908,0,\"㋐\",[]],[\"invoke\",909,0,\"ꀊ\",[]],[\"invoke\",910,0,\"ꓮ\",[]],[\"invoke\",911,0,\"ꕉ\",[]],[\"invoke\",912,0,\"ꚠ\",[]],[\"invoke\",914,0,\"ꠣ\",[]],[\"invoke\",915,0,\"ꡝ\",[]],[\"invoke\",916,0,\"ꢂ\",[]],[\"invoke\",917,0,\"꣪\",[]],[\"invoke\",918,0,\"ꤢ\",[]],[\"invoke\",920,0,\"ꦄ\",[]],[\"invoke\",921,0,\"ꨀ\",[]],[\"invoke\",922,0,\"ア\",[]],[\"invoke\",923,0,\"ᅡ\",[]],[\"invoke\",924,0,\"𐀀\",[]],[\"invoke\",926,0,\"𐊠\",[]],[\"invoke\",927,0,\"𐌀\",[]],[\"invoke\",928,0,\"𐎠\",[]],[\"invoke\",929,0,\"𐒖\",[]],[\"invoke\",930,0,\"𐔀\",[]],[\"invoke\",932,0,\"𐠀\",[]],[\"invoke\",933,0,\"𐤠\",[]],[\"invoke\",934,0,\"𐦀\",[]],[\"invoke\",935,0,\"𐦠\",[]],[\"invoke\",936,0,\"𐨀\",[]],[\"invoke\",938,0,\"𐰀\",[]],[\"invoke\",939,0,\"𐰁\",[]],[\"invoke\",940,0,\"𐲀\",[]],[\"invoke\",941,0,\"𑀅\",[]],[\"invoke\",942,0,\"𑂃\",[]],[\"invoke\",944,0,\"𑅐\",[]],[\"invoke\",945,0,\"𑆃\",[]],[\"invoke\",946,0,\"𑈀\",[]],[\"invoke\",947,0,\"𑊀\",[]],[\"invoke\",948,0,\"𑊰\",[]],[\"invoke\",950,0,\"𑍰\",[]],[\"invoke\",951,0,\"𑐀\",[]],[\"invoke\",952,0,\"𑒁\",[]],[\"invoke\",953,0,\"𑖀\",[]],[\"invoke\",955,0,\"𑚀\",[]],[\"invoke\",956,0,\"𑜒\",[]],[\"invoke\",957,0,\"𑜠\",[]],[\"invoke\",958,0,\"𑢡\",[]],[\"invoke\",959,0,\"𑫕\",[]],[\"invoke\",961,0,\"𑲏\",[]],[\"invoke\",962,0,\"𑲯\",[]],[\"invoke\",963,0,\"𒀀\",[]],[\"invoke\",964,0,\"𖧕\",[]],[\"invoke\",965,0,\"𖩆\",[]],[\"invoke\",967,0,\"𖽔\",[]],[\"invoke\",968,0,\"𛱁\",[]],[\"invoke\",969,0,\"𛱤\",[]],[\"invoke\",970,0,\"𞠣\",[]],[\"invoke\",971,0,\"🇦\",[]],[\"invoke\",973,0,\"Λ\",[]],[\"invoke\",974,0,\"Ɒ\",[]],[\"invoke\",975,0,\"ª\",[]],[\"invoke\",976,0,\"∀\",[]],[\"invoke\",977,0,\"₳\",[]],[\"invoke\",979,0,\"Ⲁ\",[]],[\"invoke\",980,0,\"𐌰\",[]],[\"invoke\",981,0,\"Ά\",[]],[\"invoke\",982,0,\"Α\",[]],[\"invoke\",983,0,\"Ἀ\",[]],[\"invoke\",985,0,\"Ἂ\",[]],[\"invoke\",986,0,\"Ἃ\",[]],[\"invoke\",987,0,\"Ἄ\",[]],[\"invoke\",988,0,\"Ἅ\",[]],[\"invoke\",989,0,\"Ἆ\",[]],[\"invoke\",991,0,\"ᾈ\",[]],[\"invoke\",992,0,\"ᾉ\",[]],[\"invoke\",993,0,\"ᾊ\",[]],[\"invoke\",994,0,\"ᾋ\",[]],[\"invoke\",995,0,\"ᾌ\",[]],[\"invoke\",997,0,\"ᾎ\",[]],[\"invoke\",998,0,\"ᾏ\",[]],[\"invoke\",999,0,\"Ᾰ\",[]],[\"invoke\",1000,0,\"Ᾱ\",[]],[\"invoke\",1002,0,\"Ά\",[]],[\"invoke\",1003,0,\"ᾼ\",[]],[\"invoke\",1004,0,\"𝚨\",[]],[\"invoke\",1005,0,\"𝛢\",[]],[\"invoke\",1006,0,\"𝜜\",[]],[\"invoke\",1008,0,\"𝞐\",[]],[\"invoke\",1009,0,\"⍶\",[]],[\"invoke\",1010,0,\"⍺\",[]],[\"invoke\",1011,0,\"⩜\",[]],[\"invoke\",1012,0,\"ᗅ\",[]],[\"invoke\",1014,0,\")˺˼𔗏𝅴𝅶𝅸𝅺⁾₎❩❫⟯﴿︶﹚)⦆󠀩❳❵⟧⟩⟫⟭⦈⦊⦖⸣⸥︘︸︺︼︾﹀﹂﹄﹈﹜﹞]}」󠁝󠁽»’”›❯\",[]],[\"invoke\",1015,0,\"(˹˻𔗎𝅳𝅵𝅷𝅹⁽₍❨❪⟮﴾︵﹙(⦅󠀨❲❴⟦⟨⟪⟬⦇⦉⦕⸢⸤︗︷︹︻︽︿﹁﹃﹇﹛﹝[{「󠁛󠁻«‘“‹❮\",[]],[\"invoke\",1016,0,\"𝪋𝪤\",[]],[\"invoke\",1017,0,\"𝪋\",[]],[\"invoke\",1018,0,\"½\",[]],[\"invoke\",1020,0,\"1/2\",[]],[\"invoke\",1021,0,\"୳\",[]],[\"invoke\",1022,0,\"൴\",[]],[\"invoke\",1023,0,\"⳽\",[]],[\"invoke\",1024,0,\"꠱\",[]],[\"invoke\",1026,0,\"𐅵\",[]],[\"invoke\",1027,0,\"𐅶\",[]],[\"invoke\",1028,0,\"𐦽\",[]],[\"invoke\",1029,0,\"𐹻\",[]],[\"invoke\",1030,0,\""\",[]],[\"invoke\",1032,0,\"\\b\",[]],[\"invoke\",1033,0,\"⌫\",[]],[\"invoke\",1034,0,\"⌦\",[]],[\"invoke\",1035,0,\"␈\",[]],[\"invoke\",1036,0,\"␡\",[]],[\"invoke\",1038,0,\"\\u000f\",[]],[\"invoke\",1039,0,\"←\",[]],[\"invoke\",1040,0,\"⌧\",[]],[\"invoke\",1041,0,\"⍒\",[]],[\"invoke\",1042,0,\"⍔\",[]],[\"invoke\",1044,0,\"⍫\",[]],[\"invoke\",1045,0,\"\\u001a\",[]],[\"invoke\",1046,0,\"␦\",[]],[\"invoke\",1047,0,\"␚\",[]],[\"invoke\",1049,0,\"?\",[]],[\"invoke\",1050,0,\"¿\",[]],[\"invoke\",1051,0,\"᥅\",[]],[\"invoke\",1052,0,\";\",[]],[\"invoke\",1053,0,\"՞\",[]],[\"invoke\",1055,0,\"፧\",[]],[\"invoke\",1056,0,\"⁇\",[]],[\"invoke\",1057,0,\"⍰\",[]],[\"invoke\",1058,0,\"❓\",[]],[\"invoke\",1059,0,\"❔\",[]],[\"invoke\",1061,0,\"⳻\",[]],[\"invoke\",1062,0,\"⸮\",[]],[\"invoke\",1063,0,\"㉄\",[]],[\"invoke\",1064,0,\"꘏\",[]],[\"invoke\",1065,0,\"꛷\",[]],[\"invoke\",1067,0,\"﹖\",[]],[\"invoke\",1068,0,\"?\",[]],[\"invoke\",1069,0,\"𑅃\",[]],[\"invoke\",1070,0,\"𞥟\",[]],[\"invoke\",1071,0,\"󠀿\",[]],[\"invoke\",1073,0,\"⯑\",[]],[\"invoke\",1074,0,\"¶\",[]],[\"invoke\",1075,0,\"⁋\",[]],[\"invoke\",1076,0,\"܀\",[]],[\"invoke\",1077,0,\"჻\",[]],[\"invoke\",1079,0,\"〷\",[]],[\"invoke\",1080,0,\"❡\",[]],[\"invoke\",1081,0,\"⸏\",[]],[\"invoke\",1082,0,\"⸐\",[]],[\"invoke\",1083,0,\"⸑\",[]],[\"invoke\",1085,0,\"\\u0014\",[]],[\"invoke\",1086,0,\"☙\",[]],[\"invoke\",1087,0,\"⸿\",[]],[\"invoke\",1088,0,\"〇\",[]],[\"invoke\",1089,0,\"๛\",[]],[\"invoke\",1091,0,\"ϓ\",[]],[\"invoke\",1092,0,\"ϔ\",[]],[\"invoke\",1093,0,\"ẛ\",[]],[\"module\",1095,130],[\"invoke\",1107,0,\"print32\",[\"i32:42\",\"i32:123\"]]]"; diff --git a/tests/corpus/webassembly-spec-imports/main.mjs b/tests/corpus/webassembly-spec-imports/main.mjs new file mode 100644 index 000000000..681908d64 --- /dev/null +++ b/tests/corpus/webassembly-spec-imports/main.mjs @@ -0,0 +1,5 @@ +// Executes WebAssembly spec tests (3 files); see tests/wasm-spec/runner.mjs. +import { run } from "../../wasm-spec/runner.mjs"; +import { modules, script } from "./data.mjs"; + +run(modules, script); diff --git a/tests/corpus/webassembly-spec-memory/data.mjs b/tests/corpus/webassembly-spec-memory/data.mjs index 9b2ec91d7..d62a7e491 100644 --- a/tests/corpus/webassembly-spec-memory/data.mjs +++ b/tests/corpus/webassembly-spec-memory/data.mjs @@ -139,13 +139,24 @@ export const modules = [ "AGFzbQEAAAABBAFgAAADAgEABQMBAAEMAUEKDgEMAEEAQQBBAPwIQAALC4MBQQEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQA=", "AGFzbQEAAAAFAwEAAQuzARwAQQALAABBAQsEYWJjZABBAAsAAEEACwNhYmMAQQALAABBAQsEYWJjZABBAAsAAEEACwNhYmMAQQALAABBAQsEYWJjZABBAAsAAEEACwNhYmMBAAEEYWJjZABBAAsAAEEBCwRhYmNkAEEACwAAQQALA2FiYwBBAAsAAEEBCwRhYmNkAEEACwAAQQALA2FiYwBBAAsAAEEBCwRhYmNkAEEACwAAQQALA2FiYwEAAQRhYmNk", "AGFzbQEAAAAFAwEAAQsHAQBBAAsBYQ==", + "AGFzbQEAAAACFAEIc3BlY3Rlc3QGbWVtb3J5AgABCwcBAEEACwFh", "AGFzbQEAAAAFAwEAAQsiBQBBAAsBYQBBAwsBYgBB5AALA2NkZQBBBQsBeABBAwsBYw==", + "AGFzbQEAAAACFAEIc3BlY3Rlc3QGbWVtb3J5AgABCycGAEEACwFhAEEBCwFiAEECCwNjZGUAQQMLAWYAQQILAWcAQQELAWg=", "AGFzbQEAAAACGAEIc3BlY3Rlc3QKZ2xvYmFsX2kzMgN/AAUDAQABCwcBACMACwFh", + "AGFzbQEAAAACKwIIc3BlY3Rlc3QKZ2xvYmFsX2kzMgN/AAhzcGVjdGVzdAZtZW1vcnkCAAELBwEAIwALAWE=", "AGFzbQEAAAAFAwEAAQsPAgBBAAsBYQBB//8DCwFi", + "AGFzbQEAAAACFAEIc3BlY3Rlc3QGbWVtb3J5AgABCw8CAEEACwFhAEH//wMLAWI=", "AGFzbQEAAAAFAwEAAgsJAQBB//8HCwFh", "AGFzbQEAAAAFAwEAAAsGAQBBAAsA", + "AGFzbQEAAAACFAEIc3BlY3Rlc3QGbWVtb3J5AgAACwYBAEEACwA=", "AGFzbQEAAAAFBAEBAAALBgEAQQALAA==", "AGFzbQEAAAAFAwEAAQsIAQBBgIAECwA=", + "AGFzbQEAAAACFAEIc3BlY3Rlc3QGbWVtb3J5AgAACwcBAEEACwFh", + "AGFzbQEAAAACFQEIc3BlY3Rlc3QGbWVtb3J5AgEAAwsHAQBBAAsBYQ==", + "AGFzbQEAAAACKwIIc3BlY3Rlc3QKZ2xvYmFsX2kzMgN/AAhzcGVjdGVzdAZtZW1vcnkCAAALBwEAIwALAWE=", + "AGFzbQEAAAACLAIIc3BlY3Rlc3QKZ2xvYmFsX2kzMgN/AAhzcGVjdGVzdAZtZW1vcnkCAQADCwcBACMACwFh", + "AGFzbQEAAAACFAEIc3BlY3Rlc3QGbWVtb3J5AgAACwcBAEEBCwFh", + "AGFzbQEAAAACFQEIc3BlY3Rlc3QGbWVtb3J5AgEAAwsHAQBBAQsBYQ==", "AGFzbQEAAAAFAwEAAAsHAQBBAAsBYQ==", "AGFzbQEAAAAFBAEBAAALBwEAQQALAWE=", "AGFzbQEAAAAFBAEBAAELBwEAQQALAWE=", @@ -153,9 +164,12 @@ export const modules = [ "AGFzbQEAAAAFBAEBAAELBgEAQQELAA==", "AGFzbQEAAAACGAEIc3BlY3Rlc3QKZ2xvYmFsX2kzMgN/AAUDAQAACwcBACMACwFh", "AGFzbQEAAAAFBAEBAQILCQEAQYCABAsBYQ==", + "AGFzbQEAAAACFAEIc3BlY3Rlc3QGbWVtb3J5AgABCwkBAEGAgAQLAWE=", "AGFzbQEAAAAFAwEAAgsJAQBBgIAICwFh", "AGFzbQEAAAAFBAEBAgMLCQEAQYCACAsBYQ==", "AGFzbQEAAAAFAwEAAQsHAQBBfwsBYQ==", + "AGFzbQEAAAACFAEIc3BlY3Rlc3QGbWVtb3J5AgABCwcBAEF/CwFh", "AGFzbQEAAAAFAwEAAgsIAQBBnH8LAWE=", + "AGFzbQEAAAACFAEIc3BlY3Rlc3QGbWVtb3J5AgABCwgBAEGcfwsBYQ==", ]; -export const script = "[[\"file\",\"address.wast\"],[\"module\",3,0],[\"invoke\",104,0,\"8u_good1\",[\"i32:0\"]],[\"invoke\",105,0,\"8u_good2\",[\"i32:0\"]],[\"invoke\",106,0,\"8u_good3\",[\"i32:0\"]],[\"invoke\",107,0,\"8u_good4\",[\"i32:0\"]],[\"invoke\",108,0,\"8u_good5\",[\"i32:0\"]],[\"invoke\",110,0,\"8s_good1\",[\"i32:0\"]],[\"invoke\",111,0,\"8s_good2\",[\"i32:0\"]],[\"invoke\",112,0,\"8s_good3\",[\"i32:0\"]],[\"invoke\",113,0,\"8s_good4\",[\"i32:0\"]],[\"invoke\",114,0,\"8s_good5\",[\"i32:0\"]],[\"invoke\",116,0,\"16u_good1\",[\"i32:0\"]],[\"invoke\",117,0,\"16u_good2\",[\"i32:0\"]],[\"invoke\",118,0,\"16u_good3\",[\"i32:0\"]],[\"invoke\",119,0,\"16u_good4\",[\"i32:0\"]],[\"invoke\",120,0,\"16u_good5\",[\"i32:0\"]],[\"invoke\",122,0,\"16s_good1\",[\"i32:0\"]],[\"invoke\",123,0,\"16s_good2\",[\"i32:0\"]],[\"invoke\",124,0,\"16s_good3\",[\"i32:0\"]],[\"invoke\",125,0,\"16s_good4\",[\"i32:0\"]],[\"invoke\",126,0,\"16s_good5\",[\"i32:0\"]],[\"invoke\",128,0,\"32_good1\",[\"i32:0\"]],[\"invoke\",129,0,\"32_good2\",[\"i32:0\"]],[\"invoke\",130,0,\"32_good3\",[\"i32:0\"]],[\"invoke\",131,0,\"32_good4\",[\"i32:0\"]],[\"invoke\",132,0,\"32_good5\",[\"i32:0\"]],[\"invoke\",134,0,\"8u_good1\",[\"i32:65507\"]],[\"invoke\",135,0,\"8u_good2\",[\"i32:65507\"]],[\"invoke\",136,0,\"8u_good3\",[\"i32:65507\"]],[\"invoke\",137,0,\"8u_good4\",[\"i32:65507\"]],[\"invoke\",138,0,\"8u_good5\",[\"i32:65507\"]],[\"invoke\",140,0,\"8s_good1\",[\"i32:65507\"]],[\"invoke\",141,0,\"8s_good2\",[\"i32:65507\"]],[\"invoke\",142,0,\"8s_good3\",[\"i32:65507\"]],[\"invoke\",143,0,\"8s_good4\",[\"i32:65507\"]],[\"invoke\",144,0,\"8s_good5\",[\"i32:65507\"]],[\"invoke\",146,0,\"16u_good1\",[\"i32:65507\"]],[\"invoke\",147,0,\"16u_good2\",[\"i32:65507\"]],[\"invoke\",148,0,\"16u_good3\",[\"i32:65507\"]],[\"invoke\",149,0,\"16u_good4\",[\"i32:65507\"]],[\"invoke\",150,0,\"16u_good5\",[\"i32:65507\"]],[\"invoke\",152,0,\"16s_good1\",[\"i32:65507\"]],[\"invoke\",153,0,\"16s_good2\",[\"i32:65507\"]],[\"invoke\",154,0,\"16s_good3\",[\"i32:65507\"]],[\"invoke\",155,0,\"16s_good4\",[\"i32:65507\"]],[\"invoke\",156,0,\"16s_good5\",[\"i32:65507\"]],[\"invoke\",158,0,\"32_good1\",[\"i32:65507\"]],[\"invoke\",159,0,\"32_good2\",[\"i32:65507\"]],[\"invoke\",160,0,\"32_good3\",[\"i32:65507\"]],[\"invoke\",161,0,\"32_good4\",[\"i32:65507\"]],[\"invoke\",162,0,\"32_good5\",[\"i32:65507\"]],[\"invoke\",164,0,\"8u_good1\",[\"i32:65508\"]],[\"invoke\",165,0,\"8u_good2\",[\"i32:65508\"]],[\"invoke\",166,0,\"8u_good3\",[\"i32:65508\"]],[\"invoke\",167,0,\"8u_good4\",[\"i32:65508\"]],[\"invoke\",168,0,\"8u_good5\",[\"i32:65508\"]],[\"invoke\",170,0,\"8s_good1\",[\"i32:65508\"]],[\"invoke\",171,0,\"8s_good2\",[\"i32:65508\"]],[\"invoke\",172,0,\"8s_good3\",[\"i32:65508\"]],[\"invoke\",173,0,\"8s_good4\",[\"i32:65508\"]],[\"invoke\",174,0,\"8s_good5\",[\"i32:65508\"]],[\"invoke\",176,0,\"16u_good1\",[\"i32:65508\"]],[\"invoke\",177,0,\"16u_good2\",[\"i32:65508\"]],[\"invoke\",178,0,\"16u_good3\",[\"i32:65508\"]],[\"invoke\",179,0,\"16u_good4\",[\"i32:65508\"]],[\"invoke\",180,0,\"16u_good5\",[\"i32:65508\"]],[\"invoke\",182,0,\"16s_good1\",[\"i32:65508\"]],[\"invoke\",183,0,\"16s_good2\",[\"i32:65508\"]],[\"invoke\",184,0,\"16s_good3\",[\"i32:65508\"]],[\"invoke\",185,0,\"16s_good4\",[\"i32:65508\"]],[\"invoke\",186,0,\"16s_good5\",[\"i32:65508\"]],[\"invoke\",188,0,\"32_good1\",[\"i32:65508\"]],[\"invoke\",189,0,\"32_good2\",[\"i32:65508\"]],[\"invoke\",190,0,\"32_good3\",[\"i32:65508\"]],[\"invoke\",191,0,\"32_good4\",[\"i32:65508\"]],[\"invoke\",192,0,\"32_good5\",[\"i32:65508\"]],[\"invoke\",194,0,\"8u_good3\",[\"i32:4294967295\"]],[\"invoke\",195,0,\"8s_good3\",[\"i32:4294967295\"]],[\"invoke\",196,0,\"16u_good3\",[\"i32:4294967295\"]],[\"invoke\",197,0,\"16s_good3\",[\"i32:4294967295\"]],[\"invoke\",198,0,\"32_good3\",[\"i32:4294967295\"]],[\"invoke\",199,0,\"32_good3\",[\"i32:4294967295\"]],[\"invoke\",201,0,\"8u_bad\",[\"i32:0\"]],[\"invoke\",202,0,\"8s_bad\",[\"i32:0\"]],[\"invoke\",203,0,\"16u_bad\",[\"i32:0\"]],[\"invoke\",204,0,\"16s_bad\",[\"i32:0\"]],[\"invoke\",205,0,\"32_bad\",[\"i32:0\"]],[\"invoke\",207,0,\"8u_bad\",[\"i32:1\"]],[\"invoke\",208,0,\"8s_bad\",[\"i32:1\"]],[\"invoke\",209,0,\"16u_bad\",[\"i32:1\"]],[\"invoke\",210,0,\"16s_bad\",[\"i32:1\"]],[\"invoke\",211,0,\"32_bad\",[\"i32:1\"]],[\"module\",223,1],[\"invoke\",362,0,\"8u_good1\",[\"i32:0\"]],[\"invoke\",363,0,\"8u_good2\",[\"i32:0\"]],[\"invoke\",364,0,\"8u_good3\",[\"i32:0\"]],[\"invoke\",365,0,\"8u_good4\",[\"i32:0\"]],[\"invoke\",366,0,\"8u_good5\",[\"i32:0\"]],[\"invoke\",368,0,\"8s_good1\",[\"i32:0\"]],[\"invoke\",369,0,\"8s_good2\",[\"i32:0\"]],[\"invoke\",370,0,\"8s_good3\",[\"i32:0\"]],[\"invoke\",371,0,\"8s_good4\",[\"i32:0\"]],[\"invoke\",372,0,\"8s_good5\",[\"i32:0\"]],[\"invoke\",374,0,\"16u_good1\",[\"i32:0\"]],[\"invoke\",375,0,\"16u_good2\",[\"i32:0\"]],[\"invoke\",376,0,\"16u_good3\",[\"i32:0\"]],[\"invoke\",377,0,\"16u_good4\",[\"i32:0\"]],[\"invoke\",378,0,\"16u_good5\",[\"i32:0\"]],[\"invoke\",380,0,\"16s_good1\",[\"i32:0\"]],[\"invoke\",381,0,\"16s_good2\",[\"i32:0\"]],[\"invoke\",382,0,\"16s_good3\",[\"i32:0\"]],[\"invoke\",383,0,\"16s_good4\",[\"i32:0\"]],[\"invoke\",384,0,\"16s_good5\",[\"i32:0\"]],[\"invoke\",386,0,\"32u_good1\",[\"i32:0\"]],[\"invoke\",387,0,\"32u_good2\",[\"i32:0\"]],[\"invoke\",388,0,\"32u_good3\",[\"i32:0\"]],[\"invoke\",389,0,\"32u_good4\",[\"i32:0\"]],[\"invoke\",390,0,\"32u_good5\",[\"i32:0\"]],[\"invoke\",392,0,\"32s_good1\",[\"i32:0\"]],[\"invoke\",393,0,\"32s_good2\",[\"i32:0\"]],[\"invoke\",394,0,\"32s_good3\",[\"i32:0\"]],[\"invoke\",395,0,\"32s_good4\",[\"i32:0\"]],[\"invoke\",396,0,\"32s_good5\",[\"i32:0\"]],[\"invoke\",398,0,\"64_good1\",[\"i32:0\"]],[\"invoke\",399,0,\"64_good2\",[\"i32:0\"]],[\"invoke\",400,0,\"64_good3\",[\"i32:0\"]],[\"invoke\",401,0,\"64_good4\",[\"i32:0\"]],[\"invoke\",402,0,\"64_good5\",[\"i32:0\"]],[\"invoke\",404,0,\"8u_good1\",[\"i32:65503\"]],[\"invoke\",405,0,\"8u_good2\",[\"i32:65503\"]],[\"invoke\",406,0,\"8u_good3\",[\"i32:65503\"]],[\"invoke\",407,0,\"8u_good4\",[\"i32:65503\"]],[\"invoke\",408,0,\"8u_good5\",[\"i32:65503\"]],[\"invoke\",410,0,\"8s_good1\",[\"i32:65503\"]],[\"invoke\",411,0,\"8s_good2\",[\"i32:65503\"]],[\"invoke\",412,0,\"8s_good3\",[\"i32:65503\"]],[\"invoke\",413,0,\"8s_good4\",[\"i32:65503\"]],[\"invoke\",414,0,\"8s_good5\",[\"i32:65503\"]],[\"invoke\",416,0,\"16u_good1\",[\"i32:65503\"]],[\"invoke\",417,0,\"16u_good2\",[\"i32:65503\"]],[\"invoke\",418,0,\"16u_good3\",[\"i32:65503\"]],[\"invoke\",419,0,\"16u_good4\",[\"i32:65503\"]],[\"invoke\",420,0,\"16u_good5\",[\"i32:65503\"]],[\"invoke\",422,0,\"16s_good1\",[\"i32:65503\"]],[\"invoke\",423,0,\"16s_good2\",[\"i32:65503\"]],[\"invoke\",424,0,\"16s_good3\",[\"i32:65503\"]],[\"invoke\",425,0,\"16s_good4\",[\"i32:65503\"]],[\"invoke\",426,0,\"16s_good5\",[\"i32:65503\"]],[\"invoke\",428,0,\"32u_good1\",[\"i32:65503\"]],[\"invoke\",429,0,\"32u_good2\",[\"i32:65503\"]],[\"invoke\",430,0,\"32u_good3\",[\"i32:65503\"]],[\"invoke\",431,0,\"32u_good4\",[\"i32:65503\"]],[\"invoke\",432,0,\"32u_good5\",[\"i32:65503\"]],[\"invoke\",434,0,\"32s_good1\",[\"i32:65503\"]],[\"invoke\",435,0,\"32s_good2\",[\"i32:65503\"]],[\"invoke\",436,0,\"32s_good3\",[\"i32:65503\"]],[\"invoke\",437,0,\"32s_good4\",[\"i32:65503\"]],[\"invoke\",438,0,\"32s_good5\",[\"i32:65503\"]],[\"invoke\",440,0,\"64_good1\",[\"i32:65503\"]],[\"invoke\",441,0,\"64_good2\",[\"i32:65503\"]],[\"invoke\",442,0,\"64_good3\",[\"i32:65503\"]],[\"invoke\",443,0,\"64_good4\",[\"i32:65503\"]],[\"invoke\",444,0,\"64_good5\",[\"i32:65503\"]],[\"invoke\",446,0,\"8u_good1\",[\"i32:65504\"]],[\"invoke\",447,0,\"8u_good2\",[\"i32:65504\"]],[\"invoke\",448,0,\"8u_good3\",[\"i32:65504\"]],[\"invoke\",449,0,\"8u_good4\",[\"i32:65504\"]],[\"invoke\",450,0,\"8u_good5\",[\"i32:65504\"]],[\"invoke\",452,0,\"8s_good1\",[\"i32:65504\"]],[\"invoke\",453,0,\"8s_good2\",[\"i32:65504\"]],[\"invoke\",454,0,\"8s_good3\",[\"i32:65504\"]],[\"invoke\",455,0,\"8s_good4\",[\"i32:65504\"]],[\"invoke\",456,0,\"8s_good5\",[\"i32:65504\"]],[\"invoke\",458,0,\"16u_good1\",[\"i32:65504\"]],[\"invoke\",459,0,\"16u_good2\",[\"i32:65504\"]],[\"invoke\",460,0,\"16u_good3\",[\"i32:65504\"]],[\"invoke\",461,0,\"16u_good4\",[\"i32:65504\"]],[\"invoke\",462,0,\"16u_good5\",[\"i32:65504\"]],[\"invoke\",464,0,\"16s_good1\",[\"i32:65504\"]],[\"invoke\",465,0,\"16s_good2\",[\"i32:65504\"]],[\"invoke\",466,0,\"16s_good3\",[\"i32:65504\"]],[\"invoke\",467,0,\"16s_good4\",[\"i32:65504\"]],[\"invoke\",468,0,\"16s_good5\",[\"i32:65504\"]],[\"invoke\",470,0,\"32u_good1\",[\"i32:65504\"]],[\"invoke\",471,0,\"32u_good2\",[\"i32:65504\"]],[\"invoke\",472,0,\"32u_good3\",[\"i32:65504\"]],[\"invoke\",473,0,\"32u_good4\",[\"i32:65504\"]],[\"invoke\",474,0,\"32u_good5\",[\"i32:65504\"]],[\"invoke\",476,0,\"32s_good1\",[\"i32:65504\"]],[\"invoke\",477,0,\"32s_good2\",[\"i32:65504\"]],[\"invoke\",478,0,\"32s_good3\",[\"i32:65504\"]],[\"invoke\",479,0,\"32s_good4\",[\"i32:65504\"]],[\"invoke\",480,0,\"32s_good5\",[\"i32:65504\"]],[\"invoke\",482,0,\"64_good1\",[\"i32:65504\"]],[\"invoke\",483,0,\"64_good2\",[\"i32:65504\"]],[\"invoke\",484,0,\"64_good3\",[\"i32:65504\"]],[\"invoke\",485,0,\"64_good4\",[\"i32:65504\"]],[\"invoke\",486,0,\"64_good5\",[\"i32:65504\"]],[\"invoke\",488,0,\"8u_good3\",[\"i32:4294967295\"]],[\"invoke\",489,0,\"8s_good3\",[\"i32:4294967295\"]],[\"invoke\",490,0,\"16u_good3\",[\"i32:4294967295\"]],[\"invoke\",491,0,\"16s_good3\",[\"i32:4294967295\"]],[\"invoke\",492,0,\"32u_good3\",[\"i32:4294967295\"]],[\"invoke\",493,0,\"32s_good3\",[\"i32:4294967295\"]],[\"invoke\",494,0,\"64_good3\",[\"i32:4294967295\"]],[\"invoke\",496,0,\"8u_bad\",[\"i32:0\"]],[\"invoke\",497,0,\"8s_bad\",[\"i32:0\"]],[\"invoke\",498,0,\"16u_bad\",[\"i32:0\"]],[\"invoke\",499,0,\"16s_bad\",[\"i32:0\"]],[\"invoke\",500,0,\"32u_bad\",[\"i32:0\"]],[\"invoke\",501,0,\"32s_bad\",[\"i32:0\"]],[\"invoke\",502,0,\"64_bad\",[\"i32:0\"]],[\"invoke\",504,0,\"8u_bad\",[\"i32:1\"]],[\"invoke\",505,0,\"8s_bad\",[\"i32:1\"]],[\"invoke\",506,0,\"16u_bad\",[\"i32:1\"]],[\"invoke\",507,0,\"16s_bad\",[\"i32:1\"]],[\"invoke\",508,0,\"32u_bad\",[\"i32:0\"]],[\"invoke\",509,0,\"32s_bad\",[\"i32:0\"]],[\"invoke\",510,0,\"64_bad\",[\"i32:1\"]],[\"module\",514,2],[\"invoke\",538,0,\"32_good1\",[\"i32:0\"]],[\"invoke\",539,0,\"32_good2\",[\"i32:0\"]],[\"invoke\",540,0,\"32_good3\",[\"i32:0\"]],[\"invoke\",541,0,\"32_good4\",[\"i32:0\"]],[\"invoke\",542,0,\"32_good5\",[\"i32:0\"]],[\"invoke\",544,0,\"32_good1\",[\"i32:65524\"]],[\"invoke\",545,0,\"32_good2\",[\"i32:65524\"]],[\"invoke\",546,0,\"32_good3\",[\"i32:65524\"]],[\"invoke\",547,0,\"32_good4\",[\"i32:65524\"]],[\"invoke\",548,0,\"32_good5\",[\"i32:65524\"]],[\"invoke\",550,0,\"32_good1\",[\"i32:65525\"]],[\"invoke\",551,0,\"32_good2\",[\"i32:65525\"]],[\"invoke\",552,0,\"32_good3\",[\"i32:65525\"]],[\"invoke\",553,0,\"32_good4\",[\"i32:65525\"]],[\"invoke\",554,0,\"32_good5\",[\"i32:65525\"]],[\"invoke\",556,0,\"32_good3\",[\"i32:4294967295\"]],[\"invoke\",557,0,\"32_good3\",[\"i32:4294967295\"]],[\"invoke\",559,0,\"32_bad\",[\"i32:0\"]],[\"invoke\",560,0,\"32_bad\",[\"i32:1\"]],[\"module\",564,3],[\"invoke\",588,0,\"64_good1\",[\"i32:0\"]],[\"invoke\",589,0,\"64_good2\",[\"i32:0\"]],[\"invoke\",590,0,\"64_good3\",[\"i32:0\"]],[\"invoke\",591,0,\"64_good4\",[\"i32:0\"]],[\"invoke\",592,0,\"64_good5\",[\"i32:0\"]],[\"invoke\",594,0,\"64_good1\",[\"i32:65510\"]],[\"invoke\",595,0,\"64_good2\",[\"i32:65510\"]],[\"invoke\",596,0,\"64_good3\",[\"i32:65510\"]],[\"invoke\",597,0,\"64_good4\",[\"i32:65510\"]],[\"invoke\",598,0,\"64_good5\",[\"i32:65510\"]],[\"invoke\",600,0,\"64_good1\",[\"i32:65511\"]],[\"invoke\",601,0,\"64_good2\",[\"i32:65511\"]],[\"invoke\",602,0,\"64_good3\",[\"i32:65511\"]],[\"invoke\",603,0,\"64_good4\",[\"i32:65511\"]],[\"invoke\",604,0,\"64_good5\",[\"i32:65511\"]],[\"invoke\",606,0,\"64_good3\",[\"i32:4294967295\"]],[\"invoke\",607,0,\"64_good3\",[\"i32:4294967295\"]],[\"invoke\",609,0,\"64_bad\",[\"i32:0\"]],[\"invoke\",610,0,\"64_bad\",[\"i32:1\"]],[\"file\",\"align.wast\"],[\"module\",3,4],[\"module\",4,5],[\"module\",5,6],[\"module\",6,7],[\"module\",7,8],[\"module\",8,9],[\"module\",9,10],[\"module\",10,11],[\"module\",11,12],[\"module\",12,13],[\"module\",13,14],[\"module\",14,15],[\"module\",15,16],[\"module\",16,17],[\"module\",17,18],[\"module\",18,19],[\"module\",19,20],[\"module\",20,21],[\"module\",21,22],[\"module\",22,23],[\"module\",23,24],[\"module\",24,25],[\"module\",25,26],[\"module\",458,27],[\"invoke\",802,0,\"f32_align_switch\",[\"i32:0\"]],[\"invoke\",803,0,\"f32_align_switch\",[\"i32:1\"]],[\"invoke\",804,0,\"f32_align_switch\",[\"i32:2\"]],[\"invoke\",805,0,\"f32_align_switch\",[\"i32:3\"]],[\"invoke\",807,0,\"f64_align_switch\",[\"i32:0\"]],[\"invoke\",808,0,\"f64_align_switch\",[\"i32:1\"]],[\"invoke\",809,0,\"f64_align_switch\",[\"i32:2\"]],[\"invoke\",810,0,\"f64_align_switch\",[\"i32:3\"]],[\"invoke\",811,0,\"f64_align_switch\",[\"i32:4\"]],[\"invoke\",813,0,\"i32_align_switch\",[\"i32:0\",\"i32:0\"]],[\"invoke\",814,0,\"i32_align_switch\",[\"i32:0\",\"i32:1\"]],[\"invoke\",815,0,\"i32_align_switch\",[\"i32:1\",\"i32:0\"]],[\"invoke\",816,0,\"i32_align_switch\",[\"i32:1\",\"i32:1\"]],[\"invoke\",817,0,\"i32_align_switch\",[\"i32:2\",\"i32:0\"]],[\"invoke\",818,0,\"i32_align_switch\",[\"i32:2\",\"i32:1\"]],[\"invoke\",819,0,\"i32_align_switch\",[\"i32:2\",\"i32:2\"]],[\"invoke\",820,0,\"i32_align_switch\",[\"i32:3\",\"i32:0\"]],[\"invoke\",821,0,\"i32_align_switch\",[\"i32:3\",\"i32:1\"]],[\"invoke\",822,0,\"i32_align_switch\",[\"i32:3\",\"i32:2\"]],[\"invoke\",823,0,\"i32_align_switch\",[\"i32:4\",\"i32:0\"]],[\"invoke\",824,0,\"i32_align_switch\",[\"i32:4\",\"i32:1\"]],[\"invoke\",825,0,\"i32_align_switch\",[\"i32:4\",\"i32:2\"]],[\"invoke\",826,0,\"i32_align_switch\",[\"i32:4\",\"i32:4\"]],[\"invoke\",828,0,\"i64_align_switch\",[\"i32:0\",\"i32:0\"]],[\"invoke\",829,0,\"i64_align_switch\",[\"i32:0\",\"i32:1\"]],[\"invoke\",830,0,\"i64_align_switch\",[\"i32:1\",\"i32:0\"]],[\"invoke\",831,0,\"i64_align_switch\",[\"i32:1\",\"i32:1\"]],[\"invoke\",832,0,\"i64_align_switch\",[\"i32:2\",\"i32:0\"]],[\"invoke\",833,0,\"i64_align_switch\",[\"i32:2\",\"i32:1\"]],[\"invoke\",834,0,\"i64_align_switch\",[\"i32:2\",\"i32:2\"]],[\"invoke\",835,0,\"i64_align_switch\",[\"i32:3\",\"i32:0\"]],[\"invoke\",836,0,\"i64_align_switch\",[\"i32:3\",\"i32:1\"]],[\"invoke\",837,0,\"i64_align_switch\",[\"i32:3\",\"i32:2\"]],[\"invoke\",838,0,\"i64_align_switch\",[\"i32:4\",\"i32:0\"]],[\"invoke\",839,0,\"i64_align_switch\",[\"i32:4\",\"i32:1\"]],[\"invoke\",840,0,\"i64_align_switch\",[\"i32:4\",\"i32:2\"]],[\"invoke\",841,0,\"i64_align_switch\",[\"i32:4\",\"i32:4\"]],[\"invoke\",842,0,\"i64_align_switch\",[\"i32:5\",\"i32:0\"]],[\"invoke\",843,0,\"i64_align_switch\",[\"i32:5\",\"i32:1\"]],[\"invoke\",844,0,\"i64_align_switch\",[\"i32:5\",\"i32:2\"]],[\"invoke\",845,0,\"i64_align_switch\",[\"i32:5\",\"i32:4\"]],[\"invoke\",846,0,\"i64_align_switch\",[\"i32:6\",\"i32:0\"]],[\"invoke\",847,0,\"i64_align_switch\",[\"i32:6\",\"i32:1\"]],[\"invoke\",848,0,\"i64_align_switch\",[\"i32:6\",\"i32:2\"]],[\"invoke\",849,0,\"i64_align_switch\",[\"i32:6\",\"i32:4\"]],[\"invoke\",850,0,\"i64_align_switch\",[\"i32:6\",\"i32:8\"]],[\"module\",854,28],[\"invoke\",864,0,\"store\",[\"i32:65532\",\"i64:18446744073709551615\"]],[\"invoke\",866,0,\"load\",[\"i32:65532\"]],[\"file\",\"endianness.wast\"],[\"module\",1,29],[\"invoke\",133,0,\"i32_load16_s\",[\"i32:4294967295\"]],[\"invoke\",134,0,\"i32_load16_s\",[\"i32:4294963054\"]],[\"invoke\",135,0,\"i32_load16_s\",[\"i32:42\"]],[\"invoke\",136,0,\"i32_load16_s\",[\"i32:12816\"]],[\"invoke\",138,0,\"i32_load16_u\",[\"i32:4294967295\"]],[\"invoke\",139,0,\"i32_load16_u\",[\"i32:4294963054\"]],[\"invoke\",140,0,\"i32_load16_u\",[\"i32:42\"]],[\"invoke\",141,0,\"i32_load16_u\",[\"i32:51966\"]],[\"invoke\",143,0,\"i32_load\",[\"i32:4294967295\"]],[\"invoke\",144,0,\"i32_load\",[\"i32:4252543054\"]],[\"invoke\",145,0,\"i32_load\",[\"i32:42424242\"]],[\"invoke\",146,0,\"i32_load\",[\"i32:2880249322\"]],[\"invoke\",148,0,\"i64_load16_s\",[\"i64:18446744073709551615\"]],[\"invoke\",149,0,\"i64_load16_s\",[\"i64:18446744073709547374\"]],[\"invoke\",150,0,\"i64_load16_s\",[\"i64:42\"]],[\"invoke\",151,0,\"i64_load16_s\",[\"i64:12816\"]],[\"invoke\",153,0,\"i64_load16_u\",[\"i64:18446744073709551615\"]],[\"invoke\",154,0,\"i64_load16_u\",[\"i64:18446744073709547374\"]],[\"invoke\",155,0,\"i64_load16_u\",[\"i64:42\"]],[\"invoke\",156,0,\"i64_load16_u\",[\"i64:51966\"]],[\"invoke\",158,0,\"i64_load32_s\",[\"i64:18446744073709551615\"]],[\"invoke\",159,0,\"i64_load32_s\",[\"i64:18446744073667127374\"]],[\"invoke\",160,0,\"i64_load32_s\",[\"i64:42424242\"]],[\"invoke\",161,0,\"i64_load32_s\",[\"i64:305419896\"]],[\"invoke\",163,0,\"i64_load32_u\",[\"i64:18446744073709551615\"]],[\"invoke\",164,0,\"i64_load32_u\",[\"i64:18446744073667127374\"]],[\"invoke\",165,0,\"i64_load32_u\",[\"i64:42424242\"]],[\"invoke\",166,0,\"i64_load32_u\",[\"i64:2880249322\"]],[\"invoke\",168,0,\"i64_load\",[\"i64:18446744073709551615\"]],[\"invoke\",169,0,\"i64_load\",[\"i64:18446744073667127374\"]],[\"invoke\",170,0,\"i64_load\",[\"i64:2880249322\"]],[\"invoke\",171,0,\"i64_load\",[\"i64:12370766947463011818\"]],[\"invoke\",173,0,\"f32_load\",[\"f32:3212836864\"]],[\"invoke\",174,0,\"f32_load\",[\"f32:1011494326\"]],[\"invoke\",175,0,\"f32_load\",[\"f32:1166316389\"]],[\"invoke\",176,0,\"f32_load\",[\"f32:2139095039\"]],[\"invoke\",178,0,\"f64_load\",[\"f64:13830554455654793216\"]],[\"invoke\",179,0,\"f64_load\",[\"f64:4653144502447687399\"]],[\"invoke\",180,0,\"f64_load\",[\"f64:4691032041816096430\"]],[\"invoke\",181,0,\"f64_load\",[\"f64:9218868437227405311\"]],[\"invoke\",184,0,\"i32_store16\",[\"i32:4294967295\"]],[\"invoke\",185,0,\"i32_store16\",[\"i32:4294963054\"]],[\"invoke\",186,0,\"i32_store16\",[\"i32:42\"]],[\"invoke\",187,0,\"i32_store16\",[\"i32:51966\"]],[\"invoke\",189,0,\"i32_store\",[\"i32:4294967295\"]],[\"invoke\",190,0,\"i32_store\",[\"i32:4294963054\"]],[\"invoke\",191,0,\"i32_store\",[\"i32:42424242\"]],[\"invoke\",192,0,\"i32_store\",[\"i32:3735931646\"]],[\"invoke\",194,0,\"i64_store16\",[\"i64:18446744073709551615\"]],[\"invoke\",195,0,\"i64_store16\",[\"i64:18446744073709547374\"]],[\"invoke\",196,0,\"i64_store16\",[\"i64:42\"]],[\"invoke\",197,0,\"i64_store16\",[\"i64:51966\"]],[\"invoke\",199,0,\"i64_store32\",[\"i64:18446744073709551615\"]],[\"invoke\",200,0,\"i64_store32\",[\"i64:18446744073709547374\"]],[\"invoke\",201,0,\"i64_store32\",[\"i64:42424242\"]],[\"invoke\",202,0,\"i64_store32\",[\"i64:3735931646\"]],[\"invoke\",204,0,\"i64_store\",[\"i64:18446744073709551615\"]],[\"invoke\",205,0,\"i64_store\",[\"i64:18446744073667127374\"]],[\"invoke\",206,0,\"i64_store\",[\"i64:2880249322\"]],[\"invoke\",207,0,\"i64_store\",[\"i64:12370766947463011818\"]],[\"invoke\",209,0,\"f32_store\",[\"f32:3212836864\"]],[\"invoke\",210,0,\"f32_store\",[\"f32:1011494326\"]],[\"invoke\",211,0,\"f32_store\",[\"f32:1166316389\"]],[\"invoke\",212,0,\"f32_store\",[\"f32:2139095039\"]],[\"invoke\",214,0,\"f64_store\",[\"f64:13830554455654793216\"]],[\"invoke\",215,0,\"f64_store\",[\"f64:4653144502447687399\"]],[\"invoke\",216,0,\"f64_store\",[\"f64:4691032041816096430\"]],[\"invoke\",217,0,\"f64_store\",[\"f64:9218868437227405311\"]],[\"file\",\"float_memory.wast\"],[\"module\",5,30],[\"invoke\",15,0,\"i32.load\",[]],[\"invoke\",16,0,\"f32.load\",[]],[\"invoke\",17,0,\"reset\",[]],[\"invoke\",18,0,\"i32.load\",[]],[\"invoke\",19,0,\"f32.load\",[]],[\"invoke\",20,0,\"f32.store\",[]],[\"invoke\",21,0,\"i32.load\",[]],[\"invoke\",22,0,\"f32.load\",[]],[\"invoke\",23,0,\"reset\",[]],[\"invoke\",24,0,\"i32.load\",[]],[\"invoke\",25,0,\"f32.load\",[]],[\"invoke\",26,0,\"i32.store\",[]],[\"invoke\",27,0,\"i32.load\",[]],[\"invoke\",28,0,\"f32.load\",[]],[\"module\",30,31],[\"invoke\",40,0,\"i64.load\",[]],[\"invoke\",41,0,\"f64.load\",[]],[\"invoke\",42,0,\"reset\",[]],[\"invoke\",43,0,\"i64.load\",[]],[\"invoke\",44,0,\"f64.load\",[]],[\"invoke\",45,0,\"f64.store\",[]],[\"invoke\",46,0,\"i64.load\",[]],[\"invoke\",47,0,\"f64.load\",[]],[\"invoke\",48,0,\"reset\",[]],[\"invoke\",49,0,\"i64.load\",[]],[\"invoke\",50,0,\"f64.load\",[]],[\"invoke\",51,0,\"i64.store\",[]],[\"invoke\",52,0,\"i64.load\",[]],[\"invoke\",53,0,\"f64.load\",[]],[\"module\",57,32],[\"invoke\",67,0,\"i32.load\",[]],[\"invoke\",68,0,\"f32.load\",[]],[\"invoke\",69,0,\"reset\",[]],[\"invoke\",70,0,\"i32.load\",[]],[\"invoke\",71,0,\"f32.load\",[]],[\"invoke\",72,0,\"f32.store\",[]],[\"invoke\",73,0,\"i32.load\",[]],[\"invoke\",74,0,\"f32.load\",[]],[\"invoke\",75,0,\"reset\",[]],[\"invoke\",76,0,\"i32.load\",[]],[\"invoke\",77,0,\"f32.load\",[]],[\"invoke\",78,0,\"i32.store\",[]],[\"invoke\",79,0,\"i32.load\",[]],[\"invoke\",80,0,\"f32.load\",[]],[\"module\",82,33],[\"invoke\",92,0,\"i64.load\",[]],[\"invoke\",93,0,\"f64.load\",[]],[\"invoke\",94,0,\"reset\",[]],[\"invoke\",95,0,\"i64.load\",[]],[\"invoke\",96,0,\"f64.load\",[]],[\"invoke\",97,0,\"f64.store\",[]],[\"invoke\",98,0,\"i64.load\",[]],[\"invoke\",99,0,\"f64.load\",[]],[\"invoke\",100,0,\"reset\",[]],[\"invoke\",101,0,\"i64.load\",[]],[\"invoke\",102,0,\"f64.load\",[]],[\"invoke\",103,0,\"i64.store\",[]],[\"invoke\",104,0,\"i64.load\",[]],[\"invoke\",105,0,\"f64.load\",[]],[\"module\",109,34],[\"invoke\",119,0,\"i32.load\",[]],[\"invoke\",120,0,\"f32.load\",[]],[\"invoke\",121,0,\"reset\",[]],[\"invoke\",122,0,\"i32.load\",[]],[\"invoke\",123,0,\"f32.load\",[]],[\"invoke\",124,0,\"f32.store\",[]],[\"invoke\",125,0,\"i32.load\",[]],[\"invoke\",126,0,\"f32.load\",[]],[\"invoke\",127,0,\"reset\",[]],[\"invoke\",128,0,\"i32.load\",[]],[\"invoke\",129,0,\"f32.load\",[]],[\"invoke\",130,0,\"i32.store\",[]],[\"invoke\",131,0,\"i32.load\",[]],[\"invoke\",132,0,\"f32.load\",[]],[\"module\",134,35],[\"invoke\",144,0,\"i64.load\",[]],[\"invoke\",145,0,\"f64.load\",[]],[\"invoke\",146,0,\"reset\",[]],[\"invoke\",147,0,\"i64.load\",[]],[\"invoke\",148,0,\"f64.load\",[]],[\"invoke\",149,0,\"f64.store\",[]],[\"invoke\",150,0,\"i64.load\",[]],[\"invoke\",151,0,\"f64.load\",[]],[\"invoke\",152,0,\"reset\",[]],[\"invoke\",153,0,\"i64.load\",[]],[\"invoke\",154,0,\"f64.load\",[]],[\"invoke\",155,0,\"i64.store\",[]],[\"invoke\",156,0,\"i64.load\",[]],[\"invoke\",157,0,\"f64.load\",[]],[\"file\",\"load.wast\"],[\"module\",3,36],[\"invoke\",161,0,\"as-br-value\",[]],[\"invoke\",163,0,\"as-br_if-cond\",[]],[\"invoke\",164,0,\"as-br_if-value\",[]],[\"invoke\",165,0,\"as-br_if-value-cond\",[]],[\"invoke\",167,0,\"as-br_table-index\",[]],[\"invoke\",168,0,\"as-br_table-value\",[]],[\"invoke\",169,0,\"as-br_table-value-index\",[]],[\"invoke\",171,0,\"as-return-value\",[]],[\"invoke\",173,0,\"as-if-cond\",[]],[\"invoke\",174,0,\"as-if-then\",[]],[\"invoke\",175,0,\"as-if-else\",[]],[\"invoke\",177,0,\"as-select-first\",[\"i32:0\",\"i32:1\"]],[\"invoke\",178,0,\"as-select-second\",[\"i32:0\",\"i32:0\"]],[\"invoke\",179,0,\"as-select-cond\",[]],[\"invoke\",181,0,\"as-call-first\",[]],[\"invoke\",182,0,\"as-call-mid\",[]],[\"invoke\",183,0,\"as-call-last\",[]],[\"invoke\",185,0,\"as-call_indirect-first\",[]],[\"invoke\",186,0,\"as-call_indirect-mid\",[]],[\"invoke\",187,0,\"as-call_indirect-last\",[]],[\"invoke\",188,0,\"as-call_indirect-index\",[]],[\"invoke\",190,0,\"as-local.set-value\",[]],[\"invoke\",191,0,\"as-local.tee-value\",[]],[\"invoke\",192,0,\"as-global.set-value\",[]],[\"invoke\",194,0,\"as-load-address\",[]],[\"invoke\",195,0,\"as-loadN-address\",[]],[\"invoke\",196,0,\"as-store-address\",[]],[\"invoke\",197,0,\"as-store-value\",[]],[\"invoke\",198,0,\"as-storeN-address\",[]],[\"invoke\",199,0,\"as-storeN-value\",[]],[\"invoke\",201,0,\"as-unary-operand\",[]],[\"invoke\",203,0,\"as-binary-left\",[]],[\"invoke\",204,0,\"as-binary-right\",[]],[\"invoke\",206,0,\"as-test-operand\",[]],[\"invoke\",208,0,\"as-compare-left\",[]],[\"invoke\",209,0,\"as-compare-right\",[]],[\"invoke\",211,0,\"as-memory.grow-size\",[]],[\"file\",\"store.wast\"],[\"module\",3,37],[\"invoke\",44,0,\"as-block-value\",[]],[\"invoke\",45,0,\"as-loop-value\",[]],[\"invoke\",47,0,\"as-br-value\",[]],[\"invoke\",48,0,\"as-br_if-value\",[]],[\"invoke\",49,0,\"as-br_if-value-cond\",[]],[\"invoke\",50,0,\"as-br_table-value\",[]],[\"invoke\",52,0,\"as-return-value\",[]],[\"invoke\",54,0,\"as-if-then\",[]],[\"invoke\",55,0,\"as-if-else\",[]],[\"file\",\"memory.wast\"],[\"module\",3,38],[\"module\",4,39],[\"module\",5,40],[\"module\",6,41],[\"module\",7,42],[\"module\",8,43],[\"module\",13,44],[\"invoke\",14,0,\"memsize\",[]],[\"module\",15,44],[\"invoke\",16,0,\"memsize\",[]],[\"module\",17,45],[\"invoke\",18,0,\"memsize\",[]],[\"module\",92,46],[\"invoke\",180,0,\"data\",[]],[\"invoke\",181,0,\"cast\",[]],[\"invoke\",183,0,\"i32_load8_s\",[\"i32:4294967295\"]],[\"invoke\",184,0,\"i32_load8_u\",[\"i32:4294967295\"]],[\"invoke\",185,0,\"i32_load16_s\",[\"i32:4294967295\"]],[\"invoke\",186,0,\"i32_load16_u\",[\"i32:4294967295\"]],[\"invoke\",188,0,\"i32_load8_s\",[\"i32:100\"]],[\"invoke\",189,0,\"i32_load8_u\",[\"i32:200\"]],[\"invoke\",190,0,\"i32_load16_s\",[\"i32:20000\"]],[\"invoke\",191,0,\"i32_load16_u\",[\"i32:40000\"]],[\"invoke\",193,0,\"i32_load8_s\",[\"i32:4275856707\"]],[\"invoke\",194,0,\"i32_load8_s\",[\"i32:878104047\"]],[\"invoke\",195,0,\"i32_load8_u\",[\"i32:4275856707\"]],[\"invoke\",196,0,\"i32_load8_u\",[\"i32:878104047\"]],[\"invoke\",197,0,\"i32_load16_s\",[\"i32:4275856707\"]],[\"invoke\",198,0,\"i32_load16_s\",[\"i32:878104047\"]],[\"invoke\",199,0,\"i32_load16_u\",[\"i32:4275856707\"]],[\"invoke\",200,0,\"i32_load16_u\",[\"i32:878104047\"]],[\"invoke\",202,0,\"i64_load8_s\",[\"i64:18446744073709551615\"]],[\"invoke\",203,0,\"i64_load8_u\",[\"i64:18446744073709551615\"]],[\"invoke\",204,0,\"i64_load16_s\",[\"i64:18446744073709551615\"]],[\"invoke\",205,0,\"i64_load16_u\",[\"i64:18446744073709551615\"]],[\"invoke\",206,0,\"i64_load32_s\",[\"i64:18446744073709551615\"]],[\"invoke\",207,0,\"i64_load32_u\",[\"i64:18446744073709551615\"]],[\"invoke\",209,0,\"i64_load8_s\",[\"i64:100\"]],[\"invoke\",210,0,\"i64_load8_u\",[\"i64:200\"]],[\"invoke\",211,0,\"i64_load16_s\",[\"i64:20000\"]],[\"invoke\",212,0,\"i64_load16_u\",[\"i64:40000\"]],[\"invoke\",213,0,\"i64_load32_s\",[\"i64:20000\"]],[\"invoke\",214,0,\"i64_load32_u\",[\"i64:40000\"]],[\"invoke\",216,0,\"i64_load8_s\",[\"i64:18364758543954109763\"]],[\"invoke\",217,0,\"i64_load8_s\",[\"i64:3771275841602506223\"]],[\"invoke\",218,0,\"i64_load8_u\",[\"i64:18364758543954109763\"]],[\"invoke\",219,0,\"i64_load8_u\",[\"i64:3771275841602506223\"]],[\"invoke\",220,0,\"i64_load16_s\",[\"i64:18364758543954109763\"]],[\"invoke\",221,0,\"i64_load16_s\",[\"i64:3771275841602506223\"]],[\"invoke\",222,0,\"i64_load16_u\",[\"i64:18364758543954109763\"]],[\"invoke\",223,0,\"i64_load16_u\",[\"i64:3771275841602506223\"]],[\"invoke\",224,0,\"i64_load32_s\",[\"i64:18364758543954109763\"]],[\"invoke\",225,0,\"i64_load32_s\",[\"i64:3771275841602506223\"]],[\"invoke\",226,0,\"i64_load32_u\",[\"i64:18364758543954109763\"]],[\"invoke\",227,0,\"i64_load32_u\",[\"i64:3771275841602506223\"]],[\"module\",246,47],[\"invoke\",260,0,\"load\",[\"i32:0\"]],[\"invoke\",261,0,\"load\",[\"i32:10000\"]],[\"invoke\",262,0,\"load\",[\"i32:20000\"]],[\"invoke\",263,0,\"load\",[\"i32:30000\"]],[\"invoke\",264,0,\"load\",[\"i32:40000\"]],[\"invoke\",265,0,\"load\",[\"i32:50000\"]],[\"invoke\",266,0,\"load\",[\"i32:60000\"]],[\"invoke\",267,0,\"load\",[\"i32:65535\"]],[\"file\",\"memory_grow.wast\"],[\"module\",1,48],[\"invoke\",14,0,\"size\",[]],[\"invoke\",15,0,\"store_at_zero\",[]],[\"invoke\",16,0,\"load_at_zero\",[]],[\"invoke\",17,0,\"store_at_page_size\",[]],[\"invoke\",18,0,\"load_at_page_size\",[]],[\"invoke\",19,0,\"grow\",[\"i32:1\"]],[\"invoke\",20,0,\"size\",[]],[\"invoke\",21,0,\"load_at_zero\",[]],[\"invoke\",22,0,\"store_at_zero\",[]],[\"invoke\",23,0,\"load_at_zero\",[]],[\"invoke\",24,0,\"store_at_page_size\",[]],[\"invoke\",25,0,\"load_at_page_size\",[]],[\"invoke\",26,0,\"grow\",[\"i32:4\"]],[\"invoke\",27,0,\"size\",[]],[\"invoke\",28,0,\"load_at_zero\",[]],[\"invoke\",29,0,\"store_at_zero\",[]],[\"invoke\",30,0,\"load_at_zero\",[]],[\"invoke\",31,0,\"load_at_page_size\",[]],[\"invoke\",32,0,\"store_at_page_size\",[]],[\"invoke\",33,0,\"load_at_page_size\",[]],[\"module\",36,49],[\"invoke\",41,0,\"grow\",[\"i32:0\"]],[\"invoke\",42,0,\"grow\",[\"i32:1\"]],[\"invoke\",43,0,\"grow\",[\"i32:0\"]],[\"invoke\",44,0,\"grow\",[\"i32:2\"]],[\"invoke\",45,0,\"grow\",[\"i32:800\"]],[\"invoke\",46,0,\"grow\",[\"i32:65536\"]],[\"invoke\",47,0,\"grow\",[\"i32:64736\"]],[\"invoke\",48,0,\"grow\",[\"i32:1\"]],[\"module\",50,50],[\"invoke\",55,0,\"grow\",[\"i32:0\"]],[\"invoke\",56,0,\"grow\",[\"i32:1\"]],[\"invoke\",57,0,\"grow\",[\"i32:1\"]],[\"invoke\",58,0,\"grow\",[\"i32:2\"]],[\"invoke\",59,0,\"grow\",[\"i32:6\"]],[\"invoke\",60,0,\"grow\",[\"i32:0\"]],[\"invoke\",61,0,\"grow\",[\"i32:1\"]],[\"invoke\",62,0,\"grow\",[\"i32:65536\"]],[\"module\",66,51],[\"invoke\",87,0,\"check-memory-zero\",[\"i32:0\",\"i32:65535\"]],[\"invoke\",88,0,\"grow\",[\"i32:1\"]],[\"invoke\",89,0,\"check-memory-zero\",[\"i32:65536\",\"i32:131071\"]],[\"invoke\",90,0,\"grow\",[\"i32:1\"]],[\"invoke\",91,0,\"check-memory-zero\",[\"i32:131072\",\"i32:196607\"]],[\"invoke\",92,0,\"grow\",[\"i32:1\"]],[\"invoke\",93,0,\"check-memory-zero\",[\"i32:196608\",\"i32:262143\"]],[\"invoke\",94,0,\"grow\",[\"i32:1\"]],[\"invoke\",95,0,\"check-memory-zero\",[\"i32:262144\",\"i32:327679\"]],[\"invoke\",96,0,\"grow\",[\"i32:1\"]],[\"invoke\",97,0,\"check-memory-zero\",[\"i32:327680\",\"i32:393215\"]],[\"module\",101,52],[\"invoke\",259,0,\"as-br-value\",[]],[\"invoke\",261,0,\"as-br_if-cond\",[]],[\"invoke\",262,0,\"as-br_if-value\",[]],[\"invoke\",263,0,\"as-br_if-value-cond\",[]],[\"invoke\",265,0,\"as-br_table-index\",[]],[\"invoke\",266,0,\"as-br_table-value\",[]],[\"invoke\",267,0,\"as-br_table-value-index\",[]],[\"invoke\",269,0,\"as-return-value\",[]],[\"invoke\",271,0,\"as-if-cond\",[]],[\"invoke\",272,0,\"as-if-then\",[]],[\"invoke\",273,0,\"as-if-else\",[]],[\"invoke\",275,0,\"as-select-first\",[\"i32:0\",\"i32:1\"]],[\"invoke\",276,0,\"as-select-second\",[\"i32:0\",\"i32:0\"]],[\"invoke\",277,0,\"as-select-cond\",[]],[\"invoke\",279,0,\"as-call-first\",[]],[\"invoke\",280,0,\"as-call-mid\",[]],[\"invoke\",281,0,\"as-call-last\",[]],[\"invoke\",283,0,\"as-call_indirect-first\",[]],[\"invoke\",284,0,\"as-call_indirect-mid\",[]],[\"invoke\",285,0,\"as-call_indirect-last\",[]],[\"invoke\",286,0,\"as-call_indirect-index\",[]],[\"invoke\",288,0,\"as-local.set-value\",[]],[\"invoke\",289,0,\"as-local.tee-value\",[]],[\"invoke\",290,0,\"as-global.set-value\",[]],[\"invoke\",292,0,\"as-load-address\",[]],[\"invoke\",293,0,\"as-loadN-address\",[]],[\"invoke\",294,0,\"as-store-address\",[]],[\"invoke\",295,0,\"as-store-value\",[]],[\"invoke\",296,0,\"as-storeN-address\",[]],[\"invoke\",297,0,\"as-storeN-value\",[]],[\"invoke\",299,0,\"as-unary-operand\",[]],[\"invoke\",301,0,\"as-binary-left\",[]],[\"invoke\",302,0,\"as-binary-right\",[]],[\"invoke\",304,0,\"as-test-operand\",[]],[\"invoke\",306,0,\"as-compare-left\",[]],[\"invoke\",307,0,\"as-compare-right\",[]],[\"invoke\",309,0,\"as-memory.grow-size\",[]],[\"module\",312,53,\"$Mgm\"],[\"register\",\"grown-memory\",\"$Mgm\"],[\"invoke\",317,\"$Mgm\",\"grow\",[]],[\"module\",318,54,\"$Mgim1\"],[\"register\",\"grown-imported-memory\",\"$Mgim1\"],[\"invoke\",324,\"$Mgim1\",\"grow\",[]],[\"module\",325,55,\"$Mgim2\"],[\"invoke\",330,\"$Mgim2\",\"size\",[]],[\"file\",\"memory_redundancy.wast\"],[\"module\",5,56],[\"invoke\",59,0,\"test_store_to_load\",[]],[\"invoke\",60,0,\"zero_everything\",[]],[\"invoke\",61,0,\"test_redundant_load\",[]],[\"invoke\",62,0,\"zero_everything\",[]],[\"invoke\",63,0,\"test_dead_store\",[]],[\"invoke\",64,0,\"zero_everything\",[]],[\"invoke\",65,0,\"malloc_aliasing\",[]],[\"file\",\"memory_size.wast\"],[\"module\",1,57],[\"invoke\",7,0,\"size\",[]],[\"invoke\",8,0,\"grow\",[\"i32:1\"]],[\"invoke\",9,0,\"size\",[]],[\"invoke\",10,0,\"grow\",[\"i32:4\"]],[\"invoke\",11,0,\"size\",[]],[\"invoke\",12,0,\"grow\",[\"i32:0\"]],[\"invoke\",13,0,\"size\",[]],[\"module\",15,58],[\"invoke\",21,0,\"size\",[]],[\"invoke\",22,0,\"grow\",[\"i32:1\"]],[\"invoke\",23,0,\"size\",[]],[\"invoke\",24,0,\"grow\",[\"i32:4\"]],[\"invoke\",25,0,\"size\",[]],[\"invoke\",26,0,\"grow\",[\"i32:0\"]],[\"invoke\",27,0,\"size\",[]],[\"module\",29,59],[\"invoke\",35,0,\"size\",[]],[\"invoke\",36,0,\"grow\",[\"i32:3\"]],[\"invoke\",37,0,\"size\",[]],[\"invoke\",38,0,\"grow\",[\"i32:1\"]],[\"invoke\",39,0,\"size\",[]],[\"invoke\",40,0,\"grow\",[\"i32:0\"]],[\"invoke\",41,0,\"size\",[]],[\"invoke\",42,0,\"grow\",[\"i32:4\"]],[\"invoke\",43,0,\"size\",[]],[\"invoke\",44,0,\"grow\",[\"i32:1\"]],[\"invoke\",45,0,\"size\",[]],[\"module\",47,60],[\"invoke\",53,0,\"size\",[]],[\"invoke\",54,0,\"grow\",[\"i32:1\"]],[\"invoke\",55,0,\"size\",[]],[\"invoke\",56,0,\"grow\",[\"i32:3\"]],[\"invoke\",57,0,\"size\",[]],[\"invoke\",58,0,\"grow\",[\"i32:0\"]],[\"invoke\",59,0,\"size\",[]],[\"invoke\",60,0,\"grow\",[\"i32:2\"]],[\"invoke\",61,0,\"size\",[]],[\"invoke\",62,0,\"grow\",[\"i32:1\"]],[\"invoke\",63,0,\"size\",[]],[\"file\",\"memory_trap.wast\"],[\"module\",1,61],[\"invoke\",21,0,\"store\",[\"i32:4294967292\",\"i32:42\"]],[\"invoke\",22,0,\"load\",[\"i32:4294967292\"]],[\"invoke\",23,0,\"store\",[\"i32:4294967293\",\"i32:305419896\"]],[\"invoke\",24,0,\"load\",[\"i32:4294967293\"]],[\"invoke\",25,0,\"store\",[\"i32:4294967294\",\"i32:13\"]],[\"invoke\",26,0,\"load\",[\"i32:4294967294\"]],[\"invoke\",27,0,\"store\",[\"i32:4294967295\",\"i32:13\"]],[\"invoke\",28,0,\"load\",[\"i32:4294967295\"]],[\"invoke\",29,0,\"store\",[\"i32:0\",\"i32:13\"]],[\"invoke\",30,0,\"load\",[\"i32:0\"]],[\"invoke\",31,0,\"store\",[\"i32:2147483648\",\"i32:13\"]],[\"invoke\",32,0,\"load\",[\"i32:2147483648\"]],[\"invoke\",33,0,\"memory.grow\",[\"i32:65537\"]],[\"module\",35,62],[\"invoke\",111,0,\"i32.store\",[\"i32:65536\",\"i32:0\"]],[\"invoke\",112,0,\"i32.store\",[\"i32:65535\",\"i32:0\"]],[\"invoke\",113,0,\"i32.store\",[\"i32:65534\",\"i32:0\"]],[\"invoke\",114,0,\"i32.store\",[\"i32:65533\",\"i32:0\"]],[\"invoke\",115,0,\"i32.store\",[\"i32:4294967295\",\"i32:0\"]],[\"invoke\",116,0,\"i32.store\",[\"i32:4294967294\",\"i32:0\"]],[\"invoke\",117,0,\"i32.store\",[\"i32:4294967293\",\"i32:0\"]],[\"invoke\",118,0,\"i32.store\",[\"i32:4294967292\",\"i32:0\"]],[\"invoke\",119,0,\"i64.store\",[\"i32:65536\",\"i64:0\"]],[\"invoke\",120,0,\"i64.store\",[\"i32:65535\",\"i64:0\"]],[\"invoke\",121,0,\"i64.store\",[\"i32:65534\",\"i64:0\"]],[\"invoke\",122,0,\"i64.store\",[\"i32:65533\",\"i64:0\"]],[\"invoke\",123,0,\"i64.store\",[\"i32:65532\",\"i64:0\"]],[\"invoke\",124,0,\"i64.store\",[\"i32:65531\",\"i64:0\"]],[\"invoke\",125,0,\"i64.store\",[\"i32:65530\",\"i64:0\"]],[\"invoke\",126,0,\"i64.store\",[\"i32:65529\",\"i64:0\"]],[\"invoke\",127,0,\"i64.store\",[\"i32:4294967295\",\"i64:0\"]],[\"invoke\",128,0,\"i64.store\",[\"i32:4294967294\",\"i64:0\"]],[\"invoke\",129,0,\"i64.store\",[\"i32:4294967293\",\"i64:0\"]],[\"invoke\",130,0,\"i64.store\",[\"i32:4294967292\",\"i64:0\"]],[\"invoke\",131,0,\"i64.store\",[\"i32:4294967291\",\"i64:0\"]],[\"invoke\",132,0,\"i64.store\",[\"i32:4294967290\",\"i64:0\"]],[\"invoke\",133,0,\"i64.store\",[\"i32:4294967289\",\"i64:0\"]],[\"invoke\",134,0,\"i64.store\",[\"i32:4294967288\",\"i64:0\"]],[\"invoke\",135,0,\"f32.store\",[\"i32:65536\",\"f32:0\"]],[\"invoke\",136,0,\"f32.store\",[\"i32:65535\",\"f32:0\"]],[\"invoke\",137,0,\"f32.store\",[\"i32:65534\",\"f32:0\"]],[\"invoke\",138,0,\"f32.store\",[\"i32:65533\",\"f32:0\"]],[\"invoke\",139,0,\"f32.store\",[\"i32:4294967295\",\"f32:0\"]],[\"invoke\",140,0,\"f32.store\",[\"i32:4294967294\",\"f32:0\"]],[\"invoke\",141,0,\"f32.store\",[\"i32:4294967293\",\"f32:0\"]],[\"invoke\",142,0,\"f32.store\",[\"i32:4294967292\",\"f32:0\"]],[\"invoke\",143,0,\"f64.store\",[\"i32:65536\",\"f64:0\"]],[\"invoke\",144,0,\"f64.store\",[\"i32:65535\",\"f64:0\"]],[\"invoke\",145,0,\"f64.store\",[\"i32:65534\",\"f64:0\"]],[\"invoke\",146,0,\"f64.store\",[\"i32:65533\",\"f64:0\"]],[\"invoke\",147,0,\"f64.store\",[\"i32:65532\",\"f64:0\"]],[\"invoke\",148,0,\"f64.store\",[\"i32:65531\",\"f64:0\"]],[\"invoke\",149,0,\"f64.store\",[\"i32:65530\",\"f64:0\"]],[\"invoke\",150,0,\"f64.store\",[\"i32:65529\",\"f64:0\"]],[\"invoke\",151,0,\"f64.store\",[\"i32:4294967295\",\"f64:0\"]],[\"invoke\",152,0,\"f64.store\",[\"i32:4294967294\",\"f64:0\"]],[\"invoke\",153,0,\"f64.store\",[\"i32:4294967293\",\"f64:0\"]],[\"invoke\",154,0,\"f64.store\",[\"i32:4294967292\",\"f64:0\"]],[\"invoke\",155,0,\"f64.store\",[\"i32:4294967291\",\"f64:0\"]],[\"invoke\",156,0,\"f64.store\",[\"i32:4294967290\",\"f64:0\"]],[\"invoke\",157,0,\"f64.store\",[\"i32:4294967289\",\"f64:0\"]],[\"invoke\",158,0,\"f64.store\",[\"i32:4294967288\",\"f64:0\"]],[\"invoke\",159,0,\"i32.store8\",[\"i32:65536\",\"i32:0\"]],[\"invoke\",160,0,\"i32.store8\",[\"i32:4294967295\",\"i32:0\"]],[\"invoke\",161,0,\"i32.store16\",[\"i32:65536\",\"i32:0\"]],[\"invoke\",162,0,\"i32.store16\",[\"i32:65535\",\"i32:0\"]],[\"invoke\",163,0,\"i32.store16\",[\"i32:4294967295\",\"i32:0\"]],[\"invoke\",164,0,\"i32.store16\",[\"i32:4294967294\",\"i32:0\"]],[\"invoke\",165,0,\"i64.store8\",[\"i32:65536\",\"i64:0\"]],[\"invoke\",166,0,\"i64.store8\",[\"i32:4294967295\",\"i64:0\"]],[\"invoke\",167,0,\"i64.store16\",[\"i32:65536\",\"i64:0\"]],[\"invoke\",168,0,\"i64.store16\",[\"i32:65535\",\"i64:0\"]],[\"invoke\",169,0,\"i64.store16\",[\"i32:4294967295\",\"i64:0\"]],[\"invoke\",170,0,\"i64.store16\",[\"i32:4294967294\",\"i64:0\"]],[\"invoke\",171,0,\"i64.store32\",[\"i32:65536\",\"i64:0\"]],[\"invoke\",172,0,\"i64.store32\",[\"i32:65535\",\"i64:0\"]],[\"invoke\",173,0,\"i64.store32\",[\"i32:65534\",\"i64:0\"]],[\"invoke\",174,0,\"i64.store32\",[\"i32:65533\",\"i64:0\"]],[\"invoke\",175,0,\"i64.store32\",[\"i32:4294967295\",\"i64:0\"]],[\"invoke\",176,0,\"i64.store32\",[\"i32:4294967294\",\"i64:0\"]],[\"invoke\",177,0,\"i64.store32\",[\"i32:4294967293\",\"i64:0\"]],[\"invoke\",178,0,\"i64.store32\",[\"i32:4294967292\",\"i64:0\"]],[\"invoke\",179,0,\"i32.load\",[\"i32:65536\"]],[\"invoke\",180,0,\"i32.load\",[\"i32:65535\"]],[\"invoke\",181,0,\"i32.load\",[\"i32:65534\"]],[\"invoke\",182,0,\"i32.load\",[\"i32:65533\"]],[\"invoke\",183,0,\"i32.load\",[\"i32:4294967295\"]],[\"invoke\",184,0,\"i32.load\",[\"i32:4294967294\"]],[\"invoke\",185,0,\"i32.load\",[\"i32:4294967293\"]],[\"invoke\",186,0,\"i32.load\",[\"i32:4294967292\"]],[\"invoke\",187,0,\"i64.load\",[\"i32:65536\"]],[\"invoke\",188,0,\"i64.load\",[\"i32:65535\"]],[\"invoke\",189,0,\"i64.load\",[\"i32:65534\"]],[\"invoke\",190,0,\"i64.load\",[\"i32:65533\"]],[\"invoke\",191,0,\"i64.load\",[\"i32:65532\"]],[\"invoke\",192,0,\"i64.load\",[\"i32:65531\"]],[\"invoke\",193,0,\"i64.load\",[\"i32:65530\"]],[\"invoke\",194,0,\"i64.load\",[\"i32:65529\"]],[\"invoke\",195,0,\"i64.load\",[\"i32:4294967295\"]],[\"invoke\",196,0,\"i64.load\",[\"i32:4294967294\"]],[\"invoke\",197,0,\"i64.load\",[\"i32:4294967293\"]],[\"invoke\",198,0,\"i64.load\",[\"i32:4294967292\"]],[\"invoke\",199,0,\"i64.load\",[\"i32:4294967291\"]],[\"invoke\",200,0,\"i64.load\",[\"i32:4294967290\"]],[\"invoke\",201,0,\"i64.load\",[\"i32:4294967289\"]],[\"invoke\",202,0,\"i64.load\",[\"i32:4294967288\"]],[\"invoke\",203,0,\"f32.load\",[\"i32:65536\"]],[\"invoke\",204,0,\"f32.load\",[\"i32:65535\"]],[\"invoke\",205,0,\"f32.load\",[\"i32:65534\"]],[\"invoke\",206,0,\"f32.load\",[\"i32:65533\"]],[\"invoke\",207,0,\"f32.load\",[\"i32:4294967295\"]],[\"invoke\",208,0,\"f32.load\",[\"i32:4294967294\"]],[\"invoke\",209,0,\"f32.load\",[\"i32:4294967293\"]],[\"invoke\",210,0,\"f32.load\",[\"i32:4294967292\"]],[\"invoke\",211,0,\"f64.load\",[\"i32:65536\"]],[\"invoke\",212,0,\"f64.load\",[\"i32:65535\"]],[\"invoke\",213,0,\"f64.load\",[\"i32:65534\"]],[\"invoke\",214,0,\"f64.load\",[\"i32:65533\"]],[\"invoke\",215,0,\"f64.load\",[\"i32:65532\"]],[\"invoke\",216,0,\"f64.load\",[\"i32:65531\"]],[\"invoke\",217,0,\"f64.load\",[\"i32:65530\"]],[\"invoke\",218,0,\"f64.load\",[\"i32:65529\"]],[\"invoke\",219,0,\"f64.load\",[\"i32:4294967295\"]],[\"invoke\",220,0,\"f64.load\",[\"i32:4294967294\"]],[\"invoke\",221,0,\"f64.load\",[\"i32:4294967293\"]],[\"invoke\",222,0,\"f64.load\",[\"i32:4294967292\"]],[\"invoke\",223,0,\"f64.load\",[\"i32:4294967291\"]],[\"invoke\",224,0,\"f64.load\",[\"i32:4294967290\"]],[\"invoke\",225,0,\"f64.load\",[\"i32:4294967289\"]],[\"invoke\",226,0,\"f64.load\",[\"i32:4294967288\"]],[\"invoke\",227,0,\"i32.load8_s\",[\"i32:65536\"]],[\"invoke\",228,0,\"i32.load8_s\",[\"i32:4294967295\"]],[\"invoke\",229,0,\"i32.load8_u\",[\"i32:65536\"]],[\"invoke\",230,0,\"i32.load8_u\",[\"i32:4294967295\"]],[\"invoke\",231,0,\"i32.load16_s\",[\"i32:65536\"]],[\"invoke\",232,0,\"i32.load16_s\",[\"i32:65535\"]],[\"invoke\",233,0,\"i32.load16_s\",[\"i32:4294967295\"]],[\"invoke\",234,0,\"i32.load16_s\",[\"i32:4294967294\"]],[\"invoke\",235,0,\"i32.load16_u\",[\"i32:65536\"]],[\"invoke\",236,0,\"i32.load16_u\",[\"i32:65535\"]],[\"invoke\",237,0,\"i32.load16_u\",[\"i32:4294967295\"]],[\"invoke\",238,0,\"i32.load16_u\",[\"i32:4294967294\"]],[\"invoke\",239,0,\"i64.load8_s\",[\"i32:65536\"]],[\"invoke\",240,0,\"i64.load8_s\",[\"i32:4294967295\"]],[\"invoke\",241,0,\"i64.load8_u\",[\"i32:65536\"]],[\"invoke\",242,0,\"i64.load8_u\",[\"i32:4294967295\"]],[\"invoke\",243,0,\"i64.load16_s\",[\"i32:65536\"]],[\"invoke\",244,0,\"i64.load16_s\",[\"i32:65535\"]],[\"invoke\",245,0,\"i64.load16_s\",[\"i32:4294967295\"]],[\"invoke\",246,0,\"i64.load16_s\",[\"i32:4294967294\"]],[\"invoke\",247,0,\"i64.load16_u\",[\"i32:65536\"]],[\"invoke\",248,0,\"i64.load16_u\",[\"i32:65535\"]],[\"invoke\",249,0,\"i64.load16_u\",[\"i32:4294967295\"]],[\"invoke\",250,0,\"i64.load16_u\",[\"i32:4294967294\"]],[\"invoke\",251,0,\"i64.load32_s\",[\"i32:65536\"]],[\"invoke\",252,0,\"i64.load32_s\",[\"i32:65535\"]],[\"invoke\",253,0,\"i64.load32_s\",[\"i32:65534\"]],[\"invoke\",254,0,\"i64.load32_s\",[\"i32:65533\"]],[\"invoke\",255,0,\"i64.load32_s\",[\"i32:4294967295\"]],[\"invoke\",256,0,\"i64.load32_s\",[\"i32:4294967294\"]],[\"invoke\",257,0,\"i64.load32_s\",[\"i32:4294967293\"]],[\"invoke\",258,0,\"i64.load32_s\",[\"i32:4294967292\"]],[\"invoke\",259,0,\"i64.load32_u\",[\"i32:65536\"]],[\"invoke\",260,0,\"i64.load32_u\",[\"i32:65535\"]],[\"invoke\",261,0,\"i64.load32_u\",[\"i32:65534\"]],[\"invoke\",262,0,\"i64.load32_u\",[\"i32:65533\"]],[\"invoke\",263,0,\"i64.load32_u\",[\"i32:4294967295\"]],[\"invoke\",264,0,\"i64.load32_u\",[\"i32:4294967294\"]],[\"invoke\",265,0,\"i64.load32_u\",[\"i32:4294967293\"]],[\"invoke\",266,0,\"i64.load32_u\",[\"i32:4294967292\"]],[\"invoke\",269,0,\"i64.load\",[\"i32:65528\"]],[\"invoke\",270,0,\"i64.load\",[\"i32:0\"]],[\"invoke\",274,0,\"i64.store\",[\"i32:65528\",\"i64:0\"]],[\"invoke\",275,0,\"i32.store\",[\"i32:65533\",\"i32:305419896\"]],[\"invoke\",276,0,\"i32.load\",[\"i32:65532\"]],[\"invoke\",277,0,\"i64.store\",[\"i32:65529\",\"i64:1311768467294899695\"]],[\"invoke\",278,0,\"i64.load\",[\"i32:65528\"]],[\"invoke\",279,0,\"f32.store\",[\"i32:65533\",\"f32:1301390004\"]],[\"invoke\",280,0,\"f32.load\",[\"i32:65532\"]],[\"invoke\",281,0,\"f64.store\",[\"i32:65529\",\"f64:4878018892390247374\"]],[\"invoke\",282,0,\"f64.load\",[\"i32:65528\"]],[\"file\",\"bulk.wast\"],[\"module\",2,63],[\"module\",6,64],[\"module\",13,65],[\"invoke\",27,0,\"fill\",[\"i32:1\",\"i32:255\",\"i32:3\"]],[\"invoke\",28,0,\"load8_u\",[\"i32:0\"]],[\"invoke\",29,0,\"load8_u\",[\"i32:1\"]],[\"invoke\",30,0,\"load8_u\",[\"i32:2\"]],[\"invoke\",31,0,\"load8_u\",[\"i32:3\"]],[\"invoke\",32,0,\"load8_u\",[\"i32:4\"]],[\"invoke\",35,0,\"fill\",[\"i32:0\",\"i32:48042\",\"i32:2\"]],[\"invoke\",36,0,\"load8_u\",[\"i32:0\"]],[\"invoke\",37,0,\"load8_u\",[\"i32:1\"]],[\"invoke\",40,0,\"fill\",[\"i32:0\",\"i32:0\",\"i32:65536\"]],[\"invoke\",43,0,\"fill\",[\"i32:65280\",\"i32:1\",\"i32:257\"]],[\"invoke\",45,0,\"load8_u\",[\"i32:65280\"]],[\"invoke\",46,0,\"load8_u\",[\"i32:65535\"]],[\"invoke\",49,0,\"fill\",[\"i32:65536\",\"i32:0\",\"i32:0\"]],[\"invoke\",52,0,\"fill\",[\"i32:65537\",\"i32:0\",\"i32:0\"]],[\"module\",57,66],[\"invoke\",71,0,\"copy\",[\"i32:10\",\"i32:0\",\"i32:4\"]],[\"invoke\",73,0,\"load8_u\",[\"i32:9\"]],[\"invoke\",74,0,\"load8_u\",[\"i32:10\"]],[\"invoke\",75,0,\"load8_u\",[\"i32:11\"]],[\"invoke\",76,0,\"load8_u\",[\"i32:12\"]],[\"invoke\",77,0,\"load8_u\",[\"i32:13\"]],[\"invoke\",78,0,\"load8_u\",[\"i32:14\"]],[\"invoke\",81,0,\"copy\",[\"i32:8\",\"i32:10\",\"i32:4\"]],[\"invoke\",82,0,\"load8_u\",[\"i32:8\"]],[\"invoke\",83,0,\"load8_u\",[\"i32:9\"]],[\"invoke\",84,0,\"load8_u\",[\"i32:10\"]],[\"invoke\",85,0,\"load8_u\",[\"i32:11\"]],[\"invoke\",86,0,\"load8_u\",[\"i32:12\"]],[\"invoke\",87,0,\"load8_u\",[\"i32:13\"]],[\"invoke\",90,0,\"copy\",[\"i32:10\",\"i32:7\",\"i32:6\"]],[\"invoke\",91,0,\"load8_u\",[\"i32:10\"]],[\"invoke\",92,0,\"load8_u\",[\"i32:11\"]],[\"invoke\",93,0,\"load8_u\",[\"i32:12\"]],[\"invoke\",94,0,\"load8_u\",[\"i32:13\"]],[\"invoke\",95,0,\"load8_u\",[\"i32:14\"]],[\"invoke\",96,0,\"load8_u\",[\"i32:15\"]],[\"invoke\",97,0,\"load8_u\",[\"i32:16\"]],[\"invoke\",100,0,\"copy\",[\"i32:65280\",\"i32:0\",\"i32:256\"]],[\"invoke\",101,0,\"copy\",[\"i32:65024\",\"i32:65280\",\"i32:256\"]],[\"invoke\",104,0,\"copy\",[\"i32:65536\",\"i32:0\",\"i32:0\"]],[\"invoke\",105,0,\"copy\",[\"i32:0\",\"i32:65536\",\"i32:0\"]],[\"invoke\",108,0,\"copy\",[\"i32:65537\",\"i32:0\",\"i32:0\"]],[\"invoke\",110,0,\"copy\",[\"i32:0\",\"i32:65537\",\"i32:0\"]],[\"module\",115,67],[\"invoke\",129,0,\"init\",[\"i32:0\",\"i32:1\",\"i32:2\"]],[\"invoke\",130,0,\"load8_u\",[\"i32:0\"]],[\"invoke\",131,0,\"load8_u\",[\"i32:1\"]],[\"invoke\",132,0,\"load8_u\",[\"i32:2\"]],[\"invoke\",135,0,\"init\",[\"i32:65532\",\"i32:0\",\"i32:4\"]],[\"invoke\",138,0,\"init\",[\"i32:65534\",\"i32:0\",\"i32:3\"]],[\"invoke\",140,0,\"load8_u\",[\"i32:65534\"]],[\"invoke\",141,0,\"load8_u\",[\"i32:65535\"]],[\"invoke\",144,0,\"init\",[\"i32:65536\",\"i32:0\",\"i32:0\"]],[\"invoke\",145,0,\"init\",[\"i32:0\",\"i32:4\",\"i32:0\"]],[\"invoke\",148,0,\"init\",[\"i32:65537\",\"i32:0\",\"i32:0\"]],[\"invoke\",150,0,\"init\",[\"i32:0\",\"i32:5\",\"i32:0\"]],[\"module\",154,68],[\"invoke\",168,0,\"init_passive\",[\"i32:1\"]],[\"invoke\",169,0,\"drop_passive\",[]],[\"invoke\",170,0,\"drop_passive\",[]],[\"invoke\",171,0,\"init_passive\",[\"i32:0\"]],[\"invoke\",172,0,\"init_passive\",[\"i32:1\"]],[\"invoke\",173,0,\"init_passive\",[\"i32:0\"]],[\"invoke\",174,0,\"drop_active\",[]],[\"invoke\",175,0,\"init_active\",[\"i32:0\"]],[\"invoke\",176,0,\"init_active\",[\"i32:1\"]],[\"invoke\",177,0,\"init_active\",[\"i32:0\"]],[\"module\",181,69],[\"module\",196,70],[\"module\",199,71],[\"invoke\",219,0,\"init\",[\"i32:2\",\"i32:0\",\"i32:2\"]],[\"invoke\",221,0,\"call\",[\"i32:2\"]],[\"invoke\",224,0,\"init\",[\"i32:0\",\"i32:1\",\"i32:2\"]],[\"invoke\",225,0,\"call\",[\"i32:0\"]],[\"invoke\",226,0,\"call\",[\"i32:1\"]],[\"invoke\",227,0,\"call\",[\"i32:2\"]],[\"invoke\",230,0,\"init\",[\"i32:1\",\"i32:2\",\"i32:2\"]],[\"invoke\",233,0,\"init\",[\"i32:3\",\"i32:0\",\"i32:0\"]],[\"invoke\",234,0,\"init\",[\"i32:0\",\"i32:4\",\"i32:0\"]],[\"invoke\",237,0,\"init\",[\"i32:4\",\"i32:0\",\"i32:0\"]],[\"invoke\",239,0,\"init\",[\"i32:0\",\"i32:5\",\"i32:0\"]],[\"module\",244,72],[\"invoke\",261,0,\"init_passive\",[\"i32:1\"]],[\"invoke\",262,0,\"drop_passive\",[]],[\"invoke\",263,0,\"drop_passive\",[]],[\"invoke\",264,0,\"init_passive\",[\"i32:0\"]],[\"invoke\",265,0,\"init_passive\",[\"i32:1\"]],[\"invoke\",266,0,\"init_passive\",[\"i32:0\"]],[\"invoke\",267,0,\"drop_active\",[]],[\"invoke\",268,0,\"init_active\",[\"i32:0\"]],[\"invoke\",269,0,\"init_active\",[\"i32:1\"]],[\"invoke\",270,0,\"init_active\",[\"i32:0\"]],[\"module\",274,73],[\"module\",297,74],[\"module\",300,75],[\"invoke\",319,0,\"copy\",[\"i32:3\",\"i32:0\",\"i32:3\"]],[\"invoke\",321,0,\"call\",[\"i32:3\"]],[\"invoke\",322,0,\"call\",[\"i32:4\"]],[\"invoke\",323,0,\"call\",[\"i32:5\"]],[\"invoke\",326,0,\"copy\",[\"i32:0\",\"i32:1\",\"i32:3\"]],[\"invoke\",328,0,\"call\",[\"i32:0\"]],[\"invoke\",329,0,\"call\",[\"i32:1\"]],[\"invoke\",330,0,\"call\",[\"i32:2\"]],[\"invoke\",333,0,\"copy\",[\"i32:2\",\"i32:0\",\"i32:3\"]],[\"invoke\",335,0,\"call\",[\"i32:2\"]],[\"invoke\",336,0,\"call\",[\"i32:3\"]],[\"invoke\",337,0,\"call\",[\"i32:4\"]],[\"invoke\",340,0,\"copy\",[\"i32:6\",\"i32:8\",\"i32:2\"]],[\"invoke\",341,0,\"copy\",[\"i32:8\",\"i32:6\",\"i32:2\"]],[\"invoke\",344,0,\"copy\",[\"i32:10\",\"i32:0\",\"i32:0\"]],[\"invoke\",345,0,\"copy\",[\"i32:0\",\"i32:10\",\"i32:0\"]],[\"invoke\",348,0,\"copy\",[\"i32:11\",\"i32:0\",\"i32:0\"]],[\"invoke\",350,0,\"copy\",[\"i32:0\",\"i32:11\",\"i32:0\"]],[\"file\",\"memory_copy.wast\"],[\"module\",6,76],[\"invoke\",15,0,\"test\",[]],[\"invoke\",27,0,\"load8_u\",[\"i32:10\"]],[\"invoke\",38,0,\"load8_u\",[\"i32:21\"]],[\"module\",48,77],[\"invoke\",60,0,\"load8_u\",[\"i32:1\"]],[\"invoke\",71,0,\"load8_u\",[\"i32:12\"]],[\"invoke\",82,0,\"load8_u\",[\"i32:23\"]],[\"module\",90,78],[\"invoke\",104,0,\"load8_u\",[\"i32:3\"]],[\"invoke\",115,0,\"load8_u\",[\"i32:14\"]],[\"invoke\",126,0,\"load8_u\",[\"i32:25\"]],[\"module\",132,79],[\"invoke\",147,0,\"load8_u\",[\"i32:4\"]],[\"invoke\",158,0,\"load8_u\",[\"i32:15\"]],[\"invoke\",169,0,\"load8_u\",[\"i32:26\"]],[\"module\",174,80],[\"invoke\",191,0,\"load8_u\",[\"i32:6\"]],[\"invoke\",202,0,\"load8_u\",[\"i32:17\"]],[\"invoke\",213,0,\"load8_u\",[\"i32:28\"]],[\"module\",216,81],[\"invoke\",235,0,\"load8_u\",[\"i32:8\"]],[\"invoke\",246,0,\"load8_u\",[\"i32:19\"]],[\"module\",258,82],[\"invoke\",267,0,\"test\",[]],[\"invoke\",278,0,\"load8_u\",[\"i32:9\"]],[\"invoke\",289,0,\"load8_u\",[\"i32:20\"]],[\"module\",300,83],[\"invoke\",311,0,\"load8_u\",[\"i32:0\"]],[\"invoke\",322,0,\"load8_u\",[\"i32:11\"]],[\"invoke\",333,0,\"load8_u\",[\"i32:22\"]],[\"module\",342,84],[\"invoke\",355,0,\"load8_u\",[\"i32:2\"]],[\"invoke\",366,0,\"load8_u\",[\"i32:13\"]],[\"invoke\",377,0,\"load8_u\",[\"i32:1014\"]],[\"invoke\",387,0,\"load8_u\",[\"i32:3004\"]],[\"invoke\",398,0,\"load8_u\",[\"i32:5193\"]],[\"invoke\",409,0,\"load8_u\",[\"i32:7382\"]],[\"invoke\",420,0,\"load8_u\",[\"i32:9571\"]],[\"invoke\",431,0,\"load8_u\",[\"i32:11760\"]],[\"invoke\",442,0,\"load8_u\",[\"i32:13949\"]],[\"invoke\",453,0,\"load8_u\",[\"i32:16138\"]],[\"invoke\",464,0,\"load8_u\",[\"i32:18327\"]],[\"invoke\",475,0,\"load8_u\",[\"i32:20516\"]],[\"invoke\",485,0,\"load8_u\",[\"i32:22506\"]],[\"invoke\",496,0,\"load8_u\",[\"i32:24695\"]],[\"invoke\",507,0,\"load8_u\",[\"i32:26884\"]],[\"invoke\",518,0,\"load8_u\",[\"i32:29073\"]],[\"invoke\",529,0,\"load8_u\",[\"i32:31262\"]],[\"invoke\",540,0,\"load8_u\",[\"i32:33451\"]],[\"invoke\",551,0,\"load8_u\",[\"i32:35640\"]],[\"invoke\",562,0,\"load8_u\",[\"i32:37829\"]],[\"invoke\",572,0,\"load8_u\",[\"i32:39819\"]],[\"invoke\",583,0,\"load8_u\",[\"i32:42008\"]],[\"invoke\",594,0,\"load8_u\",[\"i32:44197\"]],[\"invoke\",605,0,\"load8_u\",[\"i32:46386\"]],[\"invoke\",616,0,\"load8_u\",[\"i32:48575\"]],[\"invoke\",627,0,\"load8_u\",[\"i32:50764\"]],[\"invoke\",638,0,\"load8_u\",[\"i32:52953\"]],[\"invoke\",649,0,\"load8_u\",[\"i32:55142\"]],[\"invoke\",660,0,\"load8_u\",[\"i32:57331\"]],[\"invoke\",670,0,\"load8_u\",[\"i32:59321\"]],[\"invoke\",681,0,\"load8_u\",[\"i32:61510\"]],[\"invoke\",692,0,\"load8_u\",[\"i32:63699\"]],[\"module\",703,85],[\"invoke\",714,0,\"load8_u\",[\"i32:0\"]],[\"invoke\",725,0,\"load8_u\",[\"i32:11\"]],[\"invoke\",736,0,\"load8_u\",[\"i32:418\"]],[\"invoke\",747,0,\"load8_u\",[\"i32:2607\"]],[\"invoke\",758,0,\"load8_u\",[\"i32:4796\"]],[\"invoke\",768,0,\"load8_u\",[\"i32:6786\"]],[\"invoke\",779,0,\"load8_u\",[\"i32:8975\"]],[\"invoke\",790,0,\"load8_u\",[\"i32:11164\"]],[\"invoke\",801,0,\"load8_u\",[\"i32:13353\"]],[\"invoke\",812,0,\"load8_u\",[\"i32:15542\"]],[\"invoke\",823,0,\"load8_u\",[\"i32:17731\"]],[\"invoke\",834,0,\"load8_u\",[\"i32:19920\"]],[\"invoke\",845,0,\"load8_u\",[\"i32:22109\"]],[\"invoke\",856,0,\"load8_u\",[\"i32:24298\"]],[\"invoke\",866,0,\"load8_u\",[\"i32:26288\"]],[\"invoke\",877,0,\"load8_u\",[\"i32:28477\"]],[\"invoke\",888,0,\"load8_u\",[\"i32:30666\"]],[\"invoke\",899,0,\"load8_u\",[\"i32:32855\"]],[\"invoke\",910,0,\"load8_u\",[\"i32:35044\"]],[\"invoke\",921,0,\"load8_u\",[\"i32:37233\"]],[\"invoke\",932,0,\"load8_u\",[\"i32:39422\"]],[\"invoke\",943,0,\"load8_u\",[\"i32:41611\"]],[\"invoke\",953,0,\"load8_u\",[\"i32:43601\"]],[\"invoke\",964,0,\"load8_u\",[\"i32:45790\"]],[\"invoke\",975,0,\"load8_u\",[\"i32:47979\"]],[\"invoke\",986,0,\"load8_u\",[\"i32:50168\"]],[\"invoke\",997,0,\"load8_u\",[\"i32:52357\"]],[\"invoke\",1008,0,\"load8_u\",[\"i32:54546\"]],[\"invoke\",1019,0,\"load8_u\",[\"i32:56735\"]],[\"invoke\",1030,0,\"load8_u\",[\"i32:58924\"]],[\"invoke\",1041,0,\"load8_u\",[\"i32:61113\"]],[\"invoke\",1051,0,\"load8_u\",[\"i32:63103\"]],[\"invoke\",1062,0,\"load8_u\",[\"i32:65292\"]],[\"module\",1065,86],[\"invoke\",1084,0,\"load8_u\",[\"i32:1790\"]],[\"invoke\",1095,0,\"load8_u\",[\"i32:3979\"]],[\"invoke\",1106,0,\"load8_u\",[\"i32:6168\"]],[\"invoke\",1117,0,\"load8_u\",[\"i32:8357\"]],[\"invoke\",1128,0,\"load8_u\",[\"i32:10546\"]],[\"invoke\",1139,0,\"load8_u\",[\"i32:12735\"]],[\"invoke\",1149,0,\"load8_u\",[\"i32:14725\"]],[\"invoke\",1160,0,\"load8_u\",[\"i32:16914\"]],[\"invoke\",1171,0,\"load8_u\",[\"i32:19103\"]],[\"invoke\",1182,0,\"load8_u\",[\"i32:21292\"]],[\"invoke\",1193,0,\"load8_u\",[\"i32:23481\"]],[\"invoke\",1204,0,\"load8_u\",[\"i32:25670\"]],[\"invoke\",1215,0,\"load8_u\",[\"i32:27859\"]],[\"invoke\",1226,0,\"load8_u\",[\"i32:30048\"]],[\"invoke\",1237,0,\"load8_u\",[\"i32:32237\"]],[\"invoke\",1247,0,\"load8_u\",[\"i32:34227\"]],[\"invoke\",1258,0,\"load8_u\",[\"i32:36416\"]],[\"invoke\",1269,0,\"load8_u\",[\"i32:38605\"]],[\"invoke\",1280,0,\"load8_u\",[\"i32:40794\"]],[\"invoke\",1291,0,\"load8_u\",[\"i32:42983\"]],[\"invoke\",1302,0,\"load8_u\",[\"i32:45172\"]],[\"invoke\",1313,0,\"load8_u\",[\"i32:47361\"]],[\"invoke\",1324,0,\"load8_u\",[\"i32:49550\"]],[\"invoke\",1334,0,\"load8_u\",[\"i32:51540\"]],[\"invoke\",1345,0,\"load8_u\",[\"i32:53729\"]],[\"invoke\",1356,0,\"load8_u\",[\"i32:55918\"]],[\"invoke\",1367,0,\"load8_u\",[\"i32:58107\"]],[\"invoke\",1378,0,\"load8_u\",[\"i32:60296\"]],[\"invoke\",1389,0,\"load8_u\",[\"i32:62485\"]],[\"invoke\",1400,0,\"load8_u\",[\"i32:64674\"]],[\"invoke\",1411,0,\"load8_u\",[\"i32:65522\"]],[\"invoke\",1422,0,\"load8_u\",[\"i32:65533\"]],[\"module\",1426,87],[\"invoke\",1443,0,\"load8_u\",[\"i32:1392\"]],[\"invoke\",1454,0,\"load8_u\",[\"i32:3581\"]],[\"invoke\",1465,0,\"load8_u\",[\"i32:5770\"]],[\"invoke\",1476,0,\"load8_u\",[\"i32:7959\"]],[\"invoke\",1487,0,\"load8_u\",[\"i32:10148\"]],[\"invoke\",1498,0,\"load8_u\",[\"i32:12337\"]],[\"invoke\",1509,0,\"load8_u\",[\"i32:14526\"]],[\"invoke\",1520,0,\"load8_u\",[\"i32:16715\"]],[\"invoke\",1530,0,\"load8_u\",[\"i32:18705\"]],[\"invoke\",1541,0,\"load8_u\",[\"i32:20894\"]],[\"invoke\",1552,0,\"load8_u\",[\"i32:23083\"]],[\"invoke\",1563,0,\"load8_u\",[\"i32:25272\"]],[\"invoke\",1574,0,\"load8_u\",[\"i32:27461\"]],[\"invoke\",1585,0,\"load8_u\",[\"i32:29650\"]],[\"invoke\",1596,0,\"load8_u\",[\"i32:31839\"]],[\"invoke\",1607,0,\"load8_u\",[\"i32:34028\"]],[\"invoke\",1618,0,\"load8_u\",[\"i32:36217\"]],[\"invoke\",1628,0,\"load8_u\",[\"i32:38207\"]],[\"invoke\",1639,0,\"load8_u\",[\"i32:40396\"]],[\"invoke\",1650,0,\"load8_u\",[\"i32:42585\"]],[\"invoke\",1661,0,\"load8_u\",[\"i32:44774\"]],[\"invoke\",1672,0,\"load8_u\",[\"i32:46963\"]],[\"invoke\",1683,0,\"load8_u\",[\"i32:49152\"]],[\"invoke\",1694,0,\"load8_u\",[\"i32:51341\"]],[\"invoke\",1705,0,\"load8_u\",[\"i32:53530\"]],[\"invoke\",1715,0,\"load8_u\",[\"i32:55520\"]],[\"invoke\",1726,0,\"load8_u\",[\"i32:57709\"]],[\"invoke\",1737,0,\"load8_u\",[\"i32:59898\"]],[\"invoke\",1748,0,\"load8_u\",[\"i32:62087\"]],[\"invoke\",1759,0,\"load8_u\",[\"i32:64276\"]],[\"invoke\",1770,0,\"load8_u\",[\"i32:65519\"]],[\"invoke\",1781,0,\"load8_u\",[\"i32:65530\"]],[\"module\",1788,88],[\"invoke\",1803,0,\"load8_u\",[\"i32:994\"]],[\"invoke\",1814,0,\"load8_u\",[\"i32:3183\"]],[\"invoke\",1824,0,\"load8_u\",[\"i32:5173\"]],[\"invoke\",1835,0,\"load8_u\",[\"i32:7362\"]],[\"invoke\",1846,0,\"load8_u\",[\"i32:9551\"]],[\"invoke\",1857,0,\"load8_u\",[\"i32:11740\"]],[\"invoke\",1868,0,\"load8_u\",[\"i32:13929\"]],[\"invoke\",1879,0,\"load8_u\",[\"i32:16118\"]],[\"invoke\",1890,0,\"load8_u\",[\"i32:18307\"]],[\"invoke\",1901,0,\"load8_u\",[\"i32:20496\"]],[\"invoke\",1911,0,\"load8_u\",[\"i32:22486\"]],[\"invoke\",1922,0,\"load8_u\",[\"i32:24675\"]],[\"invoke\",1933,0,\"load8_u\",[\"i32:26864\"]],[\"invoke\",1944,0,\"load8_u\",[\"i32:29053\"]],[\"invoke\",1955,0,\"load8_u\",[\"i32:31242\"]],[\"invoke\",1966,0,\"load8_u\",[\"i32:33431\"]],[\"invoke\",1977,0,\"load8_u\",[\"i32:35620\"]],[\"invoke\",1988,0,\"load8_u\",[\"i32:37809\"]],[\"invoke\",1999,0,\"load8_u\",[\"i32:39998\"]],[\"invoke\",2009,0,\"load8_u\",[\"i32:41988\"]],[\"invoke\",2020,0,\"load8_u\",[\"i32:44177\"]],[\"invoke\",2031,0,\"load8_u\",[\"i32:46366\"]],[\"invoke\",2042,0,\"load8_u\",[\"i32:48555\"]],[\"invoke\",2053,0,\"load8_u\",[\"i32:50744\"]],[\"invoke\",2064,0,\"load8_u\",[\"i32:52933\"]],[\"invoke\",2075,0,\"load8_u\",[\"i32:55122\"]],[\"invoke\",2086,0,\"load8_u\",[\"i32:57311\"]],[\"invoke\",2096,0,\"load8_u\",[\"i32:59301\"]],[\"invoke\",2107,0,\"load8_u\",[\"i32:61490\"]],[\"invoke\",2118,0,\"load8_u\",[\"i32:63679\"]],[\"invoke\",2129,0,\"load8_u\",[\"i32:65487\"]],[\"invoke\",2140,0,\"load8_u\",[\"i32:65498\"]],[\"module\",2149,86],[\"invoke\",2162,0,\"load8_u\",[\"i32:596\"]],[\"invoke\",2173,0,\"load8_u\",[\"i32:2785\"]],[\"invoke\",2184,0,\"load8_u\",[\"i32:4974\"]],[\"invoke\",2195,0,\"load8_u\",[\"i32:7163\"]],[\"invoke\",2205,0,\"load8_u\",[\"i32:9153\"]],[\"invoke\",2216,0,\"load8_u\",[\"i32:11342\"]],[\"invoke\",2227,0,\"load8_u\",[\"i32:13531\"]],[\"invoke\",2238,0,\"load8_u\",[\"i32:15720\"]],[\"invoke\",2249,0,\"load8_u\",[\"i32:17909\"]],[\"invoke\",2260,0,\"load8_u\",[\"i32:20098\"]],[\"invoke\",2271,0,\"load8_u\",[\"i32:22287\"]],[\"invoke\",2282,0,\"load8_u\",[\"i32:24476\"]],[\"invoke\",2292,0,\"load8_u\",[\"i32:26466\"]],[\"invoke\",2303,0,\"load8_u\",[\"i32:28655\"]],[\"invoke\",2314,0,\"load8_u\",[\"i32:30844\"]],[\"invoke\",2325,0,\"load8_u\",[\"i32:33033\"]],[\"invoke\",2336,0,\"load8_u\",[\"i32:35222\"]],[\"invoke\",2347,0,\"load8_u\",[\"i32:37411\"]],[\"invoke\",2358,0,\"load8_u\",[\"i32:39600\"]],[\"invoke\",2369,0,\"load8_u\",[\"i32:41789\"]],[\"invoke\",2380,0,\"load8_u\",[\"i32:43978\"]],[\"invoke\",2390,0,\"load8_u\",[\"i32:45968\"]],[\"invoke\",2401,0,\"load8_u\",[\"i32:48157\"]],[\"invoke\",2412,0,\"load8_u\",[\"i32:50346\"]],[\"invoke\",2423,0,\"load8_u\",[\"i32:52535\"]],[\"invoke\",2434,0,\"load8_u\",[\"i32:54724\"]],[\"invoke\",2445,0,\"load8_u\",[\"i32:56913\"]],[\"invoke\",2456,0,\"load8_u\",[\"i32:59102\"]],[\"invoke\",2467,0,\"load8_u\",[\"i32:61291\"]],[\"invoke\",2477,0,\"load8_u\",[\"i32:63281\"]],[\"invoke\",2488,0,\"load8_u\",[\"i32:65470\"]],[\"invoke\",2499,0,\"load8_u\",[\"i32:65526\"]],[\"module\",2510,89],[\"invoke\",2521,0,\"load8_u\",[\"i32:198\"]],[\"invoke\",2532,0,\"load8_u\",[\"i32:2387\"]],[\"invoke\",2543,0,\"load8_u\",[\"i32:4576\"]],[\"invoke\",2554,0,\"load8_u\",[\"i32:6765\"]],[\"invoke\",2565,0,\"load8_u\",[\"i32:8954\"]],[\"invoke\",2576,0,\"load8_u\",[\"i32:11143\"]],[\"invoke\",2586,0,\"load8_u\",[\"i32:13133\"]],[\"invoke\",2597,0,\"load8_u\",[\"i32:15322\"]],[\"invoke\",2608,0,\"load8_u\",[\"i32:17511\"]],[\"invoke\",2619,0,\"load8_u\",[\"i32:19700\"]],[\"invoke\",2630,0,\"load8_u\",[\"i32:21889\"]],[\"invoke\",2641,0,\"load8_u\",[\"i32:24078\"]],[\"invoke\",2652,0,\"load8_u\",[\"i32:26267\"]],[\"invoke\",2663,0,\"load8_u\",[\"i32:28456\"]],[\"invoke\",2673,0,\"load8_u\",[\"i32:30446\"]],[\"invoke\",2684,0,\"load8_u\",[\"i32:32635\"]],[\"invoke\",2695,0,\"load8_u\",[\"i32:34824\"]],[\"invoke\",2706,0,\"load8_u\",[\"i32:37013\"]],[\"invoke\",2717,0,\"load8_u\",[\"i32:39202\"]],[\"invoke\",2728,0,\"load8_u\",[\"i32:41391\"]],[\"invoke\",2739,0,\"load8_u\",[\"i32:43580\"]],[\"invoke\",2750,0,\"load8_u\",[\"i32:45769\"]],[\"invoke\",2761,0,\"load8_u\",[\"i32:47958\"]],[\"invoke\",2771,0,\"load8_u\",[\"i32:49948\"]],[\"invoke\",2782,0,\"load8_u\",[\"i32:52137\"]],[\"invoke\",2793,0,\"load8_u\",[\"i32:54326\"]],[\"invoke\",2804,0,\"load8_u\",[\"i32:56515\"]],[\"invoke\",2815,0,\"load8_u\",[\"i32:58704\"]],[\"invoke\",2826,0,\"load8_u\",[\"i32:60893\"]],[\"invoke\",2837,0,\"load8_u\",[\"i32:63082\"]],[\"invoke\",2848,0,\"load8_u\",[\"i32:65271\"]],[\"invoke\",2858,0,\"load8_u\",[\"i32:65514\"]],[\"invoke\",2869,0,\"load8_u\",[\"i32:65525\"]],[\"module\",2871,86],[\"invoke\",2891,0,\"load8_u\",[\"i32:1989\"]],[\"invoke\",2902,0,\"load8_u\",[\"i32:4178\"]],[\"invoke\",2913,0,\"load8_u\",[\"i32:6367\"]],[\"invoke\",2924,0,\"load8_u\",[\"i32:8556\"]],[\"invoke\",2935,0,\"load8_u\",[\"i32:10745\"]],[\"invoke\",2946,0,\"load8_u\",[\"i32:12934\"]],[\"invoke\",2957,0,\"load8_u\",[\"i32:15123\"]],[\"invoke\",2967,0,\"load8_u\",[\"i32:17113\"]],[\"invoke\",2978,0,\"load8_u\",[\"i32:19302\"]],[\"invoke\",2989,0,\"load8_u\",[\"i32:21491\"]],[\"invoke\",3000,0,\"load8_u\",[\"i32:23680\"]],[\"invoke\",3011,0,\"load8_u\",[\"i32:25869\"]],[\"invoke\",3022,0,\"load8_u\",[\"i32:28058\"]],[\"invoke\",3033,0,\"load8_u\",[\"i32:30247\"]],[\"invoke\",3044,0,\"load8_u\",[\"i32:32436\"]],[\"invoke\",3054,0,\"load8_u\",[\"i32:34426\"]],[\"invoke\",3065,0,\"load8_u\",[\"i32:36615\"]],[\"invoke\",3076,0,\"load8_u\",[\"i32:38804\"]],[\"invoke\",3087,0,\"load8_u\",[\"i32:40993\"]],[\"invoke\",3098,0,\"load8_u\",[\"i32:43182\"]],[\"invoke\",3109,0,\"load8_u\",[\"i32:45371\"]],[\"invoke\",3120,0,\"load8_u\",[\"i32:47560\"]],[\"invoke\",3131,0,\"load8_u\",[\"i32:49749\"]],[\"invoke\",3142,0,\"load8_u\",[\"i32:51938\"]],[\"invoke\",3152,0,\"load8_u\",[\"i32:53928\"]],[\"invoke\",3163,0,\"load8_u\",[\"i32:56117\"]],[\"invoke\",3174,0,\"load8_u\",[\"i32:58306\"]],[\"invoke\",3185,0,\"load8_u\",[\"i32:60495\"]],[\"invoke\",3196,0,\"load8_u\",[\"i32:62684\"]],[\"invoke\",3207,0,\"load8_u\",[\"i32:64873\"]],[\"invoke\",3218,0,\"load8_u\",[\"i32:65523\"]],[\"invoke\",3229,0,\"load8_u\",[\"i32:65534\"]],[\"module\",3232,86],[\"invoke\",3250,0,\"load8_u\",[\"i32:1591\"]],[\"invoke\",3261,0,\"load8_u\",[\"i32:3780\"]],[\"invoke\",3272,0,\"load8_u\",[\"i32:5969\"]],[\"invoke\",3283,0,\"load8_u\",[\"i32:8158\"]],[\"invoke\",3294,0,\"load8_u\",[\"i32:10347\"]],[\"invoke\",3305,0,\"load8_u\",[\"i32:12536\"]],[\"invoke\",3316,0,\"load8_u\",[\"i32:14725\"]],[\"invoke\",3327,0,\"load8_u\",[\"i32:16914\"]],[\"invoke\",3338,0,\"load8_u\",[\"i32:19103\"]],[\"invoke\",3348,0,\"load8_u\",[\"i32:21093\"]],[\"invoke\",3359,0,\"load8_u\",[\"i32:23282\"]],[\"invoke\",3370,0,\"load8_u\",[\"i32:25471\"]],[\"invoke\",3381,0,\"load8_u\",[\"i32:27660\"]],[\"invoke\",3392,0,\"load8_u\",[\"i32:29849\"]],[\"invoke\",3403,0,\"load8_u\",[\"i32:32038\"]],[\"invoke\",3414,0,\"load8_u\",[\"i32:34227\"]],[\"invoke\",3425,0,\"load8_u\",[\"i32:36416\"]],[\"invoke\",3435,0,\"load8_u\",[\"i32:38406\"]],[\"invoke\",3446,0,\"load8_u\",[\"i32:40595\"]],[\"invoke\",3457,0,\"load8_u\",[\"i32:42784\"]],[\"invoke\",3468,0,\"load8_u\",[\"i32:44973\"]],[\"invoke\",3479,0,\"load8_u\",[\"i32:47162\"]],[\"invoke\",3490,0,\"load8_u\",[\"i32:49351\"]],[\"invoke\",3501,0,\"load8_u\",[\"i32:51540\"]],[\"invoke\",3512,0,\"load8_u\",[\"i32:53729\"]],[\"invoke\",3523,0,\"load8_u\",[\"i32:55918\"]],[\"invoke\",3533,0,\"load8_u\",[\"i32:57908\"]],[\"invoke\",3544,0,\"load8_u\",[\"i32:60097\"]],[\"invoke\",3555,0,\"load8_u\",[\"i32:62286\"]],[\"invoke\",3566,0,\"load8_u\",[\"i32:64475\"]],[\"invoke\",3577,0,\"load8_u\",[\"i32:65521\"]],[\"invoke\",3588,0,\"load8_u\",[\"i32:65532\"]],[\"module\",3593,90],[\"invoke\",3610,0,\"load8_u\",[\"i32:1392\"]],[\"invoke\",3621,0,\"load8_u\",[\"i32:3581\"]],[\"invoke\",3631,0,\"load8_u\",[\"i32:5571\"]],[\"invoke\",3642,0,\"load8_u\",[\"i32:7760\"]],[\"invoke\",3653,0,\"load8_u\",[\"i32:9949\"]],[\"invoke\",3664,0,\"load8_u\",[\"i32:12138\"]],[\"invoke\",3675,0,\"load8_u\",[\"i32:14327\"]],[\"invoke\",3686,0,\"load8_u\",[\"i32:16516\"]],[\"invoke\",3697,0,\"load8_u\",[\"i32:18705\"]],[\"invoke\",3708,0,\"load8_u\",[\"i32:20894\"]],[\"invoke\",3719,0,\"load8_u\",[\"i32:23083\"]],[\"invoke\",3729,0,\"load8_u\",[\"i32:25073\"]],[\"invoke\",3740,0,\"load8_u\",[\"i32:27262\"]],[\"invoke\",3751,0,\"load8_u\",[\"i32:29451\"]],[\"invoke\",3762,0,\"load8_u\",[\"i32:31640\"]],[\"invoke\",3773,0,\"load8_u\",[\"i32:33829\"]],[\"invoke\",3784,0,\"load8_u\",[\"i32:36018\"]],[\"invoke\",3795,0,\"load8_u\",[\"i32:38207\"]],[\"invoke\",3806,0,\"load8_u\",[\"i32:40396\"]],[\"invoke\",3816,0,\"load8_u\",[\"i32:42386\"]],[\"invoke\",3827,0,\"load8_u\",[\"i32:44575\"]],[\"invoke\",3838,0,\"load8_u\",[\"i32:46764\"]],[\"invoke\",3849,0,\"load8_u\",[\"i32:48953\"]],[\"invoke\",3860,0,\"load8_u\",[\"i32:51142\"]],[\"invoke\",3871,0,\"load8_u\",[\"i32:53331\"]],[\"invoke\",3882,0,\"load8_u\",[\"i32:55520\"]],[\"invoke\",3893,0,\"load8_u\",[\"i32:57709\"]],[\"invoke\",3904,0,\"load8_u\",[\"i32:59898\"]],[\"invoke\",3914,0,\"load8_u\",[\"i32:61888\"]],[\"invoke\",3925,0,\"load8_u\",[\"i32:64077\"]],[\"invoke\",3936,0,\"load8_u\",[\"i32:65519\"]],[\"invoke\",3947,0,\"load8_u\",[\"i32:65530\"]],[\"module\",3954,91],[\"invoke\",3969,0,\"load8_u\",[\"i32:994\"]],[\"invoke\",3980,0,\"load8_u\",[\"i32:3183\"]],[\"invoke\",3991,0,\"load8_u\",[\"i32:5372\"]],[\"invoke\",4002,0,\"load8_u\",[\"i32:7561\"]],[\"invoke\",4012,0,\"load8_u\",[\"i32:9551\"]],[\"invoke\",4023,0,\"load8_u\",[\"i32:11740\"]],[\"invoke\",4034,0,\"load8_u\",[\"i32:13929\"]],[\"invoke\",4045,0,\"load8_u\",[\"i32:16118\"]],[\"invoke\",4056,0,\"load8_u\",[\"i32:18307\"]],[\"invoke\",4067,0,\"load8_u\",[\"i32:20496\"]],[\"invoke\",4078,0,\"load8_u\",[\"i32:22685\"]],[\"invoke\",4089,0,\"load8_u\",[\"i32:24874\"]],[\"invoke\",4100,0,\"load8_u\",[\"i32:27063\"]],[\"invoke\",4110,0,\"load8_u\",[\"i32:29053\"]],[\"invoke\",4121,0,\"load8_u\",[\"i32:31242\"]],[\"invoke\",4132,0,\"load8_u\",[\"i32:33431\"]],[\"invoke\",4143,0,\"load8_u\",[\"i32:35620\"]],[\"invoke\",4154,0,\"load8_u\",[\"i32:37809\"]],[\"invoke\",4165,0,\"load8_u\",[\"i32:39998\"]],[\"invoke\",4176,0,\"load8_u\",[\"i32:42187\"]],[\"invoke\",4187,0,\"load8_u\",[\"i32:44376\"]],[\"invoke\",4197,0,\"load8_u\",[\"i32:46366\"]],[\"invoke\",4208,0,\"load8_u\",[\"i32:48555\"]],[\"invoke\",4219,0,\"load8_u\",[\"i32:50744\"]],[\"invoke\",4230,0,\"load8_u\",[\"i32:52933\"]],[\"invoke\",4241,0,\"load8_u\",[\"i32:55122\"]],[\"invoke\",4252,0,\"load8_u\",[\"i32:57311\"]],[\"invoke\",4263,0,\"load8_u\",[\"i32:59500\"]],[\"invoke\",4274,0,\"load8_u\",[\"i32:61441\"]],[\"invoke\",4285,0,\"load8_u\",[\"i32:61452\"]],[\"invoke\",4295,0,\"load8_u\",[\"i32:61908\"]],[\"invoke\",4306,0,\"load8_u\",[\"i32:64097\"]],[\"module\",4763,92],[\"invoke\",4786,0,\"checkRange\",[\"i32:20\",\"i32:65536\",\"i32:0\"]],[\"module\",4789,93],[\"module\",4815,94],[\"module\",4821,95],[\"module\",4827,96],[\"module\",4833,97],[\"module\",4839,98],[\"invoke\",4861,0,\"checkRange\",[\"i32:32768\",\"i32:65536\",\"i32:170\"]],[\"module\",4863,99],[\"module\",4869,100],[\"module\",4875,101],[\"module\",4881,102],[\"module\",4887,103],[\"module\",4893,104],[\"module\",4899,105],[\"invoke\",5123,0,\"checkRange\",[\"i32:2132\",\"i32:2827\",\"i32:10\"]],[\"invoke\",5145,0,\"checkRange\",[\"i32:5691\",\"i32:6552\",\"i32:83\"]],[\"invoke\",5167,0,\"checkRange\",[\"i32:10087\",\"i32:10206\",\"i32:5\"]],[\"invoke\",5189,0,\"checkRange\",[\"i32:13114\",\"i32:14133\",\"i32:59\"]],[\"invoke\",5209,0,\"checkRange\",[\"i32:15871\",\"i32:15998\",\"i32:95\"]],[\"invoke\",5231,0,\"checkRange\",[\"i32:18553\",\"i32:18744\",\"i32:42\"]],[\"invoke\",5253,0,\"checkRange\",[\"i32:21468\",\"i32:21492\",\"i32:93\"]],[\"invoke\",5275,0,\"checkRange\",[\"i32:24348\",\"i32:24515\",\"i32:92\"]],[\"invoke\",5297,0,\"checkRange\",[\"i32:27503\",\"i32:27573\",\"i32:77\"]],[\"invoke\",5319,0,\"checkRange\",[\"i32:31379\",\"i32:31403\",\"i32:57\"]],[\"invoke\",5341,0,\"checkRange\",[\"i32:33056\",\"i32:33660\",\"i32:89\"]],[\"invoke\",5363,0,\"checkRange\",[\"i32:35648\",\"i32:36547\",\"i32:83\"]],[\"invoke\",5385,0,\"checkRange\",[\"i32:39187\",\"i32:39859\",\"i32:92\"]],[\"invoke\",5405,0,\"checkRange\",[\"i32:42945\",\"i32:43115\",\"i32:77\"]],[\"invoke\",5427,0,\"checkRange\",[\"i32:45575\",\"i32:45640\",\"i32:77\"]],[\"invoke\",5449,0,\"checkRange\",[\"i32:47951\",\"i32:48632\",\"i32:100\"]],[\"invoke\",5471,0,\"checkRange\",[\"i32:50370\",\"i32:51358\",\"i32:92\"]],[\"invoke\",5493,0,\"checkRange\",[\"i32:55198\",\"i32:55389\",\"i32:42\"]],[\"invoke\",5515,0,\"checkRange\",[\"i32:58516\",\"i32:58835\",\"i32:95\"]],[\"invoke\",5537,0,\"checkRange\",[\"i32:60777\",\"i32:60834\",\"i32:76\"]],[\"invoke\",5559,0,\"checkRange\",[\"i32:63525\",\"i32:63605\",\"i32:74\"]],[\"file\",\"memory_fill.wast\"],[\"module\",6,106],[\"invoke\",22,0,\"test\",[]],[\"invoke\",24,0,\"checkRange\",[\"i32:0\",\"i32:65280\",\"i32:0\"]],[\"invoke\",26,0,\"checkRange\",[\"i32:65280\",\"i32:65536\",\"i32:85\"]],[\"module\",28,107],[\"invoke\",44,0,\"test\",[]],[\"module\",46,108],[\"invoke\",62,0,\"test\",[]],[\"module\",64,109],[\"invoke\",80,0,\"test\",[]],[\"invoke\",82,0,\"checkRange\",[\"i32:0\",\"i32:65536\",\"i32:0\"]],[\"module\",84,110],[\"invoke\",100,0,\"test\",[]],[\"module\",102,111],[\"invoke\",118,0,\"test\",[]],[\"module\",120,112],[\"invoke\",136,0,\"test\",[]],[\"invoke\",138,0,\"checkRange\",[\"i32:0\",\"i32:1\",\"i32:0\"]],[\"invoke\",140,0,\"checkRange\",[\"i32:1\",\"i32:65535\",\"i32:170\"]],[\"invoke\",142,0,\"checkRange\",[\"i32:65535\",\"i32:65536\",\"i32:0\"]],[\"module\",145,113],[\"invoke\",162,0,\"test\",[]],[\"invoke\",164,0,\"checkRange\",[\"i32:0\",\"i32:18\",\"i32:0\"]],[\"invoke\",166,0,\"checkRange\",[\"i32:18\",\"i32:21\",\"i32:85\"]],[\"invoke\",168,0,\"checkRange\",[\"i32:21\",\"i32:25\",\"i32:170\"]],[\"invoke\",170,0,\"checkRange\",[\"i32:25\",\"i32:28\",\"i32:85\"]],[\"invoke\",172,0,\"checkRange\",[\"i32:28\",\"i32:65536\",\"i32:0\"]],[\"module\",621,114],[\"invoke\",638,0,\"run\",[\"i32:65280\",\"i32:37\",\"i32:512\"]],[\"invoke\",641,0,\"checkRange\",[\"i32:0\",\"i32:1\",\"i32:0\"]],[\"module\",643,114],[\"invoke\",660,0,\"run\",[\"i32:65279\",\"i32:37\",\"i32:514\"]],[\"invoke\",663,0,\"checkRange\",[\"i32:0\",\"i32:1\",\"i32:0\"]],[\"module\",665,114],[\"invoke\",682,0,\"run\",[\"i32:65279\",\"i32:37\",\"i32:4294967295\"]],[\"invoke\",685,0,\"checkRange\",[\"i32:0\",\"i32:1\",\"i32:0\"]],[\"file\",\"memory_init.wast\"],[\"module\",6,115],[\"invoke\",17,0,\"test\",[]],[\"invoke\",19,0,\"load8_u\",[\"i32:0\"]],[\"invoke\",20,0,\"load8_u\",[\"i32:1\"]],[\"invoke\",21,0,\"load8_u\",[\"i32:2\"]],[\"invoke\",22,0,\"load8_u\",[\"i32:3\"]],[\"invoke\",23,0,\"load8_u\",[\"i32:4\"]],[\"invoke\",24,0,\"load8_u\",[\"i32:5\"]],[\"invoke\",25,0,\"load8_u\",[\"i32:6\"]],[\"invoke\",26,0,\"load8_u\",[\"i32:7\"]],[\"invoke\",27,0,\"load8_u\",[\"i32:8\"]],[\"invoke\",28,0,\"load8_u\",[\"i32:9\"]],[\"invoke\",29,0,\"load8_u\",[\"i32:10\"]],[\"invoke\",30,0,\"load8_u\",[\"i32:11\"]],[\"invoke\",31,0,\"load8_u\",[\"i32:12\"]],[\"invoke\",32,0,\"load8_u\",[\"i32:13\"]],[\"invoke\",33,0,\"load8_u\",[\"i32:14\"]],[\"invoke\",34,0,\"load8_u\",[\"i32:15\"]],[\"invoke\",35,0,\"load8_u\",[\"i32:16\"]],[\"invoke\",36,0,\"load8_u\",[\"i32:17\"]],[\"invoke\",37,0,\"load8_u\",[\"i32:18\"]],[\"invoke\",38,0,\"load8_u\",[\"i32:19\"]],[\"invoke\",39,0,\"load8_u\",[\"i32:20\"]],[\"invoke\",40,0,\"load8_u\",[\"i32:21\"]],[\"invoke\",41,0,\"load8_u\",[\"i32:22\"]],[\"invoke\",42,0,\"load8_u\",[\"i32:23\"]],[\"invoke\",43,0,\"load8_u\",[\"i32:24\"]],[\"invoke\",44,0,\"load8_u\",[\"i32:25\"]],[\"invoke\",45,0,\"load8_u\",[\"i32:26\"]],[\"invoke\",46,0,\"load8_u\",[\"i32:27\"]],[\"invoke\",47,0,\"load8_u\",[\"i32:28\"]],[\"invoke\",48,0,\"load8_u\",[\"i32:29\"]],[\"module\",50,116],[\"invoke\",61,0,\"test\",[]],[\"invoke\",63,0,\"load8_u\",[\"i32:0\"]],[\"invoke\",64,0,\"load8_u\",[\"i32:1\"]],[\"invoke\",65,0,\"load8_u\",[\"i32:2\"]],[\"invoke\",66,0,\"load8_u\",[\"i32:3\"]],[\"invoke\",67,0,\"load8_u\",[\"i32:4\"]],[\"invoke\",68,0,\"load8_u\",[\"i32:5\"]],[\"invoke\",69,0,\"load8_u\",[\"i32:6\"]],[\"invoke\",70,0,\"load8_u\",[\"i32:7\"]],[\"invoke\",71,0,\"load8_u\",[\"i32:8\"]],[\"invoke\",72,0,\"load8_u\",[\"i32:9\"]],[\"invoke\",73,0,\"load8_u\",[\"i32:10\"]],[\"invoke\",74,0,\"load8_u\",[\"i32:11\"]],[\"invoke\",75,0,\"load8_u\",[\"i32:12\"]],[\"invoke\",76,0,\"load8_u\",[\"i32:13\"]],[\"invoke\",77,0,\"load8_u\",[\"i32:14\"]],[\"invoke\",78,0,\"load8_u\",[\"i32:15\"]],[\"invoke\",79,0,\"load8_u\",[\"i32:16\"]],[\"invoke\",80,0,\"load8_u\",[\"i32:17\"]],[\"invoke\",81,0,\"load8_u\",[\"i32:18\"]],[\"invoke\",82,0,\"load8_u\",[\"i32:19\"]],[\"invoke\",83,0,\"load8_u\",[\"i32:20\"]],[\"invoke\",84,0,\"load8_u\",[\"i32:21\"]],[\"invoke\",85,0,\"load8_u\",[\"i32:22\"]],[\"invoke\",86,0,\"load8_u\",[\"i32:23\"]],[\"invoke\",87,0,\"load8_u\",[\"i32:24\"]],[\"invoke\",88,0,\"load8_u\",[\"i32:25\"]],[\"invoke\",89,0,\"load8_u\",[\"i32:26\"]],[\"invoke\",90,0,\"load8_u\",[\"i32:27\"]],[\"invoke\",91,0,\"load8_u\",[\"i32:28\"]],[\"invoke\",92,0,\"load8_u\",[\"i32:29\"]],[\"module\",94,117],[\"invoke\",105,0,\"test\",[]],[\"invoke\",107,0,\"load8_u\",[\"i32:0\"]],[\"invoke\",108,0,\"load8_u\",[\"i32:1\"]],[\"invoke\",109,0,\"load8_u\",[\"i32:2\"]],[\"invoke\",110,0,\"load8_u\",[\"i32:3\"]],[\"invoke\",111,0,\"load8_u\",[\"i32:4\"]],[\"invoke\",112,0,\"load8_u\",[\"i32:5\"]],[\"invoke\",113,0,\"load8_u\",[\"i32:6\"]],[\"invoke\",114,0,\"load8_u\",[\"i32:7\"]],[\"invoke\",115,0,\"load8_u\",[\"i32:8\"]],[\"invoke\",116,0,\"load8_u\",[\"i32:9\"]],[\"invoke\",117,0,\"load8_u\",[\"i32:10\"]],[\"invoke\",118,0,\"load8_u\",[\"i32:11\"]],[\"invoke\",119,0,\"load8_u\",[\"i32:12\"]],[\"invoke\",120,0,\"load8_u\",[\"i32:13\"]],[\"invoke\",121,0,\"load8_u\",[\"i32:14\"]],[\"invoke\",122,0,\"load8_u\",[\"i32:15\"]],[\"invoke\",123,0,\"load8_u\",[\"i32:16\"]],[\"invoke\",124,0,\"load8_u\",[\"i32:17\"]],[\"invoke\",125,0,\"load8_u\",[\"i32:18\"]],[\"invoke\",126,0,\"load8_u\",[\"i32:19\"]],[\"invoke\",127,0,\"load8_u\",[\"i32:20\"]],[\"invoke\",128,0,\"load8_u\",[\"i32:21\"]],[\"invoke\",129,0,\"load8_u\",[\"i32:22\"]],[\"invoke\",130,0,\"load8_u\",[\"i32:23\"]],[\"invoke\",131,0,\"load8_u\",[\"i32:24\"]],[\"invoke\",132,0,\"load8_u\",[\"i32:25\"]],[\"invoke\",133,0,\"load8_u\",[\"i32:26\"]],[\"invoke\",134,0,\"load8_u\",[\"i32:27\"]],[\"invoke\",135,0,\"load8_u\",[\"i32:28\"]],[\"invoke\",136,0,\"load8_u\",[\"i32:29\"]],[\"module\",138,118],[\"invoke\",157,0,\"test\",[]],[\"invoke\",159,0,\"load8_u\",[\"i32:0\"]],[\"invoke\",160,0,\"load8_u\",[\"i32:1\"]],[\"invoke\",161,0,\"load8_u\",[\"i32:2\"]],[\"invoke\",162,0,\"load8_u\",[\"i32:3\"]],[\"invoke\",163,0,\"load8_u\",[\"i32:4\"]],[\"invoke\",164,0,\"load8_u\",[\"i32:5\"]],[\"invoke\",165,0,\"load8_u\",[\"i32:6\"]],[\"invoke\",166,0,\"load8_u\",[\"i32:7\"]],[\"invoke\",167,0,\"load8_u\",[\"i32:8\"]],[\"invoke\",168,0,\"load8_u\",[\"i32:9\"]],[\"invoke\",169,0,\"load8_u\",[\"i32:10\"]],[\"invoke\",170,0,\"load8_u\",[\"i32:11\"]],[\"invoke\",171,0,\"load8_u\",[\"i32:12\"]],[\"invoke\",172,0,\"load8_u\",[\"i32:13\"]],[\"invoke\",173,0,\"load8_u\",[\"i32:14\"]],[\"invoke\",174,0,\"load8_u\",[\"i32:15\"]],[\"invoke\",175,0,\"load8_u\",[\"i32:16\"]],[\"invoke\",176,0,\"load8_u\",[\"i32:17\"]],[\"invoke\",177,0,\"load8_u\",[\"i32:18\"]],[\"invoke\",178,0,\"load8_u\",[\"i32:19\"]],[\"invoke\",179,0,\"load8_u\",[\"i32:20\"]],[\"invoke\",180,0,\"load8_u\",[\"i32:21\"]],[\"invoke\",181,0,\"load8_u\",[\"i32:22\"]],[\"invoke\",182,0,\"load8_u\",[\"i32:23\"]],[\"invoke\",183,0,\"load8_u\",[\"i32:24\"]],[\"invoke\",184,0,\"load8_u\",[\"i32:25\"]],[\"invoke\",185,0,\"load8_u\",[\"i32:26\"]],[\"invoke\",186,0,\"load8_u\",[\"i32:27\"]],[\"invoke\",187,0,\"load8_u\",[\"i32:28\"]],[\"invoke\",188,0,\"load8_u\",[\"i32:29\"]],[\"module\",203,119],[\"invoke\",209,0,\"test\",[]],[\"module\",211,120],[\"invoke\",217,0,\"test\",[]],[\"module\",219,121],[\"invoke\",224,0,\"test\",[]],[\"module\",240,122],[\"invoke\",246,0,\"test\",[]],[\"module\",248,123],[\"invoke\",253,0,\"test\",[]],[\"module\",255,124],[\"invoke\",260,0,\"test\",[]],[\"module\",262,125],[\"invoke\",267,0,\"test\",[]],[\"module\",269,126],[\"invoke\",274,0,\"test\",[]],[\"module\",276,127],[\"invoke\",281,0,\"test\",[]],[\"module\",283,128],[\"invoke\",288,0,\"test\",[]],[\"module\",290,129],[\"invoke\",295,0,\"test\",[]],[\"module\",297,130],[\"invoke\",302,0,\"test\",[]],[\"module\",304,131],[\"invoke\",309,0,\"test\",[]],[\"module\",815,132],[\"invoke\",833,0,\"run\",[\"i32:65528\",\"i32:16\"]],[\"invoke\",836,0,\"checkRange\",[\"i32:0\",\"i32:1\",\"i32:0\"]],[\"module\",838,132],[\"invoke\",856,0,\"run\",[\"i32:65527\",\"i32:16\"]],[\"invoke\",859,0,\"checkRange\",[\"i32:0\",\"i32:1\",\"i32:0\"]],[\"module\",861,132],[\"invoke\",879,0,\"run\",[\"i32:65472\",\"i32:30\"]],[\"invoke\",882,0,\"checkRange\",[\"i32:0\",\"i32:1\",\"i32:0\"]],[\"module\",884,132],[\"invoke\",902,0,\"run\",[\"i32:65473\",\"i32:31\"]],[\"invoke\",905,0,\"checkRange\",[\"i32:0\",\"i32:1\",\"i32:0\"]],[\"module\",907,133],[\"invoke\",925,0,\"run\",[\"i32:65528\",\"i32:4294967040\"]],[\"invoke\",928,0,\"checkRange\",[\"i32:0\",\"i32:1\",\"i32:0\"]],[\"module\",930,133],[\"invoke\",948,0,\"run\",[\"i32:0\",\"i32:4294967292\"]],[\"invoke\",951,0,\"checkRange\",[\"i32:0\",\"i32:1\",\"i32:0\"]],[\"module\",954,134],[\"file\",\"data.wast\"],[\"module\",5,135],[\"module\",39,136],[\"module\",48,137],[\"module\",66,138],[\"module\",77,138],[\"module\",100,139],[\"module\",111,140],[\"module\",116,141],[\"module\",125,142],[\"module\",130,143],[\"module\",135,141],[\"module\",144,142],[\"instantiate\",184,144],[\"instantiate\",192,145],[\"instantiate\",200,146],[\"instantiate\",207,147],[\"instantiate\",214,148],[\"instantiate\",231,149],[\"instantiate\",240,150],[\"instantiate\",255,151],[\"instantiate\",263,152],[\"instantiate\",271,153],[\"instantiate\",286,154]]"; +export const script = "[[\"file\",\"address.wast\"],[\"module\",3,0],[\"invoke\",104,0,\"8u_good1\",[\"i32:0\"]],[\"invoke\",105,0,\"8u_good2\",[\"i32:0\"]],[\"invoke\",106,0,\"8u_good3\",[\"i32:0\"]],[\"invoke\",107,0,\"8u_good4\",[\"i32:0\"]],[\"invoke\",108,0,\"8u_good5\",[\"i32:0\"]],[\"invoke\",110,0,\"8s_good1\",[\"i32:0\"]],[\"invoke\",111,0,\"8s_good2\",[\"i32:0\"]],[\"invoke\",112,0,\"8s_good3\",[\"i32:0\"]],[\"invoke\",113,0,\"8s_good4\",[\"i32:0\"]],[\"invoke\",114,0,\"8s_good5\",[\"i32:0\"]],[\"invoke\",116,0,\"16u_good1\",[\"i32:0\"]],[\"invoke\",117,0,\"16u_good2\",[\"i32:0\"]],[\"invoke\",118,0,\"16u_good3\",[\"i32:0\"]],[\"invoke\",119,0,\"16u_good4\",[\"i32:0\"]],[\"invoke\",120,0,\"16u_good5\",[\"i32:0\"]],[\"invoke\",122,0,\"16s_good1\",[\"i32:0\"]],[\"invoke\",123,0,\"16s_good2\",[\"i32:0\"]],[\"invoke\",124,0,\"16s_good3\",[\"i32:0\"]],[\"invoke\",125,0,\"16s_good4\",[\"i32:0\"]],[\"invoke\",126,0,\"16s_good5\",[\"i32:0\"]],[\"invoke\",128,0,\"32_good1\",[\"i32:0\"]],[\"invoke\",129,0,\"32_good2\",[\"i32:0\"]],[\"invoke\",130,0,\"32_good3\",[\"i32:0\"]],[\"invoke\",131,0,\"32_good4\",[\"i32:0\"]],[\"invoke\",132,0,\"32_good5\",[\"i32:0\"]],[\"invoke\",134,0,\"8u_good1\",[\"i32:65507\"]],[\"invoke\",135,0,\"8u_good2\",[\"i32:65507\"]],[\"invoke\",136,0,\"8u_good3\",[\"i32:65507\"]],[\"invoke\",137,0,\"8u_good4\",[\"i32:65507\"]],[\"invoke\",138,0,\"8u_good5\",[\"i32:65507\"]],[\"invoke\",140,0,\"8s_good1\",[\"i32:65507\"]],[\"invoke\",141,0,\"8s_good2\",[\"i32:65507\"]],[\"invoke\",142,0,\"8s_good3\",[\"i32:65507\"]],[\"invoke\",143,0,\"8s_good4\",[\"i32:65507\"]],[\"invoke\",144,0,\"8s_good5\",[\"i32:65507\"]],[\"invoke\",146,0,\"16u_good1\",[\"i32:65507\"]],[\"invoke\",147,0,\"16u_good2\",[\"i32:65507\"]],[\"invoke\",148,0,\"16u_good3\",[\"i32:65507\"]],[\"invoke\",149,0,\"16u_good4\",[\"i32:65507\"]],[\"invoke\",150,0,\"16u_good5\",[\"i32:65507\"]],[\"invoke\",152,0,\"16s_good1\",[\"i32:65507\"]],[\"invoke\",153,0,\"16s_good2\",[\"i32:65507\"]],[\"invoke\",154,0,\"16s_good3\",[\"i32:65507\"]],[\"invoke\",155,0,\"16s_good4\",[\"i32:65507\"]],[\"invoke\",156,0,\"16s_good5\",[\"i32:65507\"]],[\"invoke\",158,0,\"32_good1\",[\"i32:65507\"]],[\"invoke\",159,0,\"32_good2\",[\"i32:65507\"]],[\"invoke\",160,0,\"32_good3\",[\"i32:65507\"]],[\"invoke\",161,0,\"32_good4\",[\"i32:65507\"]],[\"invoke\",162,0,\"32_good5\",[\"i32:65507\"]],[\"invoke\",164,0,\"8u_good1\",[\"i32:65508\"]],[\"invoke\",165,0,\"8u_good2\",[\"i32:65508\"]],[\"invoke\",166,0,\"8u_good3\",[\"i32:65508\"]],[\"invoke\",167,0,\"8u_good4\",[\"i32:65508\"]],[\"invoke\",168,0,\"8u_good5\",[\"i32:65508\"]],[\"invoke\",170,0,\"8s_good1\",[\"i32:65508\"]],[\"invoke\",171,0,\"8s_good2\",[\"i32:65508\"]],[\"invoke\",172,0,\"8s_good3\",[\"i32:65508\"]],[\"invoke\",173,0,\"8s_good4\",[\"i32:65508\"]],[\"invoke\",174,0,\"8s_good5\",[\"i32:65508\"]],[\"invoke\",176,0,\"16u_good1\",[\"i32:65508\"]],[\"invoke\",177,0,\"16u_good2\",[\"i32:65508\"]],[\"invoke\",178,0,\"16u_good3\",[\"i32:65508\"]],[\"invoke\",179,0,\"16u_good4\",[\"i32:65508\"]],[\"invoke\",180,0,\"16u_good5\",[\"i32:65508\"]],[\"invoke\",182,0,\"16s_good1\",[\"i32:65508\"]],[\"invoke\",183,0,\"16s_good2\",[\"i32:65508\"]],[\"invoke\",184,0,\"16s_good3\",[\"i32:65508\"]],[\"invoke\",185,0,\"16s_good4\",[\"i32:65508\"]],[\"invoke\",186,0,\"16s_good5\",[\"i32:65508\"]],[\"invoke\",188,0,\"32_good1\",[\"i32:65508\"]],[\"invoke\",189,0,\"32_good2\",[\"i32:65508\"]],[\"invoke\",190,0,\"32_good3\",[\"i32:65508\"]],[\"invoke\",191,0,\"32_good4\",[\"i32:65508\"]],[\"invoke\",192,0,\"32_good5\",[\"i32:65508\"]],[\"invoke\",194,0,\"8u_good3\",[\"i32:4294967295\"]],[\"invoke\",195,0,\"8s_good3\",[\"i32:4294967295\"]],[\"invoke\",196,0,\"16u_good3\",[\"i32:4294967295\"]],[\"invoke\",197,0,\"16s_good3\",[\"i32:4294967295\"]],[\"invoke\",198,0,\"32_good3\",[\"i32:4294967295\"]],[\"invoke\",199,0,\"32_good3\",[\"i32:4294967295\"]],[\"invoke\",201,0,\"8u_bad\",[\"i32:0\"]],[\"invoke\",202,0,\"8s_bad\",[\"i32:0\"]],[\"invoke\",203,0,\"16u_bad\",[\"i32:0\"]],[\"invoke\",204,0,\"16s_bad\",[\"i32:0\"]],[\"invoke\",205,0,\"32_bad\",[\"i32:0\"]],[\"invoke\",207,0,\"8u_bad\",[\"i32:1\"]],[\"invoke\",208,0,\"8s_bad\",[\"i32:1\"]],[\"invoke\",209,0,\"16u_bad\",[\"i32:1\"]],[\"invoke\",210,0,\"16s_bad\",[\"i32:1\"]],[\"invoke\",211,0,\"32_bad\",[\"i32:1\"]],[\"module\",223,1],[\"invoke\",362,0,\"8u_good1\",[\"i32:0\"]],[\"invoke\",363,0,\"8u_good2\",[\"i32:0\"]],[\"invoke\",364,0,\"8u_good3\",[\"i32:0\"]],[\"invoke\",365,0,\"8u_good4\",[\"i32:0\"]],[\"invoke\",366,0,\"8u_good5\",[\"i32:0\"]],[\"invoke\",368,0,\"8s_good1\",[\"i32:0\"]],[\"invoke\",369,0,\"8s_good2\",[\"i32:0\"]],[\"invoke\",370,0,\"8s_good3\",[\"i32:0\"]],[\"invoke\",371,0,\"8s_good4\",[\"i32:0\"]],[\"invoke\",372,0,\"8s_good5\",[\"i32:0\"]],[\"invoke\",374,0,\"16u_good1\",[\"i32:0\"]],[\"invoke\",375,0,\"16u_good2\",[\"i32:0\"]],[\"invoke\",376,0,\"16u_good3\",[\"i32:0\"]],[\"invoke\",377,0,\"16u_good4\",[\"i32:0\"]],[\"invoke\",378,0,\"16u_good5\",[\"i32:0\"]],[\"invoke\",380,0,\"16s_good1\",[\"i32:0\"]],[\"invoke\",381,0,\"16s_good2\",[\"i32:0\"]],[\"invoke\",382,0,\"16s_good3\",[\"i32:0\"]],[\"invoke\",383,0,\"16s_good4\",[\"i32:0\"]],[\"invoke\",384,0,\"16s_good5\",[\"i32:0\"]],[\"invoke\",386,0,\"32u_good1\",[\"i32:0\"]],[\"invoke\",387,0,\"32u_good2\",[\"i32:0\"]],[\"invoke\",388,0,\"32u_good3\",[\"i32:0\"]],[\"invoke\",389,0,\"32u_good4\",[\"i32:0\"]],[\"invoke\",390,0,\"32u_good5\",[\"i32:0\"]],[\"invoke\",392,0,\"32s_good1\",[\"i32:0\"]],[\"invoke\",393,0,\"32s_good2\",[\"i32:0\"]],[\"invoke\",394,0,\"32s_good3\",[\"i32:0\"]],[\"invoke\",395,0,\"32s_good4\",[\"i32:0\"]],[\"invoke\",396,0,\"32s_good5\",[\"i32:0\"]],[\"invoke\",398,0,\"64_good1\",[\"i32:0\"]],[\"invoke\",399,0,\"64_good2\",[\"i32:0\"]],[\"invoke\",400,0,\"64_good3\",[\"i32:0\"]],[\"invoke\",401,0,\"64_good4\",[\"i32:0\"]],[\"invoke\",402,0,\"64_good5\",[\"i32:0\"]],[\"invoke\",404,0,\"8u_good1\",[\"i32:65503\"]],[\"invoke\",405,0,\"8u_good2\",[\"i32:65503\"]],[\"invoke\",406,0,\"8u_good3\",[\"i32:65503\"]],[\"invoke\",407,0,\"8u_good4\",[\"i32:65503\"]],[\"invoke\",408,0,\"8u_good5\",[\"i32:65503\"]],[\"invoke\",410,0,\"8s_good1\",[\"i32:65503\"]],[\"invoke\",411,0,\"8s_good2\",[\"i32:65503\"]],[\"invoke\",412,0,\"8s_good3\",[\"i32:65503\"]],[\"invoke\",413,0,\"8s_good4\",[\"i32:65503\"]],[\"invoke\",414,0,\"8s_good5\",[\"i32:65503\"]],[\"invoke\",416,0,\"16u_good1\",[\"i32:65503\"]],[\"invoke\",417,0,\"16u_good2\",[\"i32:65503\"]],[\"invoke\",418,0,\"16u_good3\",[\"i32:65503\"]],[\"invoke\",419,0,\"16u_good4\",[\"i32:65503\"]],[\"invoke\",420,0,\"16u_good5\",[\"i32:65503\"]],[\"invoke\",422,0,\"16s_good1\",[\"i32:65503\"]],[\"invoke\",423,0,\"16s_good2\",[\"i32:65503\"]],[\"invoke\",424,0,\"16s_good3\",[\"i32:65503\"]],[\"invoke\",425,0,\"16s_good4\",[\"i32:65503\"]],[\"invoke\",426,0,\"16s_good5\",[\"i32:65503\"]],[\"invoke\",428,0,\"32u_good1\",[\"i32:65503\"]],[\"invoke\",429,0,\"32u_good2\",[\"i32:65503\"]],[\"invoke\",430,0,\"32u_good3\",[\"i32:65503\"]],[\"invoke\",431,0,\"32u_good4\",[\"i32:65503\"]],[\"invoke\",432,0,\"32u_good5\",[\"i32:65503\"]],[\"invoke\",434,0,\"32s_good1\",[\"i32:65503\"]],[\"invoke\",435,0,\"32s_good2\",[\"i32:65503\"]],[\"invoke\",436,0,\"32s_good3\",[\"i32:65503\"]],[\"invoke\",437,0,\"32s_good4\",[\"i32:65503\"]],[\"invoke\",438,0,\"32s_good5\",[\"i32:65503\"]],[\"invoke\",440,0,\"64_good1\",[\"i32:65503\"]],[\"invoke\",441,0,\"64_good2\",[\"i32:65503\"]],[\"invoke\",442,0,\"64_good3\",[\"i32:65503\"]],[\"invoke\",443,0,\"64_good4\",[\"i32:65503\"]],[\"invoke\",444,0,\"64_good5\",[\"i32:65503\"]],[\"invoke\",446,0,\"8u_good1\",[\"i32:65504\"]],[\"invoke\",447,0,\"8u_good2\",[\"i32:65504\"]],[\"invoke\",448,0,\"8u_good3\",[\"i32:65504\"]],[\"invoke\",449,0,\"8u_good4\",[\"i32:65504\"]],[\"invoke\",450,0,\"8u_good5\",[\"i32:65504\"]],[\"invoke\",452,0,\"8s_good1\",[\"i32:65504\"]],[\"invoke\",453,0,\"8s_good2\",[\"i32:65504\"]],[\"invoke\",454,0,\"8s_good3\",[\"i32:65504\"]],[\"invoke\",455,0,\"8s_good4\",[\"i32:65504\"]],[\"invoke\",456,0,\"8s_good5\",[\"i32:65504\"]],[\"invoke\",458,0,\"16u_good1\",[\"i32:65504\"]],[\"invoke\",459,0,\"16u_good2\",[\"i32:65504\"]],[\"invoke\",460,0,\"16u_good3\",[\"i32:65504\"]],[\"invoke\",461,0,\"16u_good4\",[\"i32:65504\"]],[\"invoke\",462,0,\"16u_good5\",[\"i32:65504\"]],[\"invoke\",464,0,\"16s_good1\",[\"i32:65504\"]],[\"invoke\",465,0,\"16s_good2\",[\"i32:65504\"]],[\"invoke\",466,0,\"16s_good3\",[\"i32:65504\"]],[\"invoke\",467,0,\"16s_good4\",[\"i32:65504\"]],[\"invoke\",468,0,\"16s_good5\",[\"i32:65504\"]],[\"invoke\",470,0,\"32u_good1\",[\"i32:65504\"]],[\"invoke\",471,0,\"32u_good2\",[\"i32:65504\"]],[\"invoke\",472,0,\"32u_good3\",[\"i32:65504\"]],[\"invoke\",473,0,\"32u_good4\",[\"i32:65504\"]],[\"invoke\",474,0,\"32u_good5\",[\"i32:65504\"]],[\"invoke\",476,0,\"32s_good1\",[\"i32:65504\"]],[\"invoke\",477,0,\"32s_good2\",[\"i32:65504\"]],[\"invoke\",478,0,\"32s_good3\",[\"i32:65504\"]],[\"invoke\",479,0,\"32s_good4\",[\"i32:65504\"]],[\"invoke\",480,0,\"32s_good5\",[\"i32:65504\"]],[\"invoke\",482,0,\"64_good1\",[\"i32:65504\"]],[\"invoke\",483,0,\"64_good2\",[\"i32:65504\"]],[\"invoke\",484,0,\"64_good3\",[\"i32:65504\"]],[\"invoke\",485,0,\"64_good4\",[\"i32:65504\"]],[\"invoke\",486,0,\"64_good5\",[\"i32:65504\"]],[\"invoke\",488,0,\"8u_good3\",[\"i32:4294967295\"]],[\"invoke\",489,0,\"8s_good3\",[\"i32:4294967295\"]],[\"invoke\",490,0,\"16u_good3\",[\"i32:4294967295\"]],[\"invoke\",491,0,\"16s_good3\",[\"i32:4294967295\"]],[\"invoke\",492,0,\"32u_good3\",[\"i32:4294967295\"]],[\"invoke\",493,0,\"32s_good3\",[\"i32:4294967295\"]],[\"invoke\",494,0,\"64_good3\",[\"i32:4294967295\"]],[\"invoke\",496,0,\"8u_bad\",[\"i32:0\"]],[\"invoke\",497,0,\"8s_bad\",[\"i32:0\"]],[\"invoke\",498,0,\"16u_bad\",[\"i32:0\"]],[\"invoke\",499,0,\"16s_bad\",[\"i32:0\"]],[\"invoke\",500,0,\"32u_bad\",[\"i32:0\"]],[\"invoke\",501,0,\"32s_bad\",[\"i32:0\"]],[\"invoke\",502,0,\"64_bad\",[\"i32:0\"]],[\"invoke\",504,0,\"8u_bad\",[\"i32:1\"]],[\"invoke\",505,0,\"8s_bad\",[\"i32:1\"]],[\"invoke\",506,0,\"16u_bad\",[\"i32:1\"]],[\"invoke\",507,0,\"16s_bad\",[\"i32:1\"]],[\"invoke\",508,0,\"32u_bad\",[\"i32:0\"]],[\"invoke\",509,0,\"32s_bad\",[\"i32:0\"]],[\"invoke\",510,0,\"64_bad\",[\"i32:1\"]],[\"module\",514,2],[\"invoke\",538,0,\"32_good1\",[\"i32:0\"]],[\"invoke\",539,0,\"32_good2\",[\"i32:0\"]],[\"invoke\",540,0,\"32_good3\",[\"i32:0\"]],[\"invoke\",541,0,\"32_good4\",[\"i32:0\"]],[\"invoke\",542,0,\"32_good5\",[\"i32:0\"]],[\"invoke\",544,0,\"32_good1\",[\"i32:65524\"]],[\"invoke\",545,0,\"32_good2\",[\"i32:65524\"]],[\"invoke\",546,0,\"32_good3\",[\"i32:65524\"]],[\"invoke\",547,0,\"32_good4\",[\"i32:65524\"]],[\"invoke\",548,0,\"32_good5\",[\"i32:65524\"]],[\"invoke\",550,0,\"32_good1\",[\"i32:65525\"]],[\"invoke\",551,0,\"32_good2\",[\"i32:65525\"]],[\"invoke\",552,0,\"32_good3\",[\"i32:65525\"]],[\"invoke\",553,0,\"32_good4\",[\"i32:65525\"]],[\"invoke\",554,0,\"32_good5\",[\"i32:65525\"]],[\"invoke\",556,0,\"32_good3\",[\"i32:4294967295\"]],[\"invoke\",557,0,\"32_good3\",[\"i32:4294967295\"]],[\"invoke\",559,0,\"32_bad\",[\"i32:0\"]],[\"invoke\",560,0,\"32_bad\",[\"i32:1\"]],[\"module\",564,3],[\"invoke\",588,0,\"64_good1\",[\"i32:0\"]],[\"invoke\",589,0,\"64_good2\",[\"i32:0\"]],[\"invoke\",590,0,\"64_good3\",[\"i32:0\"]],[\"invoke\",591,0,\"64_good4\",[\"i32:0\"]],[\"invoke\",592,0,\"64_good5\",[\"i32:0\"]],[\"invoke\",594,0,\"64_good1\",[\"i32:65510\"]],[\"invoke\",595,0,\"64_good2\",[\"i32:65510\"]],[\"invoke\",596,0,\"64_good3\",[\"i32:65510\"]],[\"invoke\",597,0,\"64_good4\",[\"i32:65510\"]],[\"invoke\",598,0,\"64_good5\",[\"i32:65510\"]],[\"invoke\",600,0,\"64_good1\",[\"i32:65511\"]],[\"invoke\",601,0,\"64_good2\",[\"i32:65511\"]],[\"invoke\",602,0,\"64_good3\",[\"i32:65511\"]],[\"invoke\",603,0,\"64_good4\",[\"i32:65511\"]],[\"invoke\",604,0,\"64_good5\",[\"i32:65511\"]],[\"invoke\",606,0,\"64_good3\",[\"i32:4294967295\"]],[\"invoke\",607,0,\"64_good3\",[\"i32:4294967295\"]],[\"invoke\",609,0,\"64_bad\",[\"i32:0\"]],[\"invoke\",610,0,\"64_bad\",[\"i32:1\"]],[\"file\",\"align.wast\"],[\"module\",3,4],[\"module\",4,5],[\"module\",5,6],[\"module\",6,7],[\"module\",7,8],[\"module\",8,9],[\"module\",9,10],[\"module\",10,11],[\"module\",11,12],[\"module\",12,13],[\"module\",13,14],[\"module\",14,15],[\"module\",15,16],[\"module\",16,17],[\"module\",17,18],[\"module\",18,19],[\"module\",19,20],[\"module\",20,21],[\"module\",21,22],[\"module\",22,23],[\"module\",23,24],[\"module\",24,25],[\"module\",25,26],[\"module\",458,27],[\"invoke\",802,0,\"f32_align_switch\",[\"i32:0\"]],[\"invoke\",803,0,\"f32_align_switch\",[\"i32:1\"]],[\"invoke\",804,0,\"f32_align_switch\",[\"i32:2\"]],[\"invoke\",805,0,\"f32_align_switch\",[\"i32:3\"]],[\"invoke\",807,0,\"f64_align_switch\",[\"i32:0\"]],[\"invoke\",808,0,\"f64_align_switch\",[\"i32:1\"]],[\"invoke\",809,0,\"f64_align_switch\",[\"i32:2\"]],[\"invoke\",810,0,\"f64_align_switch\",[\"i32:3\"]],[\"invoke\",811,0,\"f64_align_switch\",[\"i32:4\"]],[\"invoke\",813,0,\"i32_align_switch\",[\"i32:0\",\"i32:0\"]],[\"invoke\",814,0,\"i32_align_switch\",[\"i32:0\",\"i32:1\"]],[\"invoke\",815,0,\"i32_align_switch\",[\"i32:1\",\"i32:0\"]],[\"invoke\",816,0,\"i32_align_switch\",[\"i32:1\",\"i32:1\"]],[\"invoke\",817,0,\"i32_align_switch\",[\"i32:2\",\"i32:0\"]],[\"invoke\",818,0,\"i32_align_switch\",[\"i32:2\",\"i32:1\"]],[\"invoke\",819,0,\"i32_align_switch\",[\"i32:2\",\"i32:2\"]],[\"invoke\",820,0,\"i32_align_switch\",[\"i32:3\",\"i32:0\"]],[\"invoke\",821,0,\"i32_align_switch\",[\"i32:3\",\"i32:1\"]],[\"invoke\",822,0,\"i32_align_switch\",[\"i32:3\",\"i32:2\"]],[\"invoke\",823,0,\"i32_align_switch\",[\"i32:4\",\"i32:0\"]],[\"invoke\",824,0,\"i32_align_switch\",[\"i32:4\",\"i32:1\"]],[\"invoke\",825,0,\"i32_align_switch\",[\"i32:4\",\"i32:2\"]],[\"invoke\",826,0,\"i32_align_switch\",[\"i32:4\",\"i32:4\"]],[\"invoke\",828,0,\"i64_align_switch\",[\"i32:0\",\"i32:0\"]],[\"invoke\",829,0,\"i64_align_switch\",[\"i32:0\",\"i32:1\"]],[\"invoke\",830,0,\"i64_align_switch\",[\"i32:1\",\"i32:0\"]],[\"invoke\",831,0,\"i64_align_switch\",[\"i32:1\",\"i32:1\"]],[\"invoke\",832,0,\"i64_align_switch\",[\"i32:2\",\"i32:0\"]],[\"invoke\",833,0,\"i64_align_switch\",[\"i32:2\",\"i32:1\"]],[\"invoke\",834,0,\"i64_align_switch\",[\"i32:2\",\"i32:2\"]],[\"invoke\",835,0,\"i64_align_switch\",[\"i32:3\",\"i32:0\"]],[\"invoke\",836,0,\"i64_align_switch\",[\"i32:3\",\"i32:1\"]],[\"invoke\",837,0,\"i64_align_switch\",[\"i32:3\",\"i32:2\"]],[\"invoke\",838,0,\"i64_align_switch\",[\"i32:4\",\"i32:0\"]],[\"invoke\",839,0,\"i64_align_switch\",[\"i32:4\",\"i32:1\"]],[\"invoke\",840,0,\"i64_align_switch\",[\"i32:4\",\"i32:2\"]],[\"invoke\",841,0,\"i64_align_switch\",[\"i32:4\",\"i32:4\"]],[\"invoke\",842,0,\"i64_align_switch\",[\"i32:5\",\"i32:0\"]],[\"invoke\",843,0,\"i64_align_switch\",[\"i32:5\",\"i32:1\"]],[\"invoke\",844,0,\"i64_align_switch\",[\"i32:5\",\"i32:2\"]],[\"invoke\",845,0,\"i64_align_switch\",[\"i32:5\",\"i32:4\"]],[\"invoke\",846,0,\"i64_align_switch\",[\"i32:6\",\"i32:0\"]],[\"invoke\",847,0,\"i64_align_switch\",[\"i32:6\",\"i32:1\"]],[\"invoke\",848,0,\"i64_align_switch\",[\"i32:6\",\"i32:2\"]],[\"invoke\",849,0,\"i64_align_switch\",[\"i32:6\",\"i32:4\"]],[\"invoke\",850,0,\"i64_align_switch\",[\"i32:6\",\"i32:8\"]],[\"module\",854,28],[\"invoke\",864,0,\"store\",[\"i32:65532\",\"i64:18446744073709551615\"]],[\"invoke\",866,0,\"load\",[\"i32:65532\"]],[\"file\",\"endianness.wast\"],[\"module\",1,29],[\"invoke\",133,0,\"i32_load16_s\",[\"i32:4294967295\"]],[\"invoke\",134,0,\"i32_load16_s\",[\"i32:4294963054\"]],[\"invoke\",135,0,\"i32_load16_s\",[\"i32:42\"]],[\"invoke\",136,0,\"i32_load16_s\",[\"i32:12816\"]],[\"invoke\",138,0,\"i32_load16_u\",[\"i32:4294967295\"]],[\"invoke\",139,0,\"i32_load16_u\",[\"i32:4294963054\"]],[\"invoke\",140,0,\"i32_load16_u\",[\"i32:42\"]],[\"invoke\",141,0,\"i32_load16_u\",[\"i32:51966\"]],[\"invoke\",143,0,\"i32_load\",[\"i32:4294967295\"]],[\"invoke\",144,0,\"i32_load\",[\"i32:4252543054\"]],[\"invoke\",145,0,\"i32_load\",[\"i32:42424242\"]],[\"invoke\",146,0,\"i32_load\",[\"i32:2880249322\"]],[\"invoke\",148,0,\"i64_load16_s\",[\"i64:18446744073709551615\"]],[\"invoke\",149,0,\"i64_load16_s\",[\"i64:18446744073709547374\"]],[\"invoke\",150,0,\"i64_load16_s\",[\"i64:42\"]],[\"invoke\",151,0,\"i64_load16_s\",[\"i64:12816\"]],[\"invoke\",153,0,\"i64_load16_u\",[\"i64:18446744073709551615\"]],[\"invoke\",154,0,\"i64_load16_u\",[\"i64:18446744073709547374\"]],[\"invoke\",155,0,\"i64_load16_u\",[\"i64:42\"]],[\"invoke\",156,0,\"i64_load16_u\",[\"i64:51966\"]],[\"invoke\",158,0,\"i64_load32_s\",[\"i64:18446744073709551615\"]],[\"invoke\",159,0,\"i64_load32_s\",[\"i64:18446744073667127374\"]],[\"invoke\",160,0,\"i64_load32_s\",[\"i64:42424242\"]],[\"invoke\",161,0,\"i64_load32_s\",[\"i64:305419896\"]],[\"invoke\",163,0,\"i64_load32_u\",[\"i64:18446744073709551615\"]],[\"invoke\",164,0,\"i64_load32_u\",[\"i64:18446744073667127374\"]],[\"invoke\",165,0,\"i64_load32_u\",[\"i64:42424242\"]],[\"invoke\",166,0,\"i64_load32_u\",[\"i64:2880249322\"]],[\"invoke\",168,0,\"i64_load\",[\"i64:18446744073709551615\"]],[\"invoke\",169,0,\"i64_load\",[\"i64:18446744073667127374\"]],[\"invoke\",170,0,\"i64_load\",[\"i64:2880249322\"]],[\"invoke\",171,0,\"i64_load\",[\"i64:12370766947463011818\"]],[\"invoke\",173,0,\"f32_load\",[\"f32:3212836864\"]],[\"invoke\",174,0,\"f32_load\",[\"f32:1011494326\"]],[\"invoke\",175,0,\"f32_load\",[\"f32:1166316389\"]],[\"invoke\",176,0,\"f32_load\",[\"f32:2139095039\"]],[\"invoke\",178,0,\"f64_load\",[\"f64:13830554455654793216\"]],[\"invoke\",179,0,\"f64_load\",[\"f64:4653144502447687399\"]],[\"invoke\",180,0,\"f64_load\",[\"f64:4691032041816096430\"]],[\"invoke\",181,0,\"f64_load\",[\"f64:9218868437227405311\"]],[\"invoke\",184,0,\"i32_store16\",[\"i32:4294967295\"]],[\"invoke\",185,0,\"i32_store16\",[\"i32:4294963054\"]],[\"invoke\",186,0,\"i32_store16\",[\"i32:42\"]],[\"invoke\",187,0,\"i32_store16\",[\"i32:51966\"]],[\"invoke\",189,0,\"i32_store\",[\"i32:4294967295\"]],[\"invoke\",190,0,\"i32_store\",[\"i32:4294963054\"]],[\"invoke\",191,0,\"i32_store\",[\"i32:42424242\"]],[\"invoke\",192,0,\"i32_store\",[\"i32:3735931646\"]],[\"invoke\",194,0,\"i64_store16\",[\"i64:18446744073709551615\"]],[\"invoke\",195,0,\"i64_store16\",[\"i64:18446744073709547374\"]],[\"invoke\",196,0,\"i64_store16\",[\"i64:42\"]],[\"invoke\",197,0,\"i64_store16\",[\"i64:51966\"]],[\"invoke\",199,0,\"i64_store32\",[\"i64:18446744073709551615\"]],[\"invoke\",200,0,\"i64_store32\",[\"i64:18446744073709547374\"]],[\"invoke\",201,0,\"i64_store32\",[\"i64:42424242\"]],[\"invoke\",202,0,\"i64_store32\",[\"i64:3735931646\"]],[\"invoke\",204,0,\"i64_store\",[\"i64:18446744073709551615\"]],[\"invoke\",205,0,\"i64_store\",[\"i64:18446744073667127374\"]],[\"invoke\",206,0,\"i64_store\",[\"i64:2880249322\"]],[\"invoke\",207,0,\"i64_store\",[\"i64:12370766947463011818\"]],[\"invoke\",209,0,\"f32_store\",[\"f32:3212836864\"]],[\"invoke\",210,0,\"f32_store\",[\"f32:1011494326\"]],[\"invoke\",211,0,\"f32_store\",[\"f32:1166316389\"]],[\"invoke\",212,0,\"f32_store\",[\"f32:2139095039\"]],[\"invoke\",214,0,\"f64_store\",[\"f64:13830554455654793216\"]],[\"invoke\",215,0,\"f64_store\",[\"f64:4653144502447687399\"]],[\"invoke\",216,0,\"f64_store\",[\"f64:4691032041816096430\"]],[\"invoke\",217,0,\"f64_store\",[\"f64:9218868437227405311\"]],[\"file\",\"float_memory.wast\"],[\"module\",5,30],[\"invoke\",15,0,\"i32.load\",[]],[\"invoke\",16,0,\"f32.load\",[]],[\"invoke\",17,0,\"reset\",[]],[\"invoke\",18,0,\"i32.load\",[]],[\"invoke\",19,0,\"f32.load\",[]],[\"invoke\",20,0,\"f32.store\",[]],[\"invoke\",21,0,\"i32.load\",[]],[\"invoke\",22,0,\"f32.load\",[]],[\"invoke\",23,0,\"reset\",[]],[\"invoke\",24,0,\"i32.load\",[]],[\"invoke\",25,0,\"f32.load\",[]],[\"invoke\",26,0,\"i32.store\",[]],[\"invoke\",27,0,\"i32.load\",[]],[\"invoke\",28,0,\"f32.load\",[]],[\"module\",30,31],[\"invoke\",40,0,\"i64.load\",[]],[\"invoke\",41,0,\"f64.load\",[]],[\"invoke\",42,0,\"reset\",[]],[\"invoke\",43,0,\"i64.load\",[]],[\"invoke\",44,0,\"f64.load\",[]],[\"invoke\",45,0,\"f64.store\",[]],[\"invoke\",46,0,\"i64.load\",[]],[\"invoke\",47,0,\"f64.load\",[]],[\"invoke\",48,0,\"reset\",[]],[\"invoke\",49,0,\"i64.load\",[]],[\"invoke\",50,0,\"f64.load\",[]],[\"invoke\",51,0,\"i64.store\",[]],[\"invoke\",52,0,\"i64.load\",[]],[\"invoke\",53,0,\"f64.load\",[]],[\"module\",57,32],[\"invoke\",67,0,\"i32.load\",[]],[\"invoke\",68,0,\"f32.load\",[]],[\"invoke\",69,0,\"reset\",[]],[\"invoke\",70,0,\"i32.load\",[]],[\"invoke\",71,0,\"f32.load\",[]],[\"invoke\",72,0,\"f32.store\",[]],[\"invoke\",73,0,\"i32.load\",[]],[\"invoke\",74,0,\"f32.load\",[]],[\"invoke\",75,0,\"reset\",[]],[\"invoke\",76,0,\"i32.load\",[]],[\"invoke\",77,0,\"f32.load\",[]],[\"invoke\",78,0,\"i32.store\",[]],[\"invoke\",79,0,\"i32.load\",[]],[\"invoke\",80,0,\"f32.load\",[]],[\"module\",82,33],[\"invoke\",92,0,\"i64.load\",[]],[\"invoke\",93,0,\"f64.load\",[]],[\"invoke\",94,0,\"reset\",[]],[\"invoke\",95,0,\"i64.load\",[]],[\"invoke\",96,0,\"f64.load\",[]],[\"invoke\",97,0,\"f64.store\",[]],[\"invoke\",98,0,\"i64.load\",[]],[\"invoke\",99,0,\"f64.load\",[]],[\"invoke\",100,0,\"reset\",[]],[\"invoke\",101,0,\"i64.load\",[]],[\"invoke\",102,0,\"f64.load\",[]],[\"invoke\",103,0,\"i64.store\",[]],[\"invoke\",104,0,\"i64.load\",[]],[\"invoke\",105,0,\"f64.load\",[]],[\"module\",109,34],[\"invoke\",119,0,\"i32.load\",[]],[\"invoke\",120,0,\"f32.load\",[]],[\"invoke\",121,0,\"reset\",[]],[\"invoke\",122,0,\"i32.load\",[]],[\"invoke\",123,0,\"f32.load\",[]],[\"invoke\",124,0,\"f32.store\",[]],[\"invoke\",125,0,\"i32.load\",[]],[\"invoke\",126,0,\"f32.load\",[]],[\"invoke\",127,0,\"reset\",[]],[\"invoke\",128,0,\"i32.load\",[]],[\"invoke\",129,0,\"f32.load\",[]],[\"invoke\",130,0,\"i32.store\",[]],[\"invoke\",131,0,\"i32.load\",[]],[\"invoke\",132,0,\"f32.load\",[]],[\"module\",134,35],[\"invoke\",144,0,\"i64.load\",[]],[\"invoke\",145,0,\"f64.load\",[]],[\"invoke\",146,0,\"reset\",[]],[\"invoke\",147,0,\"i64.load\",[]],[\"invoke\",148,0,\"f64.load\",[]],[\"invoke\",149,0,\"f64.store\",[]],[\"invoke\",150,0,\"i64.load\",[]],[\"invoke\",151,0,\"f64.load\",[]],[\"invoke\",152,0,\"reset\",[]],[\"invoke\",153,0,\"i64.load\",[]],[\"invoke\",154,0,\"f64.load\",[]],[\"invoke\",155,0,\"i64.store\",[]],[\"invoke\",156,0,\"i64.load\",[]],[\"invoke\",157,0,\"f64.load\",[]],[\"file\",\"load.wast\"],[\"module\",3,36],[\"invoke\",161,0,\"as-br-value\",[]],[\"invoke\",163,0,\"as-br_if-cond\",[]],[\"invoke\",164,0,\"as-br_if-value\",[]],[\"invoke\",165,0,\"as-br_if-value-cond\",[]],[\"invoke\",167,0,\"as-br_table-index\",[]],[\"invoke\",168,0,\"as-br_table-value\",[]],[\"invoke\",169,0,\"as-br_table-value-index\",[]],[\"invoke\",171,0,\"as-return-value\",[]],[\"invoke\",173,0,\"as-if-cond\",[]],[\"invoke\",174,0,\"as-if-then\",[]],[\"invoke\",175,0,\"as-if-else\",[]],[\"invoke\",177,0,\"as-select-first\",[\"i32:0\",\"i32:1\"]],[\"invoke\",178,0,\"as-select-second\",[\"i32:0\",\"i32:0\"]],[\"invoke\",179,0,\"as-select-cond\",[]],[\"invoke\",181,0,\"as-call-first\",[]],[\"invoke\",182,0,\"as-call-mid\",[]],[\"invoke\",183,0,\"as-call-last\",[]],[\"invoke\",185,0,\"as-call_indirect-first\",[]],[\"invoke\",186,0,\"as-call_indirect-mid\",[]],[\"invoke\",187,0,\"as-call_indirect-last\",[]],[\"invoke\",188,0,\"as-call_indirect-index\",[]],[\"invoke\",190,0,\"as-local.set-value\",[]],[\"invoke\",191,0,\"as-local.tee-value\",[]],[\"invoke\",192,0,\"as-global.set-value\",[]],[\"invoke\",194,0,\"as-load-address\",[]],[\"invoke\",195,0,\"as-loadN-address\",[]],[\"invoke\",196,0,\"as-store-address\",[]],[\"invoke\",197,0,\"as-store-value\",[]],[\"invoke\",198,0,\"as-storeN-address\",[]],[\"invoke\",199,0,\"as-storeN-value\",[]],[\"invoke\",201,0,\"as-unary-operand\",[]],[\"invoke\",203,0,\"as-binary-left\",[]],[\"invoke\",204,0,\"as-binary-right\",[]],[\"invoke\",206,0,\"as-test-operand\",[]],[\"invoke\",208,0,\"as-compare-left\",[]],[\"invoke\",209,0,\"as-compare-right\",[]],[\"invoke\",211,0,\"as-memory.grow-size\",[]],[\"file\",\"store.wast\"],[\"module\",3,37],[\"invoke\",44,0,\"as-block-value\",[]],[\"invoke\",45,0,\"as-loop-value\",[]],[\"invoke\",47,0,\"as-br-value\",[]],[\"invoke\",48,0,\"as-br_if-value\",[]],[\"invoke\",49,0,\"as-br_if-value-cond\",[]],[\"invoke\",50,0,\"as-br_table-value\",[]],[\"invoke\",52,0,\"as-return-value\",[]],[\"invoke\",54,0,\"as-if-then\",[]],[\"invoke\",55,0,\"as-if-else\",[]],[\"file\",\"memory.wast\"],[\"module\",3,38],[\"module\",4,39],[\"module\",5,40],[\"module\",6,41],[\"module\",7,42],[\"module\",8,43],[\"module\",13,44],[\"invoke\",14,0,\"memsize\",[]],[\"module\",15,44],[\"invoke\",16,0,\"memsize\",[]],[\"module\",17,45],[\"invoke\",18,0,\"memsize\",[]],[\"module\",92,46],[\"invoke\",180,0,\"data\",[]],[\"invoke\",181,0,\"cast\",[]],[\"invoke\",183,0,\"i32_load8_s\",[\"i32:4294967295\"]],[\"invoke\",184,0,\"i32_load8_u\",[\"i32:4294967295\"]],[\"invoke\",185,0,\"i32_load16_s\",[\"i32:4294967295\"]],[\"invoke\",186,0,\"i32_load16_u\",[\"i32:4294967295\"]],[\"invoke\",188,0,\"i32_load8_s\",[\"i32:100\"]],[\"invoke\",189,0,\"i32_load8_u\",[\"i32:200\"]],[\"invoke\",190,0,\"i32_load16_s\",[\"i32:20000\"]],[\"invoke\",191,0,\"i32_load16_u\",[\"i32:40000\"]],[\"invoke\",193,0,\"i32_load8_s\",[\"i32:4275856707\"]],[\"invoke\",194,0,\"i32_load8_s\",[\"i32:878104047\"]],[\"invoke\",195,0,\"i32_load8_u\",[\"i32:4275856707\"]],[\"invoke\",196,0,\"i32_load8_u\",[\"i32:878104047\"]],[\"invoke\",197,0,\"i32_load16_s\",[\"i32:4275856707\"]],[\"invoke\",198,0,\"i32_load16_s\",[\"i32:878104047\"]],[\"invoke\",199,0,\"i32_load16_u\",[\"i32:4275856707\"]],[\"invoke\",200,0,\"i32_load16_u\",[\"i32:878104047\"]],[\"invoke\",202,0,\"i64_load8_s\",[\"i64:18446744073709551615\"]],[\"invoke\",203,0,\"i64_load8_u\",[\"i64:18446744073709551615\"]],[\"invoke\",204,0,\"i64_load16_s\",[\"i64:18446744073709551615\"]],[\"invoke\",205,0,\"i64_load16_u\",[\"i64:18446744073709551615\"]],[\"invoke\",206,0,\"i64_load32_s\",[\"i64:18446744073709551615\"]],[\"invoke\",207,0,\"i64_load32_u\",[\"i64:18446744073709551615\"]],[\"invoke\",209,0,\"i64_load8_s\",[\"i64:100\"]],[\"invoke\",210,0,\"i64_load8_u\",[\"i64:200\"]],[\"invoke\",211,0,\"i64_load16_s\",[\"i64:20000\"]],[\"invoke\",212,0,\"i64_load16_u\",[\"i64:40000\"]],[\"invoke\",213,0,\"i64_load32_s\",[\"i64:20000\"]],[\"invoke\",214,0,\"i64_load32_u\",[\"i64:40000\"]],[\"invoke\",216,0,\"i64_load8_s\",[\"i64:18364758543954109763\"]],[\"invoke\",217,0,\"i64_load8_s\",[\"i64:3771275841602506223\"]],[\"invoke\",218,0,\"i64_load8_u\",[\"i64:18364758543954109763\"]],[\"invoke\",219,0,\"i64_load8_u\",[\"i64:3771275841602506223\"]],[\"invoke\",220,0,\"i64_load16_s\",[\"i64:18364758543954109763\"]],[\"invoke\",221,0,\"i64_load16_s\",[\"i64:3771275841602506223\"]],[\"invoke\",222,0,\"i64_load16_u\",[\"i64:18364758543954109763\"]],[\"invoke\",223,0,\"i64_load16_u\",[\"i64:3771275841602506223\"]],[\"invoke\",224,0,\"i64_load32_s\",[\"i64:18364758543954109763\"]],[\"invoke\",225,0,\"i64_load32_s\",[\"i64:3771275841602506223\"]],[\"invoke\",226,0,\"i64_load32_u\",[\"i64:18364758543954109763\"]],[\"invoke\",227,0,\"i64_load32_u\",[\"i64:3771275841602506223\"]],[\"module\",246,47],[\"invoke\",260,0,\"load\",[\"i32:0\"]],[\"invoke\",261,0,\"load\",[\"i32:10000\"]],[\"invoke\",262,0,\"load\",[\"i32:20000\"]],[\"invoke\",263,0,\"load\",[\"i32:30000\"]],[\"invoke\",264,0,\"load\",[\"i32:40000\"]],[\"invoke\",265,0,\"load\",[\"i32:50000\"]],[\"invoke\",266,0,\"load\",[\"i32:60000\"]],[\"invoke\",267,0,\"load\",[\"i32:65535\"]],[\"file\",\"memory_grow.wast\"],[\"module\",1,48],[\"invoke\",14,0,\"size\",[]],[\"invoke\",15,0,\"store_at_zero\",[]],[\"invoke\",16,0,\"load_at_zero\",[]],[\"invoke\",17,0,\"store_at_page_size\",[]],[\"invoke\",18,0,\"load_at_page_size\",[]],[\"invoke\",19,0,\"grow\",[\"i32:1\"]],[\"invoke\",20,0,\"size\",[]],[\"invoke\",21,0,\"load_at_zero\",[]],[\"invoke\",22,0,\"store_at_zero\",[]],[\"invoke\",23,0,\"load_at_zero\",[]],[\"invoke\",24,0,\"store_at_page_size\",[]],[\"invoke\",25,0,\"load_at_page_size\",[]],[\"invoke\",26,0,\"grow\",[\"i32:4\"]],[\"invoke\",27,0,\"size\",[]],[\"invoke\",28,0,\"load_at_zero\",[]],[\"invoke\",29,0,\"store_at_zero\",[]],[\"invoke\",30,0,\"load_at_zero\",[]],[\"invoke\",31,0,\"load_at_page_size\",[]],[\"invoke\",32,0,\"store_at_page_size\",[]],[\"invoke\",33,0,\"load_at_page_size\",[]],[\"module\",36,49],[\"invoke\",41,0,\"grow\",[\"i32:0\"]],[\"invoke\",42,0,\"grow\",[\"i32:1\"]],[\"invoke\",43,0,\"grow\",[\"i32:0\"]],[\"invoke\",44,0,\"grow\",[\"i32:2\"]],[\"invoke\",45,0,\"grow\",[\"i32:800\"]],[\"invoke\",46,0,\"grow\",[\"i32:65536\"]],[\"invoke\",47,0,\"grow\",[\"i32:64736\"]],[\"invoke\",48,0,\"grow\",[\"i32:1\"]],[\"module\",50,50],[\"invoke\",55,0,\"grow\",[\"i32:0\"]],[\"invoke\",56,0,\"grow\",[\"i32:1\"]],[\"invoke\",57,0,\"grow\",[\"i32:1\"]],[\"invoke\",58,0,\"grow\",[\"i32:2\"]],[\"invoke\",59,0,\"grow\",[\"i32:6\"]],[\"invoke\",60,0,\"grow\",[\"i32:0\"]],[\"invoke\",61,0,\"grow\",[\"i32:1\"]],[\"invoke\",62,0,\"grow\",[\"i32:65536\"]],[\"module\",66,51],[\"invoke\",87,0,\"check-memory-zero\",[\"i32:0\",\"i32:65535\"]],[\"invoke\",88,0,\"grow\",[\"i32:1\"]],[\"invoke\",89,0,\"check-memory-zero\",[\"i32:65536\",\"i32:131071\"]],[\"invoke\",90,0,\"grow\",[\"i32:1\"]],[\"invoke\",91,0,\"check-memory-zero\",[\"i32:131072\",\"i32:196607\"]],[\"invoke\",92,0,\"grow\",[\"i32:1\"]],[\"invoke\",93,0,\"check-memory-zero\",[\"i32:196608\",\"i32:262143\"]],[\"invoke\",94,0,\"grow\",[\"i32:1\"]],[\"invoke\",95,0,\"check-memory-zero\",[\"i32:262144\",\"i32:327679\"]],[\"invoke\",96,0,\"grow\",[\"i32:1\"]],[\"invoke\",97,0,\"check-memory-zero\",[\"i32:327680\",\"i32:393215\"]],[\"module\",101,52],[\"invoke\",259,0,\"as-br-value\",[]],[\"invoke\",261,0,\"as-br_if-cond\",[]],[\"invoke\",262,0,\"as-br_if-value\",[]],[\"invoke\",263,0,\"as-br_if-value-cond\",[]],[\"invoke\",265,0,\"as-br_table-index\",[]],[\"invoke\",266,0,\"as-br_table-value\",[]],[\"invoke\",267,0,\"as-br_table-value-index\",[]],[\"invoke\",269,0,\"as-return-value\",[]],[\"invoke\",271,0,\"as-if-cond\",[]],[\"invoke\",272,0,\"as-if-then\",[]],[\"invoke\",273,0,\"as-if-else\",[]],[\"invoke\",275,0,\"as-select-first\",[\"i32:0\",\"i32:1\"]],[\"invoke\",276,0,\"as-select-second\",[\"i32:0\",\"i32:0\"]],[\"invoke\",277,0,\"as-select-cond\",[]],[\"invoke\",279,0,\"as-call-first\",[]],[\"invoke\",280,0,\"as-call-mid\",[]],[\"invoke\",281,0,\"as-call-last\",[]],[\"invoke\",283,0,\"as-call_indirect-first\",[]],[\"invoke\",284,0,\"as-call_indirect-mid\",[]],[\"invoke\",285,0,\"as-call_indirect-last\",[]],[\"invoke\",286,0,\"as-call_indirect-index\",[]],[\"invoke\",288,0,\"as-local.set-value\",[]],[\"invoke\",289,0,\"as-local.tee-value\",[]],[\"invoke\",290,0,\"as-global.set-value\",[]],[\"invoke\",292,0,\"as-load-address\",[]],[\"invoke\",293,0,\"as-loadN-address\",[]],[\"invoke\",294,0,\"as-store-address\",[]],[\"invoke\",295,0,\"as-store-value\",[]],[\"invoke\",296,0,\"as-storeN-address\",[]],[\"invoke\",297,0,\"as-storeN-value\",[]],[\"invoke\",299,0,\"as-unary-operand\",[]],[\"invoke\",301,0,\"as-binary-left\",[]],[\"invoke\",302,0,\"as-binary-right\",[]],[\"invoke\",304,0,\"as-test-operand\",[]],[\"invoke\",306,0,\"as-compare-left\",[]],[\"invoke\",307,0,\"as-compare-right\",[]],[\"invoke\",309,0,\"as-memory.grow-size\",[]],[\"module\",312,53,\"$Mgm\"],[\"register\",\"grown-memory\",\"$Mgm\"],[\"invoke\",317,\"$Mgm\",\"grow\",[]],[\"module\",318,54,\"$Mgim1\"],[\"register\",\"grown-imported-memory\",\"$Mgim1\"],[\"invoke\",324,\"$Mgim1\",\"grow\",[]],[\"module\",325,55,\"$Mgim2\"],[\"invoke\",330,\"$Mgim2\",\"size\",[]],[\"file\",\"memory_redundancy.wast\"],[\"module\",5,56],[\"invoke\",59,0,\"test_store_to_load\",[]],[\"invoke\",60,0,\"zero_everything\",[]],[\"invoke\",61,0,\"test_redundant_load\",[]],[\"invoke\",62,0,\"zero_everything\",[]],[\"invoke\",63,0,\"test_dead_store\",[]],[\"invoke\",64,0,\"zero_everything\",[]],[\"invoke\",65,0,\"malloc_aliasing\",[]],[\"file\",\"memory_size.wast\"],[\"module\",1,57],[\"invoke\",7,0,\"size\",[]],[\"invoke\",8,0,\"grow\",[\"i32:1\"]],[\"invoke\",9,0,\"size\",[]],[\"invoke\",10,0,\"grow\",[\"i32:4\"]],[\"invoke\",11,0,\"size\",[]],[\"invoke\",12,0,\"grow\",[\"i32:0\"]],[\"invoke\",13,0,\"size\",[]],[\"module\",15,58],[\"invoke\",21,0,\"size\",[]],[\"invoke\",22,0,\"grow\",[\"i32:1\"]],[\"invoke\",23,0,\"size\",[]],[\"invoke\",24,0,\"grow\",[\"i32:4\"]],[\"invoke\",25,0,\"size\",[]],[\"invoke\",26,0,\"grow\",[\"i32:0\"]],[\"invoke\",27,0,\"size\",[]],[\"module\",29,59],[\"invoke\",35,0,\"size\",[]],[\"invoke\",36,0,\"grow\",[\"i32:3\"]],[\"invoke\",37,0,\"size\",[]],[\"invoke\",38,0,\"grow\",[\"i32:1\"]],[\"invoke\",39,0,\"size\",[]],[\"invoke\",40,0,\"grow\",[\"i32:0\"]],[\"invoke\",41,0,\"size\",[]],[\"invoke\",42,0,\"grow\",[\"i32:4\"]],[\"invoke\",43,0,\"size\",[]],[\"invoke\",44,0,\"grow\",[\"i32:1\"]],[\"invoke\",45,0,\"size\",[]],[\"module\",47,60],[\"invoke\",53,0,\"size\",[]],[\"invoke\",54,0,\"grow\",[\"i32:1\"]],[\"invoke\",55,0,\"size\",[]],[\"invoke\",56,0,\"grow\",[\"i32:3\"]],[\"invoke\",57,0,\"size\",[]],[\"invoke\",58,0,\"grow\",[\"i32:0\"]],[\"invoke\",59,0,\"size\",[]],[\"invoke\",60,0,\"grow\",[\"i32:2\"]],[\"invoke\",61,0,\"size\",[]],[\"invoke\",62,0,\"grow\",[\"i32:1\"]],[\"invoke\",63,0,\"size\",[]],[\"file\",\"memory_trap.wast\"],[\"module\",1,61],[\"invoke\",21,0,\"store\",[\"i32:4294967292\",\"i32:42\"]],[\"invoke\",22,0,\"load\",[\"i32:4294967292\"]],[\"invoke\",23,0,\"store\",[\"i32:4294967293\",\"i32:305419896\"]],[\"invoke\",24,0,\"load\",[\"i32:4294967293\"]],[\"invoke\",25,0,\"store\",[\"i32:4294967294\",\"i32:13\"]],[\"invoke\",26,0,\"load\",[\"i32:4294967294\"]],[\"invoke\",27,0,\"store\",[\"i32:4294967295\",\"i32:13\"]],[\"invoke\",28,0,\"load\",[\"i32:4294967295\"]],[\"invoke\",29,0,\"store\",[\"i32:0\",\"i32:13\"]],[\"invoke\",30,0,\"load\",[\"i32:0\"]],[\"invoke\",31,0,\"store\",[\"i32:2147483648\",\"i32:13\"]],[\"invoke\",32,0,\"load\",[\"i32:2147483648\"]],[\"invoke\",33,0,\"memory.grow\",[\"i32:65537\"]],[\"module\",35,62],[\"invoke\",111,0,\"i32.store\",[\"i32:65536\",\"i32:0\"]],[\"invoke\",112,0,\"i32.store\",[\"i32:65535\",\"i32:0\"]],[\"invoke\",113,0,\"i32.store\",[\"i32:65534\",\"i32:0\"]],[\"invoke\",114,0,\"i32.store\",[\"i32:65533\",\"i32:0\"]],[\"invoke\",115,0,\"i32.store\",[\"i32:4294967295\",\"i32:0\"]],[\"invoke\",116,0,\"i32.store\",[\"i32:4294967294\",\"i32:0\"]],[\"invoke\",117,0,\"i32.store\",[\"i32:4294967293\",\"i32:0\"]],[\"invoke\",118,0,\"i32.store\",[\"i32:4294967292\",\"i32:0\"]],[\"invoke\",119,0,\"i64.store\",[\"i32:65536\",\"i64:0\"]],[\"invoke\",120,0,\"i64.store\",[\"i32:65535\",\"i64:0\"]],[\"invoke\",121,0,\"i64.store\",[\"i32:65534\",\"i64:0\"]],[\"invoke\",122,0,\"i64.store\",[\"i32:65533\",\"i64:0\"]],[\"invoke\",123,0,\"i64.store\",[\"i32:65532\",\"i64:0\"]],[\"invoke\",124,0,\"i64.store\",[\"i32:65531\",\"i64:0\"]],[\"invoke\",125,0,\"i64.store\",[\"i32:65530\",\"i64:0\"]],[\"invoke\",126,0,\"i64.store\",[\"i32:65529\",\"i64:0\"]],[\"invoke\",127,0,\"i64.store\",[\"i32:4294967295\",\"i64:0\"]],[\"invoke\",128,0,\"i64.store\",[\"i32:4294967294\",\"i64:0\"]],[\"invoke\",129,0,\"i64.store\",[\"i32:4294967293\",\"i64:0\"]],[\"invoke\",130,0,\"i64.store\",[\"i32:4294967292\",\"i64:0\"]],[\"invoke\",131,0,\"i64.store\",[\"i32:4294967291\",\"i64:0\"]],[\"invoke\",132,0,\"i64.store\",[\"i32:4294967290\",\"i64:0\"]],[\"invoke\",133,0,\"i64.store\",[\"i32:4294967289\",\"i64:0\"]],[\"invoke\",134,0,\"i64.store\",[\"i32:4294967288\",\"i64:0\"]],[\"invoke\",135,0,\"f32.store\",[\"i32:65536\",\"f32:0\"]],[\"invoke\",136,0,\"f32.store\",[\"i32:65535\",\"f32:0\"]],[\"invoke\",137,0,\"f32.store\",[\"i32:65534\",\"f32:0\"]],[\"invoke\",138,0,\"f32.store\",[\"i32:65533\",\"f32:0\"]],[\"invoke\",139,0,\"f32.store\",[\"i32:4294967295\",\"f32:0\"]],[\"invoke\",140,0,\"f32.store\",[\"i32:4294967294\",\"f32:0\"]],[\"invoke\",141,0,\"f32.store\",[\"i32:4294967293\",\"f32:0\"]],[\"invoke\",142,0,\"f32.store\",[\"i32:4294967292\",\"f32:0\"]],[\"invoke\",143,0,\"f64.store\",[\"i32:65536\",\"f64:0\"]],[\"invoke\",144,0,\"f64.store\",[\"i32:65535\",\"f64:0\"]],[\"invoke\",145,0,\"f64.store\",[\"i32:65534\",\"f64:0\"]],[\"invoke\",146,0,\"f64.store\",[\"i32:65533\",\"f64:0\"]],[\"invoke\",147,0,\"f64.store\",[\"i32:65532\",\"f64:0\"]],[\"invoke\",148,0,\"f64.store\",[\"i32:65531\",\"f64:0\"]],[\"invoke\",149,0,\"f64.store\",[\"i32:65530\",\"f64:0\"]],[\"invoke\",150,0,\"f64.store\",[\"i32:65529\",\"f64:0\"]],[\"invoke\",151,0,\"f64.store\",[\"i32:4294967295\",\"f64:0\"]],[\"invoke\",152,0,\"f64.store\",[\"i32:4294967294\",\"f64:0\"]],[\"invoke\",153,0,\"f64.store\",[\"i32:4294967293\",\"f64:0\"]],[\"invoke\",154,0,\"f64.store\",[\"i32:4294967292\",\"f64:0\"]],[\"invoke\",155,0,\"f64.store\",[\"i32:4294967291\",\"f64:0\"]],[\"invoke\",156,0,\"f64.store\",[\"i32:4294967290\",\"f64:0\"]],[\"invoke\",157,0,\"f64.store\",[\"i32:4294967289\",\"f64:0\"]],[\"invoke\",158,0,\"f64.store\",[\"i32:4294967288\",\"f64:0\"]],[\"invoke\",159,0,\"i32.store8\",[\"i32:65536\",\"i32:0\"]],[\"invoke\",160,0,\"i32.store8\",[\"i32:4294967295\",\"i32:0\"]],[\"invoke\",161,0,\"i32.store16\",[\"i32:65536\",\"i32:0\"]],[\"invoke\",162,0,\"i32.store16\",[\"i32:65535\",\"i32:0\"]],[\"invoke\",163,0,\"i32.store16\",[\"i32:4294967295\",\"i32:0\"]],[\"invoke\",164,0,\"i32.store16\",[\"i32:4294967294\",\"i32:0\"]],[\"invoke\",165,0,\"i64.store8\",[\"i32:65536\",\"i64:0\"]],[\"invoke\",166,0,\"i64.store8\",[\"i32:4294967295\",\"i64:0\"]],[\"invoke\",167,0,\"i64.store16\",[\"i32:65536\",\"i64:0\"]],[\"invoke\",168,0,\"i64.store16\",[\"i32:65535\",\"i64:0\"]],[\"invoke\",169,0,\"i64.store16\",[\"i32:4294967295\",\"i64:0\"]],[\"invoke\",170,0,\"i64.store16\",[\"i32:4294967294\",\"i64:0\"]],[\"invoke\",171,0,\"i64.store32\",[\"i32:65536\",\"i64:0\"]],[\"invoke\",172,0,\"i64.store32\",[\"i32:65535\",\"i64:0\"]],[\"invoke\",173,0,\"i64.store32\",[\"i32:65534\",\"i64:0\"]],[\"invoke\",174,0,\"i64.store32\",[\"i32:65533\",\"i64:0\"]],[\"invoke\",175,0,\"i64.store32\",[\"i32:4294967295\",\"i64:0\"]],[\"invoke\",176,0,\"i64.store32\",[\"i32:4294967294\",\"i64:0\"]],[\"invoke\",177,0,\"i64.store32\",[\"i32:4294967293\",\"i64:0\"]],[\"invoke\",178,0,\"i64.store32\",[\"i32:4294967292\",\"i64:0\"]],[\"invoke\",179,0,\"i32.load\",[\"i32:65536\"]],[\"invoke\",180,0,\"i32.load\",[\"i32:65535\"]],[\"invoke\",181,0,\"i32.load\",[\"i32:65534\"]],[\"invoke\",182,0,\"i32.load\",[\"i32:65533\"]],[\"invoke\",183,0,\"i32.load\",[\"i32:4294967295\"]],[\"invoke\",184,0,\"i32.load\",[\"i32:4294967294\"]],[\"invoke\",185,0,\"i32.load\",[\"i32:4294967293\"]],[\"invoke\",186,0,\"i32.load\",[\"i32:4294967292\"]],[\"invoke\",187,0,\"i64.load\",[\"i32:65536\"]],[\"invoke\",188,0,\"i64.load\",[\"i32:65535\"]],[\"invoke\",189,0,\"i64.load\",[\"i32:65534\"]],[\"invoke\",190,0,\"i64.load\",[\"i32:65533\"]],[\"invoke\",191,0,\"i64.load\",[\"i32:65532\"]],[\"invoke\",192,0,\"i64.load\",[\"i32:65531\"]],[\"invoke\",193,0,\"i64.load\",[\"i32:65530\"]],[\"invoke\",194,0,\"i64.load\",[\"i32:65529\"]],[\"invoke\",195,0,\"i64.load\",[\"i32:4294967295\"]],[\"invoke\",196,0,\"i64.load\",[\"i32:4294967294\"]],[\"invoke\",197,0,\"i64.load\",[\"i32:4294967293\"]],[\"invoke\",198,0,\"i64.load\",[\"i32:4294967292\"]],[\"invoke\",199,0,\"i64.load\",[\"i32:4294967291\"]],[\"invoke\",200,0,\"i64.load\",[\"i32:4294967290\"]],[\"invoke\",201,0,\"i64.load\",[\"i32:4294967289\"]],[\"invoke\",202,0,\"i64.load\",[\"i32:4294967288\"]],[\"invoke\",203,0,\"f32.load\",[\"i32:65536\"]],[\"invoke\",204,0,\"f32.load\",[\"i32:65535\"]],[\"invoke\",205,0,\"f32.load\",[\"i32:65534\"]],[\"invoke\",206,0,\"f32.load\",[\"i32:65533\"]],[\"invoke\",207,0,\"f32.load\",[\"i32:4294967295\"]],[\"invoke\",208,0,\"f32.load\",[\"i32:4294967294\"]],[\"invoke\",209,0,\"f32.load\",[\"i32:4294967293\"]],[\"invoke\",210,0,\"f32.load\",[\"i32:4294967292\"]],[\"invoke\",211,0,\"f64.load\",[\"i32:65536\"]],[\"invoke\",212,0,\"f64.load\",[\"i32:65535\"]],[\"invoke\",213,0,\"f64.load\",[\"i32:65534\"]],[\"invoke\",214,0,\"f64.load\",[\"i32:65533\"]],[\"invoke\",215,0,\"f64.load\",[\"i32:65532\"]],[\"invoke\",216,0,\"f64.load\",[\"i32:65531\"]],[\"invoke\",217,0,\"f64.load\",[\"i32:65530\"]],[\"invoke\",218,0,\"f64.load\",[\"i32:65529\"]],[\"invoke\",219,0,\"f64.load\",[\"i32:4294967295\"]],[\"invoke\",220,0,\"f64.load\",[\"i32:4294967294\"]],[\"invoke\",221,0,\"f64.load\",[\"i32:4294967293\"]],[\"invoke\",222,0,\"f64.load\",[\"i32:4294967292\"]],[\"invoke\",223,0,\"f64.load\",[\"i32:4294967291\"]],[\"invoke\",224,0,\"f64.load\",[\"i32:4294967290\"]],[\"invoke\",225,0,\"f64.load\",[\"i32:4294967289\"]],[\"invoke\",226,0,\"f64.load\",[\"i32:4294967288\"]],[\"invoke\",227,0,\"i32.load8_s\",[\"i32:65536\"]],[\"invoke\",228,0,\"i32.load8_s\",[\"i32:4294967295\"]],[\"invoke\",229,0,\"i32.load8_u\",[\"i32:65536\"]],[\"invoke\",230,0,\"i32.load8_u\",[\"i32:4294967295\"]],[\"invoke\",231,0,\"i32.load16_s\",[\"i32:65536\"]],[\"invoke\",232,0,\"i32.load16_s\",[\"i32:65535\"]],[\"invoke\",233,0,\"i32.load16_s\",[\"i32:4294967295\"]],[\"invoke\",234,0,\"i32.load16_s\",[\"i32:4294967294\"]],[\"invoke\",235,0,\"i32.load16_u\",[\"i32:65536\"]],[\"invoke\",236,0,\"i32.load16_u\",[\"i32:65535\"]],[\"invoke\",237,0,\"i32.load16_u\",[\"i32:4294967295\"]],[\"invoke\",238,0,\"i32.load16_u\",[\"i32:4294967294\"]],[\"invoke\",239,0,\"i64.load8_s\",[\"i32:65536\"]],[\"invoke\",240,0,\"i64.load8_s\",[\"i32:4294967295\"]],[\"invoke\",241,0,\"i64.load8_u\",[\"i32:65536\"]],[\"invoke\",242,0,\"i64.load8_u\",[\"i32:4294967295\"]],[\"invoke\",243,0,\"i64.load16_s\",[\"i32:65536\"]],[\"invoke\",244,0,\"i64.load16_s\",[\"i32:65535\"]],[\"invoke\",245,0,\"i64.load16_s\",[\"i32:4294967295\"]],[\"invoke\",246,0,\"i64.load16_s\",[\"i32:4294967294\"]],[\"invoke\",247,0,\"i64.load16_u\",[\"i32:65536\"]],[\"invoke\",248,0,\"i64.load16_u\",[\"i32:65535\"]],[\"invoke\",249,0,\"i64.load16_u\",[\"i32:4294967295\"]],[\"invoke\",250,0,\"i64.load16_u\",[\"i32:4294967294\"]],[\"invoke\",251,0,\"i64.load32_s\",[\"i32:65536\"]],[\"invoke\",252,0,\"i64.load32_s\",[\"i32:65535\"]],[\"invoke\",253,0,\"i64.load32_s\",[\"i32:65534\"]],[\"invoke\",254,0,\"i64.load32_s\",[\"i32:65533\"]],[\"invoke\",255,0,\"i64.load32_s\",[\"i32:4294967295\"]],[\"invoke\",256,0,\"i64.load32_s\",[\"i32:4294967294\"]],[\"invoke\",257,0,\"i64.load32_s\",[\"i32:4294967293\"]],[\"invoke\",258,0,\"i64.load32_s\",[\"i32:4294967292\"]],[\"invoke\",259,0,\"i64.load32_u\",[\"i32:65536\"]],[\"invoke\",260,0,\"i64.load32_u\",[\"i32:65535\"]],[\"invoke\",261,0,\"i64.load32_u\",[\"i32:65534\"]],[\"invoke\",262,0,\"i64.load32_u\",[\"i32:65533\"]],[\"invoke\",263,0,\"i64.load32_u\",[\"i32:4294967295\"]],[\"invoke\",264,0,\"i64.load32_u\",[\"i32:4294967294\"]],[\"invoke\",265,0,\"i64.load32_u\",[\"i32:4294967293\"]],[\"invoke\",266,0,\"i64.load32_u\",[\"i32:4294967292\"]],[\"invoke\",269,0,\"i64.load\",[\"i32:65528\"]],[\"invoke\",270,0,\"i64.load\",[\"i32:0\"]],[\"invoke\",274,0,\"i64.store\",[\"i32:65528\",\"i64:0\"]],[\"invoke\",275,0,\"i32.store\",[\"i32:65533\",\"i32:305419896\"]],[\"invoke\",276,0,\"i32.load\",[\"i32:65532\"]],[\"invoke\",277,0,\"i64.store\",[\"i32:65529\",\"i64:1311768467294899695\"]],[\"invoke\",278,0,\"i64.load\",[\"i32:65528\"]],[\"invoke\",279,0,\"f32.store\",[\"i32:65533\",\"f32:1301390004\"]],[\"invoke\",280,0,\"f32.load\",[\"i32:65532\"]],[\"invoke\",281,0,\"f64.store\",[\"i32:65529\",\"f64:4878018892390247374\"]],[\"invoke\",282,0,\"f64.load\",[\"i32:65528\"]],[\"file\",\"bulk.wast\"],[\"module\",2,63],[\"module\",6,64],[\"module\",13,65],[\"invoke\",27,0,\"fill\",[\"i32:1\",\"i32:255\",\"i32:3\"]],[\"invoke\",28,0,\"load8_u\",[\"i32:0\"]],[\"invoke\",29,0,\"load8_u\",[\"i32:1\"]],[\"invoke\",30,0,\"load8_u\",[\"i32:2\"]],[\"invoke\",31,0,\"load8_u\",[\"i32:3\"]],[\"invoke\",32,0,\"load8_u\",[\"i32:4\"]],[\"invoke\",35,0,\"fill\",[\"i32:0\",\"i32:48042\",\"i32:2\"]],[\"invoke\",36,0,\"load8_u\",[\"i32:0\"]],[\"invoke\",37,0,\"load8_u\",[\"i32:1\"]],[\"invoke\",40,0,\"fill\",[\"i32:0\",\"i32:0\",\"i32:65536\"]],[\"invoke\",43,0,\"fill\",[\"i32:65280\",\"i32:1\",\"i32:257\"]],[\"invoke\",45,0,\"load8_u\",[\"i32:65280\"]],[\"invoke\",46,0,\"load8_u\",[\"i32:65535\"]],[\"invoke\",49,0,\"fill\",[\"i32:65536\",\"i32:0\",\"i32:0\"]],[\"invoke\",52,0,\"fill\",[\"i32:65537\",\"i32:0\",\"i32:0\"]],[\"module\",57,66],[\"invoke\",71,0,\"copy\",[\"i32:10\",\"i32:0\",\"i32:4\"]],[\"invoke\",73,0,\"load8_u\",[\"i32:9\"]],[\"invoke\",74,0,\"load8_u\",[\"i32:10\"]],[\"invoke\",75,0,\"load8_u\",[\"i32:11\"]],[\"invoke\",76,0,\"load8_u\",[\"i32:12\"]],[\"invoke\",77,0,\"load8_u\",[\"i32:13\"]],[\"invoke\",78,0,\"load8_u\",[\"i32:14\"]],[\"invoke\",81,0,\"copy\",[\"i32:8\",\"i32:10\",\"i32:4\"]],[\"invoke\",82,0,\"load8_u\",[\"i32:8\"]],[\"invoke\",83,0,\"load8_u\",[\"i32:9\"]],[\"invoke\",84,0,\"load8_u\",[\"i32:10\"]],[\"invoke\",85,0,\"load8_u\",[\"i32:11\"]],[\"invoke\",86,0,\"load8_u\",[\"i32:12\"]],[\"invoke\",87,0,\"load8_u\",[\"i32:13\"]],[\"invoke\",90,0,\"copy\",[\"i32:10\",\"i32:7\",\"i32:6\"]],[\"invoke\",91,0,\"load8_u\",[\"i32:10\"]],[\"invoke\",92,0,\"load8_u\",[\"i32:11\"]],[\"invoke\",93,0,\"load8_u\",[\"i32:12\"]],[\"invoke\",94,0,\"load8_u\",[\"i32:13\"]],[\"invoke\",95,0,\"load8_u\",[\"i32:14\"]],[\"invoke\",96,0,\"load8_u\",[\"i32:15\"]],[\"invoke\",97,0,\"load8_u\",[\"i32:16\"]],[\"invoke\",100,0,\"copy\",[\"i32:65280\",\"i32:0\",\"i32:256\"]],[\"invoke\",101,0,\"copy\",[\"i32:65024\",\"i32:65280\",\"i32:256\"]],[\"invoke\",104,0,\"copy\",[\"i32:65536\",\"i32:0\",\"i32:0\"]],[\"invoke\",105,0,\"copy\",[\"i32:0\",\"i32:65536\",\"i32:0\"]],[\"invoke\",108,0,\"copy\",[\"i32:65537\",\"i32:0\",\"i32:0\"]],[\"invoke\",110,0,\"copy\",[\"i32:0\",\"i32:65537\",\"i32:0\"]],[\"module\",115,67],[\"invoke\",129,0,\"init\",[\"i32:0\",\"i32:1\",\"i32:2\"]],[\"invoke\",130,0,\"load8_u\",[\"i32:0\"]],[\"invoke\",131,0,\"load8_u\",[\"i32:1\"]],[\"invoke\",132,0,\"load8_u\",[\"i32:2\"]],[\"invoke\",135,0,\"init\",[\"i32:65532\",\"i32:0\",\"i32:4\"]],[\"invoke\",138,0,\"init\",[\"i32:65534\",\"i32:0\",\"i32:3\"]],[\"invoke\",140,0,\"load8_u\",[\"i32:65534\"]],[\"invoke\",141,0,\"load8_u\",[\"i32:65535\"]],[\"invoke\",144,0,\"init\",[\"i32:65536\",\"i32:0\",\"i32:0\"]],[\"invoke\",145,0,\"init\",[\"i32:0\",\"i32:4\",\"i32:0\"]],[\"invoke\",148,0,\"init\",[\"i32:65537\",\"i32:0\",\"i32:0\"]],[\"invoke\",150,0,\"init\",[\"i32:0\",\"i32:5\",\"i32:0\"]],[\"module\",154,68],[\"invoke\",168,0,\"init_passive\",[\"i32:1\"]],[\"invoke\",169,0,\"drop_passive\",[]],[\"invoke\",170,0,\"drop_passive\",[]],[\"invoke\",171,0,\"init_passive\",[\"i32:0\"]],[\"invoke\",172,0,\"init_passive\",[\"i32:1\"]],[\"invoke\",173,0,\"init_passive\",[\"i32:0\"]],[\"invoke\",174,0,\"drop_active\",[]],[\"invoke\",175,0,\"init_active\",[\"i32:0\"]],[\"invoke\",176,0,\"init_active\",[\"i32:1\"]],[\"invoke\",177,0,\"init_active\",[\"i32:0\"]],[\"module\",181,69],[\"module\",196,70],[\"module\",199,71],[\"invoke\",219,0,\"init\",[\"i32:2\",\"i32:0\",\"i32:2\"]],[\"invoke\",221,0,\"call\",[\"i32:2\"]],[\"invoke\",224,0,\"init\",[\"i32:0\",\"i32:1\",\"i32:2\"]],[\"invoke\",225,0,\"call\",[\"i32:0\"]],[\"invoke\",226,0,\"call\",[\"i32:1\"]],[\"invoke\",227,0,\"call\",[\"i32:2\"]],[\"invoke\",230,0,\"init\",[\"i32:1\",\"i32:2\",\"i32:2\"]],[\"invoke\",233,0,\"init\",[\"i32:3\",\"i32:0\",\"i32:0\"]],[\"invoke\",234,0,\"init\",[\"i32:0\",\"i32:4\",\"i32:0\"]],[\"invoke\",237,0,\"init\",[\"i32:4\",\"i32:0\",\"i32:0\"]],[\"invoke\",239,0,\"init\",[\"i32:0\",\"i32:5\",\"i32:0\"]],[\"module\",244,72],[\"invoke\",261,0,\"init_passive\",[\"i32:1\"]],[\"invoke\",262,0,\"drop_passive\",[]],[\"invoke\",263,0,\"drop_passive\",[]],[\"invoke\",264,0,\"init_passive\",[\"i32:0\"]],[\"invoke\",265,0,\"init_passive\",[\"i32:1\"]],[\"invoke\",266,0,\"init_passive\",[\"i32:0\"]],[\"invoke\",267,0,\"drop_active\",[]],[\"invoke\",268,0,\"init_active\",[\"i32:0\"]],[\"invoke\",269,0,\"init_active\",[\"i32:1\"]],[\"invoke\",270,0,\"init_active\",[\"i32:0\"]],[\"module\",274,73],[\"module\",297,74],[\"module\",300,75],[\"invoke\",319,0,\"copy\",[\"i32:3\",\"i32:0\",\"i32:3\"]],[\"invoke\",321,0,\"call\",[\"i32:3\"]],[\"invoke\",322,0,\"call\",[\"i32:4\"]],[\"invoke\",323,0,\"call\",[\"i32:5\"]],[\"invoke\",326,0,\"copy\",[\"i32:0\",\"i32:1\",\"i32:3\"]],[\"invoke\",328,0,\"call\",[\"i32:0\"]],[\"invoke\",329,0,\"call\",[\"i32:1\"]],[\"invoke\",330,0,\"call\",[\"i32:2\"]],[\"invoke\",333,0,\"copy\",[\"i32:2\",\"i32:0\",\"i32:3\"]],[\"invoke\",335,0,\"call\",[\"i32:2\"]],[\"invoke\",336,0,\"call\",[\"i32:3\"]],[\"invoke\",337,0,\"call\",[\"i32:4\"]],[\"invoke\",340,0,\"copy\",[\"i32:6\",\"i32:8\",\"i32:2\"]],[\"invoke\",341,0,\"copy\",[\"i32:8\",\"i32:6\",\"i32:2\"]],[\"invoke\",344,0,\"copy\",[\"i32:10\",\"i32:0\",\"i32:0\"]],[\"invoke\",345,0,\"copy\",[\"i32:0\",\"i32:10\",\"i32:0\"]],[\"invoke\",348,0,\"copy\",[\"i32:11\",\"i32:0\",\"i32:0\"]],[\"invoke\",350,0,\"copy\",[\"i32:0\",\"i32:11\",\"i32:0\"]],[\"file\",\"memory_copy.wast\"],[\"module\",6,76],[\"invoke\",15,0,\"test\",[]],[\"invoke\",27,0,\"load8_u\",[\"i32:10\"]],[\"invoke\",38,0,\"load8_u\",[\"i32:21\"]],[\"module\",48,77],[\"invoke\",60,0,\"load8_u\",[\"i32:1\"]],[\"invoke\",71,0,\"load8_u\",[\"i32:12\"]],[\"invoke\",82,0,\"load8_u\",[\"i32:23\"]],[\"module\",90,78],[\"invoke\",104,0,\"load8_u\",[\"i32:3\"]],[\"invoke\",115,0,\"load8_u\",[\"i32:14\"]],[\"invoke\",126,0,\"load8_u\",[\"i32:25\"]],[\"module\",132,79],[\"invoke\",147,0,\"load8_u\",[\"i32:4\"]],[\"invoke\",158,0,\"load8_u\",[\"i32:15\"]],[\"invoke\",169,0,\"load8_u\",[\"i32:26\"]],[\"module\",174,80],[\"invoke\",191,0,\"load8_u\",[\"i32:6\"]],[\"invoke\",202,0,\"load8_u\",[\"i32:17\"]],[\"invoke\",213,0,\"load8_u\",[\"i32:28\"]],[\"module\",216,81],[\"invoke\",235,0,\"load8_u\",[\"i32:8\"]],[\"invoke\",246,0,\"load8_u\",[\"i32:19\"]],[\"module\",258,82],[\"invoke\",267,0,\"test\",[]],[\"invoke\",278,0,\"load8_u\",[\"i32:9\"]],[\"invoke\",289,0,\"load8_u\",[\"i32:20\"]],[\"module\",300,83],[\"invoke\",311,0,\"load8_u\",[\"i32:0\"]],[\"invoke\",322,0,\"load8_u\",[\"i32:11\"]],[\"invoke\",333,0,\"load8_u\",[\"i32:22\"]],[\"module\",342,84],[\"invoke\",355,0,\"load8_u\",[\"i32:2\"]],[\"invoke\",366,0,\"load8_u\",[\"i32:13\"]],[\"invoke\",377,0,\"load8_u\",[\"i32:1014\"]],[\"invoke\",387,0,\"load8_u\",[\"i32:3004\"]],[\"invoke\",398,0,\"load8_u\",[\"i32:5193\"]],[\"invoke\",409,0,\"load8_u\",[\"i32:7382\"]],[\"invoke\",420,0,\"load8_u\",[\"i32:9571\"]],[\"invoke\",431,0,\"load8_u\",[\"i32:11760\"]],[\"invoke\",442,0,\"load8_u\",[\"i32:13949\"]],[\"invoke\",453,0,\"load8_u\",[\"i32:16138\"]],[\"invoke\",464,0,\"load8_u\",[\"i32:18327\"]],[\"invoke\",475,0,\"load8_u\",[\"i32:20516\"]],[\"invoke\",485,0,\"load8_u\",[\"i32:22506\"]],[\"invoke\",496,0,\"load8_u\",[\"i32:24695\"]],[\"invoke\",507,0,\"load8_u\",[\"i32:26884\"]],[\"invoke\",518,0,\"load8_u\",[\"i32:29073\"]],[\"invoke\",529,0,\"load8_u\",[\"i32:31262\"]],[\"invoke\",540,0,\"load8_u\",[\"i32:33451\"]],[\"invoke\",551,0,\"load8_u\",[\"i32:35640\"]],[\"invoke\",562,0,\"load8_u\",[\"i32:37829\"]],[\"invoke\",572,0,\"load8_u\",[\"i32:39819\"]],[\"invoke\",583,0,\"load8_u\",[\"i32:42008\"]],[\"invoke\",594,0,\"load8_u\",[\"i32:44197\"]],[\"invoke\",605,0,\"load8_u\",[\"i32:46386\"]],[\"invoke\",616,0,\"load8_u\",[\"i32:48575\"]],[\"invoke\",627,0,\"load8_u\",[\"i32:50764\"]],[\"invoke\",638,0,\"load8_u\",[\"i32:52953\"]],[\"invoke\",649,0,\"load8_u\",[\"i32:55142\"]],[\"invoke\",660,0,\"load8_u\",[\"i32:57331\"]],[\"invoke\",670,0,\"load8_u\",[\"i32:59321\"]],[\"invoke\",681,0,\"load8_u\",[\"i32:61510\"]],[\"invoke\",692,0,\"load8_u\",[\"i32:63699\"]],[\"module\",703,85],[\"invoke\",714,0,\"load8_u\",[\"i32:0\"]],[\"invoke\",725,0,\"load8_u\",[\"i32:11\"]],[\"invoke\",736,0,\"load8_u\",[\"i32:418\"]],[\"invoke\",747,0,\"load8_u\",[\"i32:2607\"]],[\"invoke\",758,0,\"load8_u\",[\"i32:4796\"]],[\"invoke\",768,0,\"load8_u\",[\"i32:6786\"]],[\"invoke\",779,0,\"load8_u\",[\"i32:8975\"]],[\"invoke\",790,0,\"load8_u\",[\"i32:11164\"]],[\"invoke\",801,0,\"load8_u\",[\"i32:13353\"]],[\"invoke\",812,0,\"load8_u\",[\"i32:15542\"]],[\"invoke\",823,0,\"load8_u\",[\"i32:17731\"]],[\"invoke\",834,0,\"load8_u\",[\"i32:19920\"]],[\"invoke\",845,0,\"load8_u\",[\"i32:22109\"]],[\"invoke\",856,0,\"load8_u\",[\"i32:24298\"]],[\"invoke\",866,0,\"load8_u\",[\"i32:26288\"]],[\"invoke\",877,0,\"load8_u\",[\"i32:28477\"]],[\"invoke\",888,0,\"load8_u\",[\"i32:30666\"]],[\"invoke\",899,0,\"load8_u\",[\"i32:32855\"]],[\"invoke\",910,0,\"load8_u\",[\"i32:35044\"]],[\"invoke\",921,0,\"load8_u\",[\"i32:37233\"]],[\"invoke\",932,0,\"load8_u\",[\"i32:39422\"]],[\"invoke\",943,0,\"load8_u\",[\"i32:41611\"]],[\"invoke\",953,0,\"load8_u\",[\"i32:43601\"]],[\"invoke\",964,0,\"load8_u\",[\"i32:45790\"]],[\"invoke\",975,0,\"load8_u\",[\"i32:47979\"]],[\"invoke\",986,0,\"load8_u\",[\"i32:50168\"]],[\"invoke\",997,0,\"load8_u\",[\"i32:52357\"]],[\"invoke\",1008,0,\"load8_u\",[\"i32:54546\"]],[\"invoke\",1019,0,\"load8_u\",[\"i32:56735\"]],[\"invoke\",1030,0,\"load8_u\",[\"i32:58924\"]],[\"invoke\",1041,0,\"load8_u\",[\"i32:61113\"]],[\"invoke\",1051,0,\"load8_u\",[\"i32:63103\"]],[\"invoke\",1062,0,\"load8_u\",[\"i32:65292\"]],[\"module\",1065,86],[\"invoke\",1084,0,\"load8_u\",[\"i32:1790\"]],[\"invoke\",1095,0,\"load8_u\",[\"i32:3979\"]],[\"invoke\",1106,0,\"load8_u\",[\"i32:6168\"]],[\"invoke\",1117,0,\"load8_u\",[\"i32:8357\"]],[\"invoke\",1128,0,\"load8_u\",[\"i32:10546\"]],[\"invoke\",1139,0,\"load8_u\",[\"i32:12735\"]],[\"invoke\",1149,0,\"load8_u\",[\"i32:14725\"]],[\"invoke\",1160,0,\"load8_u\",[\"i32:16914\"]],[\"invoke\",1171,0,\"load8_u\",[\"i32:19103\"]],[\"invoke\",1182,0,\"load8_u\",[\"i32:21292\"]],[\"invoke\",1193,0,\"load8_u\",[\"i32:23481\"]],[\"invoke\",1204,0,\"load8_u\",[\"i32:25670\"]],[\"invoke\",1215,0,\"load8_u\",[\"i32:27859\"]],[\"invoke\",1226,0,\"load8_u\",[\"i32:30048\"]],[\"invoke\",1237,0,\"load8_u\",[\"i32:32237\"]],[\"invoke\",1247,0,\"load8_u\",[\"i32:34227\"]],[\"invoke\",1258,0,\"load8_u\",[\"i32:36416\"]],[\"invoke\",1269,0,\"load8_u\",[\"i32:38605\"]],[\"invoke\",1280,0,\"load8_u\",[\"i32:40794\"]],[\"invoke\",1291,0,\"load8_u\",[\"i32:42983\"]],[\"invoke\",1302,0,\"load8_u\",[\"i32:45172\"]],[\"invoke\",1313,0,\"load8_u\",[\"i32:47361\"]],[\"invoke\",1324,0,\"load8_u\",[\"i32:49550\"]],[\"invoke\",1334,0,\"load8_u\",[\"i32:51540\"]],[\"invoke\",1345,0,\"load8_u\",[\"i32:53729\"]],[\"invoke\",1356,0,\"load8_u\",[\"i32:55918\"]],[\"invoke\",1367,0,\"load8_u\",[\"i32:58107\"]],[\"invoke\",1378,0,\"load8_u\",[\"i32:60296\"]],[\"invoke\",1389,0,\"load8_u\",[\"i32:62485\"]],[\"invoke\",1400,0,\"load8_u\",[\"i32:64674\"]],[\"invoke\",1411,0,\"load8_u\",[\"i32:65522\"]],[\"invoke\",1422,0,\"load8_u\",[\"i32:65533\"]],[\"module\",1426,87],[\"invoke\",1443,0,\"load8_u\",[\"i32:1392\"]],[\"invoke\",1454,0,\"load8_u\",[\"i32:3581\"]],[\"invoke\",1465,0,\"load8_u\",[\"i32:5770\"]],[\"invoke\",1476,0,\"load8_u\",[\"i32:7959\"]],[\"invoke\",1487,0,\"load8_u\",[\"i32:10148\"]],[\"invoke\",1498,0,\"load8_u\",[\"i32:12337\"]],[\"invoke\",1509,0,\"load8_u\",[\"i32:14526\"]],[\"invoke\",1520,0,\"load8_u\",[\"i32:16715\"]],[\"invoke\",1530,0,\"load8_u\",[\"i32:18705\"]],[\"invoke\",1541,0,\"load8_u\",[\"i32:20894\"]],[\"invoke\",1552,0,\"load8_u\",[\"i32:23083\"]],[\"invoke\",1563,0,\"load8_u\",[\"i32:25272\"]],[\"invoke\",1574,0,\"load8_u\",[\"i32:27461\"]],[\"invoke\",1585,0,\"load8_u\",[\"i32:29650\"]],[\"invoke\",1596,0,\"load8_u\",[\"i32:31839\"]],[\"invoke\",1607,0,\"load8_u\",[\"i32:34028\"]],[\"invoke\",1618,0,\"load8_u\",[\"i32:36217\"]],[\"invoke\",1628,0,\"load8_u\",[\"i32:38207\"]],[\"invoke\",1639,0,\"load8_u\",[\"i32:40396\"]],[\"invoke\",1650,0,\"load8_u\",[\"i32:42585\"]],[\"invoke\",1661,0,\"load8_u\",[\"i32:44774\"]],[\"invoke\",1672,0,\"load8_u\",[\"i32:46963\"]],[\"invoke\",1683,0,\"load8_u\",[\"i32:49152\"]],[\"invoke\",1694,0,\"load8_u\",[\"i32:51341\"]],[\"invoke\",1705,0,\"load8_u\",[\"i32:53530\"]],[\"invoke\",1715,0,\"load8_u\",[\"i32:55520\"]],[\"invoke\",1726,0,\"load8_u\",[\"i32:57709\"]],[\"invoke\",1737,0,\"load8_u\",[\"i32:59898\"]],[\"invoke\",1748,0,\"load8_u\",[\"i32:62087\"]],[\"invoke\",1759,0,\"load8_u\",[\"i32:64276\"]],[\"invoke\",1770,0,\"load8_u\",[\"i32:65519\"]],[\"invoke\",1781,0,\"load8_u\",[\"i32:65530\"]],[\"module\",1788,88],[\"invoke\",1803,0,\"load8_u\",[\"i32:994\"]],[\"invoke\",1814,0,\"load8_u\",[\"i32:3183\"]],[\"invoke\",1824,0,\"load8_u\",[\"i32:5173\"]],[\"invoke\",1835,0,\"load8_u\",[\"i32:7362\"]],[\"invoke\",1846,0,\"load8_u\",[\"i32:9551\"]],[\"invoke\",1857,0,\"load8_u\",[\"i32:11740\"]],[\"invoke\",1868,0,\"load8_u\",[\"i32:13929\"]],[\"invoke\",1879,0,\"load8_u\",[\"i32:16118\"]],[\"invoke\",1890,0,\"load8_u\",[\"i32:18307\"]],[\"invoke\",1901,0,\"load8_u\",[\"i32:20496\"]],[\"invoke\",1911,0,\"load8_u\",[\"i32:22486\"]],[\"invoke\",1922,0,\"load8_u\",[\"i32:24675\"]],[\"invoke\",1933,0,\"load8_u\",[\"i32:26864\"]],[\"invoke\",1944,0,\"load8_u\",[\"i32:29053\"]],[\"invoke\",1955,0,\"load8_u\",[\"i32:31242\"]],[\"invoke\",1966,0,\"load8_u\",[\"i32:33431\"]],[\"invoke\",1977,0,\"load8_u\",[\"i32:35620\"]],[\"invoke\",1988,0,\"load8_u\",[\"i32:37809\"]],[\"invoke\",1999,0,\"load8_u\",[\"i32:39998\"]],[\"invoke\",2009,0,\"load8_u\",[\"i32:41988\"]],[\"invoke\",2020,0,\"load8_u\",[\"i32:44177\"]],[\"invoke\",2031,0,\"load8_u\",[\"i32:46366\"]],[\"invoke\",2042,0,\"load8_u\",[\"i32:48555\"]],[\"invoke\",2053,0,\"load8_u\",[\"i32:50744\"]],[\"invoke\",2064,0,\"load8_u\",[\"i32:52933\"]],[\"invoke\",2075,0,\"load8_u\",[\"i32:55122\"]],[\"invoke\",2086,0,\"load8_u\",[\"i32:57311\"]],[\"invoke\",2096,0,\"load8_u\",[\"i32:59301\"]],[\"invoke\",2107,0,\"load8_u\",[\"i32:61490\"]],[\"invoke\",2118,0,\"load8_u\",[\"i32:63679\"]],[\"invoke\",2129,0,\"load8_u\",[\"i32:65487\"]],[\"invoke\",2140,0,\"load8_u\",[\"i32:65498\"]],[\"module\",2149,86],[\"invoke\",2162,0,\"load8_u\",[\"i32:596\"]],[\"invoke\",2173,0,\"load8_u\",[\"i32:2785\"]],[\"invoke\",2184,0,\"load8_u\",[\"i32:4974\"]],[\"invoke\",2195,0,\"load8_u\",[\"i32:7163\"]],[\"invoke\",2205,0,\"load8_u\",[\"i32:9153\"]],[\"invoke\",2216,0,\"load8_u\",[\"i32:11342\"]],[\"invoke\",2227,0,\"load8_u\",[\"i32:13531\"]],[\"invoke\",2238,0,\"load8_u\",[\"i32:15720\"]],[\"invoke\",2249,0,\"load8_u\",[\"i32:17909\"]],[\"invoke\",2260,0,\"load8_u\",[\"i32:20098\"]],[\"invoke\",2271,0,\"load8_u\",[\"i32:22287\"]],[\"invoke\",2282,0,\"load8_u\",[\"i32:24476\"]],[\"invoke\",2292,0,\"load8_u\",[\"i32:26466\"]],[\"invoke\",2303,0,\"load8_u\",[\"i32:28655\"]],[\"invoke\",2314,0,\"load8_u\",[\"i32:30844\"]],[\"invoke\",2325,0,\"load8_u\",[\"i32:33033\"]],[\"invoke\",2336,0,\"load8_u\",[\"i32:35222\"]],[\"invoke\",2347,0,\"load8_u\",[\"i32:37411\"]],[\"invoke\",2358,0,\"load8_u\",[\"i32:39600\"]],[\"invoke\",2369,0,\"load8_u\",[\"i32:41789\"]],[\"invoke\",2380,0,\"load8_u\",[\"i32:43978\"]],[\"invoke\",2390,0,\"load8_u\",[\"i32:45968\"]],[\"invoke\",2401,0,\"load8_u\",[\"i32:48157\"]],[\"invoke\",2412,0,\"load8_u\",[\"i32:50346\"]],[\"invoke\",2423,0,\"load8_u\",[\"i32:52535\"]],[\"invoke\",2434,0,\"load8_u\",[\"i32:54724\"]],[\"invoke\",2445,0,\"load8_u\",[\"i32:56913\"]],[\"invoke\",2456,0,\"load8_u\",[\"i32:59102\"]],[\"invoke\",2467,0,\"load8_u\",[\"i32:61291\"]],[\"invoke\",2477,0,\"load8_u\",[\"i32:63281\"]],[\"invoke\",2488,0,\"load8_u\",[\"i32:65470\"]],[\"invoke\",2499,0,\"load8_u\",[\"i32:65526\"]],[\"module\",2510,89],[\"invoke\",2521,0,\"load8_u\",[\"i32:198\"]],[\"invoke\",2532,0,\"load8_u\",[\"i32:2387\"]],[\"invoke\",2543,0,\"load8_u\",[\"i32:4576\"]],[\"invoke\",2554,0,\"load8_u\",[\"i32:6765\"]],[\"invoke\",2565,0,\"load8_u\",[\"i32:8954\"]],[\"invoke\",2576,0,\"load8_u\",[\"i32:11143\"]],[\"invoke\",2586,0,\"load8_u\",[\"i32:13133\"]],[\"invoke\",2597,0,\"load8_u\",[\"i32:15322\"]],[\"invoke\",2608,0,\"load8_u\",[\"i32:17511\"]],[\"invoke\",2619,0,\"load8_u\",[\"i32:19700\"]],[\"invoke\",2630,0,\"load8_u\",[\"i32:21889\"]],[\"invoke\",2641,0,\"load8_u\",[\"i32:24078\"]],[\"invoke\",2652,0,\"load8_u\",[\"i32:26267\"]],[\"invoke\",2663,0,\"load8_u\",[\"i32:28456\"]],[\"invoke\",2673,0,\"load8_u\",[\"i32:30446\"]],[\"invoke\",2684,0,\"load8_u\",[\"i32:32635\"]],[\"invoke\",2695,0,\"load8_u\",[\"i32:34824\"]],[\"invoke\",2706,0,\"load8_u\",[\"i32:37013\"]],[\"invoke\",2717,0,\"load8_u\",[\"i32:39202\"]],[\"invoke\",2728,0,\"load8_u\",[\"i32:41391\"]],[\"invoke\",2739,0,\"load8_u\",[\"i32:43580\"]],[\"invoke\",2750,0,\"load8_u\",[\"i32:45769\"]],[\"invoke\",2761,0,\"load8_u\",[\"i32:47958\"]],[\"invoke\",2771,0,\"load8_u\",[\"i32:49948\"]],[\"invoke\",2782,0,\"load8_u\",[\"i32:52137\"]],[\"invoke\",2793,0,\"load8_u\",[\"i32:54326\"]],[\"invoke\",2804,0,\"load8_u\",[\"i32:56515\"]],[\"invoke\",2815,0,\"load8_u\",[\"i32:58704\"]],[\"invoke\",2826,0,\"load8_u\",[\"i32:60893\"]],[\"invoke\",2837,0,\"load8_u\",[\"i32:63082\"]],[\"invoke\",2848,0,\"load8_u\",[\"i32:65271\"]],[\"invoke\",2858,0,\"load8_u\",[\"i32:65514\"]],[\"invoke\",2869,0,\"load8_u\",[\"i32:65525\"]],[\"module\",2871,86],[\"invoke\",2891,0,\"load8_u\",[\"i32:1989\"]],[\"invoke\",2902,0,\"load8_u\",[\"i32:4178\"]],[\"invoke\",2913,0,\"load8_u\",[\"i32:6367\"]],[\"invoke\",2924,0,\"load8_u\",[\"i32:8556\"]],[\"invoke\",2935,0,\"load8_u\",[\"i32:10745\"]],[\"invoke\",2946,0,\"load8_u\",[\"i32:12934\"]],[\"invoke\",2957,0,\"load8_u\",[\"i32:15123\"]],[\"invoke\",2967,0,\"load8_u\",[\"i32:17113\"]],[\"invoke\",2978,0,\"load8_u\",[\"i32:19302\"]],[\"invoke\",2989,0,\"load8_u\",[\"i32:21491\"]],[\"invoke\",3000,0,\"load8_u\",[\"i32:23680\"]],[\"invoke\",3011,0,\"load8_u\",[\"i32:25869\"]],[\"invoke\",3022,0,\"load8_u\",[\"i32:28058\"]],[\"invoke\",3033,0,\"load8_u\",[\"i32:30247\"]],[\"invoke\",3044,0,\"load8_u\",[\"i32:32436\"]],[\"invoke\",3054,0,\"load8_u\",[\"i32:34426\"]],[\"invoke\",3065,0,\"load8_u\",[\"i32:36615\"]],[\"invoke\",3076,0,\"load8_u\",[\"i32:38804\"]],[\"invoke\",3087,0,\"load8_u\",[\"i32:40993\"]],[\"invoke\",3098,0,\"load8_u\",[\"i32:43182\"]],[\"invoke\",3109,0,\"load8_u\",[\"i32:45371\"]],[\"invoke\",3120,0,\"load8_u\",[\"i32:47560\"]],[\"invoke\",3131,0,\"load8_u\",[\"i32:49749\"]],[\"invoke\",3142,0,\"load8_u\",[\"i32:51938\"]],[\"invoke\",3152,0,\"load8_u\",[\"i32:53928\"]],[\"invoke\",3163,0,\"load8_u\",[\"i32:56117\"]],[\"invoke\",3174,0,\"load8_u\",[\"i32:58306\"]],[\"invoke\",3185,0,\"load8_u\",[\"i32:60495\"]],[\"invoke\",3196,0,\"load8_u\",[\"i32:62684\"]],[\"invoke\",3207,0,\"load8_u\",[\"i32:64873\"]],[\"invoke\",3218,0,\"load8_u\",[\"i32:65523\"]],[\"invoke\",3229,0,\"load8_u\",[\"i32:65534\"]],[\"module\",3232,86],[\"invoke\",3250,0,\"load8_u\",[\"i32:1591\"]],[\"invoke\",3261,0,\"load8_u\",[\"i32:3780\"]],[\"invoke\",3272,0,\"load8_u\",[\"i32:5969\"]],[\"invoke\",3283,0,\"load8_u\",[\"i32:8158\"]],[\"invoke\",3294,0,\"load8_u\",[\"i32:10347\"]],[\"invoke\",3305,0,\"load8_u\",[\"i32:12536\"]],[\"invoke\",3316,0,\"load8_u\",[\"i32:14725\"]],[\"invoke\",3327,0,\"load8_u\",[\"i32:16914\"]],[\"invoke\",3338,0,\"load8_u\",[\"i32:19103\"]],[\"invoke\",3348,0,\"load8_u\",[\"i32:21093\"]],[\"invoke\",3359,0,\"load8_u\",[\"i32:23282\"]],[\"invoke\",3370,0,\"load8_u\",[\"i32:25471\"]],[\"invoke\",3381,0,\"load8_u\",[\"i32:27660\"]],[\"invoke\",3392,0,\"load8_u\",[\"i32:29849\"]],[\"invoke\",3403,0,\"load8_u\",[\"i32:32038\"]],[\"invoke\",3414,0,\"load8_u\",[\"i32:34227\"]],[\"invoke\",3425,0,\"load8_u\",[\"i32:36416\"]],[\"invoke\",3435,0,\"load8_u\",[\"i32:38406\"]],[\"invoke\",3446,0,\"load8_u\",[\"i32:40595\"]],[\"invoke\",3457,0,\"load8_u\",[\"i32:42784\"]],[\"invoke\",3468,0,\"load8_u\",[\"i32:44973\"]],[\"invoke\",3479,0,\"load8_u\",[\"i32:47162\"]],[\"invoke\",3490,0,\"load8_u\",[\"i32:49351\"]],[\"invoke\",3501,0,\"load8_u\",[\"i32:51540\"]],[\"invoke\",3512,0,\"load8_u\",[\"i32:53729\"]],[\"invoke\",3523,0,\"load8_u\",[\"i32:55918\"]],[\"invoke\",3533,0,\"load8_u\",[\"i32:57908\"]],[\"invoke\",3544,0,\"load8_u\",[\"i32:60097\"]],[\"invoke\",3555,0,\"load8_u\",[\"i32:62286\"]],[\"invoke\",3566,0,\"load8_u\",[\"i32:64475\"]],[\"invoke\",3577,0,\"load8_u\",[\"i32:65521\"]],[\"invoke\",3588,0,\"load8_u\",[\"i32:65532\"]],[\"module\",3593,90],[\"invoke\",3610,0,\"load8_u\",[\"i32:1392\"]],[\"invoke\",3621,0,\"load8_u\",[\"i32:3581\"]],[\"invoke\",3631,0,\"load8_u\",[\"i32:5571\"]],[\"invoke\",3642,0,\"load8_u\",[\"i32:7760\"]],[\"invoke\",3653,0,\"load8_u\",[\"i32:9949\"]],[\"invoke\",3664,0,\"load8_u\",[\"i32:12138\"]],[\"invoke\",3675,0,\"load8_u\",[\"i32:14327\"]],[\"invoke\",3686,0,\"load8_u\",[\"i32:16516\"]],[\"invoke\",3697,0,\"load8_u\",[\"i32:18705\"]],[\"invoke\",3708,0,\"load8_u\",[\"i32:20894\"]],[\"invoke\",3719,0,\"load8_u\",[\"i32:23083\"]],[\"invoke\",3729,0,\"load8_u\",[\"i32:25073\"]],[\"invoke\",3740,0,\"load8_u\",[\"i32:27262\"]],[\"invoke\",3751,0,\"load8_u\",[\"i32:29451\"]],[\"invoke\",3762,0,\"load8_u\",[\"i32:31640\"]],[\"invoke\",3773,0,\"load8_u\",[\"i32:33829\"]],[\"invoke\",3784,0,\"load8_u\",[\"i32:36018\"]],[\"invoke\",3795,0,\"load8_u\",[\"i32:38207\"]],[\"invoke\",3806,0,\"load8_u\",[\"i32:40396\"]],[\"invoke\",3816,0,\"load8_u\",[\"i32:42386\"]],[\"invoke\",3827,0,\"load8_u\",[\"i32:44575\"]],[\"invoke\",3838,0,\"load8_u\",[\"i32:46764\"]],[\"invoke\",3849,0,\"load8_u\",[\"i32:48953\"]],[\"invoke\",3860,0,\"load8_u\",[\"i32:51142\"]],[\"invoke\",3871,0,\"load8_u\",[\"i32:53331\"]],[\"invoke\",3882,0,\"load8_u\",[\"i32:55520\"]],[\"invoke\",3893,0,\"load8_u\",[\"i32:57709\"]],[\"invoke\",3904,0,\"load8_u\",[\"i32:59898\"]],[\"invoke\",3914,0,\"load8_u\",[\"i32:61888\"]],[\"invoke\",3925,0,\"load8_u\",[\"i32:64077\"]],[\"invoke\",3936,0,\"load8_u\",[\"i32:65519\"]],[\"invoke\",3947,0,\"load8_u\",[\"i32:65530\"]],[\"module\",3954,91],[\"invoke\",3969,0,\"load8_u\",[\"i32:994\"]],[\"invoke\",3980,0,\"load8_u\",[\"i32:3183\"]],[\"invoke\",3991,0,\"load8_u\",[\"i32:5372\"]],[\"invoke\",4002,0,\"load8_u\",[\"i32:7561\"]],[\"invoke\",4012,0,\"load8_u\",[\"i32:9551\"]],[\"invoke\",4023,0,\"load8_u\",[\"i32:11740\"]],[\"invoke\",4034,0,\"load8_u\",[\"i32:13929\"]],[\"invoke\",4045,0,\"load8_u\",[\"i32:16118\"]],[\"invoke\",4056,0,\"load8_u\",[\"i32:18307\"]],[\"invoke\",4067,0,\"load8_u\",[\"i32:20496\"]],[\"invoke\",4078,0,\"load8_u\",[\"i32:22685\"]],[\"invoke\",4089,0,\"load8_u\",[\"i32:24874\"]],[\"invoke\",4100,0,\"load8_u\",[\"i32:27063\"]],[\"invoke\",4110,0,\"load8_u\",[\"i32:29053\"]],[\"invoke\",4121,0,\"load8_u\",[\"i32:31242\"]],[\"invoke\",4132,0,\"load8_u\",[\"i32:33431\"]],[\"invoke\",4143,0,\"load8_u\",[\"i32:35620\"]],[\"invoke\",4154,0,\"load8_u\",[\"i32:37809\"]],[\"invoke\",4165,0,\"load8_u\",[\"i32:39998\"]],[\"invoke\",4176,0,\"load8_u\",[\"i32:42187\"]],[\"invoke\",4187,0,\"load8_u\",[\"i32:44376\"]],[\"invoke\",4197,0,\"load8_u\",[\"i32:46366\"]],[\"invoke\",4208,0,\"load8_u\",[\"i32:48555\"]],[\"invoke\",4219,0,\"load8_u\",[\"i32:50744\"]],[\"invoke\",4230,0,\"load8_u\",[\"i32:52933\"]],[\"invoke\",4241,0,\"load8_u\",[\"i32:55122\"]],[\"invoke\",4252,0,\"load8_u\",[\"i32:57311\"]],[\"invoke\",4263,0,\"load8_u\",[\"i32:59500\"]],[\"invoke\",4274,0,\"load8_u\",[\"i32:61441\"]],[\"invoke\",4285,0,\"load8_u\",[\"i32:61452\"]],[\"invoke\",4295,0,\"load8_u\",[\"i32:61908\"]],[\"invoke\",4306,0,\"load8_u\",[\"i32:64097\"]],[\"module\",4763,92],[\"invoke\",4786,0,\"checkRange\",[\"i32:20\",\"i32:65536\",\"i32:0\"]],[\"module\",4789,93],[\"module\",4815,94],[\"module\",4821,95],[\"module\",4827,96],[\"module\",4833,97],[\"module\",4839,98],[\"invoke\",4861,0,\"checkRange\",[\"i32:32768\",\"i32:65536\",\"i32:170\"]],[\"module\",4863,99],[\"module\",4869,100],[\"module\",4875,101],[\"module\",4881,102],[\"module\",4887,103],[\"module\",4893,104],[\"module\",4899,105],[\"invoke\",5123,0,\"checkRange\",[\"i32:2132\",\"i32:2827\",\"i32:10\"]],[\"invoke\",5145,0,\"checkRange\",[\"i32:5691\",\"i32:6552\",\"i32:83\"]],[\"invoke\",5167,0,\"checkRange\",[\"i32:10087\",\"i32:10206\",\"i32:5\"]],[\"invoke\",5189,0,\"checkRange\",[\"i32:13114\",\"i32:14133\",\"i32:59\"]],[\"invoke\",5209,0,\"checkRange\",[\"i32:15871\",\"i32:15998\",\"i32:95\"]],[\"invoke\",5231,0,\"checkRange\",[\"i32:18553\",\"i32:18744\",\"i32:42\"]],[\"invoke\",5253,0,\"checkRange\",[\"i32:21468\",\"i32:21492\",\"i32:93\"]],[\"invoke\",5275,0,\"checkRange\",[\"i32:24348\",\"i32:24515\",\"i32:92\"]],[\"invoke\",5297,0,\"checkRange\",[\"i32:27503\",\"i32:27573\",\"i32:77\"]],[\"invoke\",5319,0,\"checkRange\",[\"i32:31379\",\"i32:31403\",\"i32:57\"]],[\"invoke\",5341,0,\"checkRange\",[\"i32:33056\",\"i32:33660\",\"i32:89\"]],[\"invoke\",5363,0,\"checkRange\",[\"i32:35648\",\"i32:36547\",\"i32:83\"]],[\"invoke\",5385,0,\"checkRange\",[\"i32:39187\",\"i32:39859\",\"i32:92\"]],[\"invoke\",5405,0,\"checkRange\",[\"i32:42945\",\"i32:43115\",\"i32:77\"]],[\"invoke\",5427,0,\"checkRange\",[\"i32:45575\",\"i32:45640\",\"i32:77\"]],[\"invoke\",5449,0,\"checkRange\",[\"i32:47951\",\"i32:48632\",\"i32:100\"]],[\"invoke\",5471,0,\"checkRange\",[\"i32:50370\",\"i32:51358\",\"i32:92\"]],[\"invoke\",5493,0,\"checkRange\",[\"i32:55198\",\"i32:55389\",\"i32:42\"]],[\"invoke\",5515,0,\"checkRange\",[\"i32:58516\",\"i32:58835\",\"i32:95\"]],[\"invoke\",5537,0,\"checkRange\",[\"i32:60777\",\"i32:60834\",\"i32:76\"]],[\"invoke\",5559,0,\"checkRange\",[\"i32:63525\",\"i32:63605\",\"i32:74\"]],[\"file\",\"memory_fill.wast\"],[\"module\",6,106],[\"invoke\",22,0,\"test\",[]],[\"invoke\",24,0,\"checkRange\",[\"i32:0\",\"i32:65280\",\"i32:0\"]],[\"invoke\",26,0,\"checkRange\",[\"i32:65280\",\"i32:65536\",\"i32:85\"]],[\"module\",28,107],[\"invoke\",44,0,\"test\",[]],[\"module\",46,108],[\"invoke\",62,0,\"test\",[]],[\"module\",64,109],[\"invoke\",80,0,\"test\",[]],[\"invoke\",82,0,\"checkRange\",[\"i32:0\",\"i32:65536\",\"i32:0\"]],[\"module\",84,110],[\"invoke\",100,0,\"test\",[]],[\"module\",102,111],[\"invoke\",118,0,\"test\",[]],[\"module\",120,112],[\"invoke\",136,0,\"test\",[]],[\"invoke\",138,0,\"checkRange\",[\"i32:0\",\"i32:1\",\"i32:0\"]],[\"invoke\",140,0,\"checkRange\",[\"i32:1\",\"i32:65535\",\"i32:170\"]],[\"invoke\",142,0,\"checkRange\",[\"i32:65535\",\"i32:65536\",\"i32:0\"]],[\"module\",145,113],[\"invoke\",162,0,\"test\",[]],[\"invoke\",164,0,\"checkRange\",[\"i32:0\",\"i32:18\",\"i32:0\"]],[\"invoke\",166,0,\"checkRange\",[\"i32:18\",\"i32:21\",\"i32:85\"]],[\"invoke\",168,0,\"checkRange\",[\"i32:21\",\"i32:25\",\"i32:170\"]],[\"invoke\",170,0,\"checkRange\",[\"i32:25\",\"i32:28\",\"i32:85\"]],[\"invoke\",172,0,\"checkRange\",[\"i32:28\",\"i32:65536\",\"i32:0\"]],[\"module\",621,114],[\"invoke\",638,0,\"run\",[\"i32:65280\",\"i32:37\",\"i32:512\"]],[\"invoke\",641,0,\"checkRange\",[\"i32:0\",\"i32:1\",\"i32:0\"]],[\"module\",643,114],[\"invoke\",660,0,\"run\",[\"i32:65279\",\"i32:37\",\"i32:514\"]],[\"invoke\",663,0,\"checkRange\",[\"i32:0\",\"i32:1\",\"i32:0\"]],[\"module\",665,114],[\"invoke\",682,0,\"run\",[\"i32:65279\",\"i32:37\",\"i32:4294967295\"]],[\"invoke\",685,0,\"checkRange\",[\"i32:0\",\"i32:1\",\"i32:0\"]],[\"file\",\"memory_init.wast\"],[\"module\",6,115],[\"invoke\",17,0,\"test\",[]],[\"invoke\",19,0,\"load8_u\",[\"i32:0\"]],[\"invoke\",20,0,\"load8_u\",[\"i32:1\"]],[\"invoke\",21,0,\"load8_u\",[\"i32:2\"]],[\"invoke\",22,0,\"load8_u\",[\"i32:3\"]],[\"invoke\",23,0,\"load8_u\",[\"i32:4\"]],[\"invoke\",24,0,\"load8_u\",[\"i32:5\"]],[\"invoke\",25,0,\"load8_u\",[\"i32:6\"]],[\"invoke\",26,0,\"load8_u\",[\"i32:7\"]],[\"invoke\",27,0,\"load8_u\",[\"i32:8\"]],[\"invoke\",28,0,\"load8_u\",[\"i32:9\"]],[\"invoke\",29,0,\"load8_u\",[\"i32:10\"]],[\"invoke\",30,0,\"load8_u\",[\"i32:11\"]],[\"invoke\",31,0,\"load8_u\",[\"i32:12\"]],[\"invoke\",32,0,\"load8_u\",[\"i32:13\"]],[\"invoke\",33,0,\"load8_u\",[\"i32:14\"]],[\"invoke\",34,0,\"load8_u\",[\"i32:15\"]],[\"invoke\",35,0,\"load8_u\",[\"i32:16\"]],[\"invoke\",36,0,\"load8_u\",[\"i32:17\"]],[\"invoke\",37,0,\"load8_u\",[\"i32:18\"]],[\"invoke\",38,0,\"load8_u\",[\"i32:19\"]],[\"invoke\",39,0,\"load8_u\",[\"i32:20\"]],[\"invoke\",40,0,\"load8_u\",[\"i32:21\"]],[\"invoke\",41,0,\"load8_u\",[\"i32:22\"]],[\"invoke\",42,0,\"load8_u\",[\"i32:23\"]],[\"invoke\",43,0,\"load8_u\",[\"i32:24\"]],[\"invoke\",44,0,\"load8_u\",[\"i32:25\"]],[\"invoke\",45,0,\"load8_u\",[\"i32:26\"]],[\"invoke\",46,0,\"load8_u\",[\"i32:27\"]],[\"invoke\",47,0,\"load8_u\",[\"i32:28\"]],[\"invoke\",48,0,\"load8_u\",[\"i32:29\"]],[\"module\",50,116],[\"invoke\",61,0,\"test\",[]],[\"invoke\",63,0,\"load8_u\",[\"i32:0\"]],[\"invoke\",64,0,\"load8_u\",[\"i32:1\"]],[\"invoke\",65,0,\"load8_u\",[\"i32:2\"]],[\"invoke\",66,0,\"load8_u\",[\"i32:3\"]],[\"invoke\",67,0,\"load8_u\",[\"i32:4\"]],[\"invoke\",68,0,\"load8_u\",[\"i32:5\"]],[\"invoke\",69,0,\"load8_u\",[\"i32:6\"]],[\"invoke\",70,0,\"load8_u\",[\"i32:7\"]],[\"invoke\",71,0,\"load8_u\",[\"i32:8\"]],[\"invoke\",72,0,\"load8_u\",[\"i32:9\"]],[\"invoke\",73,0,\"load8_u\",[\"i32:10\"]],[\"invoke\",74,0,\"load8_u\",[\"i32:11\"]],[\"invoke\",75,0,\"load8_u\",[\"i32:12\"]],[\"invoke\",76,0,\"load8_u\",[\"i32:13\"]],[\"invoke\",77,0,\"load8_u\",[\"i32:14\"]],[\"invoke\",78,0,\"load8_u\",[\"i32:15\"]],[\"invoke\",79,0,\"load8_u\",[\"i32:16\"]],[\"invoke\",80,0,\"load8_u\",[\"i32:17\"]],[\"invoke\",81,0,\"load8_u\",[\"i32:18\"]],[\"invoke\",82,0,\"load8_u\",[\"i32:19\"]],[\"invoke\",83,0,\"load8_u\",[\"i32:20\"]],[\"invoke\",84,0,\"load8_u\",[\"i32:21\"]],[\"invoke\",85,0,\"load8_u\",[\"i32:22\"]],[\"invoke\",86,0,\"load8_u\",[\"i32:23\"]],[\"invoke\",87,0,\"load8_u\",[\"i32:24\"]],[\"invoke\",88,0,\"load8_u\",[\"i32:25\"]],[\"invoke\",89,0,\"load8_u\",[\"i32:26\"]],[\"invoke\",90,0,\"load8_u\",[\"i32:27\"]],[\"invoke\",91,0,\"load8_u\",[\"i32:28\"]],[\"invoke\",92,0,\"load8_u\",[\"i32:29\"]],[\"module\",94,117],[\"invoke\",105,0,\"test\",[]],[\"invoke\",107,0,\"load8_u\",[\"i32:0\"]],[\"invoke\",108,0,\"load8_u\",[\"i32:1\"]],[\"invoke\",109,0,\"load8_u\",[\"i32:2\"]],[\"invoke\",110,0,\"load8_u\",[\"i32:3\"]],[\"invoke\",111,0,\"load8_u\",[\"i32:4\"]],[\"invoke\",112,0,\"load8_u\",[\"i32:5\"]],[\"invoke\",113,0,\"load8_u\",[\"i32:6\"]],[\"invoke\",114,0,\"load8_u\",[\"i32:7\"]],[\"invoke\",115,0,\"load8_u\",[\"i32:8\"]],[\"invoke\",116,0,\"load8_u\",[\"i32:9\"]],[\"invoke\",117,0,\"load8_u\",[\"i32:10\"]],[\"invoke\",118,0,\"load8_u\",[\"i32:11\"]],[\"invoke\",119,0,\"load8_u\",[\"i32:12\"]],[\"invoke\",120,0,\"load8_u\",[\"i32:13\"]],[\"invoke\",121,0,\"load8_u\",[\"i32:14\"]],[\"invoke\",122,0,\"load8_u\",[\"i32:15\"]],[\"invoke\",123,0,\"load8_u\",[\"i32:16\"]],[\"invoke\",124,0,\"load8_u\",[\"i32:17\"]],[\"invoke\",125,0,\"load8_u\",[\"i32:18\"]],[\"invoke\",126,0,\"load8_u\",[\"i32:19\"]],[\"invoke\",127,0,\"load8_u\",[\"i32:20\"]],[\"invoke\",128,0,\"load8_u\",[\"i32:21\"]],[\"invoke\",129,0,\"load8_u\",[\"i32:22\"]],[\"invoke\",130,0,\"load8_u\",[\"i32:23\"]],[\"invoke\",131,0,\"load8_u\",[\"i32:24\"]],[\"invoke\",132,0,\"load8_u\",[\"i32:25\"]],[\"invoke\",133,0,\"load8_u\",[\"i32:26\"]],[\"invoke\",134,0,\"load8_u\",[\"i32:27\"]],[\"invoke\",135,0,\"load8_u\",[\"i32:28\"]],[\"invoke\",136,0,\"load8_u\",[\"i32:29\"]],[\"module\",138,118],[\"invoke\",157,0,\"test\",[]],[\"invoke\",159,0,\"load8_u\",[\"i32:0\"]],[\"invoke\",160,0,\"load8_u\",[\"i32:1\"]],[\"invoke\",161,0,\"load8_u\",[\"i32:2\"]],[\"invoke\",162,0,\"load8_u\",[\"i32:3\"]],[\"invoke\",163,0,\"load8_u\",[\"i32:4\"]],[\"invoke\",164,0,\"load8_u\",[\"i32:5\"]],[\"invoke\",165,0,\"load8_u\",[\"i32:6\"]],[\"invoke\",166,0,\"load8_u\",[\"i32:7\"]],[\"invoke\",167,0,\"load8_u\",[\"i32:8\"]],[\"invoke\",168,0,\"load8_u\",[\"i32:9\"]],[\"invoke\",169,0,\"load8_u\",[\"i32:10\"]],[\"invoke\",170,0,\"load8_u\",[\"i32:11\"]],[\"invoke\",171,0,\"load8_u\",[\"i32:12\"]],[\"invoke\",172,0,\"load8_u\",[\"i32:13\"]],[\"invoke\",173,0,\"load8_u\",[\"i32:14\"]],[\"invoke\",174,0,\"load8_u\",[\"i32:15\"]],[\"invoke\",175,0,\"load8_u\",[\"i32:16\"]],[\"invoke\",176,0,\"load8_u\",[\"i32:17\"]],[\"invoke\",177,0,\"load8_u\",[\"i32:18\"]],[\"invoke\",178,0,\"load8_u\",[\"i32:19\"]],[\"invoke\",179,0,\"load8_u\",[\"i32:20\"]],[\"invoke\",180,0,\"load8_u\",[\"i32:21\"]],[\"invoke\",181,0,\"load8_u\",[\"i32:22\"]],[\"invoke\",182,0,\"load8_u\",[\"i32:23\"]],[\"invoke\",183,0,\"load8_u\",[\"i32:24\"]],[\"invoke\",184,0,\"load8_u\",[\"i32:25\"]],[\"invoke\",185,0,\"load8_u\",[\"i32:26\"]],[\"invoke\",186,0,\"load8_u\",[\"i32:27\"]],[\"invoke\",187,0,\"load8_u\",[\"i32:28\"]],[\"invoke\",188,0,\"load8_u\",[\"i32:29\"]],[\"module\",203,119],[\"invoke\",209,0,\"test\",[]],[\"module\",211,120],[\"invoke\",217,0,\"test\",[]],[\"module\",219,121],[\"invoke\",224,0,\"test\",[]],[\"module\",240,122],[\"invoke\",246,0,\"test\",[]],[\"module\",248,123],[\"invoke\",253,0,\"test\",[]],[\"module\",255,124],[\"invoke\",260,0,\"test\",[]],[\"module\",262,125],[\"invoke\",267,0,\"test\",[]],[\"module\",269,126],[\"invoke\",274,0,\"test\",[]],[\"module\",276,127],[\"invoke\",281,0,\"test\",[]],[\"module\",283,128],[\"invoke\",288,0,\"test\",[]],[\"module\",290,129],[\"invoke\",295,0,\"test\",[]],[\"module\",297,130],[\"invoke\",302,0,\"test\",[]],[\"module\",304,131],[\"invoke\",309,0,\"test\",[]],[\"module\",815,132],[\"invoke\",833,0,\"run\",[\"i32:65528\",\"i32:16\"]],[\"invoke\",836,0,\"checkRange\",[\"i32:0\",\"i32:1\",\"i32:0\"]],[\"module\",838,132],[\"invoke\",856,0,\"run\",[\"i32:65527\",\"i32:16\"]],[\"invoke\",859,0,\"checkRange\",[\"i32:0\",\"i32:1\",\"i32:0\"]],[\"module\",861,132],[\"invoke\",879,0,\"run\",[\"i32:65472\",\"i32:30\"]],[\"invoke\",882,0,\"checkRange\",[\"i32:0\",\"i32:1\",\"i32:0\"]],[\"module\",884,132],[\"invoke\",902,0,\"run\",[\"i32:65473\",\"i32:31\"]],[\"invoke\",905,0,\"checkRange\",[\"i32:0\",\"i32:1\",\"i32:0\"]],[\"module\",907,133],[\"invoke\",925,0,\"run\",[\"i32:65528\",\"i32:4294967040\"]],[\"invoke\",928,0,\"checkRange\",[\"i32:0\",\"i32:1\",\"i32:0\"]],[\"module\",930,133],[\"invoke\",948,0,\"run\",[\"i32:0\",\"i32:4294967292\"]],[\"invoke\",951,0,\"checkRange\",[\"i32:0\",\"i32:1\",\"i32:0\"]],[\"module\",954,134],[\"file\",\"data.wast\"],[\"module\",5,135],[\"module\",39,136],[\"module\",43,137],[\"module\",48,138],[\"module\",56,139],[\"module\",66,140],[\"module\",71,141],[\"module\",77,140],[\"module\",82,141],[\"module\",100,142],[\"module\",105,143],[\"module\",111,144],[\"module\",116,145],[\"module\",120,146],[\"module\",125,147],[\"module\",130,148],[\"module\",135,145],[\"module\",139,146],[\"module\",144,147],[\"module\",149,149],[\"module\",154,150],[\"module\",159,151],[\"module\",165,152],[\"module\",171,153],[\"module\",176,154],[\"instantiate\",184,155],[\"instantiate\",192,156],[\"instantiate\",200,157],[\"instantiate\",207,158],[\"instantiate\",214,159],[\"instantiate\",231,160],[\"instantiate\",240,161],[\"instantiate\",247,162],[\"instantiate\",255,163],[\"instantiate\",263,164],[\"instantiate\",271,165],[\"instantiate\",278,166],[\"instantiate\",286,167],[\"instantiate\",293,168]]"; diff --git a/tests/corpus/webassembly-spec-table/data.mjs b/tests/corpus/webassembly-spec-table/data.mjs index 15dbf08aa..d6334cb9e 100644 --- a/tests/corpus/webassembly-spec-table/data.mjs +++ b/tests/corpus/webassembly-spec-table/data.mjs @@ -99,25 +99,37 @@ export const modules = [ "AGFzbQEAAAABBAFgAAADAwIAAAQEAXAACgmkAz0BAAAFcATSAAvSAAvQcAvSAQsBAAABAAQAAAEBAQAABXAE0gAL0gAL0HAL0gELAQAAAQAEAAABAQBBAAsABEEACwLSAAvQcAsAQQALAABBAAsCAAEAQQALAABBAAsCAAEAQQALAABBAAsCAAAAQQALAABBAAsCAAAAQQALAABBAAsCAAAAQQALAABBAAsCAAAAQQALAARBAAsC0gAL0HALAEEACwIAAABBAAsCAAAAQQALAARBAAsC0gAL0HALAEEACwIAAABBAAsCAAAEQQALAtIAC9BwCwBBAAsABEEACwLSAAvQcAsAQQALAABBAAsCAAEAQQALAABBAAsCAAEAQQALAABBAAsCAAAAQQALAABBAAsCAAAAQQALAABBAAsCAAAAQQALAABBAAsCAAAAQQALAARBAAsC0gAL0HALAEEACwIAAABBAAsCAAAAQQALAARBAAsC0gAL0HALAEEACwIAAABBAAsCAAADAAAHcATSAAvSAAvQcAvSAQsDAAADAAQAAAEBAwAAB3AE0gAL0gAL0HAL0gELAwAAAwAEAAABAQoHAgIACwIACw==", "AGFzbQEAAAABBAFgAAADAwIAAAQFAXABAwMJDwEEQQALA9IAC9BwC9IBCwoHAgIACwIACw==", "AGFzbQEAAAABBAFgAAADAgEABAQBcAAKCQcBAEEACwEACgQBAgAL", + "AGFzbQEAAAABBAFgAAACFAEIc3BlY3Rlc3QFdGFibGUBcAAKAwIBAAkHAQBBAAsBAAoEAQIACw==", "AGFzbQEAAAABBAFgAAADAgEABAQBcAAKCR8FAEEACwEAAEEDCwEAAEEHCwEAAEEFCwEAAEEDCwEACgQBAgAL", + "AGFzbQEAAAABBAFgAAACFAEIc3BlY3Rlc3QFdGFibGUBcAAKAwIBAAkfBQBBCQsBAABBAwsBAABBBwsBAABBAwsBAABBBQsBAAoEAQIACw==", "AGFzbQEAAAABBAFgAAACGAEIc3BlY3Rlc3QKZ2xvYmFsX2kzMgN/AAMCAQAEBQFwAOgHCQcBACMACwEACgQBAgAL", "AGFzbQEAAAABBQFgAAF/AwUEAAAAAAQEAXAACgcTAgZjYWxsLTcAAgZjYWxsLTkAAwkNAgBBBwsBAABBCQsBAQodBAUAQcEACwUAQcIACwcAQQcRAAALBwBBCREAAAs=", "AGFzbQEAAAABBQFgAAF/AwUEAAAAAAQEAXAACwcTAgZjYWxsLTcAAgZjYWxsLTkAAwkXAgRBBgsC0HAL0gALBEEJCwLSAQvQcAsKHQQFAEHBAAsFAEHCAAsHAEEHEQAACwcAQQkRAAAL", "AGFzbQEAAAABBAFgAAADAgEABAQBcAAKCQcBAEEJCwEACgQBAgAL", + "AGFzbQEAAAABBAFgAAACFAEIc3BlY3Rlc3QFdGFibGUBcAAKAwIBAAkHAQBBCQsBAAoEAQIACw==", "AGFzbQEAAAAEBAFwAAAJBgEAQQALAA==", + "AGFzbQEAAAACFAEIc3BlY3Rlc3QFdGFibGUBcAAACQYBAEEACwA=", "AGFzbQEAAAAEBQFwAQAACQYBAEEACwA=", "AGFzbQEAAAAEBAFwABQJBgEAQRQLAA==", + "AGFzbQEAAAABBAFgAAACFAEIc3BlY3Rlc3QFdGFibGUBcAAAAwIBAAkHAQBBAAsBAAoEAQIACw==", + "AGFzbQEAAAABBAFgAAACFQEIc3BlY3Rlc3QFdGFibGUBcAEAZAMCAQAJBwEAQQALAQAKBAECAAs=", + "AGFzbQEAAAABBAFgAAACFAEIc3BlY3Rlc3QFdGFibGUBcAAAAwIBAAkHAQBBAQsBAAoEAQIACw==", + "AGFzbQEAAAABBAFgAAACFQEIc3BlY3Rlc3QFdGFibGUBcAEAHgMCAQAJBwEAQQELAQAKBAECAAs=", "AGFzbQEAAAABBAFgAAADAgEABAQBcAAACQcBAEEACwEACgQBAgAL", "AGFzbQEAAAABBAFgAAADAgEABAUBcAEAAAkHAQBBAAsBAAoEAQIACw==", "AGFzbQEAAAABBAFgAAADAgEABAUBcAEAAQkHAQBBAAsBAAoEAQIACw==", "AGFzbQEAAAAEBAFwAAAJBgEAQQELAA==", "AGFzbQEAAAABBAFgAAADAgEABAQBcAAKCQcBAEEKCwEACgQBAgAL", + "AGFzbQEAAAABBAFgAAACFAEIc3BlY3Rlc3QFdGFibGUBcAAKAwIBAAkHAQBBCgsBAAoEAQIACw==", "AGFzbQEAAAABBAFgAAADAgEABAUBcAEKFAkHAQBBCgsBAAoEAQIACw==", "AGFzbQEAAAABBAFgAAADAgEABAQBcAAKCQcBAEF/CwEACgQBAgAL", + "AGFzbQEAAAABBAFgAAACFAEIc3BlY3Rlc3QFdGFibGUBcAAKAwIBAAkHAQBBfwsBAAoEAQIACw==", "AGFzbQEAAAABBAFgAAADAgEABAQBcAAKCQcBAEF2CwEACgQBAgAL", + "AGFzbQEAAAABBAFgAAACFAEIc3BlY3Rlc3QFdGFibGUBcAAKAwIBAAkHAQBBdgsBAAoEAQIACw==", "AGFzbQEAAAABBAFgAAADAwIAAAQEAXAACgcIAQRpbml0AAEJBwEAQQALAQAKEQICAAsMAEEAQQBBAfwMAAAL", "AGFzbQEAAAABBAFgAAADAwIAAAQEAXAACgcIAQRpbml0AAEJBQEDAAEAChECAgALDABBAEEAQQH8DAAACw==", "AGFzbQEAAAABBQFgAAF/AwQDAAAABAQBcAAKBxQBEGNhbGwtb3ZlcndyaXR0ZW4AAgkNAgBBCQsBAABBCQsBAQoVAwUAQcEACwUAQcIACwcAQQkRAAAL", + "AGFzbQEAAAABBQFgAAF/AhQBCHNwZWN0ZXN0BXRhYmxlAXAACgMEAwAAAAccARhjYWxsLW92ZXJ3cml0dGVuLWVsZW1lbnQAAgkNAgBBCQsBAABBCQsBAQoVAwUAQcEACwUAQcIACwcAQQkRAAAL", "AGFzbQEAAAABBQFgAAF/AwYFAAAAAAAEBAFwAAoHKwQMc2hhcmVkLXRhYmxlAQAGY2FsbC03AAIGY2FsbC04AAMGY2FsbC05AAQJDQIAQQgLAQAAQQkLAQEKJQUFAEHBAAsFAEHCAAsHAEEHEQAACwcAQQgRAAALBwBBCREAAAs=", "AGFzbQEAAAABBQFgAAF/AhoBB21vZHVsZTEMc2hhcmVkLXRhYmxlAXAACgMDAgAACQ0CAEEHCwEAAEEICwEBCg0CBQBBwwALBQBBxAAL", "AGFzbQEAAAABBQFgAAF/AhoBB21vZHVsZTEMc2hhcmVkLXRhYmxlAXAACgMDAgAACQ0CAEEICwEAAEEJCwEBCg0CBQBBxQALBQBBxgAL", @@ -190,4 +202,4 @@ export const modules = [ "AGFzbQEAAAABBQFgAAF/AwMCAAAEBAFwAAEFAwEAAQcxBAZtZW1vcnkCAAV0YWJsZQEADWdldCBtZW1vcnlbMF0AAAxnZXQgdGFibGVbMF0AAQoRAgcAQQAtAAALBwBBABEAAAs=", "AGFzbQEAAAABCAJgAAF/YAAAAhsCAk1zBm1lbW9yeQIAAQJNcwV0YWJsZQFwAAEDAwIAAQgBAQkHAQBBAAsBAAoMAgYAQa29AwsDAAALCwsBAEEACwVoZWxsbw==", ]; -export const script = "[[\"file\",\"table_copy.wast\"],[\"module\",6,0],[\"register\",\"a\"],[\"module\",15,1],[\"invoke\",45,0,\"test\",[]],[\"invoke\",50,0,\"check_t0\",[\"i32:4\"]],[\"invoke\",54,0,\"check_t0\",[\"i32:8\"]],[\"invoke\",58,0,\"check_t0\",[\"i32:12\"]],[\"invoke\",62,0,\"check_t0\",[\"i32:16\"]],[\"invoke\",66,0,\"check_t0\",[\"i32:20\"]],[\"invoke\",71,0,\"check_t0\",[\"i32:25\"]],[\"invoke\",75,0,\"check_t0\",[\"i32:29\"]],[\"invoke\",79,0,\"check_t1\",[\"i32:3\"]],[\"invoke\",83,0,\"check_t1\",[\"i32:7\"]],[\"invoke\",87,0,\"check_t1\",[\"i32:11\"]],[\"invoke\",92,0,\"check_t1\",[\"i32:16\"]],[\"invoke\",96,0,\"check_t1\",[\"i32:20\"]],[\"invoke\",100,0,\"check_t1\",[\"i32:24\"]],[\"invoke\",104,0,\"check_t1\",[\"i32:28\"]],[\"module\",107,2],[\"invoke\",139,0,\"check_t0\",[\"i32:1\"]],[\"invoke\",143,0,\"check_t0\",[\"i32:5\"]],[\"invoke\",148,0,\"check_t0\",[\"i32:10\"]],[\"invoke\",152,0,\"check_t0\",[\"i32:14\"]],[\"invoke\",156,0,\"check_t0\",[\"i32:18\"]],[\"invoke\",160,0,\"check_t0\",[\"i32:22\"]],[\"invoke\",164,0,\"check_t0\",[\"i32:26\"]],[\"invoke\",169,0,\"check_t1\",[\"i32:1\"]],[\"invoke\",173,0,\"check_t1\",[\"i32:5\"]],[\"invoke\",177,0,\"check_t1\",[\"i32:9\"]],[\"invoke\",181,0,\"check_t1\",[\"i32:13\"]],[\"invoke\",185,0,\"check_t1\",[\"i32:17\"]],[\"invoke\",190,0,\"check_t1\",[\"i32:22\"]],[\"invoke\",194,0,\"check_t1\",[\"i32:26\"]],[\"module\",199,3],[\"invoke\",229,0,\"test\",[]],[\"invoke\",233,0,\"check_t0\",[\"i32:3\"]],[\"invoke\",237,0,\"check_t0\",[\"i32:7\"]],[\"invoke\",241,0,\"check_t0\",[\"i32:11\"]],[\"invoke\",246,0,\"check_t0\",[\"i32:16\"]],[\"invoke\",250,0,\"check_t0\",[\"i32:20\"]],[\"invoke\",254,0,\"check_t0\",[\"i32:24\"]],[\"invoke\",258,0,\"check_t0\",[\"i32:28\"]],[\"invoke\",262,0,\"check_t1\",[\"i32:2\"]],[\"invoke\",267,0,\"check_t1\",[\"i32:7\"]],[\"invoke\",271,0,\"check_t1\",[\"i32:11\"]],[\"invoke\",275,0,\"check_t1\",[\"i32:15\"]],[\"invoke\",279,0,\"check_t1\",[\"i32:19\"]],[\"invoke\",283,0,\"check_t1\",[\"i32:23\"]],[\"invoke\",288,0,\"check_t1\",[\"i32:28\"]],[\"module\",291,4],[\"invoke\",323,0,\"check_t0\",[\"i32:1\"]],[\"invoke\",327,0,\"check_t0\",[\"i32:5\"]],[\"invoke\",331,0,\"check_t0\",[\"i32:9\"]],[\"invoke\",335,0,\"check_t0\",[\"i32:13\"]],[\"invoke\",339,0,\"check_t0\",[\"i32:17\"]],[\"invoke\",344,0,\"check_t0\",[\"i32:22\"]],[\"invoke\",348,0,\"check_t0\",[\"i32:26\"]],[\"invoke\",352,0,\"check_t1\",[\"i32:0\"]],[\"invoke\",356,0,\"check_t1\",[\"i32:4\"]],[\"invoke\",360,0,\"check_t1\",[\"i32:8\"]],[\"invoke\",365,0,\"check_t1\",[\"i32:13\"]],[\"invoke\",369,0,\"check_t1\",[\"i32:17\"]],[\"invoke\",373,0,\"check_t1\",[\"i32:21\"]],[\"invoke\",377,0,\"check_t1\",[\"i32:25\"]],[\"invoke\",381,0,\"check_t1\",[\"i32:29\"]],[\"module\",383,5],[\"invoke\",417,0,\"check_t0\",[\"i32:3\"]],[\"invoke\",421,0,\"check_t0\",[\"i32:7\"]],[\"invoke\",425,0,\"check_t0\",[\"i32:11\"]],[\"invoke\",429,0,\"check_t0\",[\"i32:15\"]],[\"invoke\",433,0,\"check_t0\",[\"i32:19\"]],[\"invoke\",437,0,\"check_t0\",[\"i32:23\"]],[\"invoke\",442,0,\"check_t0\",[\"i32:28\"]],[\"invoke\",446,0,\"check_t1\",[\"i32:2\"]],[\"invoke\",450,0,\"check_t1\",[\"i32:6\"]],[\"invoke\",454,0,\"check_t1\",[\"i32:10\"]],[\"invoke\",458,0,\"check_t1\",[\"i32:14\"]],[\"invoke\",463,0,\"check_t1\",[\"i32:19\"]],[\"invoke\",467,0,\"check_t1\",[\"i32:23\"]],[\"invoke\",471,0,\"check_t1\",[\"i32:27\"]],[\"module\",475,6],[\"invoke\",506,0,\"check_t0\",[\"i32:0\"]],[\"invoke\",510,0,\"check_t0\",[\"i32:4\"]],[\"invoke\",515,0,\"check_t0\",[\"i32:9\"]],[\"invoke\",519,0,\"check_t0\",[\"i32:13\"]],[\"invoke\",523,0,\"check_t0\",[\"i32:17\"]],[\"invoke\",527,0,\"check_t0\",[\"i32:21\"]],[\"invoke\",531,0,\"check_t0\",[\"i32:25\"]],[\"invoke\",535,0,\"check_t0\",[\"i32:29\"]],[\"invoke\",540,0,\"check_t1\",[\"i32:4\"]],[\"invoke\",544,0,\"check_t1\",[\"i32:8\"]],[\"invoke\",548,0,\"check_t1\",[\"i32:12\"]],[\"invoke\",552,0,\"check_t1\",[\"i32:16\"]],[\"invoke\",556,0,\"check_t1\",[\"i32:20\"]],[\"invoke\",561,0,\"check_t1\",[\"i32:25\"]],[\"invoke\",565,0,\"check_t1\",[\"i32:29\"]],[\"module\",567,7],[\"invoke\",600,0,\"check_t0\",[\"i32:2\"]],[\"invoke\",604,0,\"check_t0\",[\"i32:6\"]],[\"invoke\",608,0,\"check_t0\",[\"i32:10\"]],[\"invoke\",613,0,\"check_t0\",[\"i32:15\"]],[\"invoke\",617,0,\"check_t0\",[\"i32:19\"]],[\"invoke\",621,0,\"check_t0\",[\"i32:23\"]],[\"invoke\",625,0,\"check_t0\",[\"i32:27\"]],[\"invoke\",629,0,\"check_t1\",[\"i32:1\"]],[\"invoke\",633,0,\"check_t1\",[\"i32:5\"]],[\"invoke\",638,0,\"check_t1\",[\"i32:10\"]],[\"invoke\",642,0,\"check_t1\",[\"i32:14\"]],[\"invoke\",646,0,\"check_t1\",[\"i32:18\"]],[\"invoke\",650,0,\"check_t1\",[\"i32:22\"]],[\"invoke\",654,0,\"check_t1\",[\"i32:26\"]],[\"module\",659,8],[\"invoke\",690,0,\"check_t0\",[\"i32:0\"]],[\"invoke\",694,0,\"check_t0\",[\"i32:4\"]],[\"invoke\",698,0,\"check_t0\",[\"i32:8\"]],[\"invoke\",702,0,\"check_t0\",[\"i32:12\"]],[\"invoke\",706,0,\"check_t0\",[\"i32:16\"]],[\"invoke\",711,0,\"check_t0\",[\"i32:21\"]],[\"invoke\",715,0,\"check_t0\",[\"i32:25\"]],[\"invoke\",719,0,\"check_t0\",[\"i32:29\"]],[\"invoke\",723,0,\"check_t1\",[\"i32:3\"]],[\"invoke\",727,0,\"check_t1\",[\"i32:7\"]],[\"invoke\",731,0,\"check_t1\",[\"i32:11\"]],[\"invoke\",736,0,\"check_t1\",[\"i32:16\"]],[\"invoke\",740,0,\"check_t1\",[\"i32:20\"]],[\"invoke\",744,0,\"check_t1\",[\"i32:24\"]],[\"invoke\",748,0,\"check_t1\",[\"i32:28\"]],[\"module\",751,9],[\"invoke\",783,0,\"check_t0\",[\"i32:1\"]],[\"invoke\",788,0,\"check_t0\",[\"i32:6\"]],[\"invoke\",792,0,\"check_t0\",[\"i32:10\"]],[\"invoke\",796,0,\"check_t0\",[\"i32:14\"]],[\"invoke\",800,0,\"check_t0\",[\"i32:18\"]],[\"invoke\",804,0,\"check_t0\",[\"i32:22\"]],[\"invoke\",809,0,\"check_t0\",[\"i32:27\"]],[\"invoke\",813,0,\"check_t1\",[\"i32:1\"]],[\"invoke\",817,0,\"check_t1\",[\"i32:5\"]],[\"invoke\",821,0,\"check_t1\",[\"i32:9\"]],[\"invoke\",825,0,\"check_t1\",[\"i32:13\"]],[\"invoke\",829,0,\"check_t1\",[\"i32:17\"]],[\"invoke\",834,0,\"check_t1\",[\"i32:22\"]],[\"invoke\",838,0,\"check_t1\",[\"i32:26\"]],[\"module\",843,10],[\"invoke\",873,0,\"test\",[]],[\"invoke\",877,0,\"check_t0\",[\"i32:3\"]],[\"invoke\",881,0,\"check_t0\",[\"i32:7\"]],[\"invoke\",886,0,\"check_t0\",[\"i32:12\"]],[\"invoke\",890,0,\"check_t0\",[\"i32:16\"]],[\"invoke\",894,0,\"check_t0\",[\"i32:20\"]],[\"invoke\",898,0,\"check_t0\",[\"i32:24\"]],[\"invoke\",902,0,\"check_t0\",[\"i32:28\"]],[\"invoke\",907,0,\"check_t1\",[\"i32:3\"]],[\"invoke\",911,0,\"check_t1\",[\"i32:7\"]],[\"invoke\",915,0,\"check_t1\",[\"i32:11\"]],[\"invoke\",919,0,\"check_t1\",[\"i32:15\"]],[\"invoke\",923,0,\"check_t1\",[\"i32:19\"]],[\"invoke\",927,0,\"check_t1\",[\"i32:23\"]],[\"invoke\",932,0,\"check_t1\",[\"i32:28\"]],[\"module\",935,11],[\"invoke\",967,0,\"check_t0\",[\"i32:1\"]],[\"invoke\",971,0,\"check_t0\",[\"i32:5\"]],[\"invoke\",975,0,\"check_t0\",[\"i32:9\"]],[\"invoke\",979,0,\"check_t0\",[\"i32:13\"]],[\"invoke\",984,0,\"check_t0\",[\"i32:18\"]],[\"invoke\",988,0,\"check_t0\",[\"i32:22\"]],[\"invoke\",992,0,\"check_t0\",[\"i32:26\"]],[\"invoke\",996,0,\"check_t1\",[\"i32:0\"]],[\"invoke\",1000,0,\"check_t1\",[\"i32:4\"]],[\"invoke\",1005,0,\"check_t1\",[\"i32:9\"]],[\"invoke\",1009,0,\"check_t1\",[\"i32:13\"]],[\"invoke\",1013,0,\"check_t1\",[\"i32:17\"]],[\"invoke\",1017,0,\"check_t1\",[\"i32:21\"]],[\"invoke\",1021,0,\"check_t1\",[\"i32:25\"]],[\"invoke\",1025,0,\"check_t1\",[\"i32:29\"]],[\"module\",1027,12],[\"invoke\",1061,0,\"check_t0\",[\"i32:3\"]],[\"invoke\",1065,0,\"check_t0\",[\"i32:7\"]],[\"invoke\",1069,0,\"check_t0\",[\"i32:11\"]],[\"invoke\",1073,0,\"check_t0\",[\"i32:15\"]],[\"invoke\",1077,0,\"check_t0\",[\"i32:19\"]],[\"invoke\",1082,0,\"check_t0\",[\"i32:24\"]],[\"invoke\",1086,0,\"check_t0\",[\"i32:28\"]],[\"invoke\",1090,0,\"check_t1\",[\"i32:2\"]],[\"invoke\",1094,0,\"check_t1\",[\"i32:6\"]],[\"invoke\",1098,0,\"check_t1\",[\"i32:10\"]],[\"invoke\",1103,0,\"check_t1\",[\"i32:15\"]],[\"invoke\",1107,0,\"check_t1\",[\"i32:19\"]],[\"invoke\",1111,0,\"check_t1\",[\"i32:23\"]],[\"invoke\",1115,0,\"check_t1\",[\"i32:27\"]],[\"module\",1119,13],[\"invoke\",1150,0,\"check_t0\",[\"i32:0\"]],[\"invoke\",1154,0,\"check_t0\",[\"i32:4\"]],[\"invoke\",1159,0,\"check_t0\",[\"i32:9\"]],[\"invoke\",1163,0,\"check_t0\",[\"i32:13\"]],[\"invoke\",1167,0,\"check_t0\",[\"i32:17\"]],[\"invoke\",1171,0,\"check_t0\",[\"i32:21\"]],[\"invoke\",1175,0,\"check_t0\",[\"i32:25\"]],[\"invoke\",1180,0,\"check_t1\",[\"i32:0\"]],[\"invoke\",1184,0,\"check_t1\",[\"i32:4\"]],[\"invoke\",1188,0,\"check_t1\",[\"i32:8\"]],[\"invoke\",1192,0,\"check_t1\",[\"i32:12\"]],[\"invoke\",1196,0,\"check_t1\",[\"i32:16\"]],[\"invoke\",1201,0,\"check_t1\",[\"i32:21\"]],[\"invoke\",1205,0,\"check_t1\",[\"i32:25\"]],[\"invoke\",1209,0,\"check_t1\",[\"i32:29\"]],[\"module\",1211,14],[\"invoke\",1244,0,\"check_t0\",[\"i32:2\"]],[\"invoke\",1248,0,\"check_t0\",[\"i32:6\"]],[\"invoke\",1252,0,\"check_t0\",[\"i32:10\"]],[\"invoke\",1257,0,\"check_t0\",[\"i32:15\"]],[\"invoke\",1261,0,\"check_t0\",[\"i32:19\"]],[\"invoke\",1265,0,\"check_t0\",[\"i32:23\"]],[\"invoke\",1269,0,\"check_t0\",[\"i32:27\"]],[\"invoke\",1273,0,\"check_t1\",[\"i32:1\"]],[\"invoke\",1278,0,\"check_t1\",[\"i32:6\"]],[\"invoke\",1282,0,\"check_t1\",[\"i32:10\"]],[\"invoke\",1286,0,\"check_t1\",[\"i32:14\"]],[\"invoke\",1290,0,\"check_t1\",[\"i32:18\"]],[\"invoke\",1294,0,\"check_t1\",[\"i32:22\"]],[\"invoke\",1299,0,\"check_t1\",[\"i32:27\"]],[\"module\",1303,15],[\"invoke\",1334,0,\"check_t0\",[\"i32:0\"]],[\"invoke\",1338,0,\"check_t0\",[\"i32:4\"]],[\"invoke\",1342,0,\"check_t0\",[\"i32:8\"]],[\"invoke\",1346,0,\"check_t0\",[\"i32:12\"]],[\"invoke\",1350,0,\"check_t0\",[\"i32:16\"]],[\"invoke\",1355,0,\"check_t0\",[\"i32:21\"]],[\"invoke\",1359,0,\"check_t0\",[\"i32:25\"]],[\"invoke\",1363,0,\"check_t0\",[\"i32:29\"]],[\"invoke\",1367,0,\"check_t1\",[\"i32:3\"]],[\"invoke\",1371,0,\"check_t1\",[\"i32:7\"]],[\"invoke\",1376,0,\"check_t1\",[\"i32:12\"]],[\"invoke\",1380,0,\"check_t1\",[\"i32:16\"]],[\"invoke\",1384,0,\"check_t1\",[\"i32:20\"]],[\"invoke\",1388,0,\"check_t1\",[\"i32:24\"]],[\"invoke\",1392,0,\"check_t1\",[\"i32:28\"]],[\"module\",1395,16],[\"invoke\",1428,0,\"check_t0\",[\"i32:2\"]],[\"invoke\",1432,0,\"check_t0\",[\"i32:6\"]],[\"invoke\",1436,0,\"check_t0\",[\"i32:10\"]],[\"invoke\",1440,0,\"check_t0\",[\"i32:14\"]],[\"invoke\",1444,0,\"check_t0\",[\"i32:18\"]],[\"invoke\",1448,0,\"check_t0\",[\"i32:22\"]],[\"invoke\",1453,0,\"check_t0\",[\"i32:27\"]],[\"invoke\",1457,0,\"check_t1\",[\"i32:1\"]],[\"invoke\",1461,0,\"check_t1\",[\"i32:5\"]],[\"invoke\",1465,0,\"check_t1\",[\"i32:9\"]],[\"invoke\",1469,0,\"check_t1\",[\"i32:13\"]],[\"invoke\",1474,0,\"check_t1\",[\"i32:18\"]],[\"invoke\",1478,0,\"check_t1\",[\"i32:22\"]],[\"invoke\",1482,0,\"check_t1\",[\"i32:26\"]],[\"module\",1487,17],[\"invoke\",1517,0,\"test\",[]],[\"invoke\",1521,0,\"check_t0\",[\"i32:3\"]],[\"invoke\",1526,0,\"check_t0\",[\"i32:8\"]],[\"invoke\",1530,0,\"check_t0\",[\"i32:12\"]],[\"invoke\",1534,0,\"check_t0\",[\"i32:16\"]],[\"invoke\",1538,0,\"check_t0\",[\"i32:20\"]],[\"invoke\",1542,0,\"check_t0\",[\"i32:24\"]],[\"invoke\",1546,0,\"check_t0\",[\"i32:28\"]],[\"invoke\",1551,0,\"check_t1\",[\"i32:3\"]],[\"invoke\",1555,0,\"check_t1\",[\"i32:7\"]],[\"invoke\",1559,0,\"check_t1\",[\"i32:11\"]],[\"invoke\",1563,0,\"check_t1\",[\"i32:15\"]],[\"invoke\",1567,0,\"check_t1\",[\"i32:19\"]],[\"invoke\",1572,0,\"check_t1\",[\"i32:24\"]],[\"invoke\",1576,0,\"check_t1\",[\"i32:28\"]],[\"module\",1579,18],[\"invoke\",1611,0,\"check_t0\",[\"i32:1\"]],[\"invoke\",1615,0,\"check_t0\",[\"i32:5\"]],[\"invoke\",1619,0,\"check_t0\",[\"i32:9\"]],[\"invoke\",1624,0,\"check_t0\",[\"i32:14\"]],[\"invoke\",1628,0,\"check_t0\",[\"i32:18\"]],[\"invoke\",1632,0,\"check_t0\",[\"i32:22\"]],[\"invoke\",1636,0,\"check_t0\",[\"i32:26\"]],[\"invoke\",1640,0,\"check_t1\",[\"i32:0\"]],[\"invoke\",1644,0,\"check_t1\",[\"i32:4\"]],[\"invoke\",1649,0,\"check_t1\",[\"i32:9\"]],[\"invoke\",1653,0,\"check_t1\",[\"i32:13\"]],[\"invoke\",1657,0,\"check_t1\",[\"i32:17\"]],[\"invoke\",1661,0,\"check_t1\",[\"i32:21\"]],[\"invoke\",1665,0,\"check_t1\",[\"i32:25\"]],[\"module\",1671,19],[\"invoke\",1694,0,\"test\",[]],[\"module\",1696,20],[\"module\",1721,21],[\"module\",1746,22],[\"module\",1771,23],[\"invoke\",1794,0,\"test\",[]],[\"module\",1796,24],[\"module\",1821,25],[\"module\",1846,26],[\"module\",1871,27],[\"invoke\",1894,0,\"test\",[]],[\"module\",1896,28],[\"module\",1921,29],[\"module\",1946,30],[\"module\",1971,31],[\"invoke\",1994,0,\"test\",[]],[\"module\",1996,32],[\"module\",2021,33],[\"module\",2046,34],[\"module\",2071,35],[\"invoke\",2094,0,\"test\",[]],[\"module\",2096,36],[\"module\",2121,37],[\"module\",2146,38],[\"module\",2171,39],[\"module\",2196,40],[\"invoke\",2219,0,\"test\",[]],[\"module\",2221,41],[\"invoke\",2251,0,\"test\",[\"i32:2\"]],[\"invoke\",2255,0,\"test\",[\"i32:6\"]],[\"invoke\",2259,0,\"test\",[\"i32:10\"]],[\"invoke\",2263,0,\"test\",[\"i32:14\"]],[\"invoke\",2267,0,\"test\",[\"i32:18\"]],[\"invoke\",2272,0,\"test\",[\"i32:23\"]],[\"invoke\",2276,0,\"test\",[\"i32:27\"]],[\"invoke\",2280,0,\"test\",[\"i32:31\"]],[\"module\",2282,42],[\"invoke\",2312,0,\"test\",[\"i32:2\"]],[\"invoke\",2316,0,\"test\",[\"i32:6\"]],[\"invoke\",2321,0,\"test\",[\"i32:11\"]],[\"invoke\",2325,0,\"test\",[\"i32:15\"]],[\"invoke\",2329,0,\"test\",[\"i32:19\"]],[\"invoke\",2333,0,\"test\",[\"i32:23\"]],[\"invoke\",2337,0,\"test\",[\"i32:27\"]],[\"module\",2343,43],[\"invoke\",2369,0,\"run\",[\"i32:0\",\"i32:24\",\"i32:16\"]],[\"invoke\",2374,0,\"test\",[\"i32:3\"]],[\"invoke\",2378,0,\"test\",[\"i32:7\"]],[\"invoke\",2382,0,\"test\",[\"i32:11\"]],[\"invoke\",2386,0,\"test\",[\"i32:15\"]],[\"invoke\",2390,0,\"test\",[\"i32:19\"]],[\"invoke\",2395,0,\"test\",[\"i32:24\"]],[\"invoke\",2399,0,\"test\",[\"i32:28\"]],[\"module\",2404,44],[\"invoke\",2430,0,\"run\",[\"i32:0\",\"i32:23\",\"i32:15\"]],[\"invoke\",2435,0,\"test\",[\"i32:3\"]],[\"invoke\",2439,0,\"test\",[\"i32:7\"]],[\"invoke\",2444,0,\"test\",[\"i32:12\"]],[\"invoke\",2448,0,\"test\",[\"i32:16\"]],[\"invoke\",2452,0,\"test\",[\"i32:20\"]],[\"invoke\",2456,0,\"test\",[\"i32:24\"]],[\"invoke\",2460,0,\"test\",[\"i32:28\"]],[\"module\",2465,45],[\"invoke\",2493,0,\"test\",[\"i32:0\"]],[\"invoke\",2497,0,\"test\",[\"i32:4\"]],[\"invoke\",2501,0,\"test\",[\"i32:8\"]],[\"invoke\",2505,0,\"test\",[\"i32:12\"]],[\"invoke\",2509,0,\"test\",[\"i32:16\"]],[\"invoke\",2513,0,\"test\",[\"i32:20\"]],[\"invoke\",2518,0,\"test\",[\"i32:25\"]],[\"invoke\",2522,0,\"test\",[\"i32:29\"]],[\"module\",2526,43],[\"invoke\",2554,0,\"test\",[\"i32:0\"]],[\"invoke\",2558,0,\"test\",[\"i32:4\"]],[\"invoke\",2562,0,\"test\",[\"i32:8\"]],[\"invoke\",2567,0,\"test\",[\"i32:13\"]],[\"invoke\",2571,0,\"test\",[\"i32:17\"]],[\"invoke\",2575,0,\"test\",[\"i32:21\"]],[\"invoke\",2579,0,\"test\",[\"i32:25\"]],[\"invoke\",2583,0,\"test\",[\"i32:29\"]],[\"module\",2587,46],[\"invoke\",2616,0,\"test\",[\"i32:1\"]],[\"invoke\",2620,0,\"test\",[\"i32:5\"]],[\"invoke\",2624,0,\"test\",[\"i32:9\"]],[\"invoke\",2628,0,\"test\",[\"i32:13\"]],[\"invoke\",2632,0,\"test\",[\"i32:17\"]],[\"invoke\",2636,0,\"test\",[\"i32:21\"]],[\"invoke\",2641,0,\"test\",[\"i32:26\"]],[\"invoke\",2645,0,\"test\",[\"i32:30\"]],[\"module\",2648,43],[\"invoke\",2677,0,\"test\",[\"i32:1\"]],[\"invoke\",2681,0,\"test\",[\"i32:5\"]],[\"invoke\",2685,0,\"test\",[\"i32:9\"]],[\"invoke\",2690,0,\"test\",[\"i32:14\"]],[\"invoke\",2694,0,\"test\",[\"i32:18\"]],[\"invoke\",2698,0,\"test\",[\"i32:22\"]],[\"invoke\",2702,0,\"test\",[\"i32:26\"]],[\"invoke\",2706,0,\"test\",[\"i32:30\"]],[\"module\",2709,47],[\"invoke\",2739,0,\"test\",[\"i32:2\"]],[\"invoke\",2743,0,\"test\",[\"i32:6\"]],[\"invoke\",2747,0,\"test\",[\"i32:10\"]],[\"invoke\",2751,0,\"test\",[\"i32:14\"]],[\"invoke\",2755,0,\"test\",[\"i32:18\"]],[\"invoke\",2759,0,\"test\",[\"i32:22\"]],[\"invoke\",2764,0,\"test\",[\"i32:27\"]],[\"invoke\",2768,0,\"test\",[\"i32:31\"]],[\"module\",2770,48],[\"invoke\",2800,0,\"test\",[\"i32:2\"]],[\"invoke\",2804,0,\"test\",[\"i32:6\"]],[\"invoke\",2808,0,\"test\",[\"i32:10\"]],[\"invoke\",2813,0,\"test\",[\"i32:15\"]],[\"invoke\",2817,0,\"test\",[\"i32:19\"]],[\"invoke\",2821,0,\"test\",[\"i32:23\"]],[\"invoke\",2825,0,\"test\",[\"i32:27\"]],[\"invoke\",2829,0,\"test\",[\"i32:31\"]],[\"invoke\",2834,0,\"test\",[\"i32:36\"]],[\"invoke\",2838,0,\"test\",[\"i32:40\"]],[\"invoke\",2842,0,\"test\",[\"i32:44\"]],[\"invoke\",2846,0,\"test\",[\"i32:48\"]],[\"invoke\",2850,0,\"test\",[\"i32:52\"]],[\"invoke\",2854,0,\"test\",[\"i32:56\"]],[\"invoke\",2859,0,\"test\",[\"i32:61\"]],[\"invoke\",2863,0,\"test\",[\"i32:65\"]],[\"invoke\",2867,0,\"test\",[\"i32:69\"]],[\"invoke\",2871,0,\"test\",[\"i32:73\"]],[\"invoke\",2875,0,\"test\",[\"i32:77\"]],[\"invoke\",2880,0,\"test\",[\"i32:82\"]],[\"invoke\",2884,0,\"test\",[\"i32:86\"]],[\"invoke\",2888,0,\"test\",[\"i32:90\"]],[\"invoke\",2892,0,\"test\",[\"i32:94\"]],[\"invoke\",2896,0,\"test\",[\"i32:98\"]],[\"invoke\",2901,0,\"test\",[\"i32:103\"]],[\"invoke\",2905,0,\"test\",[\"i32:107\"]],[\"invoke\",2909,0,\"test\",[\"i32:111\"]],[\"invoke\",2913,0,\"test\",[\"i32:115\"]],[\"invoke\",2917,0,\"test\",[\"i32:119\"]],[\"invoke\",2921,0,\"test\",[\"i32:123\"]],[\"module\",2927,49],[\"invoke\",2953,0,\"run\",[\"i32:112\",\"i32:0\",\"i32:4294967264\"]],[\"invoke\",2958,0,\"test\",[\"i32:3\"]],[\"invoke\",2962,0,\"test\",[\"i32:7\"]],[\"invoke\",2966,0,\"test\",[\"i32:11\"]],[\"invoke\",2970,0,\"test\",[\"i32:15\"]],[\"invoke\",2975,0,\"test\",[\"i32:20\"]],[\"invoke\",2979,0,\"test\",[\"i32:24\"]],[\"invoke\",2983,0,\"test\",[\"i32:28\"]],[\"invoke\",2987,0,\"test\",[\"i32:32\"]],[\"invoke\",2991,0,\"test\",[\"i32:36\"]],[\"invoke\",2996,0,\"test\",[\"i32:41\"]],[\"invoke\",3000,0,\"test\",[\"i32:45\"]],[\"invoke\",3004,0,\"test\",[\"i32:49\"]],[\"invoke\",3008,0,\"test\",[\"i32:53\"]],[\"invoke\",3012,0,\"test\",[\"i32:57\"]],[\"invoke\",3016,0,\"test\",[\"i32:61\"]],[\"invoke\",3021,0,\"test\",[\"i32:66\"]],[\"invoke\",3025,0,\"test\",[\"i32:70\"]],[\"invoke\",3029,0,\"test\",[\"i32:74\"]],[\"invoke\",3033,0,\"test\",[\"i32:78\"]],[\"invoke\",3037,0,\"test\",[\"i32:82\"]],[\"invoke\",3042,0,\"test\",[\"i32:87\"]],[\"invoke\",3046,0,\"test\",[\"i32:91\"]],[\"invoke\",3050,0,\"test\",[\"i32:95\"]],[\"invoke\",3054,0,\"test\",[\"i32:99\"]],[\"invoke\",3058,0,\"test\",[\"i32:103\"]],[\"invoke\",3063,0,\"test\",[\"i32:108\"]],[\"invoke\",3067,0,\"test\",[\"i32:112\"]],[\"invoke\",3071,0,\"test\",[\"i32:116\"]],[\"invoke\",3075,0,\"test\",[\"i32:120\"]],[\"invoke\",3079,0,\"test\",[\"i32:124\"]],[\"file\",\"table_fill.wast\"],[\"module\",1,50],[\"invoke\",17,0,\"get\",[\"i32:1\"]],[\"invoke\",18,0,\"get\",[\"i32:2\"]],[\"invoke\",19,0,\"get\",[\"i32:3\"]],[\"invoke\",20,0,\"get\",[\"i32:4\"]],[\"invoke\",21,0,\"get\",[\"i32:5\"]],[\"invoke\",23,0,\"fill\",[\"i32:2\",\"externref:1\",\"i32:3\"]],[\"invoke\",24,0,\"get\",[\"i32:1\"]],[\"invoke\",25,0,\"get\",[\"i32:2\"]],[\"invoke\",26,0,\"get\",[\"i32:3\"]],[\"invoke\",27,0,\"get\",[\"i32:4\"]],[\"invoke\",28,0,\"get\",[\"i32:5\"]],[\"invoke\",30,0,\"fill\",[\"i32:4\",\"externref:2\",\"i32:2\"]],[\"invoke\",31,0,\"get\",[\"i32:3\"]],[\"invoke\",32,0,\"get\",[\"i32:4\"]],[\"invoke\",33,0,\"get\",[\"i32:5\"]],[\"invoke\",34,0,\"get\",[\"i32:6\"]],[\"invoke\",36,0,\"fill\",[\"i32:4\",\"externref:3\",\"i32:0\"]],[\"invoke\",37,0,\"get\",[\"i32:3\"]],[\"invoke\",38,0,\"get\",[\"i32:4\"]],[\"invoke\",39,0,\"get\",[\"i32:5\"]],[\"invoke\",41,0,\"fill\",[\"i32:8\",\"externref:4\",\"i32:2\"]],[\"invoke\",42,0,\"get\",[\"i32:7\"]],[\"invoke\",43,0,\"get\",[\"i32:8\"]],[\"invoke\",44,0,\"get\",[\"i32:9\"]],[\"invoke\",46,0,\"fill-abbrev\",[\"i32:9\",\"externref:null\",\"i32:1\"]],[\"invoke\",47,0,\"get\",[\"i32:8\"]],[\"invoke\",48,0,\"get\",[\"i32:9\"]],[\"invoke\",50,0,\"fill\",[\"i32:10\",\"externref:5\",\"i32:0\"]],[\"invoke\",51,0,\"get\",[\"i32:9\"]],[\"invoke\",54,0,\"fill\",[\"i32:8\",\"externref:6\",\"i32:3\"]],[\"invoke\",57,0,\"get\",[\"i32:7\"]],[\"invoke\",58,0,\"get\",[\"i32:8\"]],[\"invoke\",59,0,\"get\",[\"i32:9\"]],[\"invoke\",62,0,\"fill\",[\"i32:11\",\"externref:null\",\"i32:0\"]],[\"invoke\",67,0,\"fill\",[\"i32:11\",\"externref:null\",\"i32:10\"]],[\"file\",\"table_get.wast\"],[\"module\",1,51],[\"invoke\",24,0,\"init\",[\"externref:1\"]],[\"invoke\",26,0,\"get-externref\",[\"i32:0\"]],[\"invoke\",27,0,\"get-externref\",[\"i32:1\"]],[\"invoke\",29,0,\"get-funcref\",[\"i32:0\"]],[\"invoke\",30,0,\"is_null-funcref\",[\"i32:1\"]],[\"invoke\",31,0,\"is_null-funcref\",[\"i32:2\"]],[\"invoke\",33,0,\"get-externref\",[\"i32:2\"]],[\"invoke\",34,0,\"get-funcref\",[\"i32:3\"]],[\"invoke\",35,0,\"get-externref\",[\"i32:4294967295\"]],[\"invoke\",36,0,\"get-funcref\",[\"i32:4294967295\"]],[\"file\",\"table_grow.wast\"],[\"module\",1,52],[\"invoke\",16,0,\"size\",[]],[\"invoke\",17,0,\"set\",[\"i32:0\",\"externref:2\"]],[\"invoke\",18,0,\"get\",[\"i32:0\"]],[\"invoke\",20,0,\"grow\",[\"i32:1\",\"externref:null\"]],[\"invoke\",21,0,\"size\",[]],[\"invoke\",22,0,\"get\",[\"i32:0\"]],[\"invoke\",23,0,\"set\",[\"i32:0\",\"externref:2\"]],[\"invoke\",24,0,\"get\",[\"i32:0\"]],[\"invoke\",25,0,\"set\",[\"i32:1\",\"externref:2\"]],[\"invoke\",26,0,\"get\",[\"i32:1\"]],[\"invoke\",28,0,\"grow-abbrev\",[\"i32:4\",\"externref:3\"]],[\"invoke\",29,0,\"size\",[]],[\"invoke\",30,0,\"get\",[\"i32:0\"]],[\"invoke\",31,0,\"set\",[\"i32:0\",\"externref:2\"]],[\"invoke\",32,0,\"get\",[\"i32:0\"]],[\"invoke\",33,0,\"get\",[\"i32:1\"]],[\"invoke\",34,0,\"get\",[\"i32:4\"]],[\"invoke\",35,0,\"set\",[\"i32:4\",\"externref:4\"]],[\"invoke\",36,0,\"get\",[\"i32:4\"]],[\"invoke\",37,0,\"set\",[\"i32:5\",\"externref:2\"]],[\"invoke\",38,0,\"get\",[\"i32:5\"]],[\"module\",42,53],[\"invoke\",50,0,\"grow\",[]],[\"module\",53,54],[\"invoke\",60,0,\"grow\",[\"i32:0\"]],[\"invoke\",61,0,\"grow\",[\"i32:1\"]],[\"invoke\",62,0,\"grow\",[\"i32:0\"]],[\"invoke\",63,0,\"grow\",[\"i32:2\"]],[\"invoke\",64,0,\"grow\",[\"i32:800\"]],[\"module\",67,55],[\"invoke\",74,0,\"grow\",[\"i32:0\"]],[\"invoke\",75,0,\"grow\",[\"i32:1\"]],[\"invoke\",76,0,\"grow\",[\"i32:1\"]],[\"invoke\",77,0,\"grow\",[\"i32:2\"]],[\"invoke\",78,0,\"grow\",[\"i32:6\"]],[\"invoke\",79,0,\"grow\",[\"i32:0\"]],[\"invoke\",80,0,\"grow\",[\"i32:1\"]],[\"invoke\",81,0,\"grow\",[\"i32:65536\"]],[\"module\",84,56],[\"invoke\",106,0,\"check-table-null\",[\"i32:0\",\"i32:9\"]],[\"invoke\",107,0,\"grow\",[\"i32:10\"]],[\"invoke\",108,0,\"check-table-null\",[\"i32:0\",\"i32:19\"]],[\"module\",111,57,\"$Tgt\"],[\"register\",\"grown-table\",\"$Tgt\"],[\"invoke\",116,\"$Tgt\",\"grow\",[]],[\"module\",117,58,\"$Tgit1\"],[\"register\",\"grown-imported-table\",\"$Tgit1\"],[\"invoke\",123,\"$Tgit1\",\"grow\",[]],[\"module\",124,59,\"$Tgit2\"],[\"invoke\",129,\"$Tgit2\",\"size\",[]],[\"file\",\"table_init.wast\"],[\"module\",6,0],[\"register\",\"a\"],[\"module\",15,60],[\"invoke\",41,0,\"test\",[]],[\"invoke\",43,0,\"check\",[\"i32:1\"]],[\"invoke\",45,0,\"check\",[\"i32:3\"]],[\"invoke\",47,0,\"check\",[\"i32:5\"]],[\"invoke\",48,0,\"check\",[\"i32:6\"]],[\"invoke\",50,0,\"check\",[\"i32:8\"]],[\"invoke\",52,0,\"check\",[\"i32:10\"]],[\"invoke\",53,0,\"check\",[\"i32:11\"]],[\"invoke\",55,0,\"check\",[\"i32:13\"]],[\"invoke\",57,0,\"check\",[\"i32:15\"]],[\"invoke\",58,0,\"check\",[\"i32:16\"]],[\"invoke\",60,0,\"check\",[\"i32:18\"]],[\"invoke\",62,0,\"check\",[\"i32:20\"]],[\"invoke\",64,0,\"check\",[\"i32:22\"]],[\"invoke\",65,0,\"check\",[\"i32:23\"]],[\"invoke\",67,0,\"check\",[\"i32:25\"]],[\"invoke\",69,0,\"check\",[\"i32:27\"]],[\"invoke\",70,0,\"check\",[\"i32:28\"]],[\"module\",73,61],[\"invoke\",99,0,\"test\",[]],[\"invoke\",101,0,\"check\",[\"i32:1\"]],[\"invoke\",102,0,\"check\",[\"i32:2\"]],[\"invoke\",104,0,\"check\",[\"i32:4\"]],[\"invoke\",106,0,\"check\",[\"i32:6\"]],[\"invoke\",107,0,\"check\",[\"i32:7\"]],[\"invoke\",109,0,\"check\",[\"i32:9\"]],[\"invoke\",111,0,\"check\",[\"i32:11\"]],[\"invoke\",113,0,\"check\",[\"i32:13\"]],[\"invoke\",114,0,\"check\",[\"i32:14\"]],[\"invoke\",116,0,\"check\",[\"i32:16\"]],[\"invoke\",118,0,\"check\",[\"i32:18\"]],[\"invoke\",119,0,\"check\",[\"i32:19\"]],[\"invoke\",121,0,\"check\",[\"i32:21\"]],[\"invoke\",123,0,\"check\",[\"i32:23\"]],[\"invoke\",124,0,\"check\",[\"i32:24\"]],[\"invoke\",126,0,\"check\",[\"i32:26\"]],[\"invoke\",128,0,\"check\",[\"i32:28\"]],[\"invoke\",129,0,\"check\",[\"i32:29\"]],[\"module\",131,62],[\"invoke\",166,0,\"check\",[\"i32:0\"]],[\"invoke\",168,0,\"check\",[\"i32:2\"]],[\"invoke\",170,0,\"check\",[\"i32:4\"]],[\"invoke\",171,0,\"check\",[\"i32:5\"]],[\"invoke\",173,0,\"check\",[\"i32:7\"]],[\"invoke\",175,0,\"check\",[\"i32:9\"]],[\"invoke\",176,0,\"check\",[\"i32:10\"]],[\"invoke\",178,0,\"check\",[\"i32:12\"]],[\"invoke\",180,0,\"check\",[\"i32:14\"]],[\"invoke\",181,0,\"check\",[\"i32:15\"]],[\"invoke\",183,0,\"check\",[\"i32:17\"]],[\"invoke\",185,0,\"check\",[\"i32:19\"]],[\"invoke\",186,0,\"check\",[\"i32:20\"]],[\"invoke\",188,0,\"check\",[\"i32:22\"]],[\"invoke\",190,0,\"check\",[\"i32:24\"]],[\"invoke\",192,0,\"check\",[\"i32:26\"]],[\"invoke\",193,0,\"check\",[\"i32:27\"]],[\"invoke\",195,0,\"check\",[\"i32:29\"]],[\"module\",197,63],[\"invoke\",224,0,\"check\",[\"i32:0\"]],[\"invoke\",225,0,\"check\",[\"i32:1\"]],[\"invoke\",227,0,\"check\",[\"i32:3\"]],[\"invoke\",229,0,\"check\",[\"i32:5\"]],[\"invoke\",230,0,\"check\",[\"i32:6\"]],[\"invoke\",232,0,\"check\",[\"i32:8\"]],[\"invoke\",234,0,\"check\",[\"i32:10\"]],[\"invoke\",235,0,\"check\",[\"i32:11\"]],[\"invoke\",237,0,\"check\",[\"i32:13\"]],[\"invoke\",239,0,\"check\",[\"i32:15\"]],[\"invoke\",241,0,\"check\",[\"i32:17\"]],[\"invoke\",242,0,\"check\",[\"i32:18\"]],[\"invoke\",244,0,\"check\",[\"i32:20\"]],[\"invoke\",246,0,\"check\",[\"i32:22\"]],[\"invoke\",247,0,\"check\",[\"i32:23\"]],[\"invoke\",249,0,\"check\",[\"i32:25\"]],[\"invoke\",251,0,\"check\",[\"i32:27\"]],[\"invoke\",252,0,\"check\",[\"i32:28\"]],[\"module\",255,64],[\"invoke\",281,0,\"test\",[]],[\"invoke\",283,0,\"check\",[\"i32:1\"]],[\"invoke\",284,0,\"check\",[\"i32:2\"]],[\"invoke\",286,0,\"check\",[\"i32:4\"]],[\"invoke\",288,0,\"check\",[\"i32:6\"]],[\"invoke\",290,0,\"check\",[\"i32:8\"]],[\"invoke\",291,0,\"check\",[\"i32:9\"]],[\"invoke\",293,0,\"check\",[\"i32:11\"]],[\"invoke\",295,0,\"check\",[\"i32:13\"]],[\"invoke\",296,0,\"check\",[\"i32:14\"]],[\"invoke\",298,0,\"check\",[\"i32:16\"]],[\"invoke\",300,0,\"check\",[\"i32:18\"]],[\"invoke\",301,0,\"check\",[\"i32:19\"]],[\"invoke\",303,0,\"check\",[\"i32:21\"]],[\"invoke\",305,0,\"check\",[\"i32:23\"]],[\"invoke\",306,0,\"check\",[\"i32:24\"]],[\"invoke\",308,0,\"check\",[\"i32:26\"]],[\"invoke\",310,0,\"check\",[\"i32:28\"]],[\"module\",313,65],[\"invoke\",347,0,\"test\",[]],[\"invoke\",348,0,\"check\",[\"i32:0\"]],[\"invoke\",350,0,\"check\",[\"i32:2\"]],[\"invoke\",352,0,\"check\",[\"i32:4\"]],[\"invoke\",353,0,\"check\",[\"i32:5\"]],[\"invoke\",355,0,\"check\",[\"i32:7\"]],[\"invoke\",357,0,\"check\",[\"i32:9\"]],[\"invoke\",358,0,\"check\",[\"i32:10\"]],[\"invoke\",360,0,\"check\",[\"i32:12\"]],[\"invoke\",362,0,\"check\",[\"i32:14\"]],[\"invoke\",363,0,\"check\",[\"i32:15\"]],[\"invoke\",365,0,\"check\",[\"i32:17\"]],[\"invoke\",367,0,\"check\",[\"i32:19\"]],[\"invoke\",369,0,\"check\",[\"i32:21\"]],[\"invoke\",370,0,\"check\",[\"i32:22\"]],[\"invoke\",372,0,\"check\",[\"i32:24\"]],[\"invoke\",374,0,\"check\",[\"i32:26\"]],[\"invoke\",375,0,\"check\",[\"i32:27\"]],[\"invoke\",377,0,\"check\",[\"i32:29\"]],[\"module\",407,66],[\"module\",431,67],[\"invoke\",453,0,\"test\",[]],[\"module\",455,68],[\"invoke\",477,0,\"test\",[]],[\"module\",479,69],[\"module\",503,70],[\"invoke\",525,0,\"test\",[]],[\"module\",527,71],[\"module\",551,72],[\"invoke\",573,0,\"test\",[]],[\"module\",575,73],[\"invoke\",597,0,\"test\",[]],[\"module\",599,74],[\"module\",623,75],[\"invoke\",645,0,\"test\",[]],[\"module\",647,76],[\"module\",671,77],[\"invoke\",693,0,\"test\",[]],[\"module\",695,78],[\"module\",719,79],[\"invoke\",741,0,\"test\",[]],[\"module\",743,80],[\"invoke\",765,0,\"test\",[]],[\"module\",767,81],[\"module\",791,82],[\"invoke\",813,0,\"test\",[]],[\"module\",815,83],[\"module\",839,84],[\"invoke\",861,0,\"test\",[]],[\"module\",863,85],[\"invoke\",885,0,\"test\",[]],[\"module\",887,86],[\"module\",1478,87],[\"invoke\",1506,0,\"run\",[\"i32:24\",\"i32:16\"]],[\"invoke\",1508,0,\"test\",[\"i32:1\"]],[\"invoke\",1509,0,\"test\",[\"i32:2\"]],[\"invoke\",1511,0,\"test\",[\"i32:4\"]],[\"invoke\",1513,0,\"test\",[\"i32:6\"]],[\"invoke\",1514,0,\"test\",[\"i32:7\"]],[\"invoke\",1516,0,\"test\",[\"i32:9\"]],[\"invoke\",1518,0,\"test\",[\"i32:11\"]],[\"invoke\",1520,0,\"test\",[\"i32:13\"]],[\"invoke\",1521,0,\"test\",[\"i32:14\"]],[\"invoke\",1523,0,\"test\",[\"i32:16\"]],[\"invoke\",1525,0,\"test\",[\"i32:18\"]],[\"invoke\",1526,0,\"test\",[\"i32:19\"]],[\"invoke\",1528,0,\"test\",[\"i32:21\"]],[\"invoke\",1530,0,\"test\",[\"i32:23\"]],[\"invoke\",1531,0,\"test\",[\"i32:24\"]],[\"invoke\",1533,0,\"test\",[\"i32:26\"]],[\"invoke\",1535,0,\"test\",[\"i32:28\"]],[\"invoke\",1536,0,\"test\",[\"i32:29\"]],[\"invoke\",1538,0,\"test\",[\"i32:31\"]],[\"module\",1540,87],[\"invoke\",1569,0,\"test\",[\"i32:0\"]],[\"invoke\",1571,0,\"test\",[\"i32:2\"]],[\"invoke\",1572,0,\"test\",[\"i32:3\"]],[\"invoke\",1574,0,\"test\",[\"i32:5\"]],[\"invoke\",1576,0,\"test\",[\"i32:7\"]],[\"invoke\",1577,0,\"test\",[\"i32:8\"]],[\"invoke\",1579,0,\"test\",[\"i32:10\"]],[\"invoke\",1581,0,\"test\",[\"i32:12\"]],[\"invoke\",1582,0,\"test\",[\"i32:13\"]],[\"invoke\",1584,0,\"test\",[\"i32:15\"]],[\"invoke\",1586,0,\"test\",[\"i32:17\"]],[\"invoke\",1587,0,\"test\",[\"i32:18\"]],[\"invoke\",1589,0,\"test\",[\"i32:20\"]],[\"invoke\",1591,0,\"test\",[\"i32:22\"]],[\"invoke\",1593,0,\"test\",[\"i32:24\"]],[\"invoke\",1594,0,\"test\",[\"i32:25\"]],[\"invoke\",1596,0,\"test\",[\"i32:27\"]],[\"invoke\",1598,0,\"test\",[\"i32:29\"]],[\"invoke\",1599,0,\"test\",[\"i32:30\"]],[\"module\",1602,88],[\"invoke\",1630,0,\"run\",[\"i32:96\",\"i32:32\"]],[\"invoke\",1632,0,\"test\",[\"i32:1\"]],[\"invoke\",1633,0,\"test\",[\"i32:2\"]],[\"invoke\",1635,0,\"test\",[\"i32:4\"]],[\"invoke\",1637,0,\"test\",[\"i32:6\"]],[\"invoke\",1638,0,\"test\",[\"i32:7\"]],[\"invoke\",1640,0,\"test\",[\"i32:9\"]],[\"invoke\",1642,0,\"test\",[\"i32:11\"]],[\"invoke\",1644,0,\"test\",[\"i32:13\"]],[\"invoke\",1645,0,\"test\",[\"i32:14\"]],[\"invoke\",1647,0,\"test\",[\"i32:16\"]],[\"invoke\",1649,0,\"test\",[\"i32:18\"]],[\"invoke\",1650,0,\"test\",[\"i32:19\"]],[\"invoke\",1652,0,\"test\",[\"i32:21\"]],[\"invoke\",1654,0,\"test\",[\"i32:23\"]],[\"invoke\",1655,0,\"test\",[\"i32:24\"]],[\"invoke\",1657,0,\"test\",[\"i32:26\"]],[\"invoke\",1659,0,\"test\",[\"i32:28\"]],[\"invoke\",1660,0,\"test\",[\"i32:29\"]],[\"invoke\",1662,0,\"test\",[\"i32:31\"]],[\"invoke\",1664,0,\"test\",[\"i32:33\"]],[\"invoke\",1666,0,\"test\",[\"i32:35\"]],[\"invoke\",1667,0,\"test\",[\"i32:36\"]],[\"invoke\",1669,0,\"test\",[\"i32:38\"]],[\"invoke\",1671,0,\"test\",[\"i32:40\"]],[\"invoke\",1672,0,\"test\",[\"i32:41\"]],[\"invoke\",1674,0,\"test\",[\"i32:43\"]],[\"invoke\",1676,0,\"test\",[\"i32:45\"]],[\"invoke\",1677,0,\"test\",[\"i32:46\"]],[\"invoke\",1679,0,\"test\",[\"i32:48\"]],[\"invoke\",1681,0,\"test\",[\"i32:50\"]],[\"invoke\",1682,0,\"test\",[\"i32:51\"]],[\"invoke\",1684,0,\"test\",[\"i32:53\"]],[\"invoke\",1686,0,\"test\",[\"i32:55\"]],[\"invoke\",1688,0,\"test\",[\"i32:57\"]],[\"invoke\",1689,0,\"test\",[\"i32:58\"]],[\"invoke\",1691,0,\"test\",[\"i32:60\"]],[\"invoke\",1693,0,\"test\",[\"i32:62\"]],[\"invoke\",1694,0,\"test\",[\"i32:63\"]],[\"invoke\",1696,0,\"test\",[\"i32:65\"]],[\"invoke\",1698,0,\"test\",[\"i32:67\"]],[\"invoke\",1699,0,\"test\",[\"i32:68\"]],[\"invoke\",1701,0,\"test\",[\"i32:70\"]],[\"invoke\",1703,0,\"test\",[\"i32:72\"]],[\"invoke\",1704,0,\"test\",[\"i32:73\"]],[\"invoke\",1706,0,\"test\",[\"i32:75\"]],[\"invoke\",1708,0,\"test\",[\"i32:77\"]],[\"invoke\",1710,0,\"test\",[\"i32:79\"]],[\"invoke\",1711,0,\"test\",[\"i32:80\"]],[\"invoke\",1713,0,\"test\",[\"i32:82\"]],[\"invoke\",1715,0,\"test\",[\"i32:84\"]],[\"invoke\",1716,0,\"test\",[\"i32:85\"]],[\"invoke\",1718,0,\"test\",[\"i32:87\"]],[\"invoke\",1720,0,\"test\",[\"i32:89\"]],[\"invoke\",1721,0,\"test\",[\"i32:90\"]],[\"invoke\",1723,0,\"test\",[\"i32:92\"]],[\"invoke\",1725,0,\"test\",[\"i32:94\"]],[\"invoke\",1726,0,\"test\",[\"i32:95\"]],[\"invoke\",1728,0,\"test\",[\"i32:97\"]],[\"invoke\",1730,0,\"test\",[\"i32:99\"]],[\"invoke\",1732,0,\"test\",[\"i32:101\"]],[\"invoke\",1733,0,\"test\",[\"i32:102\"]],[\"invoke\",1735,0,\"test\",[\"i32:104\"]],[\"invoke\",1737,0,\"test\",[\"i32:106\"]],[\"invoke\",1738,0,\"test\",[\"i32:107\"]],[\"invoke\",1740,0,\"test\",[\"i32:109\"]],[\"invoke\",1742,0,\"test\",[\"i32:111\"]],[\"invoke\",1743,0,\"test\",[\"i32:112\"]],[\"invoke\",1745,0,\"test\",[\"i32:114\"]],[\"invoke\",1747,0,\"test\",[\"i32:116\"]],[\"invoke\",1748,0,\"test\",[\"i32:117\"]],[\"invoke\",1750,0,\"test\",[\"i32:119\"]],[\"invoke\",1752,0,\"test\",[\"i32:121\"]],[\"invoke\",1754,0,\"test\",[\"i32:123\"]],[\"invoke\",1755,0,\"test\",[\"i32:124\"]],[\"invoke\",1757,0,\"test\",[\"i32:126\"]],[\"invoke\",1759,0,\"test\",[\"i32:128\"]],[\"invoke\",1760,0,\"test\",[\"i32:129\"]],[\"invoke\",1762,0,\"test\",[\"i32:131\"]],[\"invoke\",1764,0,\"test\",[\"i32:133\"]],[\"invoke\",1765,0,\"test\",[\"i32:134\"]],[\"invoke\",1767,0,\"test\",[\"i32:136\"]],[\"invoke\",1769,0,\"test\",[\"i32:138\"]],[\"invoke\",1770,0,\"test\",[\"i32:139\"]],[\"invoke\",1772,0,\"test\",[\"i32:141\"]],[\"invoke\",1774,0,\"test\",[\"i32:143\"]],[\"invoke\",1776,0,\"test\",[\"i32:145\"]],[\"invoke\",1777,0,\"test\",[\"i32:146\"]],[\"invoke\",1779,0,\"test\",[\"i32:148\"]],[\"invoke\",1781,0,\"test\",[\"i32:150\"]],[\"invoke\",1782,0,\"test\",[\"i32:151\"]],[\"invoke\",1784,0,\"test\",[\"i32:153\"]],[\"invoke\",1786,0,\"test\",[\"i32:155\"]],[\"invoke\",1787,0,\"test\",[\"i32:156\"]],[\"invoke\",1789,0,\"test\",[\"i32:158\"]],[\"module\",1792,88],[\"invoke\",1820,0,\"run\",[\"i32:97\",\"i32:31\"]],[\"invoke\",1821,0,\"test\",[\"i32:0\"]],[\"invoke\",1823,0,\"test\",[\"i32:2\"]],[\"invoke\",1825,0,\"test\",[\"i32:4\"]],[\"invoke\",1827,0,\"test\",[\"i32:6\"]],[\"invoke\",1828,0,\"test\",[\"i32:7\"]],[\"invoke\",1830,0,\"test\",[\"i32:9\"]],[\"invoke\",1832,0,\"test\",[\"i32:11\"]],[\"invoke\",1833,0,\"test\",[\"i32:12\"]],[\"invoke\",1835,0,\"test\",[\"i32:14\"]],[\"invoke\",1837,0,\"test\",[\"i32:16\"]],[\"invoke\",1838,0,\"test\",[\"i32:17\"]],[\"invoke\",1840,0,\"test\",[\"i32:19\"]],[\"invoke\",1842,0,\"test\",[\"i32:21\"]],[\"invoke\",1843,0,\"test\",[\"i32:22\"]],[\"invoke\",1845,0,\"test\",[\"i32:24\"]],[\"invoke\",1847,0,\"test\",[\"i32:26\"]],[\"invoke\",1849,0,\"test\",[\"i32:28\"]],[\"invoke\",1850,0,\"test\",[\"i32:29\"]],[\"invoke\",1852,0,\"test\",[\"i32:31\"]],[\"invoke\",1854,0,\"test\",[\"i32:33\"]],[\"invoke\",1855,0,\"test\",[\"i32:34\"]],[\"invoke\",1857,0,\"test\",[\"i32:36\"]],[\"invoke\",1859,0,\"test\",[\"i32:38\"]],[\"invoke\",1860,0,\"test\",[\"i32:39\"]],[\"invoke\",1862,0,\"test\",[\"i32:41\"]],[\"invoke\",1864,0,\"test\",[\"i32:43\"]],[\"invoke\",1865,0,\"test\",[\"i32:44\"]],[\"invoke\",1867,0,\"test\",[\"i32:46\"]],[\"invoke\",1869,0,\"test\",[\"i32:48\"]],[\"invoke\",1871,0,\"test\",[\"i32:50\"]],[\"invoke\",1872,0,\"test\",[\"i32:51\"]],[\"invoke\",1874,0,\"test\",[\"i32:53\"]],[\"invoke\",1876,0,\"test\",[\"i32:55\"]],[\"invoke\",1877,0,\"test\",[\"i32:56\"]],[\"invoke\",1879,0,\"test\",[\"i32:58\"]],[\"invoke\",1881,0,\"test\",[\"i32:60\"]],[\"invoke\",1882,0,\"test\",[\"i32:61\"]],[\"invoke\",1884,0,\"test\",[\"i32:63\"]],[\"invoke\",1886,0,\"test\",[\"i32:65\"]],[\"invoke\",1887,0,\"test\",[\"i32:66\"]],[\"invoke\",1889,0,\"test\",[\"i32:68\"]],[\"invoke\",1891,0,\"test\",[\"i32:70\"]],[\"invoke\",1893,0,\"test\",[\"i32:72\"]],[\"invoke\",1894,0,\"test\",[\"i32:73\"]],[\"invoke\",1896,0,\"test\",[\"i32:75\"]],[\"invoke\",1898,0,\"test\",[\"i32:77\"]],[\"invoke\",1899,0,\"test\",[\"i32:78\"]],[\"invoke\",1901,0,\"test\",[\"i32:80\"]],[\"invoke\",1903,0,\"test\",[\"i32:82\"]],[\"invoke\",1904,0,\"test\",[\"i32:83\"]],[\"invoke\",1906,0,\"test\",[\"i32:85\"]],[\"invoke\",1908,0,\"test\",[\"i32:87\"]],[\"invoke\",1909,0,\"test\",[\"i32:88\"]],[\"invoke\",1911,0,\"test\",[\"i32:90\"]],[\"invoke\",1913,0,\"test\",[\"i32:92\"]],[\"invoke\",1915,0,\"test\",[\"i32:94\"]],[\"invoke\",1916,0,\"test\",[\"i32:95\"]],[\"invoke\",1918,0,\"test\",[\"i32:97\"]],[\"invoke\",1920,0,\"test\",[\"i32:99\"]],[\"invoke\",1921,0,\"test\",[\"i32:100\"]],[\"invoke\",1923,0,\"test\",[\"i32:102\"]],[\"invoke\",1925,0,\"test\",[\"i32:104\"]],[\"invoke\",1926,0,\"test\",[\"i32:105\"]],[\"invoke\",1928,0,\"test\",[\"i32:107\"]],[\"invoke\",1930,0,\"test\",[\"i32:109\"]],[\"invoke\",1931,0,\"test\",[\"i32:110\"]],[\"invoke\",1933,0,\"test\",[\"i32:112\"]],[\"invoke\",1935,0,\"test\",[\"i32:114\"]],[\"invoke\",1937,0,\"test\",[\"i32:116\"]],[\"invoke\",1938,0,\"test\",[\"i32:117\"]],[\"invoke\",1940,0,\"test\",[\"i32:119\"]],[\"invoke\",1942,0,\"test\",[\"i32:121\"]],[\"invoke\",1943,0,\"test\",[\"i32:122\"]],[\"invoke\",1945,0,\"test\",[\"i32:124\"]],[\"invoke\",1947,0,\"test\",[\"i32:126\"]],[\"invoke\",1948,0,\"test\",[\"i32:127\"]],[\"invoke\",1950,0,\"test\",[\"i32:129\"]],[\"invoke\",1952,0,\"test\",[\"i32:131\"]],[\"invoke\",1953,0,\"test\",[\"i32:132\"]],[\"invoke\",1955,0,\"test\",[\"i32:134\"]],[\"invoke\",1957,0,\"test\",[\"i32:136\"]],[\"invoke\",1959,0,\"test\",[\"i32:138\"]],[\"invoke\",1960,0,\"test\",[\"i32:139\"]],[\"invoke\",1962,0,\"test\",[\"i32:141\"]],[\"invoke\",1964,0,\"test\",[\"i32:143\"]],[\"invoke\",1965,0,\"test\",[\"i32:144\"]],[\"invoke\",1967,0,\"test\",[\"i32:146\"]],[\"invoke\",1969,0,\"test\",[\"i32:148\"]],[\"invoke\",1970,0,\"test\",[\"i32:149\"]],[\"invoke\",1972,0,\"test\",[\"i32:151\"]],[\"invoke\",1974,0,\"test\",[\"i32:153\"]],[\"invoke\",1975,0,\"test\",[\"i32:154\"]],[\"invoke\",1977,0,\"test\",[\"i32:156\"]],[\"invoke\",1979,0,\"test\",[\"i32:158\"]],[\"module\",1982,89],[\"invoke\",2010,0,\"run\",[\"i32:48\",\"i32:4294967280\"]],[\"invoke\",2011,0,\"test\",[\"i32:0\"]],[\"invoke\",2013,0,\"test\",[\"i32:2\"]],[\"invoke\",2015,0,\"test\",[\"i32:4\"]],[\"invoke\",2016,0,\"test\",[\"i32:5\"]],[\"invoke\",2018,0,\"test\",[\"i32:7\"]],[\"invoke\",2020,0,\"test\",[\"i32:9\"]],[\"invoke\",2021,0,\"test\",[\"i32:10\"]],[\"invoke\",2023,0,\"test\",[\"i32:12\"]],[\"invoke\",2025,0,\"test\",[\"i32:14\"]],[\"invoke\",2026,0,\"test\",[\"i32:15\"]],[\"invoke\",2028,0,\"test\",[\"i32:17\"]],[\"invoke\",2030,0,\"test\",[\"i32:19\"]],[\"invoke\",2032,0,\"test\",[\"i32:21\"]],[\"invoke\",2033,0,\"test\",[\"i32:22\"]],[\"invoke\",2035,0,\"test\",[\"i32:24\"]],[\"invoke\",2037,0,\"test\",[\"i32:26\"]],[\"invoke\",2038,0,\"test\",[\"i32:27\"]],[\"invoke\",2040,0,\"test\",[\"i32:29\"]],[\"invoke\",2042,0,\"test\",[\"i32:31\"]],[\"invoke\",2043,0,\"test\",[\"i32:32\"]],[\"invoke\",2045,0,\"test\",[\"i32:34\"]],[\"invoke\",2047,0,\"test\",[\"i32:36\"]],[\"invoke\",2048,0,\"test\",[\"i32:37\"]],[\"invoke\",2050,0,\"test\",[\"i32:39\"]],[\"invoke\",2052,0,\"test\",[\"i32:41\"]],[\"invoke\",2054,0,\"test\",[\"i32:43\"]],[\"invoke\",2055,0,\"test\",[\"i32:44\"]],[\"invoke\",2057,0,\"test\",[\"i32:46\"]],[\"invoke\",2059,0,\"test\",[\"i32:48\"]],[\"invoke\",2060,0,\"test\",[\"i32:49\"]],[\"invoke\",2062,0,\"test\",[\"i32:51\"]],[\"invoke\",2064,0,\"test\",[\"i32:53\"]],[\"invoke\",2065,0,\"test\",[\"i32:54\"]],[\"invoke\",2067,0,\"test\",[\"i32:56\"]],[\"invoke\",2069,0,\"test\",[\"i32:58\"]],[\"invoke\",2070,0,\"test\",[\"i32:59\"]],[\"invoke\",2072,0,\"test\",[\"i32:61\"]],[\"invoke\",2074,0,\"test\",[\"i32:63\"]],[\"module\",2076,90],[\"invoke\",2105,0,\"test\",[\"i32:0\"]],[\"invoke\",2106,0,\"test\",[\"i32:1\"]],[\"invoke\",2108,0,\"test\",[\"i32:3\"]],[\"invoke\",2110,0,\"test\",[\"i32:5\"]],[\"invoke\",2111,0,\"test\",[\"i32:6\"]],[\"invoke\",2113,0,\"test\",[\"i32:8\"]],[\"invoke\",2115,0,\"test\",[\"i32:10\"]],[\"invoke\",2116,0,\"test\",[\"i32:11\"]],[\"invoke\",2118,0,\"test\",[\"i32:13\"]],[\"invoke\",2120,0,\"test\",[\"i32:15\"]],[\"module\",2122,91],[\"file\",\"table_set.wast\"],[\"module\",1,92],[\"invoke\",29,0,\"get-externref\",[\"i32:0\"]],[\"invoke\",30,0,\"set-externref\",[\"i32:0\",\"externref:1\"]],[\"invoke\",31,0,\"get-externref\",[\"i32:0\"]],[\"invoke\",32,0,\"set-externref\",[\"i32:0\",\"externref:null\"]],[\"invoke\",33,0,\"get-externref\",[\"i32:0\"]],[\"invoke\",35,0,\"get-funcref\",[\"i32:0\"]],[\"invoke\",36,0,\"set-funcref-from\",[\"i32:0\",\"i32:1\"]],[\"invoke\",37,0,\"is_null-funcref\",[\"i32:0\"]],[\"invoke\",38,0,\"set-funcref\",[\"i32:0\",\"funcref:null\"]],[\"invoke\",39,0,\"get-funcref\",[\"i32:0\"]],[\"invoke\",41,0,\"set-externref\",[\"i32:2\",\"externref:null\"]],[\"invoke\",42,0,\"set-funcref\",[\"i32:3\",\"funcref:null\"]],[\"invoke\",43,0,\"set-externref\",[\"i32:4294967295\",\"externref:null\"]],[\"invoke\",44,0,\"set-funcref\",[\"i32:4294967295\",\"funcref:null\"]],[\"invoke\",46,0,\"set-externref\",[\"i32:2\",\"externref:0\"]],[\"invoke\",47,0,\"set-funcref-from\",[\"i32:3\",\"i32:1\"]],[\"invoke\",48,0,\"set-externref\",[\"i32:4294967295\",\"externref:0\"]],[\"invoke\",49,0,\"set-funcref-from\",[\"i32:4294967295\",\"i32:1\"]],[\"file\",\"table_size.wast\"],[\"module\",1,93],[\"invoke\",26,0,\"size-t0\",[]],[\"invoke\",27,0,\"grow-t0\",[\"i32:1\"]],[\"invoke\",28,0,\"size-t0\",[]],[\"invoke\",29,0,\"grow-t0\",[\"i32:4\"]],[\"invoke\",30,0,\"size-t0\",[]],[\"invoke\",31,0,\"grow-t0\",[\"i32:0\"]],[\"invoke\",32,0,\"size-t0\",[]],[\"invoke\",34,0,\"size-t1\",[]],[\"invoke\",35,0,\"grow-t1\",[\"i32:1\"]],[\"invoke\",36,0,\"size-t1\",[]],[\"invoke\",37,0,\"grow-t1\",[\"i32:4\"]],[\"invoke\",38,0,\"size-t1\",[]],[\"invoke\",39,0,\"grow-t1\",[\"i32:0\"]],[\"invoke\",40,0,\"size-t1\",[]],[\"invoke\",42,0,\"size-t2\",[]],[\"invoke\",43,0,\"grow-t2\",[\"i32:3\"]],[\"invoke\",44,0,\"size-t2\",[]],[\"invoke\",45,0,\"grow-t2\",[\"i32:1\"]],[\"invoke\",46,0,\"size-t2\",[]],[\"invoke\",47,0,\"grow-t2\",[\"i32:0\"]],[\"invoke\",48,0,\"size-t2\",[]],[\"invoke\",49,0,\"grow-t2\",[\"i32:4\"]],[\"invoke\",50,0,\"size-t2\",[]],[\"invoke\",51,0,\"grow-t2\",[\"i32:1\"]],[\"invoke\",52,0,\"size-t2\",[]],[\"invoke\",54,0,\"size-t3\",[]],[\"invoke\",55,0,\"grow-t3\",[\"i32:1\"]],[\"invoke\",56,0,\"size-t3\",[]],[\"invoke\",57,0,\"grow-t3\",[\"i32:3\"]],[\"invoke\",58,0,\"size-t3\",[]],[\"invoke\",59,0,\"grow-t3\",[\"i32:0\"]],[\"invoke\",60,0,\"size-t3\",[]],[\"invoke\",61,0,\"grow-t3\",[\"i32:2\"]],[\"invoke\",62,0,\"size-t3\",[]],[\"invoke\",63,0,\"grow-t3\",[\"i32:1\"]],[\"invoke\",64,0,\"size-t3\",[]],[\"file\",\"elem.wast\"],[\"module\",4,94],[\"module\",80,95],[\"module\",90,96],[\"module\",101,97],[\"module\",120,98],[\"module\",127,98],[\"module\",134,99],[\"invoke\",148,0,\"call-7\",[]],[\"invoke\",149,0,\"call-9\",[]],[\"module\",153,100],[\"invoke\",167,0,\"call-7\",[]],[\"invoke\",168,0,\"call-9\",[]],[\"module\",182,101],[\"module\",193,102],[\"module\",202,103],[\"module\",207,104],[\"instantiate\",239,105],[\"instantiate\",248,106],[\"instantiate\",257,107],[\"instantiate\",266,108],[\"instantiate\",273,109],[\"instantiate\",290,110],[\"instantiate\",307,111],[\"instantiate\",324,112],[\"module\",342,113],[\"invoke\",350,0,\"init\",[]],[\"module\",352,114],[\"invoke\",360,0,\"init\",[]],[\"module\",537,115],[\"invoke\",548,0,\"call-overwritten\",[]],[\"module\",565,116,\"$module1\"],[\"register\",\"module1\",\"$module1\"],[\"invoke\",585,\"$module1\",\"call-7\",[]],[\"invoke\",586,\"$module1\",\"call-8\",[]],[\"invoke\",587,\"$module1\",\"call-9\",[]],[\"module\",589,117,\"$module2\"],[\"invoke\",598,\"$module1\",\"call-7\",[]],[\"invoke\",599,\"$module1\",\"call-8\",[]],[\"invoke\",600,\"$module1\",\"call-9\",[]],[\"module\",602,118,\"$module3\"],[\"invoke\",611,\"$module1\",\"call-7\",[]],[\"invoke\",612,\"$module1\",\"call-8\",[]],[\"invoke\",613,\"$module1\",\"call-9\",[]],[\"module\",646,119,\"$m\"],[\"register\",\"exporter\",\"$m\"],[\"invoke\",655,\"$m\",\"get\",[\"i32:0\"]],[\"invoke\",656,\"$m\",\"get\",[\"i32:1\"]],[\"invoke\",658,\"$m\",\"set\",[\"i32:0\",\"externref:42\"]],[\"invoke\",659,\"$m\",\"set\",[\"i32:1\",\"externref:137\"]],[\"invoke\",661,\"$m\",\"get\",[\"i32:0\"]],[\"invoke\",662,\"$m\",\"get\",[\"i32:1\"]],[\"module\",664,120],[\"invoke\",668,\"$m\",\"get\",[\"i32:0\"]],[\"invoke\",669,\"$m\",\"get\",[\"i32:1\"]],[\"module\",673,121,\"$module4\"],[\"register\",\"module4\",\"$module4\"],[\"module\",682,122],[\"invoke\",692,0,\"call_imported_elem\",[]],[\"file\",\"ref_func.wast\"],[\"module\",1,123],[\"register\",\"M\"],[\"module\",6,124],[\"invoke\",56,0,\"is_null-f\",[]],[\"invoke\",57,0,\"is_null-g\",[]],[\"invoke\",58,0,\"is_null-v\",[]],[\"invoke\",60,0,\"call-f\",[\"i32:4\"]],[\"invoke\",61,0,\"call-g\",[\"i32:4\"]],[\"invoke\",62,0,\"call-v\",[\"i32:4\"]],[\"invoke\",63,0,\"set-g\",[]],[\"invoke\",64,0,\"call-v\",[\"i32:4\"]],[\"invoke\",65,0,\"set-f\",[]],[\"invoke\",66,0,\"call-v\",[\"i32:4\"]],[\"module\",80,125],[\"file\",\"ref_is_null.wast\"],[\"module\",1,126],[\"invoke\",30,0,\"funcref\",[\"funcref:null\"]],[\"invoke\",31,0,\"externref\",[\"externref:null\"]],[\"invoke\",33,0,\"externref\",[\"externref:1\"]],[\"invoke\",35,0,\"init\",[\"externref:0\"]],[\"invoke\",37,0,\"funcref-elem\",[\"i32:0\"]],[\"invoke\",38,0,\"externref-elem\",[\"i32:0\"]],[\"invoke\",40,0,\"funcref-elem\",[\"i32:1\"]],[\"invoke\",41,0,\"externref-elem\",[\"i32:1\"]],[\"invoke\",43,0,\"deinit\",[]],[\"invoke\",45,0,\"funcref-elem\",[\"i32:0\"]],[\"invoke\",46,0,\"externref-elem\",[\"i32:0\"]],[\"invoke\",48,0,\"funcref-elem\",[\"i32:1\"]],[\"invoke\",49,0,\"externref-elem\",[\"i32:1\"]],[\"file\",\"ref_null.wast\"],[\"module\",1,127],[\"invoke\",9,0,\"externref\",[]],[\"invoke\",10,0,\"funcref\",[]],[\"file\",\"exports.wast\"],[\"module\",3,128],[\"module\",4,129],[\"module\",5,130],[\"module\",7,128],[\"module\",8,131],[\"module\",9,132],[\"module\",10,128],[\"module\",11,128],[\"module\",12,128],[\"module\",13,128],[\"module\",14,128],[\"module\",16,133,\"$Func\"],[\"invoke\",22,0,\"e\",[\"i32:42\"]],[\"invoke\",23,\"$Func\",\"e\",[\"i32:42\"]],[\"module\",24,134],[\"module\",25,134,\"$Other1\"],[\"invoke\",26,\"$Func\",\"e\",[\"i32:42\"]],[\"module\",28,135],[\"invoke\",34,0,\"a\",[]],[\"invoke\",35,0,\"b\",[]],[\"invoke\",36,0,\"c\",[]],[\"module\",74,136],[\"module\",75,137],[\"module\",76,138],[\"module\",78,136],[\"module\",79,136],[\"module\",80,136],[\"module\",81,136],[\"module\",82,136],[\"module\",83,136],[\"module\",85,139,\"$Global\"],[\"get\",89,0,\"e\",[]],[\"get\",90,\"$Global\",\"e\",[]],[\"module\",91,134],[\"module\",92,134,\"$Other2\"],[\"get\",93,\"$Global\",\"e\",[]],[\"module\",131,140],[\"module\",132,141],[\"module\",133,142],[\"module\",135,140],[\"module\",136,143],[\"module\",137,140],[\"module\",138,143],[\"module\",139,140],[\"module\",140,143],[\"module\",141,140],[\"module\",142,143],[\"module\",143,140],[\"module\",144,143],[\"module\",145,140],[\"module\",146,143],[\"module\",186,144],[\"module\",187,145],[\"module\",191,144],[\"module\",192,146],[\"module\",193,144],[\"module\",194,146],[\"module\",195,144],[\"module\",196,146],[\"module\",197,144],[\"module\",198,146],[\"module\",199,144],[\"module\",200,146],[\"module\",201,144],[\"module\",202,146],[\"file\",\"linking.wast\"],[\"module\",3,147,\"$Mf\"],[\"register\",\"Mf\",\"$Mf\"],[\"module\",9,148,\"$Nf\"],[\"invoke\",17,\"$Mf\",\"call\",[]],[\"invoke\",18,\"$Nf\",\"Mf.call\",[]],[\"invoke\",19,\"$Nf\",\"call\",[]],[\"invoke\",20,\"$Nf\",\"call Mf.call\",[]],[\"module\",22,149],[\"register\",\"reexport_f\"],[\"instantiate\",28,150],[\"instantiate\",32,151],[\"module\",39,152,\"$Mg\"],[\"register\",\"Mg\",\"$Mg\"],[\"module\",50,153,\"$Ng\"],[\"get\",67,\"$Mg\",\"glob\",[]],[\"get\",68,\"$Ng\",\"Mg.glob\",[]],[\"get\",69,\"$Ng\",\"glob\",[]],[\"invoke\",70,\"$Mg\",\"get\",[]],[\"invoke\",71,\"$Ng\",\"Mg.get\",[]],[\"invoke\",72,\"$Ng\",\"get\",[]],[\"get\",74,\"$Mg\",\"mut_glob\",[]],[\"get\",75,\"$Ng\",\"Mg.mut_glob\",[]],[\"invoke\",76,\"$Mg\",\"get_mut\",[]],[\"invoke\",77,\"$Ng\",\"Mg.get_mut\",[]],[\"invoke\",79,\"$Mg\",\"set_mut\",[\"i32:241\"]],[\"get\",80,\"$Mg\",\"mut_glob\",[]],[\"get\",81,\"$Ng\",\"Mg.mut_glob\",[]],[\"invoke\",82,\"$Mg\",\"get_mut\",[]],[\"invoke\",83,\"$Ng\",\"Mg.get_mut\",[]],[\"instantiate\",87,154],[\"instantiate\",91,155],[\"module\",96,156,\"$Mref_ex\"],[\"register\",\"Mref_ex\",\"$Mref_ex\"],[\"module\",104,157,\"$Mref_im\"],[\"instantiate\",113,158],[\"instantiate\",117,159],[\"instantiate\",123,160],[\"instantiate\",127,161],[\"module\",134,162,\"$Mt\"],[\"register\",\"Mt\",\"$Mt\"],[\"module\",149,163,\"$Nt\"],[\"invoke\",168,\"$Mt\",\"call\",[\"i32:2\"]],[\"invoke\",169,\"$Nt\",\"Mt.call\",[\"i32:2\"]],[\"invoke\",170,\"$Nt\",\"call\",[\"i32:2\"]],[\"invoke\",171,\"$Nt\",\"call Mt.call\",[\"i32:2\"]],[\"invoke\",173,\"$Mt\",\"call\",[\"i32:1\"]],[\"invoke\",174,\"$Nt\",\"Mt.call\",[\"i32:1\"]],[\"invoke\",175,\"$Nt\",\"call\",[\"i32:1\"]],[\"invoke\",176,\"$Nt\",\"call Mt.call\",[\"i32:1\"]],[\"invoke\",178,\"$Mt\",\"call\",[\"i32:0\"]],[\"invoke\",179,\"$Nt\",\"Mt.call\",[\"i32:0\"]],[\"invoke\",180,\"$Nt\",\"call\",[\"i32:0\"]],[\"invoke\",181,\"$Nt\",\"call Mt.call\",[\"i32:0\"]],[\"invoke\",183,\"$Mt\",\"call\",[\"i32:20\"]],[\"invoke\",184,\"$Nt\",\"Mt.call\",[\"i32:20\"]],[\"invoke\",185,\"$Nt\",\"call\",[\"i32:7\"]],[\"invoke\",186,\"$Nt\",\"call Mt.call\",[\"i32:20\"]],[\"invoke\",188,\"$Nt\",\"call\",[\"i32:3\"]],[\"invoke\",189,\"$Nt\",\"call\",[\"i32:4\"]],[\"module\",191,164,\"$Ot\"],[\"invoke\",204,\"$Mt\",\"call\",[\"i32:3\"]],[\"invoke\",205,\"$Nt\",\"Mt.call\",[\"i32:3\"]],[\"invoke\",206,\"$Nt\",\"call Mt.call\",[\"i32:3\"]],[\"invoke\",207,\"$Ot\",\"call\",[\"i32:3\"]],[\"invoke\",209,\"$Mt\",\"call\",[\"i32:2\"]],[\"invoke\",210,\"$Nt\",\"Mt.call\",[\"i32:2\"]],[\"invoke\",211,\"$Nt\",\"call\",[\"i32:2\"]],[\"invoke\",212,\"$Nt\",\"call Mt.call\",[\"i32:2\"]],[\"invoke\",213,\"$Ot\",\"call\",[\"i32:2\"]],[\"invoke\",215,\"$Mt\",\"call\",[\"i32:1\"]],[\"invoke\",216,\"$Nt\",\"Mt.call\",[\"i32:1\"]],[\"invoke\",217,\"$Nt\",\"call\",[\"i32:1\"]],[\"invoke\",218,\"$Nt\",\"call Mt.call\",[\"i32:1\"]],[\"invoke\",219,\"$Ot\",\"call\",[\"i32:1\"]],[\"invoke\",221,\"$Mt\",\"call\",[\"i32:0\"]],[\"invoke\",222,\"$Nt\",\"Mt.call\",[\"i32:0\"]],[\"invoke\",223,\"$Nt\",\"call\",[\"i32:0\"]],[\"invoke\",224,\"$Nt\",\"call Mt.call\",[\"i32:0\"]],[\"invoke\",225,\"$Ot\",\"call\",[\"i32:0\"]],[\"invoke\",227,\"$Ot\",\"call\",[\"i32:20\"]],[\"module\",229,165],[\"module\",235,166,\"$G1\"],[\"register\",\"G1\",\"$G1\"],[\"module\",237,167,\"$G2\"],[\"get\",241,\"$G2\",\"g\",[]],[\"instantiate\",244,168],[\"instantiate\",253,169],[\"invoke\",262,\"$Mt\",\"call\",[\"i32:7\"]],[\"instantiate\",267,170],[\"invoke\",275,\"$Mt\",\"call\",[\"i32:7\"]],[\"invoke\",276,\"$Mt\",\"call\",[\"i32:8\"]],[\"instantiate\",279,171],[\"invoke\",288,\"$Mt\",\"call\",[\"i32:7\"]],[\"module\",291,172,\"$Mtable_ex\"],[\"register\",\"Mtable_ex\",\"$Mtable_ex\"],[\"module\",297,173],[\"instantiate\",303,174],[\"instantiate\",307,175],[\"module\",314,176,\"$Mm\"],[\"register\",\"Mm\",\"$Mm\"],[\"module\",324,177,\"$Nm\"],[\"invoke\",336,\"$Mm\",\"load\",[\"i32:12\"]],[\"invoke\",337,\"$Nm\",\"Mm.load\",[\"i32:12\"]],[\"invoke\",338,\"$Nm\",\"load\",[\"i32:12\"]],[\"module\",340,178,\"$Om\"],[\"invoke\",349,\"$Mm\",\"load\",[\"i32:12\"]],[\"invoke\",350,\"$Nm\",\"Mm.load\",[\"i32:12\"]],[\"invoke\",351,\"$Nm\",\"load\",[\"i32:12\"]],[\"invoke\",352,\"$Om\",\"load\",[\"i32:12\"]],[\"module\",354,179],[\"instantiate\",360,180],[\"module\",367,181,\"$Pm\"],[\"invoke\",375,\"$Pm\",\"grow\",[\"i32:0\"]],[\"invoke\",376,\"$Pm\",\"grow\",[\"i32:2\"]],[\"invoke\",377,\"$Pm\",\"grow\",[\"i32:0\"]],[\"invoke\",378,\"$Pm\",\"grow\",[\"i32:1\"]],[\"invoke\",379,\"$Pm\",\"grow\",[\"i32:1\"]],[\"invoke\",380,\"$Pm\",\"grow\",[\"i32:0\"]],[\"invoke\",381,\"$Pm\",\"grow\",[\"i32:1\"]],[\"invoke\",382,\"$Pm\",\"grow\",[\"i32:0\"]],[\"instantiate\",385,182],[\"invoke\",393,\"$Mm\",\"load\",[\"i32:0\"]],[\"instantiate\",398,183],[\"invoke\",406,\"$Mm\",\"load\",[\"i32:0\"]],[\"invoke\",407,\"$Mm\",\"load\",[\"i32:327670\"]],[\"instantiate\",410,184],[\"invoke\",419,\"$Mm\",\"load\",[\"i32:0\"]],[\"module\",422,185,\"$Ms\"],[\"register\",\"Ms\",\"$Ms\"],[\"instantiate\",436,186],[\"invoke\",452,\"$Ms\",\"get memory[0]\",[]],[\"invoke\",453,\"$Ms\",\"get table[0]\",[]]]"; +export const script = "[[\"file\",\"table_copy.wast\"],[\"module\",6,0],[\"register\",\"a\"],[\"module\",15,1],[\"invoke\",45,0,\"test\",[]],[\"invoke\",50,0,\"check_t0\",[\"i32:4\"]],[\"invoke\",54,0,\"check_t0\",[\"i32:8\"]],[\"invoke\",58,0,\"check_t0\",[\"i32:12\"]],[\"invoke\",62,0,\"check_t0\",[\"i32:16\"]],[\"invoke\",66,0,\"check_t0\",[\"i32:20\"]],[\"invoke\",71,0,\"check_t0\",[\"i32:25\"]],[\"invoke\",75,0,\"check_t0\",[\"i32:29\"]],[\"invoke\",79,0,\"check_t1\",[\"i32:3\"]],[\"invoke\",83,0,\"check_t1\",[\"i32:7\"]],[\"invoke\",87,0,\"check_t1\",[\"i32:11\"]],[\"invoke\",92,0,\"check_t1\",[\"i32:16\"]],[\"invoke\",96,0,\"check_t1\",[\"i32:20\"]],[\"invoke\",100,0,\"check_t1\",[\"i32:24\"]],[\"invoke\",104,0,\"check_t1\",[\"i32:28\"]],[\"module\",107,2],[\"invoke\",139,0,\"check_t0\",[\"i32:1\"]],[\"invoke\",143,0,\"check_t0\",[\"i32:5\"]],[\"invoke\",148,0,\"check_t0\",[\"i32:10\"]],[\"invoke\",152,0,\"check_t0\",[\"i32:14\"]],[\"invoke\",156,0,\"check_t0\",[\"i32:18\"]],[\"invoke\",160,0,\"check_t0\",[\"i32:22\"]],[\"invoke\",164,0,\"check_t0\",[\"i32:26\"]],[\"invoke\",169,0,\"check_t1\",[\"i32:1\"]],[\"invoke\",173,0,\"check_t1\",[\"i32:5\"]],[\"invoke\",177,0,\"check_t1\",[\"i32:9\"]],[\"invoke\",181,0,\"check_t1\",[\"i32:13\"]],[\"invoke\",185,0,\"check_t1\",[\"i32:17\"]],[\"invoke\",190,0,\"check_t1\",[\"i32:22\"]],[\"invoke\",194,0,\"check_t1\",[\"i32:26\"]],[\"module\",199,3],[\"invoke\",229,0,\"test\",[]],[\"invoke\",233,0,\"check_t0\",[\"i32:3\"]],[\"invoke\",237,0,\"check_t0\",[\"i32:7\"]],[\"invoke\",241,0,\"check_t0\",[\"i32:11\"]],[\"invoke\",246,0,\"check_t0\",[\"i32:16\"]],[\"invoke\",250,0,\"check_t0\",[\"i32:20\"]],[\"invoke\",254,0,\"check_t0\",[\"i32:24\"]],[\"invoke\",258,0,\"check_t0\",[\"i32:28\"]],[\"invoke\",262,0,\"check_t1\",[\"i32:2\"]],[\"invoke\",267,0,\"check_t1\",[\"i32:7\"]],[\"invoke\",271,0,\"check_t1\",[\"i32:11\"]],[\"invoke\",275,0,\"check_t1\",[\"i32:15\"]],[\"invoke\",279,0,\"check_t1\",[\"i32:19\"]],[\"invoke\",283,0,\"check_t1\",[\"i32:23\"]],[\"invoke\",288,0,\"check_t1\",[\"i32:28\"]],[\"module\",291,4],[\"invoke\",323,0,\"check_t0\",[\"i32:1\"]],[\"invoke\",327,0,\"check_t0\",[\"i32:5\"]],[\"invoke\",331,0,\"check_t0\",[\"i32:9\"]],[\"invoke\",335,0,\"check_t0\",[\"i32:13\"]],[\"invoke\",339,0,\"check_t0\",[\"i32:17\"]],[\"invoke\",344,0,\"check_t0\",[\"i32:22\"]],[\"invoke\",348,0,\"check_t0\",[\"i32:26\"]],[\"invoke\",352,0,\"check_t1\",[\"i32:0\"]],[\"invoke\",356,0,\"check_t1\",[\"i32:4\"]],[\"invoke\",360,0,\"check_t1\",[\"i32:8\"]],[\"invoke\",365,0,\"check_t1\",[\"i32:13\"]],[\"invoke\",369,0,\"check_t1\",[\"i32:17\"]],[\"invoke\",373,0,\"check_t1\",[\"i32:21\"]],[\"invoke\",377,0,\"check_t1\",[\"i32:25\"]],[\"invoke\",381,0,\"check_t1\",[\"i32:29\"]],[\"module\",383,5],[\"invoke\",417,0,\"check_t0\",[\"i32:3\"]],[\"invoke\",421,0,\"check_t0\",[\"i32:7\"]],[\"invoke\",425,0,\"check_t0\",[\"i32:11\"]],[\"invoke\",429,0,\"check_t0\",[\"i32:15\"]],[\"invoke\",433,0,\"check_t0\",[\"i32:19\"]],[\"invoke\",437,0,\"check_t0\",[\"i32:23\"]],[\"invoke\",442,0,\"check_t0\",[\"i32:28\"]],[\"invoke\",446,0,\"check_t1\",[\"i32:2\"]],[\"invoke\",450,0,\"check_t1\",[\"i32:6\"]],[\"invoke\",454,0,\"check_t1\",[\"i32:10\"]],[\"invoke\",458,0,\"check_t1\",[\"i32:14\"]],[\"invoke\",463,0,\"check_t1\",[\"i32:19\"]],[\"invoke\",467,0,\"check_t1\",[\"i32:23\"]],[\"invoke\",471,0,\"check_t1\",[\"i32:27\"]],[\"module\",475,6],[\"invoke\",506,0,\"check_t0\",[\"i32:0\"]],[\"invoke\",510,0,\"check_t0\",[\"i32:4\"]],[\"invoke\",515,0,\"check_t0\",[\"i32:9\"]],[\"invoke\",519,0,\"check_t0\",[\"i32:13\"]],[\"invoke\",523,0,\"check_t0\",[\"i32:17\"]],[\"invoke\",527,0,\"check_t0\",[\"i32:21\"]],[\"invoke\",531,0,\"check_t0\",[\"i32:25\"]],[\"invoke\",535,0,\"check_t0\",[\"i32:29\"]],[\"invoke\",540,0,\"check_t1\",[\"i32:4\"]],[\"invoke\",544,0,\"check_t1\",[\"i32:8\"]],[\"invoke\",548,0,\"check_t1\",[\"i32:12\"]],[\"invoke\",552,0,\"check_t1\",[\"i32:16\"]],[\"invoke\",556,0,\"check_t1\",[\"i32:20\"]],[\"invoke\",561,0,\"check_t1\",[\"i32:25\"]],[\"invoke\",565,0,\"check_t1\",[\"i32:29\"]],[\"module\",567,7],[\"invoke\",600,0,\"check_t0\",[\"i32:2\"]],[\"invoke\",604,0,\"check_t0\",[\"i32:6\"]],[\"invoke\",608,0,\"check_t0\",[\"i32:10\"]],[\"invoke\",613,0,\"check_t0\",[\"i32:15\"]],[\"invoke\",617,0,\"check_t0\",[\"i32:19\"]],[\"invoke\",621,0,\"check_t0\",[\"i32:23\"]],[\"invoke\",625,0,\"check_t0\",[\"i32:27\"]],[\"invoke\",629,0,\"check_t1\",[\"i32:1\"]],[\"invoke\",633,0,\"check_t1\",[\"i32:5\"]],[\"invoke\",638,0,\"check_t1\",[\"i32:10\"]],[\"invoke\",642,0,\"check_t1\",[\"i32:14\"]],[\"invoke\",646,0,\"check_t1\",[\"i32:18\"]],[\"invoke\",650,0,\"check_t1\",[\"i32:22\"]],[\"invoke\",654,0,\"check_t1\",[\"i32:26\"]],[\"module\",659,8],[\"invoke\",690,0,\"check_t0\",[\"i32:0\"]],[\"invoke\",694,0,\"check_t0\",[\"i32:4\"]],[\"invoke\",698,0,\"check_t0\",[\"i32:8\"]],[\"invoke\",702,0,\"check_t0\",[\"i32:12\"]],[\"invoke\",706,0,\"check_t0\",[\"i32:16\"]],[\"invoke\",711,0,\"check_t0\",[\"i32:21\"]],[\"invoke\",715,0,\"check_t0\",[\"i32:25\"]],[\"invoke\",719,0,\"check_t0\",[\"i32:29\"]],[\"invoke\",723,0,\"check_t1\",[\"i32:3\"]],[\"invoke\",727,0,\"check_t1\",[\"i32:7\"]],[\"invoke\",731,0,\"check_t1\",[\"i32:11\"]],[\"invoke\",736,0,\"check_t1\",[\"i32:16\"]],[\"invoke\",740,0,\"check_t1\",[\"i32:20\"]],[\"invoke\",744,0,\"check_t1\",[\"i32:24\"]],[\"invoke\",748,0,\"check_t1\",[\"i32:28\"]],[\"module\",751,9],[\"invoke\",783,0,\"check_t0\",[\"i32:1\"]],[\"invoke\",788,0,\"check_t0\",[\"i32:6\"]],[\"invoke\",792,0,\"check_t0\",[\"i32:10\"]],[\"invoke\",796,0,\"check_t0\",[\"i32:14\"]],[\"invoke\",800,0,\"check_t0\",[\"i32:18\"]],[\"invoke\",804,0,\"check_t0\",[\"i32:22\"]],[\"invoke\",809,0,\"check_t0\",[\"i32:27\"]],[\"invoke\",813,0,\"check_t1\",[\"i32:1\"]],[\"invoke\",817,0,\"check_t1\",[\"i32:5\"]],[\"invoke\",821,0,\"check_t1\",[\"i32:9\"]],[\"invoke\",825,0,\"check_t1\",[\"i32:13\"]],[\"invoke\",829,0,\"check_t1\",[\"i32:17\"]],[\"invoke\",834,0,\"check_t1\",[\"i32:22\"]],[\"invoke\",838,0,\"check_t1\",[\"i32:26\"]],[\"module\",843,10],[\"invoke\",873,0,\"test\",[]],[\"invoke\",877,0,\"check_t0\",[\"i32:3\"]],[\"invoke\",881,0,\"check_t0\",[\"i32:7\"]],[\"invoke\",886,0,\"check_t0\",[\"i32:12\"]],[\"invoke\",890,0,\"check_t0\",[\"i32:16\"]],[\"invoke\",894,0,\"check_t0\",[\"i32:20\"]],[\"invoke\",898,0,\"check_t0\",[\"i32:24\"]],[\"invoke\",902,0,\"check_t0\",[\"i32:28\"]],[\"invoke\",907,0,\"check_t1\",[\"i32:3\"]],[\"invoke\",911,0,\"check_t1\",[\"i32:7\"]],[\"invoke\",915,0,\"check_t1\",[\"i32:11\"]],[\"invoke\",919,0,\"check_t1\",[\"i32:15\"]],[\"invoke\",923,0,\"check_t1\",[\"i32:19\"]],[\"invoke\",927,0,\"check_t1\",[\"i32:23\"]],[\"invoke\",932,0,\"check_t1\",[\"i32:28\"]],[\"module\",935,11],[\"invoke\",967,0,\"check_t0\",[\"i32:1\"]],[\"invoke\",971,0,\"check_t0\",[\"i32:5\"]],[\"invoke\",975,0,\"check_t0\",[\"i32:9\"]],[\"invoke\",979,0,\"check_t0\",[\"i32:13\"]],[\"invoke\",984,0,\"check_t0\",[\"i32:18\"]],[\"invoke\",988,0,\"check_t0\",[\"i32:22\"]],[\"invoke\",992,0,\"check_t0\",[\"i32:26\"]],[\"invoke\",996,0,\"check_t1\",[\"i32:0\"]],[\"invoke\",1000,0,\"check_t1\",[\"i32:4\"]],[\"invoke\",1005,0,\"check_t1\",[\"i32:9\"]],[\"invoke\",1009,0,\"check_t1\",[\"i32:13\"]],[\"invoke\",1013,0,\"check_t1\",[\"i32:17\"]],[\"invoke\",1017,0,\"check_t1\",[\"i32:21\"]],[\"invoke\",1021,0,\"check_t1\",[\"i32:25\"]],[\"invoke\",1025,0,\"check_t1\",[\"i32:29\"]],[\"module\",1027,12],[\"invoke\",1061,0,\"check_t0\",[\"i32:3\"]],[\"invoke\",1065,0,\"check_t0\",[\"i32:7\"]],[\"invoke\",1069,0,\"check_t0\",[\"i32:11\"]],[\"invoke\",1073,0,\"check_t0\",[\"i32:15\"]],[\"invoke\",1077,0,\"check_t0\",[\"i32:19\"]],[\"invoke\",1082,0,\"check_t0\",[\"i32:24\"]],[\"invoke\",1086,0,\"check_t0\",[\"i32:28\"]],[\"invoke\",1090,0,\"check_t1\",[\"i32:2\"]],[\"invoke\",1094,0,\"check_t1\",[\"i32:6\"]],[\"invoke\",1098,0,\"check_t1\",[\"i32:10\"]],[\"invoke\",1103,0,\"check_t1\",[\"i32:15\"]],[\"invoke\",1107,0,\"check_t1\",[\"i32:19\"]],[\"invoke\",1111,0,\"check_t1\",[\"i32:23\"]],[\"invoke\",1115,0,\"check_t1\",[\"i32:27\"]],[\"module\",1119,13],[\"invoke\",1150,0,\"check_t0\",[\"i32:0\"]],[\"invoke\",1154,0,\"check_t0\",[\"i32:4\"]],[\"invoke\",1159,0,\"check_t0\",[\"i32:9\"]],[\"invoke\",1163,0,\"check_t0\",[\"i32:13\"]],[\"invoke\",1167,0,\"check_t0\",[\"i32:17\"]],[\"invoke\",1171,0,\"check_t0\",[\"i32:21\"]],[\"invoke\",1175,0,\"check_t0\",[\"i32:25\"]],[\"invoke\",1180,0,\"check_t1\",[\"i32:0\"]],[\"invoke\",1184,0,\"check_t1\",[\"i32:4\"]],[\"invoke\",1188,0,\"check_t1\",[\"i32:8\"]],[\"invoke\",1192,0,\"check_t1\",[\"i32:12\"]],[\"invoke\",1196,0,\"check_t1\",[\"i32:16\"]],[\"invoke\",1201,0,\"check_t1\",[\"i32:21\"]],[\"invoke\",1205,0,\"check_t1\",[\"i32:25\"]],[\"invoke\",1209,0,\"check_t1\",[\"i32:29\"]],[\"module\",1211,14],[\"invoke\",1244,0,\"check_t0\",[\"i32:2\"]],[\"invoke\",1248,0,\"check_t0\",[\"i32:6\"]],[\"invoke\",1252,0,\"check_t0\",[\"i32:10\"]],[\"invoke\",1257,0,\"check_t0\",[\"i32:15\"]],[\"invoke\",1261,0,\"check_t0\",[\"i32:19\"]],[\"invoke\",1265,0,\"check_t0\",[\"i32:23\"]],[\"invoke\",1269,0,\"check_t0\",[\"i32:27\"]],[\"invoke\",1273,0,\"check_t1\",[\"i32:1\"]],[\"invoke\",1278,0,\"check_t1\",[\"i32:6\"]],[\"invoke\",1282,0,\"check_t1\",[\"i32:10\"]],[\"invoke\",1286,0,\"check_t1\",[\"i32:14\"]],[\"invoke\",1290,0,\"check_t1\",[\"i32:18\"]],[\"invoke\",1294,0,\"check_t1\",[\"i32:22\"]],[\"invoke\",1299,0,\"check_t1\",[\"i32:27\"]],[\"module\",1303,15],[\"invoke\",1334,0,\"check_t0\",[\"i32:0\"]],[\"invoke\",1338,0,\"check_t0\",[\"i32:4\"]],[\"invoke\",1342,0,\"check_t0\",[\"i32:8\"]],[\"invoke\",1346,0,\"check_t0\",[\"i32:12\"]],[\"invoke\",1350,0,\"check_t0\",[\"i32:16\"]],[\"invoke\",1355,0,\"check_t0\",[\"i32:21\"]],[\"invoke\",1359,0,\"check_t0\",[\"i32:25\"]],[\"invoke\",1363,0,\"check_t0\",[\"i32:29\"]],[\"invoke\",1367,0,\"check_t1\",[\"i32:3\"]],[\"invoke\",1371,0,\"check_t1\",[\"i32:7\"]],[\"invoke\",1376,0,\"check_t1\",[\"i32:12\"]],[\"invoke\",1380,0,\"check_t1\",[\"i32:16\"]],[\"invoke\",1384,0,\"check_t1\",[\"i32:20\"]],[\"invoke\",1388,0,\"check_t1\",[\"i32:24\"]],[\"invoke\",1392,0,\"check_t1\",[\"i32:28\"]],[\"module\",1395,16],[\"invoke\",1428,0,\"check_t0\",[\"i32:2\"]],[\"invoke\",1432,0,\"check_t0\",[\"i32:6\"]],[\"invoke\",1436,0,\"check_t0\",[\"i32:10\"]],[\"invoke\",1440,0,\"check_t0\",[\"i32:14\"]],[\"invoke\",1444,0,\"check_t0\",[\"i32:18\"]],[\"invoke\",1448,0,\"check_t0\",[\"i32:22\"]],[\"invoke\",1453,0,\"check_t0\",[\"i32:27\"]],[\"invoke\",1457,0,\"check_t1\",[\"i32:1\"]],[\"invoke\",1461,0,\"check_t1\",[\"i32:5\"]],[\"invoke\",1465,0,\"check_t1\",[\"i32:9\"]],[\"invoke\",1469,0,\"check_t1\",[\"i32:13\"]],[\"invoke\",1474,0,\"check_t1\",[\"i32:18\"]],[\"invoke\",1478,0,\"check_t1\",[\"i32:22\"]],[\"invoke\",1482,0,\"check_t1\",[\"i32:26\"]],[\"module\",1487,17],[\"invoke\",1517,0,\"test\",[]],[\"invoke\",1521,0,\"check_t0\",[\"i32:3\"]],[\"invoke\",1526,0,\"check_t0\",[\"i32:8\"]],[\"invoke\",1530,0,\"check_t0\",[\"i32:12\"]],[\"invoke\",1534,0,\"check_t0\",[\"i32:16\"]],[\"invoke\",1538,0,\"check_t0\",[\"i32:20\"]],[\"invoke\",1542,0,\"check_t0\",[\"i32:24\"]],[\"invoke\",1546,0,\"check_t0\",[\"i32:28\"]],[\"invoke\",1551,0,\"check_t1\",[\"i32:3\"]],[\"invoke\",1555,0,\"check_t1\",[\"i32:7\"]],[\"invoke\",1559,0,\"check_t1\",[\"i32:11\"]],[\"invoke\",1563,0,\"check_t1\",[\"i32:15\"]],[\"invoke\",1567,0,\"check_t1\",[\"i32:19\"]],[\"invoke\",1572,0,\"check_t1\",[\"i32:24\"]],[\"invoke\",1576,0,\"check_t1\",[\"i32:28\"]],[\"module\",1579,18],[\"invoke\",1611,0,\"check_t0\",[\"i32:1\"]],[\"invoke\",1615,0,\"check_t0\",[\"i32:5\"]],[\"invoke\",1619,0,\"check_t0\",[\"i32:9\"]],[\"invoke\",1624,0,\"check_t0\",[\"i32:14\"]],[\"invoke\",1628,0,\"check_t0\",[\"i32:18\"]],[\"invoke\",1632,0,\"check_t0\",[\"i32:22\"]],[\"invoke\",1636,0,\"check_t0\",[\"i32:26\"]],[\"invoke\",1640,0,\"check_t1\",[\"i32:0\"]],[\"invoke\",1644,0,\"check_t1\",[\"i32:4\"]],[\"invoke\",1649,0,\"check_t1\",[\"i32:9\"]],[\"invoke\",1653,0,\"check_t1\",[\"i32:13\"]],[\"invoke\",1657,0,\"check_t1\",[\"i32:17\"]],[\"invoke\",1661,0,\"check_t1\",[\"i32:21\"]],[\"invoke\",1665,0,\"check_t1\",[\"i32:25\"]],[\"module\",1671,19],[\"invoke\",1694,0,\"test\",[]],[\"module\",1696,20],[\"module\",1721,21],[\"module\",1746,22],[\"module\",1771,23],[\"invoke\",1794,0,\"test\",[]],[\"module\",1796,24],[\"module\",1821,25],[\"module\",1846,26],[\"module\",1871,27],[\"invoke\",1894,0,\"test\",[]],[\"module\",1896,28],[\"module\",1921,29],[\"module\",1946,30],[\"module\",1971,31],[\"invoke\",1994,0,\"test\",[]],[\"module\",1996,32],[\"module\",2021,33],[\"module\",2046,34],[\"module\",2071,35],[\"invoke\",2094,0,\"test\",[]],[\"module\",2096,36],[\"module\",2121,37],[\"module\",2146,38],[\"module\",2171,39],[\"module\",2196,40],[\"invoke\",2219,0,\"test\",[]],[\"module\",2221,41],[\"invoke\",2251,0,\"test\",[\"i32:2\"]],[\"invoke\",2255,0,\"test\",[\"i32:6\"]],[\"invoke\",2259,0,\"test\",[\"i32:10\"]],[\"invoke\",2263,0,\"test\",[\"i32:14\"]],[\"invoke\",2267,0,\"test\",[\"i32:18\"]],[\"invoke\",2272,0,\"test\",[\"i32:23\"]],[\"invoke\",2276,0,\"test\",[\"i32:27\"]],[\"invoke\",2280,0,\"test\",[\"i32:31\"]],[\"module\",2282,42],[\"invoke\",2312,0,\"test\",[\"i32:2\"]],[\"invoke\",2316,0,\"test\",[\"i32:6\"]],[\"invoke\",2321,0,\"test\",[\"i32:11\"]],[\"invoke\",2325,0,\"test\",[\"i32:15\"]],[\"invoke\",2329,0,\"test\",[\"i32:19\"]],[\"invoke\",2333,0,\"test\",[\"i32:23\"]],[\"invoke\",2337,0,\"test\",[\"i32:27\"]],[\"module\",2343,43],[\"invoke\",2369,0,\"run\",[\"i32:0\",\"i32:24\",\"i32:16\"]],[\"invoke\",2374,0,\"test\",[\"i32:3\"]],[\"invoke\",2378,0,\"test\",[\"i32:7\"]],[\"invoke\",2382,0,\"test\",[\"i32:11\"]],[\"invoke\",2386,0,\"test\",[\"i32:15\"]],[\"invoke\",2390,0,\"test\",[\"i32:19\"]],[\"invoke\",2395,0,\"test\",[\"i32:24\"]],[\"invoke\",2399,0,\"test\",[\"i32:28\"]],[\"module\",2404,44],[\"invoke\",2430,0,\"run\",[\"i32:0\",\"i32:23\",\"i32:15\"]],[\"invoke\",2435,0,\"test\",[\"i32:3\"]],[\"invoke\",2439,0,\"test\",[\"i32:7\"]],[\"invoke\",2444,0,\"test\",[\"i32:12\"]],[\"invoke\",2448,0,\"test\",[\"i32:16\"]],[\"invoke\",2452,0,\"test\",[\"i32:20\"]],[\"invoke\",2456,0,\"test\",[\"i32:24\"]],[\"invoke\",2460,0,\"test\",[\"i32:28\"]],[\"module\",2465,45],[\"invoke\",2493,0,\"test\",[\"i32:0\"]],[\"invoke\",2497,0,\"test\",[\"i32:4\"]],[\"invoke\",2501,0,\"test\",[\"i32:8\"]],[\"invoke\",2505,0,\"test\",[\"i32:12\"]],[\"invoke\",2509,0,\"test\",[\"i32:16\"]],[\"invoke\",2513,0,\"test\",[\"i32:20\"]],[\"invoke\",2518,0,\"test\",[\"i32:25\"]],[\"invoke\",2522,0,\"test\",[\"i32:29\"]],[\"module\",2526,43],[\"invoke\",2554,0,\"test\",[\"i32:0\"]],[\"invoke\",2558,0,\"test\",[\"i32:4\"]],[\"invoke\",2562,0,\"test\",[\"i32:8\"]],[\"invoke\",2567,0,\"test\",[\"i32:13\"]],[\"invoke\",2571,0,\"test\",[\"i32:17\"]],[\"invoke\",2575,0,\"test\",[\"i32:21\"]],[\"invoke\",2579,0,\"test\",[\"i32:25\"]],[\"invoke\",2583,0,\"test\",[\"i32:29\"]],[\"module\",2587,46],[\"invoke\",2616,0,\"test\",[\"i32:1\"]],[\"invoke\",2620,0,\"test\",[\"i32:5\"]],[\"invoke\",2624,0,\"test\",[\"i32:9\"]],[\"invoke\",2628,0,\"test\",[\"i32:13\"]],[\"invoke\",2632,0,\"test\",[\"i32:17\"]],[\"invoke\",2636,0,\"test\",[\"i32:21\"]],[\"invoke\",2641,0,\"test\",[\"i32:26\"]],[\"invoke\",2645,0,\"test\",[\"i32:30\"]],[\"module\",2648,43],[\"invoke\",2677,0,\"test\",[\"i32:1\"]],[\"invoke\",2681,0,\"test\",[\"i32:5\"]],[\"invoke\",2685,0,\"test\",[\"i32:9\"]],[\"invoke\",2690,0,\"test\",[\"i32:14\"]],[\"invoke\",2694,0,\"test\",[\"i32:18\"]],[\"invoke\",2698,0,\"test\",[\"i32:22\"]],[\"invoke\",2702,0,\"test\",[\"i32:26\"]],[\"invoke\",2706,0,\"test\",[\"i32:30\"]],[\"module\",2709,47],[\"invoke\",2739,0,\"test\",[\"i32:2\"]],[\"invoke\",2743,0,\"test\",[\"i32:6\"]],[\"invoke\",2747,0,\"test\",[\"i32:10\"]],[\"invoke\",2751,0,\"test\",[\"i32:14\"]],[\"invoke\",2755,0,\"test\",[\"i32:18\"]],[\"invoke\",2759,0,\"test\",[\"i32:22\"]],[\"invoke\",2764,0,\"test\",[\"i32:27\"]],[\"invoke\",2768,0,\"test\",[\"i32:31\"]],[\"module\",2770,48],[\"invoke\",2800,0,\"test\",[\"i32:2\"]],[\"invoke\",2804,0,\"test\",[\"i32:6\"]],[\"invoke\",2808,0,\"test\",[\"i32:10\"]],[\"invoke\",2813,0,\"test\",[\"i32:15\"]],[\"invoke\",2817,0,\"test\",[\"i32:19\"]],[\"invoke\",2821,0,\"test\",[\"i32:23\"]],[\"invoke\",2825,0,\"test\",[\"i32:27\"]],[\"invoke\",2829,0,\"test\",[\"i32:31\"]],[\"invoke\",2834,0,\"test\",[\"i32:36\"]],[\"invoke\",2838,0,\"test\",[\"i32:40\"]],[\"invoke\",2842,0,\"test\",[\"i32:44\"]],[\"invoke\",2846,0,\"test\",[\"i32:48\"]],[\"invoke\",2850,0,\"test\",[\"i32:52\"]],[\"invoke\",2854,0,\"test\",[\"i32:56\"]],[\"invoke\",2859,0,\"test\",[\"i32:61\"]],[\"invoke\",2863,0,\"test\",[\"i32:65\"]],[\"invoke\",2867,0,\"test\",[\"i32:69\"]],[\"invoke\",2871,0,\"test\",[\"i32:73\"]],[\"invoke\",2875,0,\"test\",[\"i32:77\"]],[\"invoke\",2880,0,\"test\",[\"i32:82\"]],[\"invoke\",2884,0,\"test\",[\"i32:86\"]],[\"invoke\",2888,0,\"test\",[\"i32:90\"]],[\"invoke\",2892,0,\"test\",[\"i32:94\"]],[\"invoke\",2896,0,\"test\",[\"i32:98\"]],[\"invoke\",2901,0,\"test\",[\"i32:103\"]],[\"invoke\",2905,0,\"test\",[\"i32:107\"]],[\"invoke\",2909,0,\"test\",[\"i32:111\"]],[\"invoke\",2913,0,\"test\",[\"i32:115\"]],[\"invoke\",2917,0,\"test\",[\"i32:119\"]],[\"invoke\",2921,0,\"test\",[\"i32:123\"]],[\"module\",2927,49],[\"invoke\",2953,0,\"run\",[\"i32:112\",\"i32:0\",\"i32:4294967264\"]],[\"invoke\",2958,0,\"test\",[\"i32:3\"]],[\"invoke\",2962,0,\"test\",[\"i32:7\"]],[\"invoke\",2966,0,\"test\",[\"i32:11\"]],[\"invoke\",2970,0,\"test\",[\"i32:15\"]],[\"invoke\",2975,0,\"test\",[\"i32:20\"]],[\"invoke\",2979,0,\"test\",[\"i32:24\"]],[\"invoke\",2983,0,\"test\",[\"i32:28\"]],[\"invoke\",2987,0,\"test\",[\"i32:32\"]],[\"invoke\",2991,0,\"test\",[\"i32:36\"]],[\"invoke\",2996,0,\"test\",[\"i32:41\"]],[\"invoke\",3000,0,\"test\",[\"i32:45\"]],[\"invoke\",3004,0,\"test\",[\"i32:49\"]],[\"invoke\",3008,0,\"test\",[\"i32:53\"]],[\"invoke\",3012,0,\"test\",[\"i32:57\"]],[\"invoke\",3016,0,\"test\",[\"i32:61\"]],[\"invoke\",3021,0,\"test\",[\"i32:66\"]],[\"invoke\",3025,0,\"test\",[\"i32:70\"]],[\"invoke\",3029,0,\"test\",[\"i32:74\"]],[\"invoke\",3033,0,\"test\",[\"i32:78\"]],[\"invoke\",3037,0,\"test\",[\"i32:82\"]],[\"invoke\",3042,0,\"test\",[\"i32:87\"]],[\"invoke\",3046,0,\"test\",[\"i32:91\"]],[\"invoke\",3050,0,\"test\",[\"i32:95\"]],[\"invoke\",3054,0,\"test\",[\"i32:99\"]],[\"invoke\",3058,0,\"test\",[\"i32:103\"]],[\"invoke\",3063,0,\"test\",[\"i32:108\"]],[\"invoke\",3067,0,\"test\",[\"i32:112\"]],[\"invoke\",3071,0,\"test\",[\"i32:116\"]],[\"invoke\",3075,0,\"test\",[\"i32:120\"]],[\"invoke\",3079,0,\"test\",[\"i32:124\"]],[\"file\",\"table_fill.wast\"],[\"module\",1,50],[\"invoke\",17,0,\"get\",[\"i32:1\"]],[\"invoke\",18,0,\"get\",[\"i32:2\"]],[\"invoke\",19,0,\"get\",[\"i32:3\"]],[\"invoke\",20,0,\"get\",[\"i32:4\"]],[\"invoke\",21,0,\"get\",[\"i32:5\"]],[\"invoke\",23,0,\"fill\",[\"i32:2\",\"externref:1\",\"i32:3\"]],[\"invoke\",24,0,\"get\",[\"i32:1\"]],[\"invoke\",25,0,\"get\",[\"i32:2\"]],[\"invoke\",26,0,\"get\",[\"i32:3\"]],[\"invoke\",27,0,\"get\",[\"i32:4\"]],[\"invoke\",28,0,\"get\",[\"i32:5\"]],[\"invoke\",30,0,\"fill\",[\"i32:4\",\"externref:2\",\"i32:2\"]],[\"invoke\",31,0,\"get\",[\"i32:3\"]],[\"invoke\",32,0,\"get\",[\"i32:4\"]],[\"invoke\",33,0,\"get\",[\"i32:5\"]],[\"invoke\",34,0,\"get\",[\"i32:6\"]],[\"invoke\",36,0,\"fill\",[\"i32:4\",\"externref:3\",\"i32:0\"]],[\"invoke\",37,0,\"get\",[\"i32:3\"]],[\"invoke\",38,0,\"get\",[\"i32:4\"]],[\"invoke\",39,0,\"get\",[\"i32:5\"]],[\"invoke\",41,0,\"fill\",[\"i32:8\",\"externref:4\",\"i32:2\"]],[\"invoke\",42,0,\"get\",[\"i32:7\"]],[\"invoke\",43,0,\"get\",[\"i32:8\"]],[\"invoke\",44,0,\"get\",[\"i32:9\"]],[\"invoke\",46,0,\"fill-abbrev\",[\"i32:9\",\"externref:null\",\"i32:1\"]],[\"invoke\",47,0,\"get\",[\"i32:8\"]],[\"invoke\",48,0,\"get\",[\"i32:9\"]],[\"invoke\",50,0,\"fill\",[\"i32:10\",\"externref:5\",\"i32:0\"]],[\"invoke\",51,0,\"get\",[\"i32:9\"]],[\"invoke\",54,0,\"fill\",[\"i32:8\",\"externref:6\",\"i32:3\"]],[\"invoke\",57,0,\"get\",[\"i32:7\"]],[\"invoke\",58,0,\"get\",[\"i32:8\"]],[\"invoke\",59,0,\"get\",[\"i32:9\"]],[\"invoke\",62,0,\"fill\",[\"i32:11\",\"externref:null\",\"i32:0\"]],[\"invoke\",67,0,\"fill\",[\"i32:11\",\"externref:null\",\"i32:10\"]],[\"file\",\"table_get.wast\"],[\"module\",1,51],[\"invoke\",24,0,\"init\",[\"externref:1\"]],[\"invoke\",26,0,\"get-externref\",[\"i32:0\"]],[\"invoke\",27,0,\"get-externref\",[\"i32:1\"]],[\"invoke\",29,0,\"get-funcref\",[\"i32:0\"]],[\"invoke\",30,0,\"is_null-funcref\",[\"i32:1\"]],[\"invoke\",31,0,\"is_null-funcref\",[\"i32:2\"]],[\"invoke\",33,0,\"get-externref\",[\"i32:2\"]],[\"invoke\",34,0,\"get-funcref\",[\"i32:3\"]],[\"invoke\",35,0,\"get-externref\",[\"i32:4294967295\"]],[\"invoke\",36,0,\"get-funcref\",[\"i32:4294967295\"]],[\"file\",\"table_grow.wast\"],[\"module\",1,52],[\"invoke\",16,0,\"size\",[]],[\"invoke\",17,0,\"set\",[\"i32:0\",\"externref:2\"]],[\"invoke\",18,0,\"get\",[\"i32:0\"]],[\"invoke\",20,0,\"grow\",[\"i32:1\",\"externref:null\"]],[\"invoke\",21,0,\"size\",[]],[\"invoke\",22,0,\"get\",[\"i32:0\"]],[\"invoke\",23,0,\"set\",[\"i32:0\",\"externref:2\"]],[\"invoke\",24,0,\"get\",[\"i32:0\"]],[\"invoke\",25,0,\"set\",[\"i32:1\",\"externref:2\"]],[\"invoke\",26,0,\"get\",[\"i32:1\"]],[\"invoke\",28,0,\"grow-abbrev\",[\"i32:4\",\"externref:3\"]],[\"invoke\",29,0,\"size\",[]],[\"invoke\",30,0,\"get\",[\"i32:0\"]],[\"invoke\",31,0,\"set\",[\"i32:0\",\"externref:2\"]],[\"invoke\",32,0,\"get\",[\"i32:0\"]],[\"invoke\",33,0,\"get\",[\"i32:1\"]],[\"invoke\",34,0,\"get\",[\"i32:4\"]],[\"invoke\",35,0,\"set\",[\"i32:4\",\"externref:4\"]],[\"invoke\",36,0,\"get\",[\"i32:4\"]],[\"invoke\",37,0,\"set\",[\"i32:5\",\"externref:2\"]],[\"invoke\",38,0,\"get\",[\"i32:5\"]],[\"module\",42,53],[\"invoke\",50,0,\"grow\",[]],[\"module\",53,54],[\"invoke\",60,0,\"grow\",[\"i32:0\"]],[\"invoke\",61,0,\"grow\",[\"i32:1\"]],[\"invoke\",62,0,\"grow\",[\"i32:0\"]],[\"invoke\",63,0,\"grow\",[\"i32:2\"]],[\"invoke\",64,0,\"grow\",[\"i32:800\"]],[\"module\",67,55],[\"invoke\",74,0,\"grow\",[\"i32:0\"]],[\"invoke\",75,0,\"grow\",[\"i32:1\"]],[\"invoke\",76,0,\"grow\",[\"i32:1\"]],[\"invoke\",77,0,\"grow\",[\"i32:2\"]],[\"invoke\",78,0,\"grow\",[\"i32:6\"]],[\"invoke\",79,0,\"grow\",[\"i32:0\"]],[\"invoke\",80,0,\"grow\",[\"i32:1\"]],[\"invoke\",81,0,\"grow\",[\"i32:65536\"]],[\"module\",84,56],[\"invoke\",106,0,\"check-table-null\",[\"i32:0\",\"i32:9\"]],[\"invoke\",107,0,\"grow\",[\"i32:10\"]],[\"invoke\",108,0,\"check-table-null\",[\"i32:0\",\"i32:19\"]],[\"module\",111,57,\"$Tgt\"],[\"register\",\"grown-table\",\"$Tgt\"],[\"invoke\",116,\"$Tgt\",\"grow\",[]],[\"module\",117,58,\"$Tgit1\"],[\"register\",\"grown-imported-table\",\"$Tgit1\"],[\"invoke\",123,\"$Tgit1\",\"grow\",[]],[\"module\",124,59,\"$Tgit2\"],[\"invoke\",129,\"$Tgit2\",\"size\",[]],[\"file\",\"table_init.wast\"],[\"module\",6,0],[\"register\",\"a\"],[\"module\",15,60],[\"invoke\",41,0,\"test\",[]],[\"invoke\",43,0,\"check\",[\"i32:1\"]],[\"invoke\",45,0,\"check\",[\"i32:3\"]],[\"invoke\",47,0,\"check\",[\"i32:5\"]],[\"invoke\",48,0,\"check\",[\"i32:6\"]],[\"invoke\",50,0,\"check\",[\"i32:8\"]],[\"invoke\",52,0,\"check\",[\"i32:10\"]],[\"invoke\",53,0,\"check\",[\"i32:11\"]],[\"invoke\",55,0,\"check\",[\"i32:13\"]],[\"invoke\",57,0,\"check\",[\"i32:15\"]],[\"invoke\",58,0,\"check\",[\"i32:16\"]],[\"invoke\",60,0,\"check\",[\"i32:18\"]],[\"invoke\",62,0,\"check\",[\"i32:20\"]],[\"invoke\",64,0,\"check\",[\"i32:22\"]],[\"invoke\",65,0,\"check\",[\"i32:23\"]],[\"invoke\",67,0,\"check\",[\"i32:25\"]],[\"invoke\",69,0,\"check\",[\"i32:27\"]],[\"invoke\",70,0,\"check\",[\"i32:28\"]],[\"module\",73,61],[\"invoke\",99,0,\"test\",[]],[\"invoke\",101,0,\"check\",[\"i32:1\"]],[\"invoke\",102,0,\"check\",[\"i32:2\"]],[\"invoke\",104,0,\"check\",[\"i32:4\"]],[\"invoke\",106,0,\"check\",[\"i32:6\"]],[\"invoke\",107,0,\"check\",[\"i32:7\"]],[\"invoke\",109,0,\"check\",[\"i32:9\"]],[\"invoke\",111,0,\"check\",[\"i32:11\"]],[\"invoke\",113,0,\"check\",[\"i32:13\"]],[\"invoke\",114,0,\"check\",[\"i32:14\"]],[\"invoke\",116,0,\"check\",[\"i32:16\"]],[\"invoke\",118,0,\"check\",[\"i32:18\"]],[\"invoke\",119,0,\"check\",[\"i32:19\"]],[\"invoke\",121,0,\"check\",[\"i32:21\"]],[\"invoke\",123,0,\"check\",[\"i32:23\"]],[\"invoke\",124,0,\"check\",[\"i32:24\"]],[\"invoke\",126,0,\"check\",[\"i32:26\"]],[\"invoke\",128,0,\"check\",[\"i32:28\"]],[\"invoke\",129,0,\"check\",[\"i32:29\"]],[\"module\",131,62],[\"invoke\",166,0,\"check\",[\"i32:0\"]],[\"invoke\",168,0,\"check\",[\"i32:2\"]],[\"invoke\",170,0,\"check\",[\"i32:4\"]],[\"invoke\",171,0,\"check\",[\"i32:5\"]],[\"invoke\",173,0,\"check\",[\"i32:7\"]],[\"invoke\",175,0,\"check\",[\"i32:9\"]],[\"invoke\",176,0,\"check\",[\"i32:10\"]],[\"invoke\",178,0,\"check\",[\"i32:12\"]],[\"invoke\",180,0,\"check\",[\"i32:14\"]],[\"invoke\",181,0,\"check\",[\"i32:15\"]],[\"invoke\",183,0,\"check\",[\"i32:17\"]],[\"invoke\",185,0,\"check\",[\"i32:19\"]],[\"invoke\",186,0,\"check\",[\"i32:20\"]],[\"invoke\",188,0,\"check\",[\"i32:22\"]],[\"invoke\",190,0,\"check\",[\"i32:24\"]],[\"invoke\",192,0,\"check\",[\"i32:26\"]],[\"invoke\",193,0,\"check\",[\"i32:27\"]],[\"invoke\",195,0,\"check\",[\"i32:29\"]],[\"module\",197,63],[\"invoke\",224,0,\"check\",[\"i32:0\"]],[\"invoke\",225,0,\"check\",[\"i32:1\"]],[\"invoke\",227,0,\"check\",[\"i32:3\"]],[\"invoke\",229,0,\"check\",[\"i32:5\"]],[\"invoke\",230,0,\"check\",[\"i32:6\"]],[\"invoke\",232,0,\"check\",[\"i32:8\"]],[\"invoke\",234,0,\"check\",[\"i32:10\"]],[\"invoke\",235,0,\"check\",[\"i32:11\"]],[\"invoke\",237,0,\"check\",[\"i32:13\"]],[\"invoke\",239,0,\"check\",[\"i32:15\"]],[\"invoke\",241,0,\"check\",[\"i32:17\"]],[\"invoke\",242,0,\"check\",[\"i32:18\"]],[\"invoke\",244,0,\"check\",[\"i32:20\"]],[\"invoke\",246,0,\"check\",[\"i32:22\"]],[\"invoke\",247,0,\"check\",[\"i32:23\"]],[\"invoke\",249,0,\"check\",[\"i32:25\"]],[\"invoke\",251,0,\"check\",[\"i32:27\"]],[\"invoke\",252,0,\"check\",[\"i32:28\"]],[\"module\",255,64],[\"invoke\",281,0,\"test\",[]],[\"invoke\",283,0,\"check\",[\"i32:1\"]],[\"invoke\",284,0,\"check\",[\"i32:2\"]],[\"invoke\",286,0,\"check\",[\"i32:4\"]],[\"invoke\",288,0,\"check\",[\"i32:6\"]],[\"invoke\",290,0,\"check\",[\"i32:8\"]],[\"invoke\",291,0,\"check\",[\"i32:9\"]],[\"invoke\",293,0,\"check\",[\"i32:11\"]],[\"invoke\",295,0,\"check\",[\"i32:13\"]],[\"invoke\",296,0,\"check\",[\"i32:14\"]],[\"invoke\",298,0,\"check\",[\"i32:16\"]],[\"invoke\",300,0,\"check\",[\"i32:18\"]],[\"invoke\",301,0,\"check\",[\"i32:19\"]],[\"invoke\",303,0,\"check\",[\"i32:21\"]],[\"invoke\",305,0,\"check\",[\"i32:23\"]],[\"invoke\",306,0,\"check\",[\"i32:24\"]],[\"invoke\",308,0,\"check\",[\"i32:26\"]],[\"invoke\",310,0,\"check\",[\"i32:28\"]],[\"module\",313,65],[\"invoke\",347,0,\"test\",[]],[\"invoke\",348,0,\"check\",[\"i32:0\"]],[\"invoke\",350,0,\"check\",[\"i32:2\"]],[\"invoke\",352,0,\"check\",[\"i32:4\"]],[\"invoke\",353,0,\"check\",[\"i32:5\"]],[\"invoke\",355,0,\"check\",[\"i32:7\"]],[\"invoke\",357,0,\"check\",[\"i32:9\"]],[\"invoke\",358,0,\"check\",[\"i32:10\"]],[\"invoke\",360,0,\"check\",[\"i32:12\"]],[\"invoke\",362,0,\"check\",[\"i32:14\"]],[\"invoke\",363,0,\"check\",[\"i32:15\"]],[\"invoke\",365,0,\"check\",[\"i32:17\"]],[\"invoke\",367,0,\"check\",[\"i32:19\"]],[\"invoke\",369,0,\"check\",[\"i32:21\"]],[\"invoke\",370,0,\"check\",[\"i32:22\"]],[\"invoke\",372,0,\"check\",[\"i32:24\"]],[\"invoke\",374,0,\"check\",[\"i32:26\"]],[\"invoke\",375,0,\"check\",[\"i32:27\"]],[\"invoke\",377,0,\"check\",[\"i32:29\"]],[\"module\",407,66],[\"module\",431,67],[\"invoke\",453,0,\"test\",[]],[\"module\",455,68],[\"invoke\",477,0,\"test\",[]],[\"module\",479,69],[\"module\",503,70],[\"invoke\",525,0,\"test\",[]],[\"module\",527,71],[\"module\",551,72],[\"invoke\",573,0,\"test\",[]],[\"module\",575,73],[\"invoke\",597,0,\"test\",[]],[\"module\",599,74],[\"module\",623,75],[\"invoke\",645,0,\"test\",[]],[\"module\",647,76],[\"module\",671,77],[\"invoke\",693,0,\"test\",[]],[\"module\",695,78],[\"module\",719,79],[\"invoke\",741,0,\"test\",[]],[\"module\",743,80],[\"invoke\",765,0,\"test\",[]],[\"module\",767,81],[\"module\",791,82],[\"invoke\",813,0,\"test\",[]],[\"module\",815,83],[\"module\",839,84],[\"invoke\",861,0,\"test\",[]],[\"module\",863,85],[\"invoke\",885,0,\"test\",[]],[\"module\",887,86],[\"module\",1478,87],[\"invoke\",1506,0,\"run\",[\"i32:24\",\"i32:16\"]],[\"invoke\",1508,0,\"test\",[\"i32:1\"]],[\"invoke\",1509,0,\"test\",[\"i32:2\"]],[\"invoke\",1511,0,\"test\",[\"i32:4\"]],[\"invoke\",1513,0,\"test\",[\"i32:6\"]],[\"invoke\",1514,0,\"test\",[\"i32:7\"]],[\"invoke\",1516,0,\"test\",[\"i32:9\"]],[\"invoke\",1518,0,\"test\",[\"i32:11\"]],[\"invoke\",1520,0,\"test\",[\"i32:13\"]],[\"invoke\",1521,0,\"test\",[\"i32:14\"]],[\"invoke\",1523,0,\"test\",[\"i32:16\"]],[\"invoke\",1525,0,\"test\",[\"i32:18\"]],[\"invoke\",1526,0,\"test\",[\"i32:19\"]],[\"invoke\",1528,0,\"test\",[\"i32:21\"]],[\"invoke\",1530,0,\"test\",[\"i32:23\"]],[\"invoke\",1531,0,\"test\",[\"i32:24\"]],[\"invoke\",1533,0,\"test\",[\"i32:26\"]],[\"invoke\",1535,0,\"test\",[\"i32:28\"]],[\"invoke\",1536,0,\"test\",[\"i32:29\"]],[\"invoke\",1538,0,\"test\",[\"i32:31\"]],[\"module\",1540,87],[\"invoke\",1569,0,\"test\",[\"i32:0\"]],[\"invoke\",1571,0,\"test\",[\"i32:2\"]],[\"invoke\",1572,0,\"test\",[\"i32:3\"]],[\"invoke\",1574,0,\"test\",[\"i32:5\"]],[\"invoke\",1576,0,\"test\",[\"i32:7\"]],[\"invoke\",1577,0,\"test\",[\"i32:8\"]],[\"invoke\",1579,0,\"test\",[\"i32:10\"]],[\"invoke\",1581,0,\"test\",[\"i32:12\"]],[\"invoke\",1582,0,\"test\",[\"i32:13\"]],[\"invoke\",1584,0,\"test\",[\"i32:15\"]],[\"invoke\",1586,0,\"test\",[\"i32:17\"]],[\"invoke\",1587,0,\"test\",[\"i32:18\"]],[\"invoke\",1589,0,\"test\",[\"i32:20\"]],[\"invoke\",1591,0,\"test\",[\"i32:22\"]],[\"invoke\",1593,0,\"test\",[\"i32:24\"]],[\"invoke\",1594,0,\"test\",[\"i32:25\"]],[\"invoke\",1596,0,\"test\",[\"i32:27\"]],[\"invoke\",1598,0,\"test\",[\"i32:29\"]],[\"invoke\",1599,0,\"test\",[\"i32:30\"]],[\"module\",1602,88],[\"invoke\",1630,0,\"run\",[\"i32:96\",\"i32:32\"]],[\"invoke\",1632,0,\"test\",[\"i32:1\"]],[\"invoke\",1633,0,\"test\",[\"i32:2\"]],[\"invoke\",1635,0,\"test\",[\"i32:4\"]],[\"invoke\",1637,0,\"test\",[\"i32:6\"]],[\"invoke\",1638,0,\"test\",[\"i32:7\"]],[\"invoke\",1640,0,\"test\",[\"i32:9\"]],[\"invoke\",1642,0,\"test\",[\"i32:11\"]],[\"invoke\",1644,0,\"test\",[\"i32:13\"]],[\"invoke\",1645,0,\"test\",[\"i32:14\"]],[\"invoke\",1647,0,\"test\",[\"i32:16\"]],[\"invoke\",1649,0,\"test\",[\"i32:18\"]],[\"invoke\",1650,0,\"test\",[\"i32:19\"]],[\"invoke\",1652,0,\"test\",[\"i32:21\"]],[\"invoke\",1654,0,\"test\",[\"i32:23\"]],[\"invoke\",1655,0,\"test\",[\"i32:24\"]],[\"invoke\",1657,0,\"test\",[\"i32:26\"]],[\"invoke\",1659,0,\"test\",[\"i32:28\"]],[\"invoke\",1660,0,\"test\",[\"i32:29\"]],[\"invoke\",1662,0,\"test\",[\"i32:31\"]],[\"invoke\",1664,0,\"test\",[\"i32:33\"]],[\"invoke\",1666,0,\"test\",[\"i32:35\"]],[\"invoke\",1667,0,\"test\",[\"i32:36\"]],[\"invoke\",1669,0,\"test\",[\"i32:38\"]],[\"invoke\",1671,0,\"test\",[\"i32:40\"]],[\"invoke\",1672,0,\"test\",[\"i32:41\"]],[\"invoke\",1674,0,\"test\",[\"i32:43\"]],[\"invoke\",1676,0,\"test\",[\"i32:45\"]],[\"invoke\",1677,0,\"test\",[\"i32:46\"]],[\"invoke\",1679,0,\"test\",[\"i32:48\"]],[\"invoke\",1681,0,\"test\",[\"i32:50\"]],[\"invoke\",1682,0,\"test\",[\"i32:51\"]],[\"invoke\",1684,0,\"test\",[\"i32:53\"]],[\"invoke\",1686,0,\"test\",[\"i32:55\"]],[\"invoke\",1688,0,\"test\",[\"i32:57\"]],[\"invoke\",1689,0,\"test\",[\"i32:58\"]],[\"invoke\",1691,0,\"test\",[\"i32:60\"]],[\"invoke\",1693,0,\"test\",[\"i32:62\"]],[\"invoke\",1694,0,\"test\",[\"i32:63\"]],[\"invoke\",1696,0,\"test\",[\"i32:65\"]],[\"invoke\",1698,0,\"test\",[\"i32:67\"]],[\"invoke\",1699,0,\"test\",[\"i32:68\"]],[\"invoke\",1701,0,\"test\",[\"i32:70\"]],[\"invoke\",1703,0,\"test\",[\"i32:72\"]],[\"invoke\",1704,0,\"test\",[\"i32:73\"]],[\"invoke\",1706,0,\"test\",[\"i32:75\"]],[\"invoke\",1708,0,\"test\",[\"i32:77\"]],[\"invoke\",1710,0,\"test\",[\"i32:79\"]],[\"invoke\",1711,0,\"test\",[\"i32:80\"]],[\"invoke\",1713,0,\"test\",[\"i32:82\"]],[\"invoke\",1715,0,\"test\",[\"i32:84\"]],[\"invoke\",1716,0,\"test\",[\"i32:85\"]],[\"invoke\",1718,0,\"test\",[\"i32:87\"]],[\"invoke\",1720,0,\"test\",[\"i32:89\"]],[\"invoke\",1721,0,\"test\",[\"i32:90\"]],[\"invoke\",1723,0,\"test\",[\"i32:92\"]],[\"invoke\",1725,0,\"test\",[\"i32:94\"]],[\"invoke\",1726,0,\"test\",[\"i32:95\"]],[\"invoke\",1728,0,\"test\",[\"i32:97\"]],[\"invoke\",1730,0,\"test\",[\"i32:99\"]],[\"invoke\",1732,0,\"test\",[\"i32:101\"]],[\"invoke\",1733,0,\"test\",[\"i32:102\"]],[\"invoke\",1735,0,\"test\",[\"i32:104\"]],[\"invoke\",1737,0,\"test\",[\"i32:106\"]],[\"invoke\",1738,0,\"test\",[\"i32:107\"]],[\"invoke\",1740,0,\"test\",[\"i32:109\"]],[\"invoke\",1742,0,\"test\",[\"i32:111\"]],[\"invoke\",1743,0,\"test\",[\"i32:112\"]],[\"invoke\",1745,0,\"test\",[\"i32:114\"]],[\"invoke\",1747,0,\"test\",[\"i32:116\"]],[\"invoke\",1748,0,\"test\",[\"i32:117\"]],[\"invoke\",1750,0,\"test\",[\"i32:119\"]],[\"invoke\",1752,0,\"test\",[\"i32:121\"]],[\"invoke\",1754,0,\"test\",[\"i32:123\"]],[\"invoke\",1755,0,\"test\",[\"i32:124\"]],[\"invoke\",1757,0,\"test\",[\"i32:126\"]],[\"invoke\",1759,0,\"test\",[\"i32:128\"]],[\"invoke\",1760,0,\"test\",[\"i32:129\"]],[\"invoke\",1762,0,\"test\",[\"i32:131\"]],[\"invoke\",1764,0,\"test\",[\"i32:133\"]],[\"invoke\",1765,0,\"test\",[\"i32:134\"]],[\"invoke\",1767,0,\"test\",[\"i32:136\"]],[\"invoke\",1769,0,\"test\",[\"i32:138\"]],[\"invoke\",1770,0,\"test\",[\"i32:139\"]],[\"invoke\",1772,0,\"test\",[\"i32:141\"]],[\"invoke\",1774,0,\"test\",[\"i32:143\"]],[\"invoke\",1776,0,\"test\",[\"i32:145\"]],[\"invoke\",1777,0,\"test\",[\"i32:146\"]],[\"invoke\",1779,0,\"test\",[\"i32:148\"]],[\"invoke\",1781,0,\"test\",[\"i32:150\"]],[\"invoke\",1782,0,\"test\",[\"i32:151\"]],[\"invoke\",1784,0,\"test\",[\"i32:153\"]],[\"invoke\",1786,0,\"test\",[\"i32:155\"]],[\"invoke\",1787,0,\"test\",[\"i32:156\"]],[\"invoke\",1789,0,\"test\",[\"i32:158\"]],[\"module\",1792,88],[\"invoke\",1820,0,\"run\",[\"i32:97\",\"i32:31\"]],[\"invoke\",1821,0,\"test\",[\"i32:0\"]],[\"invoke\",1823,0,\"test\",[\"i32:2\"]],[\"invoke\",1825,0,\"test\",[\"i32:4\"]],[\"invoke\",1827,0,\"test\",[\"i32:6\"]],[\"invoke\",1828,0,\"test\",[\"i32:7\"]],[\"invoke\",1830,0,\"test\",[\"i32:9\"]],[\"invoke\",1832,0,\"test\",[\"i32:11\"]],[\"invoke\",1833,0,\"test\",[\"i32:12\"]],[\"invoke\",1835,0,\"test\",[\"i32:14\"]],[\"invoke\",1837,0,\"test\",[\"i32:16\"]],[\"invoke\",1838,0,\"test\",[\"i32:17\"]],[\"invoke\",1840,0,\"test\",[\"i32:19\"]],[\"invoke\",1842,0,\"test\",[\"i32:21\"]],[\"invoke\",1843,0,\"test\",[\"i32:22\"]],[\"invoke\",1845,0,\"test\",[\"i32:24\"]],[\"invoke\",1847,0,\"test\",[\"i32:26\"]],[\"invoke\",1849,0,\"test\",[\"i32:28\"]],[\"invoke\",1850,0,\"test\",[\"i32:29\"]],[\"invoke\",1852,0,\"test\",[\"i32:31\"]],[\"invoke\",1854,0,\"test\",[\"i32:33\"]],[\"invoke\",1855,0,\"test\",[\"i32:34\"]],[\"invoke\",1857,0,\"test\",[\"i32:36\"]],[\"invoke\",1859,0,\"test\",[\"i32:38\"]],[\"invoke\",1860,0,\"test\",[\"i32:39\"]],[\"invoke\",1862,0,\"test\",[\"i32:41\"]],[\"invoke\",1864,0,\"test\",[\"i32:43\"]],[\"invoke\",1865,0,\"test\",[\"i32:44\"]],[\"invoke\",1867,0,\"test\",[\"i32:46\"]],[\"invoke\",1869,0,\"test\",[\"i32:48\"]],[\"invoke\",1871,0,\"test\",[\"i32:50\"]],[\"invoke\",1872,0,\"test\",[\"i32:51\"]],[\"invoke\",1874,0,\"test\",[\"i32:53\"]],[\"invoke\",1876,0,\"test\",[\"i32:55\"]],[\"invoke\",1877,0,\"test\",[\"i32:56\"]],[\"invoke\",1879,0,\"test\",[\"i32:58\"]],[\"invoke\",1881,0,\"test\",[\"i32:60\"]],[\"invoke\",1882,0,\"test\",[\"i32:61\"]],[\"invoke\",1884,0,\"test\",[\"i32:63\"]],[\"invoke\",1886,0,\"test\",[\"i32:65\"]],[\"invoke\",1887,0,\"test\",[\"i32:66\"]],[\"invoke\",1889,0,\"test\",[\"i32:68\"]],[\"invoke\",1891,0,\"test\",[\"i32:70\"]],[\"invoke\",1893,0,\"test\",[\"i32:72\"]],[\"invoke\",1894,0,\"test\",[\"i32:73\"]],[\"invoke\",1896,0,\"test\",[\"i32:75\"]],[\"invoke\",1898,0,\"test\",[\"i32:77\"]],[\"invoke\",1899,0,\"test\",[\"i32:78\"]],[\"invoke\",1901,0,\"test\",[\"i32:80\"]],[\"invoke\",1903,0,\"test\",[\"i32:82\"]],[\"invoke\",1904,0,\"test\",[\"i32:83\"]],[\"invoke\",1906,0,\"test\",[\"i32:85\"]],[\"invoke\",1908,0,\"test\",[\"i32:87\"]],[\"invoke\",1909,0,\"test\",[\"i32:88\"]],[\"invoke\",1911,0,\"test\",[\"i32:90\"]],[\"invoke\",1913,0,\"test\",[\"i32:92\"]],[\"invoke\",1915,0,\"test\",[\"i32:94\"]],[\"invoke\",1916,0,\"test\",[\"i32:95\"]],[\"invoke\",1918,0,\"test\",[\"i32:97\"]],[\"invoke\",1920,0,\"test\",[\"i32:99\"]],[\"invoke\",1921,0,\"test\",[\"i32:100\"]],[\"invoke\",1923,0,\"test\",[\"i32:102\"]],[\"invoke\",1925,0,\"test\",[\"i32:104\"]],[\"invoke\",1926,0,\"test\",[\"i32:105\"]],[\"invoke\",1928,0,\"test\",[\"i32:107\"]],[\"invoke\",1930,0,\"test\",[\"i32:109\"]],[\"invoke\",1931,0,\"test\",[\"i32:110\"]],[\"invoke\",1933,0,\"test\",[\"i32:112\"]],[\"invoke\",1935,0,\"test\",[\"i32:114\"]],[\"invoke\",1937,0,\"test\",[\"i32:116\"]],[\"invoke\",1938,0,\"test\",[\"i32:117\"]],[\"invoke\",1940,0,\"test\",[\"i32:119\"]],[\"invoke\",1942,0,\"test\",[\"i32:121\"]],[\"invoke\",1943,0,\"test\",[\"i32:122\"]],[\"invoke\",1945,0,\"test\",[\"i32:124\"]],[\"invoke\",1947,0,\"test\",[\"i32:126\"]],[\"invoke\",1948,0,\"test\",[\"i32:127\"]],[\"invoke\",1950,0,\"test\",[\"i32:129\"]],[\"invoke\",1952,0,\"test\",[\"i32:131\"]],[\"invoke\",1953,0,\"test\",[\"i32:132\"]],[\"invoke\",1955,0,\"test\",[\"i32:134\"]],[\"invoke\",1957,0,\"test\",[\"i32:136\"]],[\"invoke\",1959,0,\"test\",[\"i32:138\"]],[\"invoke\",1960,0,\"test\",[\"i32:139\"]],[\"invoke\",1962,0,\"test\",[\"i32:141\"]],[\"invoke\",1964,0,\"test\",[\"i32:143\"]],[\"invoke\",1965,0,\"test\",[\"i32:144\"]],[\"invoke\",1967,0,\"test\",[\"i32:146\"]],[\"invoke\",1969,0,\"test\",[\"i32:148\"]],[\"invoke\",1970,0,\"test\",[\"i32:149\"]],[\"invoke\",1972,0,\"test\",[\"i32:151\"]],[\"invoke\",1974,0,\"test\",[\"i32:153\"]],[\"invoke\",1975,0,\"test\",[\"i32:154\"]],[\"invoke\",1977,0,\"test\",[\"i32:156\"]],[\"invoke\",1979,0,\"test\",[\"i32:158\"]],[\"module\",1982,89],[\"invoke\",2010,0,\"run\",[\"i32:48\",\"i32:4294967280\"]],[\"invoke\",2011,0,\"test\",[\"i32:0\"]],[\"invoke\",2013,0,\"test\",[\"i32:2\"]],[\"invoke\",2015,0,\"test\",[\"i32:4\"]],[\"invoke\",2016,0,\"test\",[\"i32:5\"]],[\"invoke\",2018,0,\"test\",[\"i32:7\"]],[\"invoke\",2020,0,\"test\",[\"i32:9\"]],[\"invoke\",2021,0,\"test\",[\"i32:10\"]],[\"invoke\",2023,0,\"test\",[\"i32:12\"]],[\"invoke\",2025,0,\"test\",[\"i32:14\"]],[\"invoke\",2026,0,\"test\",[\"i32:15\"]],[\"invoke\",2028,0,\"test\",[\"i32:17\"]],[\"invoke\",2030,0,\"test\",[\"i32:19\"]],[\"invoke\",2032,0,\"test\",[\"i32:21\"]],[\"invoke\",2033,0,\"test\",[\"i32:22\"]],[\"invoke\",2035,0,\"test\",[\"i32:24\"]],[\"invoke\",2037,0,\"test\",[\"i32:26\"]],[\"invoke\",2038,0,\"test\",[\"i32:27\"]],[\"invoke\",2040,0,\"test\",[\"i32:29\"]],[\"invoke\",2042,0,\"test\",[\"i32:31\"]],[\"invoke\",2043,0,\"test\",[\"i32:32\"]],[\"invoke\",2045,0,\"test\",[\"i32:34\"]],[\"invoke\",2047,0,\"test\",[\"i32:36\"]],[\"invoke\",2048,0,\"test\",[\"i32:37\"]],[\"invoke\",2050,0,\"test\",[\"i32:39\"]],[\"invoke\",2052,0,\"test\",[\"i32:41\"]],[\"invoke\",2054,0,\"test\",[\"i32:43\"]],[\"invoke\",2055,0,\"test\",[\"i32:44\"]],[\"invoke\",2057,0,\"test\",[\"i32:46\"]],[\"invoke\",2059,0,\"test\",[\"i32:48\"]],[\"invoke\",2060,0,\"test\",[\"i32:49\"]],[\"invoke\",2062,0,\"test\",[\"i32:51\"]],[\"invoke\",2064,0,\"test\",[\"i32:53\"]],[\"invoke\",2065,0,\"test\",[\"i32:54\"]],[\"invoke\",2067,0,\"test\",[\"i32:56\"]],[\"invoke\",2069,0,\"test\",[\"i32:58\"]],[\"invoke\",2070,0,\"test\",[\"i32:59\"]],[\"invoke\",2072,0,\"test\",[\"i32:61\"]],[\"invoke\",2074,0,\"test\",[\"i32:63\"]],[\"module\",2076,90],[\"invoke\",2105,0,\"test\",[\"i32:0\"]],[\"invoke\",2106,0,\"test\",[\"i32:1\"]],[\"invoke\",2108,0,\"test\",[\"i32:3\"]],[\"invoke\",2110,0,\"test\",[\"i32:5\"]],[\"invoke\",2111,0,\"test\",[\"i32:6\"]],[\"invoke\",2113,0,\"test\",[\"i32:8\"]],[\"invoke\",2115,0,\"test\",[\"i32:10\"]],[\"invoke\",2116,0,\"test\",[\"i32:11\"]],[\"invoke\",2118,0,\"test\",[\"i32:13\"]],[\"invoke\",2120,0,\"test\",[\"i32:15\"]],[\"module\",2122,91],[\"file\",\"table_set.wast\"],[\"module\",1,92],[\"invoke\",29,0,\"get-externref\",[\"i32:0\"]],[\"invoke\",30,0,\"set-externref\",[\"i32:0\",\"externref:1\"]],[\"invoke\",31,0,\"get-externref\",[\"i32:0\"]],[\"invoke\",32,0,\"set-externref\",[\"i32:0\",\"externref:null\"]],[\"invoke\",33,0,\"get-externref\",[\"i32:0\"]],[\"invoke\",35,0,\"get-funcref\",[\"i32:0\"]],[\"invoke\",36,0,\"set-funcref-from\",[\"i32:0\",\"i32:1\"]],[\"invoke\",37,0,\"is_null-funcref\",[\"i32:0\"]],[\"invoke\",38,0,\"set-funcref\",[\"i32:0\",\"funcref:null\"]],[\"invoke\",39,0,\"get-funcref\",[\"i32:0\"]],[\"invoke\",41,0,\"set-externref\",[\"i32:2\",\"externref:null\"]],[\"invoke\",42,0,\"set-funcref\",[\"i32:3\",\"funcref:null\"]],[\"invoke\",43,0,\"set-externref\",[\"i32:4294967295\",\"externref:null\"]],[\"invoke\",44,0,\"set-funcref\",[\"i32:4294967295\",\"funcref:null\"]],[\"invoke\",46,0,\"set-externref\",[\"i32:2\",\"externref:0\"]],[\"invoke\",47,0,\"set-funcref-from\",[\"i32:3\",\"i32:1\"]],[\"invoke\",48,0,\"set-externref\",[\"i32:4294967295\",\"externref:0\"]],[\"invoke\",49,0,\"set-funcref-from\",[\"i32:4294967295\",\"i32:1\"]],[\"file\",\"table_size.wast\"],[\"module\",1,93],[\"invoke\",26,0,\"size-t0\",[]],[\"invoke\",27,0,\"grow-t0\",[\"i32:1\"]],[\"invoke\",28,0,\"size-t0\",[]],[\"invoke\",29,0,\"grow-t0\",[\"i32:4\"]],[\"invoke\",30,0,\"size-t0\",[]],[\"invoke\",31,0,\"grow-t0\",[\"i32:0\"]],[\"invoke\",32,0,\"size-t0\",[]],[\"invoke\",34,0,\"size-t1\",[]],[\"invoke\",35,0,\"grow-t1\",[\"i32:1\"]],[\"invoke\",36,0,\"size-t1\",[]],[\"invoke\",37,0,\"grow-t1\",[\"i32:4\"]],[\"invoke\",38,0,\"size-t1\",[]],[\"invoke\",39,0,\"grow-t1\",[\"i32:0\"]],[\"invoke\",40,0,\"size-t1\",[]],[\"invoke\",42,0,\"size-t2\",[]],[\"invoke\",43,0,\"grow-t2\",[\"i32:3\"]],[\"invoke\",44,0,\"size-t2\",[]],[\"invoke\",45,0,\"grow-t2\",[\"i32:1\"]],[\"invoke\",46,0,\"size-t2\",[]],[\"invoke\",47,0,\"grow-t2\",[\"i32:0\"]],[\"invoke\",48,0,\"size-t2\",[]],[\"invoke\",49,0,\"grow-t2\",[\"i32:4\"]],[\"invoke\",50,0,\"size-t2\",[]],[\"invoke\",51,0,\"grow-t2\",[\"i32:1\"]],[\"invoke\",52,0,\"size-t2\",[]],[\"invoke\",54,0,\"size-t3\",[]],[\"invoke\",55,0,\"grow-t3\",[\"i32:1\"]],[\"invoke\",56,0,\"size-t3\",[]],[\"invoke\",57,0,\"grow-t3\",[\"i32:3\"]],[\"invoke\",58,0,\"size-t3\",[]],[\"invoke\",59,0,\"grow-t3\",[\"i32:0\"]],[\"invoke\",60,0,\"size-t3\",[]],[\"invoke\",61,0,\"grow-t3\",[\"i32:2\"]],[\"invoke\",62,0,\"size-t3\",[]],[\"invoke\",63,0,\"grow-t3\",[\"i32:1\"]],[\"invoke\",64,0,\"size-t3\",[]],[\"file\",\"elem.wast\"],[\"module\",4,94],[\"module\",80,95],[\"module\",90,96],[\"module\",95,97],[\"module\",101,98],[\"module\",110,99],[\"module\",120,100],[\"module\",127,100],[\"module\",134,101],[\"invoke\",148,0,\"call-7\",[]],[\"invoke\",149,0,\"call-9\",[]],[\"module\",153,102],[\"invoke\",167,0,\"call-7\",[]],[\"invoke\",168,0,\"call-9\",[]],[\"module\",182,103],[\"module\",187,104],[\"module\",193,105],[\"module\",197,106],[\"module\",202,107],[\"module\",207,108],[\"module\",212,109],[\"module\",218,110],[\"module\",224,111],[\"module\",230,112],[\"instantiate\",239,113],[\"instantiate\",248,114],[\"instantiate\",257,115],[\"instantiate\",266,116],[\"instantiate\",273,117],[\"instantiate\",281,118],[\"instantiate\",290,119],[\"instantiate\",298,118],[\"instantiate\",307,120],[\"instantiate\",315,121],[\"instantiate\",324,122],[\"instantiate\",332,123],[\"module\",342,124],[\"invoke\",350,0,\"init\",[]],[\"module\",352,125],[\"invoke\",360,0,\"init\",[]],[\"module\",537,126],[\"invoke\",548,0,\"call-overwritten\",[]],[\"module\",550,127],[\"invoke\",561,0,\"call-overwritten-element\",[]],[\"module\",565,128,\"$module1\"],[\"register\",\"module1\",\"$module1\"],[\"invoke\",585,\"$module1\",\"call-7\",[]],[\"invoke\",586,\"$module1\",\"call-8\",[]],[\"invoke\",587,\"$module1\",\"call-9\",[]],[\"module\",589,129,\"$module2\"],[\"invoke\",598,\"$module1\",\"call-7\",[]],[\"invoke\",599,\"$module1\",\"call-8\",[]],[\"invoke\",600,\"$module1\",\"call-9\",[]],[\"module\",602,130,\"$module3\"],[\"invoke\",611,\"$module1\",\"call-7\",[]],[\"invoke\",612,\"$module1\",\"call-8\",[]],[\"invoke\",613,\"$module1\",\"call-9\",[]],[\"module\",646,131,\"$m\"],[\"register\",\"exporter\",\"$m\"],[\"invoke\",655,\"$m\",\"get\",[\"i32:0\"]],[\"invoke\",656,\"$m\",\"get\",[\"i32:1\"]],[\"invoke\",658,\"$m\",\"set\",[\"i32:0\",\"externref:42\"]],[\"invoke\",659,\"$m\",\"set\",[\"i32:1\",\"externref:137\"]],[\"invoke\",661,\"$m\",\"get\",[\"i32:0\"]],[\"invoke\",662,\"$m\",\"get\",[\"i32:1\"]],[\"module\",664,132],[\"invoke\",668,\"$m\",\"get\",[\"i32:0\"]],[\"invoke\",669,\"$m\",\"get\",[\"i32:1\"]],[\"module\",673,133,\"$module4\"],[\"register\",\"module4\",\"$module4\"],[\"module\",682,134],[\"invoke\",692,0,\"call_imported_elem\",[]],[\"file\",\"ref_func.wast\"],[\"module\",1,135],[\"register\",\"M\"],[\"module\",6,136],[\"invoke\",56,0,\"is_null-f\",[]],[\"invoke\",57,0,\"is_null-g\",[]],[\"invoke\",58,0,\"is_null-v\",[]],[\"invoke\",60,0,\"call-f\",[\"i32:4\"]],[\"invoke\",61,0,\"call-g\",[\"i32:4\"]],[\"invoke\",62,0,\"call-v\",[\"i32:4\"]],[\"invoke\",63,0,\"set-g\",[]],[\"invoke\",64,0,\"call-v\",[\"i32:4\"]],[\"invoke\",65,0,\"set-f\",[]],[\"invoke\",66,0,\"call-v\",[\"i32:4\"]],[\"module\",80,137],[\"file\",\"ref_is_null.wast\"],[\"module\",1,138],[\"invoke\",30,0,\"funcref\",[\"funcref:null\"]],[\"invoke\",31,0,\"externref\",[\"externref:null\"]],[\"invoke\",33,0,\"externref\",[\"externref:1\"]],[\"invoke\",35,0,\"init\",[\"externref:0\"]],[\"invoke\",37,0,\"funcref-elem\",[\"i32:0\"]],[\"invoke\",38,0,\"externref-elem\",[\"i32:0\"]],[\"invoke\",40,0,\"funcref-elem\",[\"i32:1\"]],[\"invoke\",41,0,\"externref-elem\",[\"i32:1\"]],[\"invoke\",43,0,\"deinit\",[]],[\"invoke\",45,0,\"funcref-elem\",[\"i32:0\"]],[\"invoke\",46,0,\"externref-elem\",[\"i32:0\"]],[\"invoke\",48,0,\"funcref-elem\",[\"i32:1\"]],[\"invoke\",49,0,\"externref-elem\",[\"i32:1\"]],[\"file\",\"ref_null.wast\"],[\"module\",1,139],[\"invoke\",9,0,\"externref\",[]],[\"invoke\",10,0,\"funcref\",[]],[\"file\",\"exports.wast\"],[\"module\",3,140],[\"module\",4,141],[\"module\",5,142],[\"module\",7,140],[\"module\",8,143],[\"module\",9,144],[\"module\",10,140],[\"module\",11,140],[\"module\",12,140],[\"module\",13,140],[\"module\",14,140],[\"module\",16,145,\"$Func\"],[\"invoke\",22,0,\"e\",[\"i32:42\"]],[\"invoke\",23,\"$Func\",\"e\",[\"i32:42\"]],[\"module\",24,146],[\"module\",25,146,\"$Other1\"],[\"invoke\",26,\"$Func\",\"e\",[\"i32:42\"]],[\"module\",28,147],[\"invoke\",34,0,\"a\",[]],[\"invoke\",35,0,\"b\",[]],[\"invoke\",36,0,\"c\",[]],[\"module\",74,148],[\"module\",75,149],[\"module\",76,150],[\"module\",78,148],[\"module\",79,148],[\"module\",80,148],[\"module\",81,148],[\"module\",82,148],[\"module\",83,148],[\"module\",85,151,\"$Global\"],[\"get\",89,0,\"e\",[]],[\"get\",90,\"$Global\",\"e\",[]],[\"module\",91,146],[\"module\",92,146,\"$Other2\"],[\"get\",93,\"$Global\",\"e\",[]],[\"module\",131,152],[\"module\",132,153],[\"module\",133,154],[\"module\",135,152],[\"module\",136,155],[\"module\",137,152],[\"module\",138,155],[\"module\",139,152],[\"module\",140,155],[\"module\",141,152],[\"module\",142,155],[\"module\",143,152],[\"module\",144,155],[\"module\",145,152],[\"module\",146,155],[\"module\",186,156],[\"module\",187,157],[\"module\",191,156],[\"module\",192,158],[\"module\",193,156],[\"module\",194,158],[\"module\",195,156],[\"module\",196,158],[\"module\",197,156],[\"module\",198,158],[\"module\",199,156],[\"module\",200,158],[\"module\",201,156],[\"module\",202,158],[\"file\",\"linking.wast\"],[\"module\",3,159,\"$Mf\"],[\"register\",\"Mf\",\"$Mf\"],[\"module\",9,160,\"$Nf\"],[\"invoke\",17,\"$Mf\",\"call\",[]],[\"invoke\",18,\"$Nf\",\"Mf.call\",[]],[\"invoke\",19,\"$Nf\",\"call\",[]],[\"invoke\",20,\"$Nf\",\"call Mf.call\",[]],[\"module\",22,161],[\"register\",\"reexport_f\"],[\"instantiate\",28,162],[\"instantiate\",32,163],[\"module\",39,164,\"$Mg\"],[\"register\",\"Mg\",\"$Mg\"],[\"module\",50,165,\"$Ng\"],[\"get\",67,\"$Mg\",\"glob\",[]],[\"get\",68,\"$Ng\",\"Mg.glob\",[]],[\"get\",69,\"$Ng\",\"glob\",[]],[\"invoke\",70,\"$Mg\",\"get\",[]],[\"invoke\",71,\"$Ng\",\"Mg.get\",[]],[\"invoke\",72,\"$Ng\",\"get\",[]],[\"get\",74,\"$Mg\",\"mut_glob\",[]],[\"get\",75,\"$Ng\",\"Mg.mut_glob\",[]],[\"invoke\",76,\"$Mg\",\"get_mut\",[]],[\"invoke\",77,\"$Ng\",\"Mg.get_mut\",[]],[\"invoke\",79,\"$Mg\",\"set_mut\",[\"i32:241\"]],[\"get\",80,\"$Mg\",\"mut_glob\",[]],[\"get\",81,\"$Ng\",\"Mg.mut_glob\",[]],[\"invoke\",82,\"$Mg\",\"get_mut\",[]],[\"invoke\",83,\"$Ng\",\"Mg.get_mut\",[]],[\"instantiate\",87,166],[\"instantiate\",91,167],[\"module\",96,168,\"$Mref_ex\"],[\"register\",\"Mref_ex\",\"$Mref_ex\"],[\"module\",104,169,\"$Mref_im\"],[\"instantiate\",113,170],[\"instantiate\",117,171],[\"instantiate\",123,172],[\"instantiate\",127,173],[\"module\",134,174,\"$Mt\"],[\"register\",\"Mt\",\"$Mt\"],[\"module\",149,175,\"$Nt\"],[\"invoke\",168,\"$Mt\",\"call\",[\"i32:2\"]],[\"invoke\",169,\"$Nt\",\"Mt.call\",[\"i32:2\"]],[\"invoke\",170,\"$Nt\",\"call\",[\"i32:2\"]],[\"invoke\",171,\"$Nt\",\"call Mt.call\",[\"i32:2\"]],[\"invoke\",173,\"$Mt\",\"call\",[\"i32:1\"]],[\"invoke\",174,\"$Nt\",\"Mt.call\",[\"i32:1\"]],[\"invoke\",175,\"$Nt\",\"call\",[\"i32:1\"]],[\"invoke\",176,\"$Nt\",\"call Mt.call\",[\"i32:1\"]],[\"invoke\",178,\"$Mt\",\"call\",[\"i32:0\"]],[\"invoke\",179,\"$Nt\",\"Mt.call\",[\"i32:0\"]],[\"invoke\",180,\"$Nt\",\"call\",[\"i32:0\"]],[\"invoke\",181,\"$Nt\",\"call Mt.call\",[\"i32:0\"]],[\"invoke\",183,\"$Mt\",\"call\",[\"i32:20\"]],[\"invoke\",184,\"$Nt\",\"Mt.call\",[\"i32:20\"]],[\"invoke\",185,\"$Nt\",\"call\",[\"i32:7\"]],[\"invoke\",186,\"$Nt\",\"call Mt.call\",[\"i32:20\"]],[\"invoke\",188,\"$Nt\",\"call\",[\"i32:3\"]],[\"invoke\",189,\"$Nt\",\"call\",[\"i32:4\"]],[\"module\",191,176,\"$Ot\"],[\"invoke\",204,\"$Mt\",\"call\",[\"i32:3\"]],[\"invoke\",205,\"$Nt\",\"Mt.call\",[\"i32:3\"]],[\"invoke\",206,\"$Nt\",\"call Mt.call\",[\"i32:3\"]],[\"invoke\",207,\"$Ot\",\"call\",[\"i32:3\"]],[\"invoke\",209,\"$Mt\",\"call\",[\"i32:2\"]],[\"invoke\",210,\"$Nt\",\"Mt.call\",[\"i32:2\"]],[\"invoke\",211,\"$Nt\",\"call\",[\"i32:2\"]],[\"invoke\",212,\"$Nt\",\"call Mt.call\",[\"i32:2\"]],[\"invoke\",213,\"$Ot\",\"call\",[\"i32:2\"]],[\"invoke\",215,\"$Mt\",\"call\",[\"i32:1\"]],[\"invoke\",216,\"$Nt\",\"Mt.call\",[\"i32:1\"]],[\"invoke\",217,\"$Nt\",\"call\",[\"i32:1\"]],[\"invoke\",218,\"$Nt\",\"call Mt.call\",[\"i32:1\"]],[\"invoke\",219,\"$Ot\",\"call\",[\"i32:1\"]],[\"invoke\",221,\"$Mt\",\"call\",[\"i32:0\"]],[\"invoke\",222,\"$Nt\",\"Mt.call\",[\"i32:0\"]],[\"invoke\",223,\"$Nt\",\"call\",[\"i32:0\"]],[\"invoke\",224,\"$Nt\",\"call Mt.call\",[\"i32:0\"]],[\"invoke\",225,\"$Ot\",\"call\",[\"i32:0\"]],[\"invoke\",227,\"$Ot\",\"call\",[\"i32:20\"]],[\"module\",229,177],[\"module\",235,178,\"$G1\"],[\"register\",\"G1\",\"$G1\"],[\"module\",237,179,\"$G2\"],[\"get\",241,\"$G2\",\"g\",[]],[\"instantiate\",244,180],[\"instantiate\",253,181],[\"invoke\",262,\"$Mt\",\"call\",[\"i32:7\"]],[\"instantiate\",267,182],[\"invoke\",275,\"$Mt\",\"call\",[\"i32:7\"]],[\"invoke\",276,\"$Mt\",\"call\",[\"i32:8\"]],[\"instantiate\",279,183],[\"invoke\",288,\"$Mt\",\"call\",[\"i32:7\"]],[\"module\",291,184,\"$Mtable_ex\"],[\"register\",\"Mtable_ex\",\"$Mtable_ex\"],[\"module\",297,185],[\"instantiate\",303,186],[\"instantiate\",307,187],[\"module\",314,188,\"$Mm\"],[\"register\",\"Mm\",\"$Mm\"],[\"module\",324,189,\"$Nm\"],[\"invoke\",336,\"$Mm\",\"load\",[\"i32:12\"]],[\"invoke\",337,\"$Nm\",\"Mm.load\",[\"i32:12\"]],[\"invoke\",338,\"$Nm\",\"load\",[\"i32:12\"]],[\"module\",340,190,\"$Om\"],[\"invoke\",349,\"$Mm\",\"load\",[\"i32:12\"]],[\"invoke\",350,\"$Nm\",\"Mm.load\",[\"i32:12\"]],[\"invoke\",351,\"$Nm\",\"load\",[\"i32:12\"]],[\"invoke\",352,\"$Om\",\"load\",[\"i32:12\"]],[\"module\",354,191],[\"instantiate\",360,192],[\"module\",367,193,\"$Pm\"],[\"invoke\",375,\"$Pm\",\"grow\",[\"i32:0\"]],[\"invoke\",376,\"$Pm\",\"grow\",[\"i32:2\"]],[\"invoke\",377,\"$Pm\",\"grow\",[\"i32:0\"]],[\"invoke\",378,\"$Pm\",\"grow\",[\"i32:1\"]],[\"invoke\",379,\"$Pm\",\"grow\",[\"i32:1\"]],[\"invoke\",380,\"$Pm\",\"grow\",[\"i32:0\"]],[\"invoke\",381,\"$Pm\",\"grow\",[\"i32:1\"]],[\"invoke\",382,\"$Pm\",\"grow\",[\"i32:0\"]],[\"instantiate\",385,194],[\"invoke\",393,\"$Mm\",\"load\",[\"i32:0\"]],[\"instantiate\",398,195],[\"invoke\",406,\"$Mm\",\"load\",[\"i32:0\"]],[\"invoke\",407,\"$Mm\",\"load\",[\"i32:327670\"]],[\"instantiate\",410,196],[\"invoke\",419,\"$Mm\",\"load\",[\"i32:0\"]],[\"module\",422,197,\"$Ms\"],[\"register\",\"Ms\",\"$Ms\"],[\"instantiate\",436,198],[\"invoke\",452,\"$Ms\",\"get memory[0]\",[]],[\"invoke\",453,\"$Ms\",\"get table[0]\",[]]]"; diff --git a/tests/corpus/webassembly-wpt-jsapi.mjs b/tests/corpus/webassembly-wpt-jsapi.mjs new file mode 100644 index 000000000..419e655ab --- /dev/null +++ b/tests/corpus/webassembly-wpt-jsapi.mjs @@ -0,0 +1,1117 @@ +// A port of web-platform-tests wasm/jsapi tests for the parts W5 adds: +// global/constructor, global/value-get-set, global/valueOf, +// memory/constructor, memory/grow, table/constructor, table/get-set, +// table/grow, module/imports, module/exports and module/customSections +// (WPT, 3-Clause BSD). Each test keeps its WPT name and body, with these +// changes: modules that WasmModuleBuilder builds at run time are inline +// base64 literals (built by the same builder), a minimal testharness prints +// one line per test, and the assertions the port cannot express are left +// out: the classes as values (prototypes, function name and length, +// property descriptors, branding through Function.prototype.call), +// Symbol arguments, Object.isFrozen and Object.isExtensible of +// ArrayBuffers (general scriptc gaps), and the old buffer's detachment +// after Memory.grow (a documented difference). assert_Global's parameter carries a JSDoc type: +// scriptc answers a Global's valueOf() only on receivers typed as one. +const modules = { + empty: "AGFzbQEAAAA=", + imports: "AGFzbQEAAAABBAFgAAACRAQGbW9kdWxlAmZuAAAGbW9kdWxlBmdsb2JhbAN/AAZtb2R1bGUGbWVtb3J5AgEAgAEGbW9kdWxlBXRhYmxlAXABAIAB", + importsEmptyModule: "AGFzbQEAAAABBAFgAAACLAQAAmZuAAAABmdsb2JhbAN/AAAGbWVtb3J5AgEAgAEABXRhYmxlAXABAIAB", + importsEmptyNames: "AGFzbQEAAAABBAFgAAACHQQBYQAAAAFiAAN/AAFjAAIBAIABAWQAAXABAIAB", + importsEmptyBoth: "AGFzbQEAAAABBAFgAAACGQQAAAAAAAADfwAAAAIBAIABAAABcAEAgAE=", + exports: "AGFzbQEAAAABBwJgAABgAAADAwIAAQQEAXAAAQUFAQEAgAIGEgJ/AUEHC3wBRDMzMzMzM/M/CwcwBgJmbgAAA2ZuMgABBXRhYmxlAQAGZ2xvYmFsAwAHZ2xvYmFsMgMBBm1lbW9yeQIACgcCAgALAgALABEEbmFtZQEKAgACZm4BA2ZuMg==", + exportsEmptyFunction: "AGFzbQEAAAABBAFgAAADAgEABwQBAAAACgQBAgALAAoEbmFtZQEDAQAA", + exportsEmptyTable: "AGFzbQEAAAAEBAFwAAEHBAEAAQA=", + exportsEmptyGlobal: "AGFzbQEAAAAGBgF/AUEHCwcEAQADAA==", + custom: "AGFzbQEAAAAAEARuYW1lV2ViQXNzZW1ibHkACgRuYW1lSlNBUEkADwNmb29XZWJBc3NlbWJseQ==", + customSurrogates: "AGFzbQEAAAAAFgp5ZWXwkJC3ZWV5V2ViQXNzZW1ibHk=", + customReplacement: "AGFzbQEAAAAAEwduYe+/vW1lV2ViQXNzZW1ibHk=", + customCopy: "AGFzbQEAAAAACARjb3B5ESIz", + fn: "AGFzbQEAAAABBAFgAAADAgEABwYBAmZuAAAKBAECAAsADARuYW1lAQUBAAJmbg==", + functions: "AGFzbQEAAAABCAJgAXwAYAAAAwMCAAEHDAICZm4AAANmbjIAAQoHAgIACwIACwARBG5hbWUBCgIAAmZuAQNmbjI=", +}; +const binary = (name) => Buffer.from(modules[name], "base64"); + +// ── a minimal testharness ───────────────────────────────────────────────── +class AssertionError extends Error {} +function fail(message) { + throw new AssertionError(message); +} +function same(a, b) { + return a === b ? a !== 0 || 1 / a === 1 / b : a !== a && b !== b; +} +function format_value(v) { + if (typeof v === "string") return JSON.stringify(v); + if (typeof v === "bigint") return `${v}n`; + if (typeof v === "number") return same(v, -0) ? "-0" : String(v); + if (typeof v === "boolean") return String(v); + if (v === null) return "null"; + if (v === undefined) return "undefined"; + return typeof v; +} +function assert_equals(actual, expected, description = "") { + if (!same(actual, expected)) fail(`${description} expected ${format_value(expected)} but got ${format_value(actual)}`); +} +function assert_not_equals(actual, expected, description = "") { + if (same(actual, expected)) fail(`${description} got disallowed value ${format_value(actual)}`); +} +function assert_true(actual, description = "") { + if (actual !== true) fail(`${description} expected true got ${format_value(actual)}`); +} +function assert_false(actual, description = "") { + if (actual !== false) fail(`${description} expected false got ${format_value(actual)}`); +} +function assert_array_equals(actual, expected, description = "") { + if (actual.length !== expected.length) fail(`${description} lengths differ, expected ${expected.length} got ${actual.length}`); + for (let i = 0; i < expected.length; i++) { + if (!same(actual[i], expected[i])) fail(`${description} property ${i}, expected ${format_value(expected[i])} but got ${format_value(actual[i])}`); + } +} +function assert_throws_js(name, func, description = "") { + try { + func(); + } catch (e) { + if (e instanceof AssertionError) throw e; + if (!(e instanceof Error) || e.name !== name) fail(`${description} threw ${e instanceof Error ? e.name : format_value(e)}, expected ${name}`); + return; + } + fail(`${description} did not throw`); +} +let passed = 0; +let failed = 0; +function test(func, name) { + try { + func(); + passed++; + console.log(`PASS ${name}`); + } catch (e) { + failed++; + console.log(`FAIL ${name}: ${e instanceof Error ? e.message : format_value(e)}`); + } +} + +// ── global/constructor.any.js ───────────────────────────────────────────── +/** @param {WebAssembly.Global} actual (typed so that valueOf() reaches the Global) */ +function assert_Global(actual, expected) { + assert_equals(actual.value, expected, "value"); + assert_equals(actual.valueOf(), expected, "valueOf"); +} + +test(() => { + // @ts-ignore: no descriptor + assert_throws_js("TypeError", () => new WebAssembly.Global()); +}, "No arguments"); + +test(() => { + const argument = { value: "i32" }; + // @ts-ignore: called without new + assert_throws_js("TypeError", () => WebAssembly.Global(argument)); +}, "Calling"); + +test(() => { + const order = []; + new WebAssembly.Global( + { + get value() { + order.push("descriptor value"); + return { + toString() { + order.push("descriptor value toString"); + return "f64"; + }, + }; + }, + get mutable() { + order.push("descriptor mutable"); + return false; + }, + }, + { + valueOf() { + order.push("value valueOf()"); + }, + }, + ); + assert_array_equals(order, ["descriptor mutable", "descriptor value", "descriptor value toString", "value valueOf()"]); +}, "Order of evaluation"); + +test(() => { + const invalidArguments = [undefined, null, false, true, "", "test", 1, NaN, {}]; + for (const invalidArgument of invalidArguments) { + // @ts-ignore: an invalid descriptor + assert_throws_js("TypeError", () => new WebAssembly.Global(invalidArgument), `new Global(${format_value(invalidArgument)})`); + } +}, "Invalid descriptor argument"); + +test(() => { + const invalidTypes = ["i16", "i128", "f16", "f128", "u32", "u64", "i32\0"]; + for (const value of invalidTypes) { + const argument = { value }; + // @ts-ignore: an invalid type + assert_throws_js("TypeError", () => new WebAssembly.Global(argument)); + } +}, "Invalid type argument"); + +test(() => { + const argument = { value: "v128" }; + // @ts-ignore: v128 + assert_throws_js("TypeError", () => new WebAssembly.Global(argument)); +}, "Construct v128 global"); + +test(() => { + const argument = { value: "i64" }; + const global = new WebAssembly.Global(argument); + assert_Global(global, 0n); +}, "i64 with default"); + +for (const type of ["i32", "f32", "f64"]) { + test(() => { + const argument = { value: type }; + const global = new WebAssembly.Global(argument); + assert_Global(global, 0); + }, `Default value for type ${type}`); + + const valueArguments = [ + { value: undefined, expected: 0, name: "undefined" }, + { value: null, expected: 0, name: "null" }, + { value: true, expected: 1, name: "true" }, + { value: false, expected: 0, name: "false" }, + { value: 2, expected: 2, name: "2" }, + { value: "3", expected: 3, name: '"3"' }, + { value: { toString() { return "5"; } }, expected: 5, name: "object with toString returning string" }, + { value: { valueOf() { return "8"; } }, expected: 8, name: "object with valueOf returning string" }, + { value: { toString() { return 6; } }, expected: 6, name: "object with toString returning number" }, + { value: { valueOf() { return 9; } }, expected: 9, name: "object with valueOf returning number" }, + ]; + for (const { value, expected, name } of valueArguments) { + test(() => { + const argument = { value: type }; + const global = new WebAssembly.Global(argument, value); + assert_Global(global, expected); + }, `Explicit value ${name} for type ${type}`); + } + + test(() => { + const argument = { value: type }; + assert_throws_js("TypeError", () => new WebAssembly.Global(argument, 0n)); + }, `BigInt value for type ${type}`); +} + +const bigintArguments = [ + { value: undefined, expected: 0n, name: "undefined" }, + { value: true, expected: 1n, name: "true" }, + { value: false, expected: 0n, name: "false" }, + { value: "3", expected: 3n, name: '"3"' }, + { value: 123n, expected: 123n, name: "123n" }, + { value: { toString() { return "5"; } }, expected: 5n, name: "object with toString returning string" }, + { value: { valueOf() { return "8"; } }, expected: 8n, name: "object with valueOf returning string" }, + { value: { toString() { return 6n; } }, expected: 6n, name: "object with toString returning bigint" }, + { value: { valueOf() { return 9n; } }, expected: 9n, name: "object with valueOf returning bigint" }, +]; +for (const { value, expected, name } of bigintArguments) { + test(() => { + const argument = { value: "i64" }; + const global = new WebAssembly.Global(argument, value); + assert_Global(global, expected); + }, `Explicit value ${name} for type i64`); +} + +const invalidBigints = [ + { value: null, name: "null" }, + { value: 666, name: "666" }, + { value: { toString() { return 5; } }, name: "object" }, + { value: { valueOf() { return 8; } }, name: "object" }, +]; +for (const { value: invalidBigint, name } of invalidBigints) { + test(() => { + const argument = { value: "i64" }; + assert_throws_js("TypeError", () => new WebAssembly.Global(argument, invalidBigint)); + }, `Pass non-bigint as i64 Global value: ${name}`); +} + +test(() => { + const argument = { value: "i32" }; + const global = new WebAssembly.Global(argument, 0, {}); + assert_Global(global, 0); +}, "Stray argument"); + +// ── global/value-get-set.any.js and global/valueOf.any.js ──────────────── +for (const type of ["i32", "i64", "f32", "f64"]) { + const [initial, value, invalid] = type === "i64" ? [0n, 1n, 2] : [0, 1, 2n]; + const immutableOptions = [ + { opts: {}, name: "missing" }, + { opts: { mutable: undefined }, name: "undefined" }, + { opts: { mutable: null }, name: "null" }, + { opts: { mutable: false }, name: "false" }, + { opts: { mutable: "" }, name: "empty string" }, + { opts: { mutable: 0 }, name: "zero" }, + ]; + for (const { opts, name } of immutableOptions) { + test(() => { + const global = new WebAssembly.Global({ ...opts, value: type }); + assert_equals(global.value, initial, "initial value"); + assert_equals(global.valueOf(), initial, "initial valueOf"); + assert_throws_js("TypeError", () => { + global.value = value; + }); + assert_equals(global.value, initial, "post-set value"); + assert_equals(global.valueOf(), initial, "post-set valueOf"); + }, `Immutable ${type} (${name})`); + + test(() => { + const global = new WebAssembly.Global({ ...opts, value: type }); + let touched = false; + const value = { + valueOf() { + touched = true; + return 1; + }, + toString() { + touched = true; + return "1"; + }, + }; + assert_throws_js("TypeError", () => { + global.value = value; + }); + assert_false(touched, "should not call valueOf or toString"); + assert_equals(global.value, initial, "post-set value"); + assert_equals(global.valueOf(), initial, "post-set valueOf"); + }, `Immutable ${type} with ToNumber side-effects (${name})`); + } + + const mutableOptions = [ + { opts: { mutable: true }, name: "true" }, + { opts: { mutable: 1 }, name: "one" }, + { opts: { mutable: "x" }, name: "string" }, + ]; + for (const { opts, name } of mutableOptions) { + test(() => { + const global = new WebAssembly.Global({ ...opts, value: type }); + assert_equals(global.value, initial, "initial value"); + assert_equals(global.valueOf(), initial, "initial valueOf"); + global.value = value; + assert_throws_js("TypeError", () => { + global.value = invalid; + }); + assert_equals(global.value, value, "post-set value"); + assert_equals(global.valueOf(), value, "post-set valueOf"); + }, `Mutable ${type} (${name})`); + } +} + +test(() => { + const argument = { value: "i64", mutable: true }; + const global = new WebAssembly.Global(argument); + assert_equals(global.value, 0n, "initial value using ToJSValue"); + const valid = [ + { input: 123n, output: 123n }, + { input: 2n ** 63n, output: -(2n ** 63n) }, + { input: true, output: 1n }, + { input: false, output: 0n }, + { input: "456", output: 456n }, + ]; + for (const { input, output } of valid) { + global.value = input; + assert_equals(global.valueOf(), output, "post-set valueOf"); + } + const invalid = [undefined, null, 0, 1, 4.2]; + for (const input of invalid) { + assert_throws_js("TypeError", () => { + global.value = input; + }); + } +}, "i64 mutability"); + +test(() => { + const argument = { value: "i32" }; + const global = new WebAssembly.Global(argument, 0); + assert_equals(global.valueOf({}), 0); +}, "valueOf: Stray argument"); + +// ── memory/constructor.any.js ───────────────────────────────────────────── +function assert_ArrayBuffer(actual, { size = 0 }, message) { + assert_equals(actual.toString(), "[object ArrayBuffer]", `${message}: toString()`); + assert_equals(actual.byteLength, 0x10000 * size, `${message}: size`); + if (size > 0) { + const array = new Uint8Array(actual); + assert_equals(array[0], 0, `${message}: first element`); + assert_equals(array[array.byteLength - 1], 0, `${message}: last element`); + } +} + +function assert_Memory(memory, { size = 0 }) { + assert_equals(memory.buffer, memory.buffer, "buffer should be idempotent"); + assert_ArrayBuffer(memory.buffer, { size }); +} + +test(() => { + // @ts-ignore: no descriptor + assert_throws_js("TypeError", () => new WebAssembly.Memory()); +}, "Memory: No arguments"); + +test(() => { + const argument = { initial: 0 }; + // @ts-ignore: called without new + assert_throws_js("TypeError", () => WebAssembly.Memory(argument)); +}, "Memory: Calling"); + +test(() => { + const invalidArguments = [undefined, null, false, true, "", "test", 1, NaN, {}]; + for (const invalidArgument of invalidArguments) { + // @ts-ignore: an invalid descriptor + assert_throws_js("TypeError", () => new WebAssembly.Memory(invalidArgument), `new Memory(${format_value(invalidArgument)})`); + } +}, "Memory: Invalid descriptor argument"); + +test(() => { + assert_throws_js("TypeError", () => new WebAssembly.Memory({ initial: undefined })); +}, "Undefined initial value in descriptor"); + +const outOfRangeValues = [NaN, Infinity, -Infinity, -1, 0x100000000, 0x1000000000]; +for (const value of outOfRangeValues) { + test(() => { + assert_throws_js("TypeError", () => new WebAssembly.Memory({ initial: value })); + }, `Memory: Out-of-range initial value in descriptor: ${format_value(value)}`); + + test(() => { + assert_throws_js("TypeError", () => new WebAssembly.Memory({ initial: 0, maximum: value })); + }, `Memory: Out-of-range maximum value in descriptor: ${format_value(value)}`); +} + +test(() => { + assert_throws_js("RangeError", () => new WebAssembly.Memory({ initial: 10, maximum: 9 })); +}, "Memory: Initial value exceeds maximum"); + +test(() => { + const order = []; + new WebAssembly.Memory({ + get maximum() { + order.push("maximum"); + return { + valueOf() { + order.push("maximum valueOf"); + return 1; + }, + }; + }, + get initial() { + order.push("initial"); + return { + valueOf() { + order.push("initial valueOf"); + return 1; + }, + }; + }, + }); + assert_array_equals(order, ["initial", "initial valueOf", "maximum", "maximum valueOf"]); +}, "Memory: Order of evaluation for descriptor"); + +test(() => { + const argument = { initial: 0 }; + const memory = new WebAssembly.Memory(argument); + assert_Memory(memory, { size: 0 }); +}, "Zero initial"); + +test(() => { + const argument = { initial: 4 }; + const memory = new WebAssembly.Memory(argument); + assert_Memory(memory, { size: 4 }); +}, "Non-zero initial"); + +test(() => { + const argument = { initial: 0 }; + const memory = new WebAssembly.Memory(argument, {}); + assert_Memory(memory, { size: 0 }); +}, "Memory: Stray argument"); + +// ── memory/grow.any.js ──────────────────────────────────────────────────── +test(() => { + const argument = { initial: 0 }; + const memory = new WebAssembly.Memory(argument); + // @ts-ignore: no delta + assert_throws_js("TypeError", () => memory.grow()); +}, "grow: Missing arguments"); + +for (const { initial, delta, label } of [ + { initial: 0, delta: 2, label: "Zero initial" }, + { initial: { valueOf() { return 0; } }, delta: { valueOf() { return 2; } }, label: "Zero initial with valueOf" }, + { initial: 3, delta: 2, label: "Non-zero initial" }, +]) { + test(() => { + const memory = new WebAssembly.Memory({ initial }); + const before = Number(initial); + const oldMemory = memory.buffer; + assert_ArrayBuffer(oldMemory, { size: before }, "Buffer before growing"); + const result = memory.grow(delta); + assert_equals(result, before); + const newMemory = memory.buffer; + assert_not_equals(oldMemory, newMemory); + assert_ArrayBuffer(newMemory, { size: before + 2 }, "New buffer after growing"); + }, `grow: ${label}`); +} + +test(() => { + const argument = { initial: 0, maximum: 2 }; + const memory = new WebAssembly.Memory(argument); + const result = memory.grow(2); + assert_equals(result, 0); + assert_ArrayBuffer(memory.buffer, { size: 2 }, "New buffer after growing"); +}, "grow: Zero initial with respected maximum"); + +test(() => { + const argument = { initial: 0, maximum: 2 }; + const memory = new WebAssembly.Memory(argument); + assert_equals(memory.grow(1), 0); + assert_equals(memory.grow(1), 1); + assert_ArrayBuffer(memory.buffer, { size: 2 }, "Buffer after growing twice"); +}, "grow: Zero initial with respected maximum grown twice"); + +test(() => { + const argument = { initial: 1, maximum: 2 }; + const memory = new WebAssembly.Memory(argument); + const oldMemory = memory.buffer; + assert_throws_js("RangeError", () => memory.grow(2)); + assert_equals(memory.buffer, oldMemory); + assert_ArrayBuffer(memory.buffer, { size: 1 }, "Buffer after trying to grow"); +}, "grow: Zero initial growing too much"); + +const outOfRangeDeltas = [undefined, NaN, Infinity, -Infinity, -1, 0x100000000, 0x1000000000]; +for (const value of outOfRangeDeltas) { + test(() => { + const argument = { initial: 0 }; + const memory = new WebAssembly.Memory(argument); + assert_throws_js("TypeError", () => memory.grow(value)); + }, `grow: Out-of-range argument: ${format_value(value)}`); +} + +test(() => { + const argument = { initial: 0 }; + const memory = new WebAssembly.Memory(argument); + const result = memory.grow(2, {}); + assert_equals(result, 0); +}, "grow: Stray argument"); + +// ── table/constructor.any.js ────────────────────────────────────────────── +function assert_equal_to_array(table, expected, message = "") { + assert_equals(table.length, expected.length, `${message}: length`); + assert_throws_js("TypeError", () => table.get(-1), `${message}: table.get(-1)`); + for (let i = 0; i < expected.length; ++i) { + assert_equals(table.get(i), expected[i], `${message}: table.get(${i} of ${expected.length})`); + } + assert_throws_js("RangeError", () => table.get(expected.length), `${message}: table.get(${expected.length} of ${expected.length})`); + assert_throws_js("RangeError", () => table.get(expected.length + 1), `${message}: table.get(${expected.length + 1} of ${expected.length})`); +} + +function assert_Table(actual, expected) { + assert_equals(actual.length, expected.length, "length"); + for (let i = 0; i < expected.length; ++i) { + assert_equals(actual.get(i), null, `actual.get(${i})`); + } +} + +test(() => { + // @ts-ignore: no descriptor + assert_throws_js("TypeError", () => new WebAssembly.Table()); +}, "Table: No arguments"); + +test(() => { + const argument = { element: "anyfunc", initial: 0 }; + // @ts-ignore: called without new + assert_throws_js("TypeError", () => WebAssembly.Table(argument)); +}, "Table: Calling"); + +test(() => { + // @ts-ignore: an empty descriptor + assert_throws_js("TypeError", () => new WebAssembly.Table({})); +}, "Empty descriptor"); + +test(() => { + const invalidArguments = [undefined, null, false, true, "", "test", 1, NaN, {}]; + for (const invalidArgument of invalidArguments) { + // @ts-ignore: an invalid descriptor + assert_throws_js("TypeError", () => new WebAssembly.Table(invalidArgument), `new Table(${format_value(invalidArgument)})`); + } +}, "Table: Invalid descriptor argument"); + +test(() => { + assert_throws_js("TypeError", () => new WebAssembly.Table({ element: "anyfunc", initial: undefined })); +}, "Table: Undefined initial value in descriptor"); + +test(() => { + // @ts-ignore: an undefined element + assert_throws_js("TypeError", () => new WebAssembly.Table({ element: undefined, initial: 0 })); +}, "Undefined element value in descriptor"); + +for (const value of outOfRangeValues) { + test(() => { + assert_throws_js("TypeError", () => new WebAssembly.Table({ element: "anyfunc", initial: value })); + }, `Table: Out-of-range initial value in descriptor: ${format_value(value)}`); + + test(() => { + assert_throws_js("TypeError", () => new WebAssembly.Table({ element: "anyfunc", initial: 0, maximum: value })); + }, `Table: Out-of-range maximum value in descriptor: ${format_value(value)}`); +} + +test(() => { + assert_throws_js("RangeError", () => new WebAssembly.Table({ element: "anyfunc", initial: 10, maximum: 9 })); +}, "Table: Initial value exceeds maximum"); + +test(() => { + const argument = { element: "anyfunc", initial: 0 }; + const table = new WebAssembly.Table(argument); + assert_Table(table, { length: 0 }); +}, "Basic (zero)"); + +test(() => { + const argument = { element: "anyfunc", initial: 5 }; + const table = new WebAssembly.Table(argument); + assert_Table(table, { length: 5 }); +}, "Basic (non-zero)"); + +test(() => { + const argument = { element: "anyfunc", initial: 0 }; + const table = new WebAssembly.Table(argument, null, {}); + assert_Table(table, { length: 0 }); +}, "Table: Stray argument"); + +test(() => { + const table = new WebAssembly.Table({ + // @ts-ignore: an object element + element: { + toString() { + return "anyfunc"; + }, + }, + initial: 1, + }); + assert_Table(table, { length: 1 }); +}, "Type conversion for descriptor.element"); + +test(() => { + const order = []; + new WebAssembly.Table({ + get maximum() { + order.push("maximum"); + return { + valueOf() { + order.push("maximum valueOf"); + return 1; + }, + }; + }, + get initial() { + order.push("initial"); + return { + valueOf() { + order.push("initial valueOf"); + return 1; + }, + }; + }, + get element() { + order.push("element"); + return { + toString() { + order.push("element toString"); + return "anyfunc"; + }, + }; + }, + }); + assert_array_equals(order, ["element", "element toString", "initial", "initial valueOf", "maximum", "maximum valueOf"]); +}, "Table: Order of evaluation for descriptor"); + +test(() => { + const testObject = {}; + const argument = { element: "externref", initial: 3 }; + const table = new WebAssembly.Table(argument, testObject); + assert_equals(table.length, 3); + assert_equals(table.get(0), testObject); + assert_equals(table.get(1), testObject); + assert_equals(table.get(2), testObject); +}, "initialize externref table with default value"); + +test(() => { + const argument = { element: "i32", initial: 3 }; + // @ts-ignore: an invalid element + assert_throws_js("TypeError", () => new WebAssembly.Table(argument)); +}, "initialize table with a wrong element value"); + +test(() => { + const fn = new WebAssembly.Instance(new WebAssembly.Module(binary("fn"))).exports.fn; + const argument = { element: "anyfunc", initial: 3 }; + const table = new WebAssembly.Table(argument, fn); + assert_equals(table.length, 3); + assert_equals(table.get(0), fn); + assert_equals(table.get(1), fn); + assert_equals(table.get(2), fn); +}, "initialize anyfunc table with default value"); + +test(() => { + const argument = { element: "anyfunc", initial: 3 }; + assert_throws_js("TypeError", () => new WebAssembly.Table(argument, {})); + assert_throws_js("TypeError", () => new WebAssembly.Table(argument, "cannot be used as a wasm function")); + assert_throws_js("TypeError", () => new WebAssembly.Table(argument, 37)); +}, "initialize anyfunc table with a bad default value"); + +test(() => { + assert_throws_js("RangeError", () => new WebAssembly.Table({ element: "anyfunc", initial: 3, maximum: 2 }, 37)); +}, "initialize anyfunc table with a bad default value and a bad descriptor"); + +// ── table/get-set.any.js ────────────────────────────────────────────────── +const functions = new WebAssembly.Instance(new WebAssembly.Module(binary("functions")), {}).exports; + +test(() => { + const argument = { element: "anyfunc", initial: 5 }; + const table = new WebAssembly.Table(argument); + // @ts-ignore: no index + assert_throws_js("TypeError", () => table.get()); +}, "Missing arguments: get"); + +test(() => { + const argument = { element: "anyfunc", initial: 5 }; + const table = new WebAssembly.Table(argument); + // @ts-ignore: no index + assert_throws_js("TypeError", () => table.set()); +}, "Missing arguments: set"); + +test(() => { + const argument = { element: "anyfunc", initial: 5 }; + const table = new WebAssembly.Table(argument); + assert_equal_to_array(table, [null, null, null, null, null]); + const { fn, fn2 } = functions; + assert_equals(table.set(0, fn), undefined, "set() returns undefined."); + table.set(2, fn2); + table.set(4, fn); + assert_equal_to_array(table, [fn, null, fn2, null, fn]); + table.set(0, null); + assert_equal_to_array(table, [null, null, fn2, null, fn]); +}, "Basic"); + +test(() => { + const argument = { element: "anyfunc", initial: 5 }; + const table = new WebAssembly.Table(argument); + assert_equal_to_array(table, [null, null, null, null, null]); + const { fn, fn2 } = functions; + table.set(0, fn); + table.set(2, fn2); + table.set(4, fn); + assert_equal_to_array(table, [fn, null, fn2, null, fn]); + table.grow(4); + assert_equal_to_array(table, [fn, null, fn2, null, fn, null, null, null, null]); +}, "Growing"); + +test(() => { + const argument = { element: "anyfunc", initial: 5 }; + const table = new WebAssembly.Table(argument); + assert_equal_to_array(table, [null, null, null, null, null]); + const { fn } = functions; + assert_throws_js("TypeError", () => table.set(-1, fn)); + assert_throws_js("RangeError", () => table.set(5, fn)); + assert_equal_to_array(table, [null, null, null, null, null]); +}, "Setting out-of-bounds"); + +test(() => { + const argument = { element: "anyfunc", initial: 1 }; + const table = new WebAssembly.Table(argument); + assert_equal_to_array(table, [null]); + const invalidArguments = [undefined, true, false, "test", 7, NaN, {}]; + for (const argument of invalidArguments) { + assert_throws_js("TypeError", () => table.set(0, argument), `set(${format_value(argument)})`); + } + assert_equal_to_array(table, [null]); +}, "Setting non-function"); + +test(() => { + const argument = { element: "anyfunc", initial: 1 }; + const table = new WebAssembly.Table(argument); + assert_equal_to_array(table, [null]); + const fn = function () {}; + assert_throws_js("TypeError", () => table.set(0, fn)); + assert_equal_to_array(table, [null]); +}, "Setting non-wasm function"); + +test(() => { + const argument = { element: "anyfunc", initial: 1 }; + const table = new WebAssembly.Table(argument); + assert_equal_to_array(table, [null]); + const fn = () => {}; + assert_throws_js("TypeError", () => table.set(0, fn)); + assert_equal_to_array(table, [null]); +}, "Setting non-wasm arrow function"); + +const outOfRangeIndices = [ + { value: undefined, name: "undefined" }, + { value: NaN, name: "NaN" }, + { value: Infinity, name: "Infinity" }, + { value: -Infinity, name: "-Infinity" }, + { value: -1, name: "-1" }, + { value: 0x100000000, name: "4294967296" }, + { value: 0x1000000000, name: "68719476736" }, + { value: "0x100000000", name: '"0x100000000"' }, + { value: { valueOf() { return 0x100000000; } }, name: "object" }, +]; +for (const { value, name } of outOfRangeIndices) { + test(() => { + const argument = { element: "anyfunc", initial: 1 }; + const table = new WebAssembly.Table(argument); + assert_throws_js("TypeError", () => table.get(value)); + }, `Getting out-of-range argument: ${name}`); + + test(() => { + const argument = { element: "anyfunc", initial: 1 }; + const table = new WebAssembly.Table(argument); + assert_throws_js("TypeError", () => table.set(value, null)); + }, `Setting out-of-range argument: ${name}`); +} + +test(() => { + const argument = { element: "anyfunc", initial: 1 }; + const table = new WebAssembly.Table(argument); + let called = 0; + const value = { + valueOf() { + called++; + return 0; + }, + }; + assert_throws_js("TypeError", () => table.set(value, {})); + assert_equals(called, 1); +}, "Order of argument conversion"); + +test(() => { + const { fn } = functions; + const argument = { element: "anyfunc", initial: 1 }; + const table = new WebAssembly.Table(argument); + assert_equals(table.get(0, {}), null); + assert_equals(table.set(0, fn, {}), undefined); +}, "get-set: Stray argument"); + +test(() => { + const fn = new WebAssembly.Instance(new WebAssembly.Module(binary("fn"))).exports.fn; + const argument = { element: "anyfunc", initial: 1 }; + const table = new WebAssembly.Table(argument, fn); + assert_equals(table.get(0), fn); + table.set(0); + assert_equals(table.get(0), null); + table.set(0, fn); + assert_equals(table.get(0), fn); + assert_throws_js("TypeError", () => table.set(0, {})); + assert_throws_js("TypeError", () => table.set(0, 37)); +}, "Arguments for anyfunc table set"); + +test(() => { + const testObject = {}; + const argument = { element: "externref", initial: 1 }; + const table = new WebAssembly.Table(argument, testObject); + assert_equals(table.get(0), testObject); + table.set(0); + assert_equals(table.get(0), undefined); + table.set(0, testObject); + assert_equals(table.get(0), testObject); + table.set(0, 37); + assert_equals(table.get(0), 37); +}, "Arguments for externref table set"); + +// ── table/grow.any.js ───────────────────────────────────────────────────── +function nulls(n) { + return Array(n).fill(null); +} + +test(() => { + const argument = { element: "anyfunc", initial: 5 }; + const table = new WebAssembly.Table(argument); + // @ts-ignore: no delta + assert_throws_js("TypeError", () => table.grow()); +}, "Table grow: Missing arguments"); + +test(() => { + const argument = { element: "anyfunc", initial: 5 }; + const table = new WebAssembly.Table(argument); + assert_equal_to_array(table, nulls(5), "before"); + const result = table.grow(3); + assert_equals(result, 5); + assert_equal_to_array(table, nulls(8), "after"); +}, "Table grow: Basic"); + +test(() => { + const argument = { element: "anyfunc", initial: 3, maximum: 5 }; + const table = new WebAssembly.Table(argument); + assert_equal_to_array(table, nulls(3), "before"); + const result = table.grow(2); + assert_equals(result, 3); + assert_equal_to_array(table, nulls(5), "after"); +}, "Reached maximum"); + +test(() => { + const argument = { element: "anyfunc", initial: 2, maximum: 5 }; + const table = new WebAssembly.Table(argument); + assert_equal_to_array(table, nulls(2), "before"); + assert_throws_js("RangeError", () => table.grow(4)); + assert_equal_to_array(table, nulls(2), "after"); +}, "Exceeded maximum"); + +for (const { value, name } of outOfRangeIndices) { + test(() => { + const argument = { element: "anyfunc", initial: 1 }; + const table = new WebAssembly.Table(argument); + assert_throws_js("TypeError", () => table.grow(value)); + }, `Table grow: Out-of-range argument: ${name}`); +} + +test(() => { + const argument = { element: "anyfunc", initial: 5 }; + const table = new WebAssembly.Table(argument); + assert_equal_to_array(table, nulls(5), "before"); + const result = table.grow(3, null, {}); + assert_equals(result, 5); + assert_equal_to_array(table, nulls(8), "after"); +}, "Table grow: Stray argument"); + +test(() => { + const fn = new WebAssembly.Instance(new WebAssembly.Module(binary("fn"))).exports.fn; + const argument = { element: "anyfunc", initial: 1 }; + const table = new WebAssembly.Table(argument, fn); + assert_equal_to_array(table, [fn], "before"); + const result = table.grow(2, fn); + assert_equals(result, 1); + assert_equal_to_array(table, [fn, fn, fn], "after"); +}, "Grow with exported-function argument"); + +test(() => { + const argument = { element: "anyfunc", initial: 1 }; + const table = new WebAssembly.Table(argument); + assert_throws_js("TypeError", () => table.grow(2, {})); +}, "Grow with non-function argument"); + +test(() => { + const argument = { element: "anyfunc", initial: 1 }; + const table = new WebAssembly.Table(argument); + assert_throws_js("TypeError", () => table.grow(2, () => true)); +}, "Grow with JS-function argument"); + +// ── module/imports.any.js and module/exports.any.js ────────────────────── +function assert_descriptors(actual, expected, keys) { + assert_true(Array.isArray(actual), "Should be array"); + assert_true(Object.isExtensible(actual), "isExtensible"); + assert_equals(actual.length, expected.length); + for (let i = 0; i < expected.length; ++i) { + assert_true(Object.isExtensible(actual[i]), "isExtensible"); + assert_array_equals(Object.keys(actual[i]), keys, "keys"); + for (const key of keys) assert_equals(actual[i][key], expected[i][key], key); + } +} +const assert_imports = (imports, expected) => assert_descriptors(imports, expected, ["module", "name", "kind"]); +const assert_exports = (exports, expected) => assert_descriptors(exports, expected, ["name", "kind"]); +const emptyModule = new WebAssembly.Module(binary("empty")); + +for (const which of ["imports", "exports"]) { + test(() => { + // @ts-ignore: no module + assert_throws_js("TypeError", () => (which === "imports" ? WebAssembly.Module.imports() : WebAssembly.Module.exports())); + }, `${which}: Missing arguments`); + + test(() => { + const invalidArguments = [undefined, null, true, "", 1, {}]; + for (const argument of invalidArguments) { + assert_throws_js( + "TypeError", + // @ts-ignore: not a module + () => (which === "imports" ? WebAssembly.Module.imports(argument) : WebAssembly.Module.exports(argument)), + `${which}(${format_value(argument)})`, + ); + } + }, `${which}: Non-Module arguments`); + + test(() => { + const result = which === "imports" ? WebAssembly.Module.imports(emptyModule) : WebAssembly.Module.exports(emptyModule); + assert_true(Array.isArray(result)); + }, `${which}: Return type`); + + test(() => { + if (which === "imports") assert_imports(WebAssembly.Module.imports(emptyModule), []); + else assert_exports(WebAssembly.Module.exports(emptyModule), []); + }, `${which}: Empty module`); + + test(() => { + if (which === "imports") + assert_not_equals(WebAssembly.Module.imports(emptyModule), WebAssembly.Module.imports(emptyModule)); + else assert_not_equals(WebAssembly.Module.exports(emptyModule), WebAssembly.Module.exports(emptyModule)); + }, `${which}: Empty module: array caching`); +} + +test(() => { + const module = new WebAssembly.Module(binary("imports")); + const imports = WebAssembly.Module.imports(module); + const expected = [ + { module: "module", kind: "function", name: "fn" }, + { module: "module", kind: "global", name: "global" }, + { module: "module", kind: "memory", name: "memory" }, + { module: "module", kind: "table", name: "table" }, + ]; + assert_imports(imports, expected); +}, "imports"); + +for (const { key, label, expected } of [ + { + key: "importsEmptyModule", + label: "imports with empty module name", + expected: [ + { module: "", kind: "function", name: "fn" }, + { module: "", kind: "global", name: "global" }, + { module: "", kind: "memory", name: "memory" }, + { module: "", kind: "table", name: "table" }, + ], + }, + { + key: "importsEmptyNames", + label: "imports with empty names", + expected: [ + { module: "a", kind: "function", name: "" }, + { module: "b", kind: "global", name: "" }, + { module: "c", kind: "memory", name: "" }, + { module: "d", kind: "table", name: "" }, + ], + }, + { + key: "importsEmptyBoth", + label: "imports with empty module names and names", + expected: [ + { module: "", kind: "function", name: "" }, + { module: "", kind: "global", name: "" }, + { module: "", kind: "memory", name: "" }, + { module: "", kind: "table", name: "" }, + ], + }, +]) { + test(() => { + const module = new WebAssembly.Module(binary(key)); + assert_imports(WebAssembly.Module.imports(module), expected); + }, label); +} + +test(() => { + assert_imports(WebAssembly.Module.imports(emptyModule, {}), []); +}, "imports: Stray argument"); + +test(() => { + const module = new WebAssembly.Module(binary("exports")); + const exports = WebAssembly.Module.exports(module); + const expected = [ + { kind: "function", name: "fn" }, + { kind: "function", name: "fn2" }, + { kind: "table", name: "table" }, + { kind: "global", name: "global" }, + { kind: "global", name: "global2" }, + { kind: "memory", name: "memory" }, + ]; + assert_exports(exports, expected); +}, "exports"); + +for (const { key, kind } of [ + { key: "exportsEmptyFunction", kind: "function" }, + { key: "exportsEmptyTable", kind: "table" }, + { key: "exportsEmptyGlobal", kind: "global" }, +]) { + test(() => { + const module = new WebAssembly.Module(binary(key)); + assert_exports(WebAssembly.Module.exports(module), [{ kind, name: "" }]); + }, `exports with empty name: ${kind}`); +} + +test(() => { + assert_exports(WebAssembly.Module.exports(emptyModule, {}), []); +}, "exports: Stray argument"); + +// ── module/customSections.any.js ────────────────────────────────────────── +function assert_sections(sections, expected) { + assert_true(Array.isArray(sections), "Should be array"); + assert_true(Object.isExtensible(sections), "isExtensible"); + assert_equals(sections.length, expected.length); + for (let i = 0; i < expected.length; ++i) { + assert_true(sections[i] instanceof ArrayBuffer, "ArrayBuffer"); + assert_array_equals(new Uint8Array(sections[i]), expected[i]); + } +} + +test(() => { + // @ts-ignore: no arguments + assert_throws_js("TypeError", () => WebAssembly.Module.customSections()); + // @ts-ignore: no name + assert_throws_js("TypeError", () => WebAssembly.Module.customSections(emptyModule)); +}, "customSections: Missing arguments"); + +test(() => { + const invalidArguments = [undefined, null, true, "", 1, {}]; + for (const argument of invalidArguments) { + // @ts-ignore: not a module + assert_throws_js("TypeError", () => WebAssembly.Module.customSections(argument, ""), `customSections(${format_value(argument)})`); + } +}, "customSections: Non-Module arguments"); + +test(() => { + assert_sections(WebAssembly.Module.customSections(emptyModule, ""), []); +}, "customSections: Empty module"); + +test(() => { + assert_not_equals(WebAssembly.Module.customSections(emptyModule, ""), WebAssembly.Module.customSections(emptyModule, "")); +}, "customSections: Empty module: array caching"); + +const bytes1 = [87, 101, 98, 65, 115, 115, 101, 109, 98, 108, 121]; +const bytes2 = [74, 83, 65, 80, 73]; + +test(() => { + const module = new WebAssembly.Module(binary("custom")); + assert_sections(WebAssembly.Module.customSections(module, "name"), [bytes1, bytes2]); + assert_sections(WebAssembly.Module.customSections(module, "foo"), [bytes1]); + assert_sections(WebAssembly.Module.customSections(module, ""), []); + assert_sections(WebAssembly.Module.customSections(module, "\0"), []); + assert_sections(WebAssembly.Module.customSections(module, "name\0"), []); + assert_sections(WebAssembly.Module.customSections(module, "foo\0"), []); +}, "Custom sections"); + +test(() => { + const name = "yee𐐷eey"; + const module = new WebAssembly.Module(binary("customSurrogates")); + assert_sections(WebAssembly.Module.customSections(module, name), [bytes1]); + assert_sections(WebAssembly.Module.customSections(module, "yee�eey"), []); + assert_sections(WebAssembly.Module.customSections(module, "yee��eey"), []); +}, "Custom sections with surrogate pairs"); + +test(() => { + const module = new WebAssembly.Module(binary("customReplacement")); + assert_sections(WebAssembly.Module.customSections(module, "name"), []); + assert_sections(WebAssembly.Module.customSections(module, "na�me"), [bytes1]); + assert_sections(WebAssembly.Module.customSections(module, "na\uDC01me"), []); +}, "Custom sections with U+FFFD"); + +test(() => { + assert_sections(WebAssembly.Module.customSections(emptyModule, "", {}), []); +}, "customSections: Stray argument"); + +test(() => { + const module = new WebAssembly.Module(binary("customCopy")); + const first = WebAssembly.Module.customSections(module, "copy"); + const second = WebAssembly.Module.customSections(module, "copy"); + assert_equals(first.length, 1); + assert_equals(second.length, 1); + assert_not_equals(first, second); + assert_not_equals(first[0], second[0]); +}, "Custom section buffers are independent copies"); + +test(() => { + const bytes = [0x11, 0x22, 0x33]; + const module = new WebAssembly.Module(binary("customCopy")); + const first = WebAssembly.Module.customSections(module, "copy"); + const second = WebAssembly.Module.customSections(module, "copy"); + const view = new Uint8Array(first[0]); + view[0] = 0xff; + view[1] = 0xee; + view[2] = 0xdd; + const again = WebAssembly.Module.customSections(module, "copy"); + assert_array_equals(new Uint8Array(again[0]), bytes); + assert_array_equals(new Uint8Array(second[0]), bytes); +}, "Mutating a custom section buffer does not change other copies"); + +console.log(`${passed} passed, ${failed} failed`); diff --git a/tests/coverage-fixtures/wasm/ext.so b/tests/coverage-fixtures/wasm/ext.so new file mode 100644 index 0000000000000000000000000000000000000000..d8b6461083bc0ee6db05933d542c24b1df7dea13 GIT binary patch literal 36 rcmZQbEY4+QU|?WmWlUgTtY>CoWMF4!WKOLpVPN25V`O1)WakC|QojTH literal 0 HcmV?d00001 diff --git a/tests/coverage-fixtures/wasm/glue.js b/tests/coverage-fixtures/wasm/glue.js index 631560632..4a18af315 100644 --- a/tests/coverage-fixtures/wasm/glue.js +++ b/tests/coverage-fixtures/wasm/glue.js @@ -8,6 +8,15 @@ export function hasModule() { return existsSync(new URL("./add.wasm", import.meta.url).pathname); } +// WebAssembly shared libraries (quickjs-wasi's extensions) use .so: ext.so +// is a module, native.so is not and is not reported. +export function sharedLibraries() { + return [ + existsSync(new URL("./ext.so", import.meta.url).pathname), + existsSync(new URL("./native.so", import.meta.url).pathname), + ]; +} + export function inlineModule() { return ANSWER; } diff --git a/tests/coverage-fixtures/wasm/main.ts b/tests/coverage-fixtures/wasm/main.ts index a2d80b6b3..4e54283e7 100644 --- a/tests/coverage-fixtures/wasm/main.ts +++ b/tests/coverage-fixtures/wasm/main.ts @@ -1,9 +1,10 @@ // The WebAssembly modules this program can load are visible to the build in -// four ways (wasm/discover.ts): a path the compiler resolves (glue.js), an -// inline base64 literal (glue.js), a .wasm file shipped by a package whose -// code mentions WebAssembly (wasm-fixture-pkg), and the scriptc.wasm list in -// package.json (build/generated.wasm). -import { hasModule, inlineModule } from "./glue.js"; +// four ways (wasm/discover.ts): a path the compiler resolves (glue.js, +// including a .so shared library), an inline base64 literal (glue.js), a +// .wasm file shipped by a package whose code mentions WebAssembly +// (wasm-fixture-pkg), and the scriptc.wasm list in package.json +// (build/generated.wasm). +import { hasModule, inlineModule, sharedLibraries } from "./glue.js"; import { describeModule } from "wasm-fixture-pkg"; -console.log(hasModule(), inlineModule().length, describeModule()); +console.log(hasModule(), inlineModule().length, describeModule(), sharedLibraries()); diff --git a/tests/coverage-fixtures/wasm/native.so b/tests/coverage-fixtures/wasm/native.so new file mode 100644 index 0000000000000000000000000000000000000000..66d7d9f629a698a934bba1fce8f776473cf19b07 GIT binary patch literal 25 gcmb<-^>JfjWMs(8FHuNTC{HZTRmjaxDa}a*09SVhs{jB1 literal 0 HcmV?d00001 diff --git a/tests/diagnostics/webassembly-fences.ts b/tests/diagnostics/webassembly-fences.ts index ffa2f96e3..fe90d41c9 100644 --- a/tests/diagnostics/webassembly-fences.ts +++ b/tests/diagnostics/webassembly-fences.ts @@ -1,18 +1,17 @@ // The parts of the WebAssembly API scriptc does not implement yet refuse at -// each reached use, in each spelling. compile, instantiate, validate, -// Module, Instance and the error classes compile (tests/corpus/webassembly-*). +// each reached use, in each spelling. compile, instantiate, validate, the +// Module, Instance, Memory, Table and Global constructors, Module.imports, +// exports and customSections, and the error classes compile +// (tests/corpus/webassembly-*). async function load(bytes: Uint8Array): Promise { const module = await WebAssembly.compile(bytes); - const memory = new WebAssembly.Memory({ initial: 1 }); - const table = new WebAssembly.Table({ initial: 1, element: "anyfunc" }); - const global = new WebAssembly.Global({ value: "i32", mutable: true }, 1); - console.log(memory.buffer.byteLength, table.length, global.value); console.log(WebAssembly.Module.imports(module).length); - console.log(WebAssembly.Module.customSections(module, "name").length); const streamed = await WebAssembly.instantiateStreaming(fetch("https://example.com/x.wasm")); console.log(streamed.instance); const Ctor = WebAssembly.Module; console.log(Ctor); + const Memory = WebAssembly.Memory; + console.log(new Memory({ initial: 1 })); } await load(new Uint8Array([0, 97, 115, 109, 1, 0, 0, 0])); diff --git a/tests/harness/__snapshots__/coverage-wasm.txt b/tests/harness/__snapshots__/coverage-wasm.txt index 0a56ccd1d..9d0ca414a 100644 --- a/tests/harness/__snapshots__/coverage-wasm.txt +++ b/tests/harness/__snapshots__/coverage-wasm.txt @@ -1,8 +1,8 @@ scriptc coverage tests/coverage-fixtures/wasm/main.ts - statements analyzed 8 - compile statically 8 (100%) - in dynamic tier 2 (operate on values typed only at runtime; --explain-dynamic lists them) + statements analyzed 9 + compile statically 9 (100%) + in dynamic tier 4 (operate on values typed only at runtime; --explain-dynamic lists them) npm package compilation: wasm-fixture-pkg static @@ -14,6 +14,9 @@ scriptc coverage tests/coverage-fixtures/wasm/main.ts tests/coverage-fixtures/wasm/add.wasm 41 bytes valid 1 function, 0 imports, 1 export found: path at tests/coverage-fixtures/wasm/glue.js:8 + tests/coverage-fixtures/wasm/ext.so 36 bytes valid + 1 function, 0 imports, 1 export + found: path at tests/coverage-fixtures/wasm/glue.js:15 tests/coverage-fixtures/wasm/node_modules/wasm-fixture-pkg/lib/core.wasm 27 bytes invalid: type mismatch (at byte 27) 1 function, 0 imports, 0 exports found: shipped by the package at tests/coverage-fixtures/wasm/node_modules/wasm-fixture-pkg diff --git a/tests/harness/__snapshots__/webassembly-fences.ts.txt b/tests/harness/__snapshots__/webassembly-fences.ts.txt index f4a1eeae1..49c423707 100644 --- a/tests/harness/__snapshots__/webassembly-fences.ts.txt +++ b/tests/harness/__snapshots__/webassembly-fences.ts.txt @@ -1,71 +1,26 @@ -webassembly-fences.ts:6:22 - error SC2020: 'new WebAssembly.Memory' is not supported yet: scriptc implements WebAssembly.compile, instantiate, validate, Module, Instance and the error classes over modules translated at build time, but not this part of the API +webassembly-fences.ts:9:26 - error SC2020: 'WebAssembly.instantiateStreaming' is not supported yet: scriptc implements WebAssembly.compile, instantiate, validate, the Module, Instance, Memory, Table and Global constructors, Module.imports, exports and customSections, and the error classes over modules translated at build time, but not this part of the API - 5 | const module = await WebAssembly.compile(bytes); - 6 | const memory = new WebAssembly.Memory({ initial: 1 }); - | ^~~~~~~~~~~~~~~~~~ - 7 | const table = new WebAssembly.Table({ initial: 1, element: "anyfunc" }); - - hint: the Memory, Table and Global constructors, Module.imports, exports and customSections, and the streaming functions are planned; 'scriptc coverage' lists the modules this program loads (https://scriptc.dev/limitations) - -webassembly-fences.ts:7:21 - error SC2020: 'new WebAssembly.Table' is not supported yet: scriptc implements WebAssembly.compile, instantiate, validate, Module, Instance and the error classes over modules translated at build time, but not this part of the API - - 6 | const memory = new WebAssembly.Memory({ initial: 1 }); - 7 | const table = new WebAssembly.Table({ initial: 1, element: "anyfunc" }); - | ^~~~~~~~~~~~~~~~~ - 8 | const global = new WebAssembly.Global({ value: "i32", mutable: true }, 1); - - hint: the Memory, Table and Global constructors, Module.imports, exports and customSections, and the streaming functions are planned; 'scriptc coverage' lists the modules this program loads (https://scriptc.dev/limitations) - -webassembly-fences.ts:8:22 - error SC2020: 'new WebAssembly.Global' is not supported yet: scriptc implements WebAssembly.compile, instantiate, validate, Module, Instance and the error classes over modules translated at build time, but not this part of the API - - 7 | const table = new WebAssembly.Table({ initial: 1, element: "anyfunc" }); - 8 | const global = new WebAssembly.Global({ value: "i32", mutable: true }, 1); - | ^~~~~~~~~~~~~~~~~~ - 9 | console.log(memory.buffer.byteLength, table.length, global.value); - - hint: the Memory, Table and Global constructors, Module.imports, exports and customSections, and the streaming functions are planned; 'scriptc coverage' lists the modules this program loads (https://scriptc.dev/limitations) - -webassembly-fences.ts:9:15 - error SC2004: uses of 'memory' inherit the blocker on its declaration - - 8 | const global = new WebAssembly.Global({ value: "i32", mutable: true }, 1); - 9 | console.log(memory.buffer.byteLength, table.length, global.value); - | ^~~~~~~~~~~~~~~~~~~~~~~~ - 10 | console.log(WebAssembly.Module.imports(module).length); - - hint: the declaration of 'memory' did not compile — fix the diagnostic reported there and these sites clear with it - -webassembly-fences.ts:10:15 - error SC2020: 'WebAssembly.Module.imports' is not supported yet: scriptc implements WebAssembly.compile, instantiate, validate, Module, Instance and the error classes over modules translated at build time, but not this part of the API - - 9 | console.log(memory.buffer.byteLength, table.length, global.value); - 10 | console.log(WebAssembly.Module.imports(module).length); - | ^~~~~~~~~~~~~~~~~~~~~~~~~~ - 11 | console.log(WebAssembly.Module.customSections(module, "name").length); - - hint: the Memory, Table and Global constructors, Module.imports, exports and customSections, and the streaming functions are planned; 'scriptc coverage' lists the modules this program loads (https://scriptc.dev/limitations) - -webassembly-fences.ts:11:15 - error SC2020: 'WebAssembly.Module.customSections' is not supported yet: scriptc implements WebAssembly.compile, instantiate, validate, Module, Instance and the error classes over modules translated at build time, but not this part of the API - - 10 | console.log(WebAssembly.Module.imports(module).length); - 11 | console.log(WebAssembly.Module.customSections(module, "name").length); - | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - 12 | const streamed = await WebAssembly.instantiateStreaming(fetch("https://example.com/x.wasm")); - - hint: the Memory, Table and Global constructors, Module.imports, exports and customSections, and the streaming functions are planned; 'scriptc coverage' lists the modules this program loads (https://scriptc.dev/limitations) - -webassembly-fences.ts:12:26 - error SC2020: 'WebAssembly.instantiateStreaming' is not supported yet: scriptc implements WebAssembly.compile, instantiate, validate, Module, Instance and the error classes over modules translated at build time, but not this part of the API - - 11 | console.log(WebAssembly.Module.customSections(module, "name").length); - 12 | const streamed = await WebAssembly.instantiateStreaming(fetch("https://example.com/x.wasm")); + 8 | console.log(WebAssembly.Module.imports(module).length); + 9 | const streamed = await WebAssembly.instantiateStreaming(fetch("https://example.com/x.wasm")); | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - 13 | console.log(streamed.instance); + 10 | console.log(streamed.instance); - hint: the Memory, Table and Global constructors, Module.imports, exports and customSections, and the streaming functions are planned; 'scriptc coverage' lists the modules this program loads (https://scriptc.dev/limitations) + hint: the streaming functions, Tag and Exception, and the classes used as values other than with new and instanceof are planned; 'scriptc coverage' lists the modules this program loads (https://scriptc.dev/limitations) -webassembly-fences.ts:14:16 - error SC2020: 'WebAssembly.Module' is not supported yet: scriptc implements WebAssembly.compile, instantiate, validate, Module, Instance and the error classes over modules translated at build time, but not this part of the API +webassembly-fences.ts:11:16 - error SC2020: 'WebAssembly.Module' is not supported yet: scriptc implements WebAssembly.compile, instantiate, validate, the Module, Instance, Memory, Table and Global constructors, Module.imports, exports and customSections, and the error classes over modules translated at build time, but not this part of the API - 13 | console.log(streamed.instance); - 14 | const Ctor = WebAssembly.Module; + 10 | console.log(streamed.instance); + 11 | const Ctor = WebAssembly.Module; | ^~~~~~~~~~~~~~~~~~ - 15 | console.log(Ctor); + 12 | console.log(Ctor); - hint: the Memory, Table and Global constructors, Module.imports, exports and customSections, and the streaming functions are planned; 'scriptc coverage' lists the modules this program loads (https://scriptc.dev/limitations) \ No newline at end of file + hint: the streaming functions, Tag and Exception, and the classes used as values other than with new and instanceof are planned; 'scriptc coverage' lists the modules this program loads (https://scriptc.dev/limitations) + +webassembly-fences.ts:13:18 - error SC2020: 'WebAssembly.Memory' is not supported yet: scriptc implements WebAssembly.compile, instantiate, validate, the Module, Instance, Memory, Table and Global constructors, Module.imports, exports and customSections, and the error classes over modules translated at build time, but not this part of the API + + 12 | console.log(Ctor); + 13 | const Memory = WebAssembly.Memory; + | ^~~~~~~~~~~~~~~~~~ + 14 | console.log(new Memory({ initial: 1 })); + + hint: the streaming functions, Tag and Exception, and the classes used as values other than with new and instanceof are planned; 'scriptc coverage' lists the modules this program loads (https://scriptc.dev/limitations) \ No newline at end of file diff --git a/tests/harness/coverage.test.ts b/tests/harness/coverage.test.ts index f1347bf23..9d67f39c1 100644 --- a/tests/harness/coverage.test.ts +++ b/tests/harness/coverage.test.ts @@ -44,8 +44,9 @@ test("unreached blockers report in their own group", async () => { }); test("WebAssembly modules the build can see report with their sources", async () => { - // A resolved path and an inline literal (glue.js), a packaged .wasm - // (wasm-fixture-pkg), and package.json's scriptc.wasm list. + // A resolved path, a resolved .so shared library and an inline literal + // (glue.js), a packaged .wasm (wasm-fixture-pkg), and package.json's + // scriptc.wasm list. A resolved .so that is not a module is not reported. await expect(report(fixture("wasm/main.ts"))).toMatchFileSnapshot( "__snapshots__/coverage-wasm.txt", ); @@ -61,6 +62,7 @@ test("WebAssembly modules the build can see report with their sources", async () ).toEqual([ ["inline", "valid", [], ["inline"]], ["tests/coverage-fixtures/wasm/add.wasm", "valid", [], ["path", "config"]], + ["tests/coverage-fixtures/wasm/ext.so", "valid", [], ["path"]], [ "tests/coverage-fixtures/wasm/node_modules/wasm-fixture-pkg/lib/core.wasm", "invalid", @@ -70,7 +72,7 @@ test("WebAssembly modules the build can see report with their sources", async () ["tests/coverage-fixtures/wasm/build/generated.wasm", "unsupported", ["simd"], ["config"]], ]); const envelope = coverageEnvelope(coverage, { compilerVersion: "test" }); - expect(envelope.wasmModules?.map((m) => m.sha256.length)).toEqual([64, 64, 64, 64]); + expect(envelope.wasmModules?.map((m) => m.sha256.length)).toEqual([64, 64, 64, 64, 64]); }); test("divergences report as warnings, never as blockers", async () => { diff --git a/tests/wasm-spec/corpus/dylink-ext.wat b/tests/wasm-spec/corpus/dylink-ext.wat new file mode 100644 index 000000000..87023c646 --- /dev/null +++ b/tests/wasm-spec/corpus/dylink-ext.wat @@ -0,0 +1,42 @@ +;; tests/corpus/webassembly-dylink: an extension shaped like quickjs-wasi's +;; WebAssembly shared libraries (wasi-sdk -shared): it shares the main +;; module's memory and table, places its data and functions at the +;; __memory_base and __table_base the loader assigns, reaches the main +;; module through GOT.mem and GOT.func globals and env imports, and leaves +;; one symbol unresolved and one resolved by its own export. The program's +;; base64 copy appends the dylink.0 section: memory size 48 (alignment 2^2), +;; table size 2, needed "libc.so". +(module + (type $i_i (func (param i32) (result i32))) + (import "env" "memory" (memory 1)) + (import "env" "__indirect_function_table" (table 0 funcref)) + (import "env" "__stack_pointer" (global $sp (mut i32))) + (import "env" "__memory_base" (global $mb i32)) + (import "env" "__table_base" (global $tb i32)) + (import "GOT.mem" "counter" (global $got_counter (mut i32))) + (import "GOT.func" "helper" (global $got_helper (mut i32))) + (import "env" "js_register" (func $js_register (param i32 i32) (result i32))) + (import "env" "missing_symbol" (func $missing (param i32) (result i32))) + (import "env" "self_symbol" (func $self (param i32) (result i32))) + (data (global.get $mb) "name=demo\0aversion=1.2\00") + (elem (global.get $tb) $triple $via_helper) + (func $triple (type $i_i) (i32.mul (local.get 0) (i32.const 3))) + (func $via_helper (type $i_i) + (call_indirect (type $i_i) (local.get 0) (global.get $got_helper))) + (func (export "__wasm_apply_data_relocs") + (global.set $got_counter (i32.add (global.get $mb) (i32.const 32)))) + (func (export "__wasm_call_ctors") + (i32.store (global.get $got_counter) (i32.const 5))) + (func (export "qjs_ext_demo_init") (param i32 i32) (result i32) + (drop (call $js_register (local.get 0) (global.get $tb))) + (drop (call $js_register (local.get 0) (i32.add (global.get $tb) (i32.const 1)))) + (i32.sub (local.get 1) (i32.const 200))) + (func (export "qjs_ext_demo_versions") (result i32) (global.get $mb)) + (func (export "self_symbol") (param i32) (result i32) (i32.add (local.get 0) (i32.const 1))) + (func (export "use_self") (param i32) (result i32) (call $self (local.get 0))) + (func (export "use_missing") (param i32) (result i32) (call $missing (local.get 0))) + (func (export "stack_pointer") (result i32) (global.get $sp)) + (func (export "bump_counter") (result i32) + (i32.store (global.get $got_counter) (i32.add (i32.load (global.get $got_counter)) (i32.const 1))) + (i32.load (global.get $got_counter))) +) diff --git a/tests/wasm-spec/corpus/dylink-main.wat b/tests/wasm-spec/corpus/dylink-main.wat new file mode 100644 index 000000000..0cba26bb3 --- /dev/null +++ b/tests/wasm-spec/corpus/dylink-main.wat @@ -0,0 +1,33 @@ +;; tests/corpus/webassembly-dylink: the main module of a dynamically linked +;; program, shaped like quickjs-wasi's quickjs.wasm: it exports its memory, +;; its function table, __stack_pointer, malloc and the entry points an +;; extension's init function calls. +(module + (type $i_i (func (param i32) (result i32))) + (memory (export "memory") 2) + (table (export "__indirect_function_table") 4 funcref) + (global $sp (export "__stack_pointer") (mut i32) (i32.const 65536)) + (global $heap (mut i32) (i32.const 1024)) + (global $registered (mut i32) (i32.const 0)) + (func (export "malloc") (param i32) (result i32) + (local i32) + (local.set 1 (global.get $heap)) + (global.set $heap + (i32.and (i32.add (i32.add (global.get $heap) (local.get 0)) (i32.const 15)) (i32.const -16))) + (local.get 1)) + (func (export "qjs_get_context_ptr") (result i32) (i32.const 100)) + (func (export "qjs_get_runtime_ptr") (result i32) (i32.const 200)) + ;; js_register(ctx, tableIndex): remembers a function an extension adds. + (func (export "js_register") (param i32 i32) (result i32) + (i32.store + (i32.add (i32.const 512) (i32.mul (global.get $registered) (i32.const 4))) + (local.get 1)) + (global.set $registered (i32.add (global.get $registered) (i32.const 1))) + (global.get $registered)) + ;; js_call(n, x): calls the n-th registered function through the table. + (func (export "js_call") (param i32 i32) (result i32) + (call_indirect (type $i_i) + (local.get 1) + (i32.load (i32.add (i32.const 512) (i32.mul (local.get 0) (i32.const 4)))))) + (func (export "helper") (param i32) (result i32) (i32.add (local.get 0) (i32.const 1000))) +) diff --git a/tests/wasm-spec/corpus/jsapi-own.wat b/tests/wasm-spec/corpus/jsapi-own.wat new file mode 100644 index 000000000..be2795793 --- /dev/null +++ b/tests/wasm-spec/corpus/jsapi-own.wat @@ -0,0 +1,11 @@ +;; tests/corpus/webassembly-jsapi.mjs: a module that defines and exports a +;; memory, a table and globals, exported twice each, for identity and for +;; importing what one instance exports into another. +(module + (memory (export "mem") (export "mem2") 1 4) + (table (export "tab") 2 8 funcref) + (global (export "g") (export "g2") (mut i32) (i32.const 7)) + (global (export "fixed") f64 (f64.const 1.5)) + (func (export "id") (param i32) (result i32) (local.get 0)) + (elem (i32.const 1) 0) +) diff --git a/tests/wasm-spec/corpus/jsapi.wat b/tests/wasm-spec/corpus/jsapi.wat new file mode 100644 index 000000000..63e2d4241 --- /dev/null +++ b/tests/wasm-spec/corpus/jsapi.wat @@ -0,0 +1,37 @@ +;; tests/corpus/webassembly-jsapi.mjs: imports of every kind, filled by the +;; Memory, Table and Global constructors. The program's base64 copy also +;; carries custom sections ("dylink.0" [1 2 3], "meta" "hello", "dylink.0" +;; [4 5] and "" []), appended after wat2wasm. +(module + (import "env" "log" (func $log (param i32))) + (import "env" "memory" (memory 1 4)) + (import "env" "table" (table 2 8 funcref)) + (import "env" "counter" (global $counter (mut i32))) + (import "env" "scale" (global $scale f64)) + (import "env" "big" (global $big (mut i64))) + (import "env" "ref" (global $ref (mut externref))) + (type $i_i (func (param i32) (result i32))) + (func $double (type $i_i) (i32.mul (local.get 0) (i32.const 2))) + (func $square (type $i_i) (i32.mul (local.get 0) (local.get 0))) + (elem (i32.const 0) $double $square) + (func (export "bump") (result i32) + (global.set $counter (i32.add (global.get $counter) (i32.const 1))) + (global.get $counter)) + (func (export "scaled") (param f64) (result f64) (f64.mul (local.get 0) (global.get $scale))) + (func (export "bigAdd") (param i64) (global.set $big (i64.add (global.get $big) (local.get 0)))) + (func (export "getRef") (result externref) (global.get $ref)) + (func (export "setRef") (param externref) (global.set $ref (local.get 0))) + (func (export "apply") (param i32 i32) (result i32) + (call_indirect (type $i_i) (local.get 1) (local.get 0))) + (func (export "store") (param i32 i32) (i32.store8 (local.get 0) (local.get 1))) + (func (export "load") (param i32) (result i32) (i32.load8_u (local.get 0))) + (func (export "size") (result i32) (memory.size)) + (func (export "grow") (param i32) (result i32) (memory.grow (local.get 0))) + (func (export "tableSize") (result i32) (table.size 0)) + (func (export "say") (param i32) (call $log (local.get 0))) + (export "memory" (memory 0)) + (export "table" (table 0)) + (export "counter" (global $counter)) + (export "alsoCounter" (global $counter)) + (export "double" (func $double)) +) diff --git a/tests/wasm-spec/gen.mjs b/tests/wasm-spec/gen.mjs index 3019c56f5..bfb6dd9ee 100644 --- a/tests/wasm-spec/gen.mjs +++ b/tests/wasm-spec/gen.mjs @@ -10,10 +10,10 @@ * --spec /test/core * * Left out: modules given as text, modules the decoder does not accept - * within the translatable feature level, modules that import a spectest - * memory or table (the Memory and Table constructors are not supported - * yet) and every command that targets them, and commands with v128 - * values. Also left out, because they test NaN payloads, which neither + * within the translatable feature level and every command that targets + * them, and commands with v128 values. The runner's spectest module + * provides the harness's memory and table (new WebAssembly.Memory and + * Table), so modules that import them run. Also left out, because they test NaN payloads, which neither * side preserves the way the other does: actions whose f32 or f64 * arguments are NaNs other than the canonical quiet NaN (V8 and scriptc * carry NaN payloads through JavaScript numbers differently), and @@ -113,6 +113,7 @@ export const GROUPS = { "exports", "linking", ], + imports: ["imports", "table", "names"], }; const MAX_ACTIONS = 400; @@ -204,13 +205,7 @@ for (const [group, names] of Object.entries(GROUPS)) { } const bytes = readFileSync(join(dir, c.filename)); const summary = analyzeWasm(new Uint8Array(bytes)); - const imports = WebAssembly.Module.imports(new WebAssembly.Module(bytes)); - const usable = - summary.status === "valid" && - !imports.some( - (i) => i.module === "spectest" && (i.kind === "memory" || i.kind === "table"), - ); - if (!usable) { + if (summary.status !== "valid") { if (c.type === "module") { currentSkipped = true; if (c.name) skipped.add(c.name); diff --git a/tests/wasm-spec/runner.mjs b/tests/wasm-spec/runner.mjs index 5c1b8b6b4..25e8ed393 100644 --- a/tests/wasm-spec/runner.mjs +++ b/tests/wasm-spec/runner.mjs @@ -5,7 +5,8 @@ * error. Module bytes are inline base64 literals in each program's * data.mjs, which the build translates. */ -/** The spec harness's `spectest` module, without its memory and table. */ +/** The spec harness's `spectest` module (the reference interpreter's JS + * harness), with its memory and table. */ function spectestImports(log) { return { print() { @@ -33,6 +34,8 @@ function spectestImports(log) { global_i64: 666n, global_f32: 666.6, global_f64: 666.6, + table: new WebAssembly.Table({ initial: 10, maximum: 20, element: "anyfunc" }), + memory: new WebAssembly.Memory({ initial: 1, maximum: 2 }), }; } From f42fd146d9eaff8d192d9326ccbd97d7499f0997 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Mon, 19 Oct 2026 14:00:23 -0700 Subject: [PATCH 016/122] Lower esbuild's lazy ES modules; select @types-typed packages by default An __esm initializer becomes a run-once closure over its body in the bundle's own scope. Interop bindings it assigns once from require() become that require at the assignment, with their member reads respelled as reads of the required module; a namespace read only through members becomes an object literal of getters over the module's live bindings. Asynchronous initializers, initializer results, and namespaces kept as values (a memoized lazy loader) stay refused with a message naming them. The default selection now also compiles a package typed by a DefinitelyTyped twin that describes its installed release (same major, or major.minor below 1.0), and untyped dependencies of compiled packages. A twin for another release is refused with both versions. Co-Authored-By: Claude Opus 5.5 --- .../src/frontend/npm-static-bundled-chunks.ts | 89 +-- .../frontend/npm-static-bundled-esm.test.ts | 149 +++++ .../src/frontend/npm-static-bundled-esm.ts | 582 ++++++++++++++++++ .../src/frontend/npm-static-rewrite-host.ts | 12 + .../src/frontend/npm-static-rewrite-syntax.ts | 1 + packages/compiler/src/frontend/npm-static.ts | 53 +- packages/compiler/src/frontend/pipeline.ts | 21 +- packages/compiler/src/frontend/services.ts | 10 +- .../node_modules/@types/twinstale/index.d.ts | 1 + .../@types/twinstale/package.json | 5 + .../node_modules/twinstale/index.js | 6 + .../node_modules/twinstale/package.json | 5 + tests/fixtures/npm-static/twinstale-cli.ts | 5 + .../npm/cases/2563-bundled-esm-inits/main.ts | 16 + .../npm/node_modules/gtesm/dist/index.d.ts | 2 + .../npm/node_modules/gtesm/dist/index.js | 90 +++ .../npm/node_modules/gtesm/package.json | 10 + .../npm/node_modules/gtesmws/dist/index.d.ts | 2 + .../npm/node_modules/gtesmws/dist/index.js | 1 + .../npm/node_modules/gtesmws/package.json | 10 + tests/harness/npm-cases.ts | 4 +- tests/harness/npm-static.test.ts | 54 +- 22 files changed, 1064 insertions(+), 64 deletions(-) create mode 100644 packages/compiler/src/frontend/npm-static-bundled-esm.test.ts create mode 100644 packages/compiler/src/frontend/npm-static-bundled-esm.ts create mode 100644 tests/fixtures/npm-static/node_modules/@types/twinstale/index.d.ts create mode 100644 tests/fixtures/npm-static/node_modules/@types/twinstale/package.json create mode 100644 tests/fixtures/npm-static/node_modules/twinstale/index.js create mode 100644 tests/fixtures/npm-static/node_modules/twinstale/package.json create mode 100644 tests/fixtures/npm-static/twinstale-cli.ts create mode 100644 tests/fixtures/npm/cases/2563-bundled-esm-inits/main.ts create mode 100644 tests/fixtures/npm/node_modules/gtesm/dist/index.d.ts create mode 100644 tests/fixtures/npm/node_modules/gtesm/dist/index.js create mode 100644 tests/fixtures/npm/node_modules/gtesm/package.json create mode 100644 tests/fixtures/npm/node_modules/gtesmws/dist/index.d.ts create mode 100644 tests/fixtures/npm/node_modules/gtesmws/dist/index.js create mode 100644 tests/fixtures/npm/node_modules/gtesmws/package.json diff --git a/packages/compiler/src/frontend/npm-static-bundled-chunks.ts b/packages/compiler/src/frontend/npm-static-bundled-chunks.ts index cdb9dff64..28fe4a1b9 100644 --- a/packages/compiler/src/frontend/npm-static-bundled-chunks.ts +++ b/packages/compiler/src/frontend/npm-static-bundled-chunks.ts @@ -31,7 +31,7 @@ export interface BundledChunkSplit { chunks: { path: string; source: string }[]; } -interface Edit { +export interface Edit { start: number; end: number; text: string; @@ -42,11 +42,11 @@ const CHUNK_SHAPE_DEGRADE = "recognizer cannot verify"; /** The helper a counter-suffixed bundler name spells (`__commonJS2`). */ -function helperBase(name: string): string { +export function helperBase(name: string): string { return /^__[A-Za-z]+\d+$/.test(name) ? name.replace(/\d+$/, "") : name; } -function unparen(e: ts.Expression): ts.Expression { +export function unparen(e: ts.Expression): ts.Expression { while (ts.isParenthesizedExpression(e)) e = e.expression; return e; } @@ -54,7 +54,7 @@ function unparen(e: ts.Expression): ts.Expression { /** True for an identifier occurrence that reads or writes a binding (as * opposed to a property name, a label, or a declaration's own name). * Shorthand property values count: `{ x }` reads x. */ -function isReference(id: ts.Identifier): boolean { +export function isReference(id: ts.Identifier): boolean { const p = id.parent; if (p === undefined) return false; if (ts.isPropertyAccessExpression(p)) return p.expression === id; @@ -83,7 +83,7 @@ function isReference(id: ts.Identifier): boolean { /** Names a subtree declares anywhere inside it: variables (destructuring * included), parameters, function and class names, catch bindings. */ -function declaredNames(root: ts.Node): Set { +export function declaredNames(root: ts.Node): Set { const names = new Set(); // Binding patterns nest BindingElements, which the walk visits too. ts.walkPreorder(root, (n) => { @@ -106,7 +106,7 @@ function declaredNames(root: ts.Node): Set { } /** Every binding reference inside a subtree. */ -function references(root: ts.Node): ts.Identifier[] { +export function references(root: ts.Node): ts.Identifier[] { const out: ts.Identifier[] = []; ts.walkPreorder(root, (n) => { if (n.kind >= ts.SyntaxKind.FirstJSDocNode && n.kind <= ts.SyntaxKind.LastJSDocNode) @@ -119,7 +119,7 @@ function references(root: ts.Node): ts.Identifier[] { /** True when the reference is written (assignment target, update operand, * loop target, delete operand). */ -function isWritten(id: ts.Identifier): boolean { +export function isWritten(id: ts.Identifier): boolean { let child: ts.Node = id; for (let p = child.parent; p !== undefined; child = p, p = p.parent) { if (ts.isBinaryExpression(p)) { @@ -247,7 +247,7 @@ interface TopLevel { } /** True when the file opens with a "use strict" directive prologue. */ -function strictPrologue(stmts: readonly ts.Statement[]): boolean { +export function strictPrologue(stmts: readonly ts.Statement[]): boolean { for (const s of stmts) { if (!ts.isExpressionStatement(s) || !ts.isStringLiteral(s.expression)) return false; if (s.expression.text === "use strict") return true; @@ -430,7 +430,7 @@ export function splitBundledChunks( return { text: applyEdits(source, 0, source.length, bundleEdits), chunks: out }; } -function countDeclarations(sf: ts.SourceFile, name: string): number { +export function countDeclarations(sf: ts.SourceFile, name: string): number { let n = 0; ts.walkPreorder(sf, (node) => { if ( @@ -454,7 +454,7 @@ function countDeclarations(sf: ts.SourceFile, name: string): number { /** Applies non-overlapping edits inside [from, to): replaced spans keep * their newlines (blanked text stays line-aligned); edits nested in a * blanked span vanish with it. */ -function applyEdits(source: string, from: number, to: number, edits: Edit[]): string { +export function applyEdits(source: string, from: number, to: number, edits: Edit[]): string { const inside = edits .filter((e) => e.start >= from && e.end <= to) .sort((a, b) => a.start - b.start || b.end - a.end); @@ -472,37 +472,13 @@ function applyEdits(source: string, from: number, to: number, edits: Edit[]): st return text + source.slice(at, to); } -/** The virtual module text for one chunk, or a degrade reason. */ -function chunkSource( - sf: ts.SourceFile, - chunk: Chunk, - chunks: ReadonlyMap, - top: ReadonlyMap, - stable: (name: string) => boolean, - callEdits: readonly Edit[], - bundleStrict: boolean, -): string | { degrade: string } { - const why = (reason: string): { degrade: string } => ({ - degrade: `its bundled CommonJS module '${chunk.name}' ${reason}`, - }); - const [exportsParam, moduleParam] = chunk.params; - const body = chunk.body; - const local = declaredNames(body); - for (const p of chunk.params) { - if ( - local.has(p) || - (p !== "exports" && local.has("exports")) || - (p !== "module" && local.has("module")) - ) - return why( - "rebinds its module parameters inside its body (an identifier-minified bundle the recognizer cannot respell)", - ); - } - - /* The factory's own frame must stay unobservable: its receiver, its - * arguments object, and a top-level return have no module equivalent. */ +/** How code in a factory body (a chunk or a lazy ES-module initializer) + * observes the factory's own frame, or null: its receiver, its arguments + * object, `super`, a meta property, or (when `returns`) a top-level + * return have no module-scope equivalent. */ +export function factoryFrameUse(body: ts.Block, returns: boolean): string | null { let frameUse: string | null = null; - /** The innermost frame boundary between a node and the chunk body. */ + /** The innermost frame boundary between a node and the factory body. */ const frameOf = (n: ts.Node): "factory" | "arrow" | "function" => { let arrow = false; for (let p = n.parent; p !== undefined && p !== body; p = p.parent) { @@ -532,12 +508,43 @@ function chunkSource( use = "reads its factory's arguments object"; else if (ts.isMetaProperty(n)) use = "reads a meta property at its top level"; else if (ts.isReturnStatement(n)) { - if (frameOf(n) === "factory") frameUse = "returns from its top level"; + if (returns && frameOf(n) === "factory") frameUse = "returns from its top level"; return undefined; } if (use !== null && frameOf(n) !== "function") frameUse = use; return undefined; }); + return frameUse; +} + +/** The virtual module text for one chunk, or a degrade reason. */ +function chunkSource( + sf: ts.SourceFile, + chunk: Chunk, + chunks: ReadonlyMap, + top: ReadonlyMap, + stable: (name: string) => boolean, + callEdits: readonly Edit[], + bundleStrict: boolean, +): string | { degrade: string } { + const why = (reason: string): { degrade: string } => ({ + degrade: `its bundled CommonJS module '${chunk.name}' ${reason}`, + }); + const [exportsParam, moduleParam] = chunk.params; + const body = chunk.body; + const local = declaredNames(body); + for (const p of chunk.params) { + if ( + local.has(p) || + (p !== "exports" && local.has("exports")) || + (p !== "module" && local.has("module")) + ) + return why( + "rebinds its module parameters inside its body (an identifier-minified bundle the recognizer cannot respell)", + ); + } + + const frameUse = factoryFrameUse(body, true); if (frameUse !== null) return why(frameUse); const edits: Edit[] = callEdits.filter((e) => e.start >= body.pos && e.end <= body.end); diff --git a/packages/compiler/src/frontend/npm-static-bundled-esm.test.ts b/packages/compiler/src/frontend/npm-static-bundled-esm.test.ts new file mode 100644 index 000000000..9c064cb55 --- /dev/null +++ b/packages/compiler/src/frontend/npm-static-bundled-esm.test.ts @@ -0,0 +1,149 @@ +import { spawnSync } from "node:child_process"; +import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { afterAll, expect, test } from "vitest"; +import { nodeFrontendServices } from "./services-node.js"; + +const helpers = `var __getOwnPropNames = Object.getOwnPropertyNames; +var __esm = (fn, res) => function __init() { + return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res; +}; +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __create = Object.create; +var __getProtoOf = Object.getPrototypeOf; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( + isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, + mod +)); +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); +`; + +const dirs: string[] = []; +afterAll(() => { + for (const dir of dirs) rmSync(dir, { recursive: true, force: true }); +}); + +function lower(source: string, file = "/pkg/dist/index.js") { + return nodeFrontendServices().lowerEsmInits(source, file); +} + +function run(file: string) { + const result = spawnSync(process.execPath, [file], { encoding: "utf8" }); + return { status: result.status, stdout: result.stdout, stderr: result.stderr }; +} + +function expectSameBehavior(source: string): string { + const dir = mkdtempSync(join(tmpdir(), "scriptc-esm-inits-")); + dirs.push(dir); + const original = join(dir, "original.js"); + const lowered = join(dir, "index.js"); + writeFileSync(original, source); + const result = lower(source, lowered); + expect(typeof result).toBe("string"); + writeFileSync(lowered, result as string); + const oracle = run(original); + expect(oracle.status).toBe(0); + expect(run(lowered)).toEqual(oracle); + return result as string; +} + +const lazyModule = ` +function helper(x) { COUNT++; return "[" + x + "]"; } +var COUNT; +var init_helper = __esm({ + "src/helper.ts"() { + console.log("helper init"); + COUNT = 0; + } +}); +var lazy_exports = {}; +__export(lazy_exports, { + VALUE: () => VALUE, + value: () => value +}); +function value() { + return VALUE + ":" + COUNT + ":" + import_os.default.EOL.length; +} +var path, import_os, VALUE; +var init_lazy = __esm({ + "src/lazy.ts"() { + path = __toESM(require("path")); + import_os = __toESM(require("os")); + init_helper(); + console.log("lazy init"); + VALUE = helper(path.basename("/a/b.txt")); + } +}); +`; + +test("initializers run once, at their first call, and namespaces read live bindings", () => { + const lowered = expectSameBehavior(`"use strict"; +${helpers}${lazyModule} +console.log("start"); +function load() { + const lazy = (init_lazy(), __toCommonJS(lazy_exports)); + return lazy.value() + " " + lazy.VALUE; +} +console.log(load()); +COUNT = 5; +console.log(load(), (init_lazy(), __toCommonJS(lazy_exports)).value()); +`); + expect(lowered).not.toContain("__esm({"); + expect(lowered).not.toContain("import_os"); + expect(lowered).toContain('(require("os")).EOL'); +}); + +test("minified initializers sharing a var statement lower the same way", () => { + expectSameBehavior( + helpers + + `var x_exports={};__export(x_exports,{v:()=>v});var v,import_path,init_x=__esm({"x.ts"(){"use strict";import_path=__toESM(require("path")),v=import_path.default.sep}});` + + `console.log((init_x(),__toCommonJS(x_exports)).v);`, + ); +}); + +test.each([ + [ + "is asynchronous", + `var init_a = __esm({ async "a.ts"() { await 0; } });\ninit_a();`, + ], + [ + "is used other than as a statement call", + `var init_a = __esm({ "a.ts"() {} });\nvar r = init_a();`, + ], + [ + "reads its factory's receiver", + `var init_a = __esm({ "a.ts"() { console.log(this); } });\ninit_a();`, + ], + [ + "stores the namespace of a bundled ES module ('a_exports') in a variable", + `var a_exports = {};\n__export(a_exports, { v: () => v });\nvar v, init_a = __esm({ "a.ts"() { v = 1; } });\nvar ns, load = () => ns ??= __toESM((init_a(), __toCommonJS(a_exports)));\nload();`, + ], + [ + "reads the namespace of a bundled ES module ('a_exports')", + `var a_exports = {};\n__export(a_exports, { v: () => v });\nvar v, init_a = __esm({ "a.ts"() { v = 1; } });\nconsole.log(Object.keys((init_a(), __toCommonJS(a_exports))));`, + ], +])("an initializer that %s keeps the package refused", (reason, code) => { + expect(lower(`${helpers}${code}\n`)).toEqual({ + degrade: expect.stringContaining(reason) as string, + }); +}); + +test("files without __esm initializers and ES-module bundles are left alone", () => { + expect(lower("exports.x = 1;\n")).toBeNull(); + expect(lower(`${helpers}var init_a = __esm({ "a.ts"() {} });\ninit_a();\nexport {};\n`)).toBeNull(); +}); diff --git a/packages/compiler/src/frontend/npm-static-bundled-esm.ts b/packages/compiler/src/frontend/npm-static-bundled-esm.ts new file mode 100644 index 000000000..ae5985e51 --- /dev/null +++ b/packages/compiler/src/frontend/npm-static-bundled-esm.ts @@ -0,0 +1,582 @@ +/* Lazily initialized ES modules in a CommonJS bundle. esbuild scope-hoists + * every ES module it bundles into the bundle's own scope; one that some + * code require()s instead of importing runs from a lazy initializer, + * + * var COUNT, import_fs; + * var init_x = __esm({ "src/x.ts"() { import_fs = __toESM(require("fs")); COUNT = 0; } }); + * + * and the requiring site reads its namespace as + * `(init_x(), __toCommonJS(x_exports))`. `init_x()` runs the body once (a + * call during the body answers immediately). The pass below keeps that + * shape in the bundle's scope and respells only what has no static + * representation: + * + * - the initializer becomes a run-once closure over the same body; + * - an interop binding the body assigns once from require() (`import_fs` + * above) becomes that require at the assignment's position, and its + * member reads become member reads of the required module, exactly + * like the top-level interop erasure (npm-static-rewrite-syntax.ts); + * - a namespace read only through member reads becomes an object + * literal of getters over the module's live bindings (what + * __toCommonJS copies, minus the non-enumerable __esModule mark no + * member read can reach). + * + * The respelled interop reads resolve the required module even if they + * ran before the initializer, where Node would read a member of + * undefined and throw; esbuild only reaches them through the module's + * own code, which runs after its initializer. Shapes outside the + * recognized forms answer a degrade reason. */ +import * as ts from "./ts7/syntax.js"; +import { bareRequireSpecOf } from "./cjs-syntax.js"; +import { helperTokens } from "./helper-tokens.js"; +import { + applyEdits, + countDeclarations, + declaredNames, + factoryFrameUse, + helperBase, + isWritten, + references, + unparen, + type Edit, +} from "./npm-static-bundled-chunks.js"; + +export interface EsmInitHost { + requireTargetEsModuleStamped(fromFile: string, specifier: string): boolean; +} + +const ESM_SHAPE_DEGRADE = + "its shipped JS initializes bundled ES modules through the __esm helper in a shape the " + + "recognizer cannot verify"; + +/* The recognized __esm forms over its parameter names (FN, RES) and the + * own-key reader (KEYS): keyed `(FN, RES) => function [name]() { return + * ; }` with a one-method object argument, and arrow + * `(FN, RES) => () => ()` with a function argument. */ +const KEYED_RETURN = "FN && (RES = (0, FN[KEYS(FN)[0]])(FN = 0)), RES"; +const ARROW_BODY = "(FN && (RES = FN(FN = 0)), RES)"; + +function template(text: string, fn: string, res: string, keys: string): string | null { + return helperTokens( + text + .replace(/\bFN\b/g, fn) + .replace(/\bRES\b/g, res) + .replace(/\bKEYS\b/g, keys), + ); +} + +function esmHelperForm( + init: ts.Expression, + sf: ts.SourceFile, + ownNames: (name: string) => boolean, +): "keyed" | "arrow" | null { + const fn = unparen(init); + if (!ts.isArrowFunction(fn) || fn.parameters.nodes.length !== 2 || ts.isBlock(fn.body)) + return null; + const [p0, p1] = fn.parameters.nodes; + if ( + !p0 || + !p1 || + !ts.isIdentifier(p0.name) || + !ts.isIdentifier(p1.name) || + p0.initializer || + p1.initializer || + p0.name.text === p1.name.text + ) + return null; + const [f, r] = [p0.name.text, p1.name.text]; + const body = unparen(fn.body); + if (ts.isFunctionExpression(body)) { + if (body.parameters.nodes.length !== 0 || body.asteriskToken || body.modifiers?.nodes.length) + return null; + const stmts = body.body.statements.nodes; + const ret = stmts[0]; + if (stmts.length !== 1 || !ret || !ts.isReturnStatement(ret) || !ret.expression) return null; + const text = ret.expression.getText(sf); + const keys = /\[\s*([A-Za-z_$][\w$]*(?:\.keys)?)\s*\(/.exec(text)?.[1]; + if (keys === undefined || (keys !== "Object.keys" && !ownNames(keys))) return null; + return helperTokens(text) === template(KEYED_RETURN, f, r, keys) ? "keyed" : null; + } + if (ts.isArrowFunction(body) && body.parameters.nodes.length === 0 && !ts.isBlock(body.body)) { + const actual = helperTokens(body.body.getText(sf)); + return actual !== null && actual === template(ARROW_BODY, f, r, "") ? "arrow" : null; + } + return null; +} + +/** Pads the given declarators out of their statements (separating commas + * included; a statement whose declarators all go pads whole). */ +function removeDeclarators(sf: ts.SourceFile, dead: ReadonlySet): Edit[] { + const edits: Edit[] = []; + const stmts = new Set(); + for (const d of dead) { + const stmt = d.parent?.parent; + if (stmt !== undefined && ts.isVariableStatement(stmt)) stmts.add(stmt); + } + for (const stmt of stmts) { + const decls = stmt.declarationList.declarations.nodes; + const deadAt = decls.map((d) => dead.has(d)); + if (deadAt.every((x) => x)) { + edits.push({ start: stmt.getStart(sf), end: stmt.getEnd(), text: "" }); + continue; + } + const lastLive = deadAt.lastIndexOf(false); + for (let i = 0; i < decls.length; i++) { + if (!deadAt[i]) continue; + edits.push( + i < lastLive + ? { start: decls[i]!.getStart(sf), end: decls[i + 1]!.getStart(sf), text: "" } + : { start: decls[lastLive]!.getEnd(), end: decls[i]!.getEnd(), text: "" }, + ); + } + } + return edits; +} + +/** The elements of an expression statement's comma chain, in order. */ +function commaElements(e: ts.Expression): ts.Expression[] { + const out: ts.Expression[] = []; + const visit = (x: ts.Expression): void => { + if (ts.isBinaryExpression(x) && x.operatorToken.kind === ts.SyntaxKind.CommaToken) { + visit(x.left); + visit(x.right); + } else out.push(x); + }; + visit(e); + return out; +} + +/** True when a call's result is discarded: an expression statement or a + * non-final comma operand (parentheses included). */ +function resultUnused(call: ts.Expression): boolean { + let child: ts.Node = call; + let p = call.parent; + while (p !== undefined && ts.isParenthesizedExpression(p)) { + child = p; + p = p.parent; + } + if (p === undefined) return false; + if (ts.isExpressionStatement(p)) return true; + if (ts.isBinaryExpression(p) && p.operatorToken.kind === ts.SyntaxKind.CommaToken) { + if (p.left === child) return true; + return resultUnused(p); + } + return false; +} + +/** True when a member access is a plain read (not written, deleted or + * updated). */ +function plainRead(access: ts.Expression): boolean { + const p = access.parent; + if (p === undefined) return true; + if (ts.isDeleteExpression(p)) return false; + if ( + ts.isBinaryExpression(p) && + p.left === access && + p.operatorToken.kind >= ts.SyntaxKind.FirstAssignment && + p.operatorToken.kind <= ts.SyntaxKind.LastAssignment + ) + return false; + if ( + (ts.isPrefixUnaryExpression(p) || ts.isPostfixUnaryExpression(p)) && + (p.operator === ts.SyntaxKind.PlusPlusToken || p.operator === ts.SyntaxKind.MinusMinusToken) + ) + return false; + return true; +} + +/** The `__export(T, { name: () => value, … })` getter table of T, or null. */ +function exportTableOf( + call: ts.CallExpression, +): { target: string; entries: [string, ts.Expression][] } | null { + if (!ts.isIdentifier(call.expression) || helperBase(call.expression.text) !== "__export") + return null; + const [target, table] = call.arguments.nodes; + if ( + call.arguments.nodes.length !== 2 || + !target || + !table || + !ts.isIdentifier(target) || + !ts.isObjectLiteralExpression(table) + ) + return null; + const entries: [string, ts.Expression][] = []; + for (const prop of table.properties.nodes) { + if (!ts.isPropertyAssignment(prop)) return null; + if (!ts.isIdentifier(prop.name) && !ts.isStringLiteral(prop.name)) return null; + const getter = unparen(prop.initializer); + if (!ts.isArrowFunction(getter) || getter.parameters.nodes.length !== 0) return null; + if (ts.isBlock(getter.body)) return null; + entries.push([prop.name.text, getter.body]); + } + return { target: target.text, entries }; +} + +/** Lowers a CommonJS bundle's __esm initializers (see the header). Null: + * nothing to lower. */ +export function lowerBundledEsmInits( + sf: ts.SourceFile, + filePath: string, + host: EsmInitHost, +): string | { degrade: string } | null { + const source = sf.text; + if (!source.includes("__esm")) return null; + if (!filePath.endsWith(".cjs") && ts.isExternalModule(sf)) return null; + + interface Top { + stmt: ts.Statement; + node: ts.VariableDeclaration | ts.FunctionDeclaration; + } + const top = new Map(); + const duplicate = new Set(); + for (const stmt of sf.statements.nodes) { + const add = (name: string, node: Top["node"]): void => { + if (top.has(name)) duplicate.add(name); + else top.set(name, { stmt, node }); + }; + if (ts.isFunctionDeclaration(stmt) && stmt.name !== undefined) add(stmt.name.text, stmt); + else if (ts.isVariableStatement(stmt)) { + for (const d of stmt.declarationList.declarations.nodes) { + if (ts.isIdentifier(d.name)) add(d.name.text, d); + } + } + } + const helpers = [...top.keys()].filter((n) => helperBase(n) === "__esm"); + if (helpers.length === 0) return null; + + const allRefs = references(sf); + const writes = new Map(); + for (const r of allRefs) if (isWritten(r)) writes.set(r.text, (writes.get(r.text) ?? 0) + 1); + const declaredAnywhere = declaredNames(sf); + if (declaredAnywhere.has("Object") || writes.has("Object")) return { degrade: ESM_SHAPE_DEGRADE }; + const stable = (name: string): boolean => !duplicate.has(name) && !writes.has(name); + const ownNames = (name: string): boolean => { + const t = top.get(name); + const init = t !== undefined && ts.isVariableDeclaration(t.node) ? t.node.initializer : undefined; + return ( + init !== undefined && + stable(name) && + helperTokens(init.getText(sf)) === helperTokens("Object.getOwnPropertyNames") + ); + }; + const forms = new Map(); + for (const name of helpers) { + const t = top.get(name)!; + const init = ts.isVariableDeclaration(t.node) ? t.node.initializer : undefined; + const form = init !== undefined && stable(name) ? esmHelperForm(init, sf, ownNames) : null; + if (form === null) return { degrade: ESM_SHAPE_DEGRADE }; + forms.set(name, form); + } + + // The initializers: `NAME = HELPER(factory)` declarators. + interface Init { + name: string; + call: ts.CallExpression; + body: ts.Block; + } + const inits = new Map(); + for (const [name, t] of top) { + if (!ts.isVariableDeclaration(t.node) || t.node.initializer === undefined) continue; + const call = unparen(t.node.initializer); + if (!ts.isCallExpression(call) || !ts.isIdentifier(call.expression)) continue; + const form = forms.get(call.expression.text); + if (form === undefined) continue; + if (call.arguments.nodes.length !== 1 || !stable(name)) return { degrade: ESM_SHAPE_DEGRADE }; + let factory: ts.Node = call.arguments.nodes[0]!; + if (form === "keyed") { + if (!ts.isObjectLiteralExpression(factory) || factory.properties.nodes.length !== 1) + return { degrade: ESM_SHAPE_DEGRADE }; + factory = factory.properties.nodes[0]!; + if (!ts.isMethodDeclaration(factory) || ts.isComputedPropertyName(factory.name)) + return { degrade: ESM_SHAPE_DEGRADE }; + } else { + factory = unparen(factory as ts.Expression); + if (!ts.isArrowFunction(factory) && !ts.isFunctionExpression(factory)) + return { degrade: ESM_SHAPE_DEGRADE }; + } + const fn = factory as ts.MethodDeclaration | ts.ArrowFunction | ts.FunctionExpression; + if (fn.modifiers?.nodes.some((m) => m.kind === ts.SyntaxKind.AsyncKeyword)) + return { + degrade: `its bundled ES module initializer '${name}' is asynchronous (top-level await), which the lazy initialization rewrite does not cover`, + }; + if ( + fn.body === undefined || + !ts.isBlock(fn.body) || + fn.asteriskToken !== undefined || + (fn.modifiers?.nodes.length ?? 0) > 0 || + fn.parameters.nodes.length > 0 + ) + return { degrade: ESM_SHAPE_DEGRADE }; + const frame = factoryFrameUse(fn.body, false); + if (frame !== null) + return { degrade: `its bundled ES module initializer '${name}' ${frame}` }; + inits.set(name, { name, call, body: fn.body }); + } + if (inits.size === 0) return null; + + const edits: Edit[] = []; + // Every initializer reference is a direct call whose result is unused. + for (const r of allRefs) { + if (!inits.has(r.text)) continue; + const call = r.parent; + if ( + call === undefined || + !ts.isCallExpression(call) || + call.expression !== r || + call.arguments.nodes.length !== 0 || + !resultUnused(call) + ) + return { + degrade: `its bundled ES module initializer '${r.text}' is used other than as a statement call`, + }; + } + for (const name of inits.keys()) { + if (countDeclarations(sf, name) !== 1) return { degrade: ESM_SHAPE_DEGRADE }; + } + + /* Interop bindings an initializer assigns once from require(). */ + const refsByName = new Map(); + for (const r of allRefs) { + const list = refsByName.get(r.text); + if (list === undefined) refsByName.set(r.text, [r]); + else list.push(r); + } + const deadDeclarators = new Set(); + for (const init of inits.values()) { + for (const stmt of init.body.statements.nodes) { + if (!ts.isExpressionStatement(stmt)) continue; + for (const el of commaElements(stmt.expression)) { + if ( + !ts.isBinaryExpression(el) || + el.operatorToken.kind !== ts.SyntaxKind.EqualsToken || + !ts.isIdentifier(el.left) + ) + continue; + const name = el.left.text; + const decl = top.get(name)?.node; + if ( + decl === undefined || + !ts.isVariableDeclaration(decl) || + decl.initializer !== undefined || + duplicate.has(name) || + writes.get(name) !== 1 || + countDeclarations(sf, name) !== 1 + ) + continue; + let rhs = unparen(el.right); + let isNodeMode = false; + let interop = false; + if ( + ts.isCallExpression(rhs) && + ts.isIdentifier(rhs.expression) && + helperBase(rhs.expression.text) === "__toESM" + ) { + const [arg, mode] = rhs.arguments.nodes; + if (!arg || rhs.arguments.nodes.length > 2) continue; + if (mode !== undefined) { + if (ts.isNumericLiteral(mode)) isNodeMode = Number(mode.text) !== 0; + else if (mode.kind === ts.SyntaxKind.TrueKeyword) isNodeMode = true; + else if (mode.kind !== ts.SyntaxKind.FalseKeyword) continue; + } + rhs = unparen(arg); + interop = true; + } + const spec = bareRequireSpecOf(rhs); + if (spec === null) continue; + const req = rhs.getText(sf); + const defaultIsModule = + interop && (isNodeMode || !host.requireTargetEsModuleStamped(filePath, spec)); + // Every other reference must be a plain member read. + const uses: Edit[] = []; + let ok = true; + for (const r of refsByName.get(name) ?? []) { + if (r === el.left) continue; + const access = r.parent; + if ( + access === undefined || + !ts.isPropertyAccessExpression(access) || + access.expression !== r || + !plainRead(access) + ) { + ok = false; + break; + } + if (defaultIsModule && access.name.text === "default") + uses.push({ start: access.getStart(sf), end: access.getEnd(), text: `(${req})` }); + else uses.push({ start: r.getStart(sf), end: r.getEnd(), text: req }); + } + if (!ok) continue; + edits.push({ start: el.getStart(sf), end: el.getEnd(), text: req }, ...uses); + deadDeclarators.add(decl); + } + } + } + edits.push(...removeDeclarators(sf, deadDeclarators)); + + /* Namespaces of lazily initialized modules read through members. */ + let mainTarget: string | null = null; + for (const stmt of sf.statements.nodes) { + if (!ts.isExpressionStatement(stmt) || !ts.isBinaryExpression(stmt.expression)) continue; + const e = stmt.expression; + const rhs = e.right; + if ( + e.operatorToken.kind === ts.SyntaxKind.EqualsToken && + ts.isPropertyAccessExpression(e.left) && + ts.isIdentifier(e.left.expression) && + e.left.expression.text === "module" && + e.left.name.text === "exports" && + ts.isCallExpression(rhs) && + ts.isIdentifier(rhs.expression) && + helperBase(rhs.expression.text) === "__toCommonJS" && + rhs.arguments.nodes.length === 1 && + ts.isIdentifier(rhs.arguments.nodes[0]!) + ) + mainTarget = (rhs.arguments.nodes[0] as ts.Identifier).text; + } + const tables = new Map(); + for (const stmt of sf.statements.nodes) { + if (!ts.isExpressionStatement(stmt) || !ts.isCallExpression(stmt.expression)) continue; + const table = exportTableOf(stmt.expression); + if (table !== null && table.target !== mainTarget) { + if (tables.has(table.target)) return { degrade: ESM_SHAPE_DEGRADE }; + tables.set(table.target, { stmt, entries: table.entries }); + } + } + const lowered = new Set(); + for (const r of allRefs) { + const table = tables.get(r.text); + if (table === undefined) continue; + const p = r.parent; + // The table's own declaration and __export call are handled below. + if (p !== undefined && ts.isVariableDeclaration(p) && p.name === r) continue; + if ( + p !== undefined && + ts.isCallExpression(p) && + p.arguments.nodes[0] === r && + ts.isExpressionStatement(p.parent) && + p.parent === table.stmt + ) + continue; + const why = `reads the namespace of a bundled ES module ('${r.text}') in a way the static rewrite cannot follow (only member reads of its exports compile)`; + if ( + p === undefined || + !ts.isCallExpression(p) || + !ts.isIdentifier(p.expression) || + helperBase(p.expression.text) !== "__toCommonJS" || + p.arguments.nodes.length !== 1 + ) + return { degrade: `its shipped JS ${why}` }; + // The namespace expression: `(init_x(), __toCommonJS(T))` or the call. + let ns: ts.Expression = p; + for (;;) { + const up = ns.parent; + if (up !== undefined && ts.isParenthesizedExpression(up)) ns = up; + else if ( + up !== undefined && + ts.isBinaryExpression(up) && + up.operatorToken.kind === ts.SyntaxKind.CommaToken && + up.right === ns + ) + ns = up; + else break; + } + const names = new Set(table.entries.map(([n]) => n)); + const memberRead = (access: ts.Node | undefined, of: ts.Node): boolean => + access !== undefined && + ts.isPropertyAccessExpression(access) && + access.expression === of && + names.has(access.name.text) && + plainRead(access); + const holder = ns.parent; + let readable = memberRead(holder, ns); + if ( + !readable && + holder !== undefined && + ts.isVariableDeclaration(holder) && + holder.initializer === ns && + ts.isIdentifier(holder.name) + ) { + const local = holder.name.text; + const localRefs = (refsByName.get(local) ?? []).filter((x) => x !== holder.name); + readable = + countDeclarations(sf, local) === 1 && + !writes.has(local) && + localRefs.every((x) => memberRead(x.parent, x)); + } + if (!readable) { + // A namespace kept for later (`x ??= __toESM(ns)`, a hand-rolled + // lazy loader) is a value with no static representation. + let outer: ts.Node | undefined = holder; + while ( + outer !== undefined && + (ts.isParenthesizedExpression(outer) || + (ts.isCallExpression(outer) && + ts.isIdentifier(outer.expression) && + helperBase(outer.expression.text) === "__toESM")) + ) + outer = outer.parent; + if ( + outer !== undefined && + ts.isBinaryExpression(outer) && + outer.operatorToken.kind >= ts.SyntaxKind.FirstAssignment && + outer.operatorToken.kind <= ts.SyntaxKind.LastAssignment + ) + return { + degrade: `its shipped JS stores the namespace of a bundled ES module ('${r.text}') in a variable (a memoized lazy loader), a module object the static rewrite cannot represent`, + }; + return { degrade: `its shipped JS ${why}` }; + } + // The getters read top-level bindings: nothing between the use site + // and the top level may rebind one of their names. + const read = new Set(); + for (const [, value] of table.entries) for (const x of references(value)) read.add(x.text); + for (let a = p.parent; a !== undefined && !ts.isSourceFile(a); a = a.parent) { + if (ts.isFunctionLike(a) || ts.isBlock(a) || ts.isClassDeclaration(a) || ts.isClassExpression(a)) { + for (const n of declaredNames(a)) { + if (read.has(n)) return { degrade: `its shipped JS ${why}` }; + } + } + } + const getters = table.entries + .map( + ([n, value]) => + `get ${/^[A-Za-z_$][\w$]*$/.test(n) ? n : JSON.stringify(n)}() { return ${applyEdits(source, value.getStart(sf), value.getEnd(), edits)}; }`, + ) + .join(", "); + edits.push({ start: p.getStart(sf), end: p.getEnd(), text: `{ ${getters} }` }); + lowered.add(r.text); + } + for (const target of lowered) { + const table = tables.get(target)!; + const decl = top.get(target)?.node; + if ( + decl === undefined || + !ts.isVariableDeclaration(decl) || + decl.initializer === undefined || + !ts.isObjectLiteralExpression(unparen(decl.initializer)) || + (unparen(decl.initializer) as ts.ObjectLiteralExpression).properties.nodes.length !== 0 || + writes.has(target) + ) + return { degrade: ESM_SHAPE_DEGRADE }; + edits.push({ start: table.stmt.getStart(sf), end: table.stmt.getEnd(), text: "" }); + edits.push(...removeDeclarators(sf, new Set([decl]))); + } + + /* The run-once closures. */ + for (const init of inits.values()) { + const { call, body } = init; + edits.push( + { + start: call.getStart(sf), + end: body.getStart(sf) + 1, + text: "(() => { let __scriptc_done = false; function __scriptc_init() {", + }, + { + start: body.getEnd() - 1, + end: call.getEnd(), + text: "} return function () { if (__scriptc_done) return; __scriptc_done = true; __scriptc_init(); }; })()", + }, + ); + } + return applyEdits(source, 0, source.length, edits); +} diff --git a/packages/compiler/src/frontend/npm-static-rewrite-host.ts b/packages/compiler/src/frontend/npm-static-rewrite-host.ts index 524239901..9ff14c455 100644 --- a/packages/compiler/src/frontend/npm-static-rewrite-host.ts +++ b/packages/compiler/src/frontend/npm-static-rewrite-host.ts @@ -11,6 +11,7 @@ import { import { resolveExports } from "./resolve.js"; import { packageNameOfSpecifier } from "./workspace-registry.js"; import { isBundlerCjsCandidate, rewriteBundlerCjsSyntax } from "./npm-static-rewrite-syntax.js"; +import { lowerBundledEsmInits } from "./npm-static-bundled-esm.js"; const NODE_REQUIRE_CONDITIONS = new Set(["require", "node", "default"]); @@ -164,6 +165,17 @@ function starTargetNames(parser: Ts7SourceParser, file: string): Set { } } +export function lowerEsmInitsWithParser( + parser: Ts7SourceParser, + source: string, + filePath: string, +): string | { degrade: string } | null { + if (!source.includes("__esm")) return null; + return lowerBundledEsmInits(parser.parse(filePath, source, "js"), filePath, { + requireTargetEsModuleStamped: (from, spec) => requireTargetEsModuleStamped(parser, from, spec), + }); +} + export function rewriteBundlerCjsWithParser( parser: Ts7SourceParser, source: string, diff --git a/packages/compiler/src/frontend/npm-static-rewrite-syntax.ts b/packages/compiler/src/frontend/npm-static-rewrite-syntax.ts index 1730e1153..08dbc0db5 100644 --- a/packages/compiler/src/frontend/npm-static-rewrite-syntax.ts +++ b/packages/compiler/src/frontend/npm-static-rewrite-syntax.ts @@ -893,6 +893,7 @@ export function rewriteBundlerCjsSyntax( "__toESM", "__toCommonJS", "__commonJS", + "__esm", "__exportStar", "__createBinding", "__setModuleDefault", diff --git a/packages/compiler/src/frontend/npm-static.ts b/packages/compiler/src/frontend/npm-static.ts index 7dffd05f7..7c9b7f6a1 100644 --- a/packages/compiler/src/frontend/npm-static.ts +++ b/packages/compiler/src/frontend/npm-static.ts @@ -407,6 +407,15 @@ export function npmStaticFsShadow(services: FrontendServices): NpmStaticFsShadow for (const chunk of split.chunks) setVirtualSource(chunk.path, chunk.source); original = split.text; } + // Then its lazily initialized ES modules become run-once closures. + const esm = original === null ? null : services.lowerEsmInits(original, path); + if (esm !== null && typeof esm === "object") { + reportFileDegrade(path, target.pkg, esm.degrade); + original = null; + } else if (esm !== null) { + original = esm; + } + const respelled = split !== null || esm !== null; if (original !== null) { const namepaths = services.jsDocNamepaths(path, original); const source = namepaths ?? original; @@ -441,7 +450,7 @@ export function npmStaticFsShadow(services: FrontendServices): NpmStaticFsShadow findWidened?.text ?? classFields?.text ?? namepaths ?? - (split !== null ? original : null); + (respelled ? original : null); } } } catch { @@ -492,6 +501,48 @@ function hasTransformMarkers(source: string): boolean { return TRANSFORM_MARKERS.some((m) => source.includes(m)); } +/** The installed version of the package owning `file` (the innermost + * node_modules segment), or null when no package.json names one. */ +function installedVersionOf(file: string): string | null { + const norm = file.split("\\").join("/"); + const idx = norm.lastIndexOf("/node_modules/"); + if (idx < 0) return null; + const pkg = npmPackageNameOf(norm); + if (pkg === null) return null; + const text = trackedReadFile(`${norm.slice(0, idx)}/node_modules/${pkg}/package.json`); + if (text === null) return null; + try { + const version = (JSON.parse(text) as { version?: unknown }).version; + return typeof version === "string" ? version : null; + } catch { + return null; + } +} + +/** Why a DefinitelyTyped twin cannot vouch for the runtime package `pkg`, + * or null when it can. DefinitelyTyped versions an @types package by the + * library version it describes (major.minor; 0.x minors break): the twin + * must describe the release that is installed, read from both package + * manifests next to the resolved declaration and JavaScript files. */ +export function definitelyTypedMismatch( + pkg: string, + typesFile: string, + jsFile: string, +): string | null { + const typesVersion = installedVersionOf(typesFile); + const version = installedVersionOf(jsFile); + const twin = npmPackageNameOf(typesFile.split("\\").join("/")) ?? "its @types package"; + if (typesVersion === null || version === null) + return `its declared types come from the separate ${twin} package, whose version cannot be matched against the installed ${pkg}`; + const line = (v: string): string => { + const [major = "", minor = ""] = v.split("."); + return major === "0" ? `0.${minor}` : major; + }; + return line(typesVersion) === line(version) + ? null + : `its declared types come from ${twin}@${typesVersion}, which describes a different release than the installed ${pkg}@${version}`; +} + /** Auto-mode eligibility for one candidate package: `typesFile` is what * the ordinary (types-first) resolution answered for the import, * `jsEntry` what the js-only resolution answers. Returns null when diff --git a/packages/compiler/src/frontend/pipeline.ts b/packages/compiler/src/frontend/pipeline.ts index 676b2bb3e..5c929960a 100644 --- a/packages/compiler/src/frontend/pipeline.ts +++ b/packages/compiler/src/frontend/pipeline.ts @@ -16,6 +16,7 @@ import { type LoadResult, } from "./program.js"; import { + definitelyTypedMismatch, npmStaticFileDegrades, npmStaticIneligibleReason, npmStaticOffenders, @@ -219,13 +220,25 @@ function detectAutoPackages( chosen.push(runtimePkg); continue; } + // Both answers compile the JavaScript with inferred types; the + // declarations only check the importer. A DefinitelyTyped twin + // qualifies when it describes the installed release. A package + // without declarations qualifies as a dependency of a compiled + // package, whose JavaScript importer has no declarations to check. + const refusal = + npm === null + ? owner === null + ? "it ships no own .d.ts declaration surface" + : null + : definitelyTypedMismatch(runtimePkg, npm.typesFile, js!.typesFile); + if (refusal === null) { + chosen.push(runtimePkg); + continue; + } statuses.push({ package: runtimePkg, status: "refused", - detail: - npm === null - ? "it ships no own .d.ts declaration surface" - : `its declared types come from the separate ${npm.packageName} package, not the package itself`, + detail: refusal, hint: npmStaticOptInHint(runtimePkg, owner), }); continue; diff --git a/packages/compiler/src/frontend/services.ts b/packages/compiler/src/frontend/services.ts index c6bb24ae5..aeed52161 100644 --- a/packages/compiler/src/frontend/services.ts +++ b/packages/compiler/src/frontend/services.ts @@ -1,6 +1,6 @@ import { Ts7SourceParser, type Ts7SourceKind } from "./ts7/source-parser.js"; import { Ts7Host, type Ts7ApiFactory, type Ts7HostOptions } from "./ts7/program-host.js"; -import { rewriteBundlerCjsWithParser } from "./npm-static-rewrite-host.js"; +import { lowerEsmInitsWithParser, rewriteBundlerCjsWithParser } from "./npm-static-rewrite-host.js"; import { isBundlerCjsCandidate } from "./npm-static-rewrite-syntax.js"; import { mayBundleChunks, @@ -76,6 +76,14 @@ export class FrontendServices { if (!mayBundleChunks(source)) return null; return splitBundledChunks(this.parse(path, source, "js"), path); } + /** A CommonJS bundle's lazily initialized ES modules (__esm) lowered to + * run-once closures (null: none; a degrade reason: shapes the rewrite + * cannot lower). */ + lowerEsmInits(source: string, path: string): string | { degrade: string } | null { + this.ensureOpen(); + if (!source.includes("__esm")) return null; + return lowerEsmInitsWithParser(this.sourceParser(), source, path); + } nullableClassFields(path: string, source: string): syntax.NpmStaticOverloadRewrite | null { return syntax.applyNpmStaticNullableClassFields(this.parse(path, source, "js"), source); } diff --git a/tests/fixtures/npm-static/node_modules/@types/twinstale/index.d.ts b/tests/fixtures/npm-static/node_modules/@types/twinstale/index.d.ts new file mode 100644 index 000000000..0017b82be --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/@types/twinstale/index.d.ts @@ -0,0 +1 @@ +export default function stale(text: string): string; diff --git a/tests/fixtures/npm-static/node_modules/@types/twinstale/package.json b/tests/fixtures/npm-static/node_modules/@types/twinstale/package.json new file mode 100644 index 000000000..7f7850ce6 --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/@types/twinstale/package.json @@ -0,0 +1,5 @@ +{ + "name": "@types/twinstale", + "version": "2.0.4", + "types": "index.d.ts" +} diff --git a/tests/fixtures/npm-static/node_modules/twinstale/index.js b/tests/fixtures/npm-static/node_modules/twinstale/index.js new file mode 100644 index 000000000..129abfcce --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/twinstale/index.js @@ -0,0 +1,6 @@ +// The 3.x release changed the export: its @types twin still describes 2.x. +'use strict'; + +module.exports = function stale(text) { + return 'stale:' + text; +}; diff --git a/tests/fixtures/npm-static/node_modules/twinstale/package.json b/tests/fixtures/npm-static/node_modules/twinstale/package.json new file mode 100644 index 000000000..9c104d6bf --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/twinstale/package.json @@ -0,0 +1,5 @@ +{ + "name": "twinstale", + "version": "3.1.0", + "main": "index.js" +} diff --git a/tests/fixtures/npm-static/twinstale-cli.ts b/tests/fixtures/npm-static/twinstale-cli.ts new file mode 100644 index 000000000..1938cbf8b --- /dev/null +++ b/tests/fixtures/npm-static/twinstale-cli.ts @@ -0,0 +1,5 @@ +// twinstale's @types twin describes an older major release than the +// installed package, so it cannot vouch for the JavaScript. +import stale from "twinstale"; + +console.log(stale("ready")); diff --git a/tests/fixtures/npm/cases/2563-bundled-esm-inits/main.ts b/tests/fixtures/npm/cases/2563-bundled-esm-inits/main.ts new file mode 100644 index 000000000..02ac60cba --- /dev/null +++ b/tests/fixtures/npm/cases/2563-bundled-esm-inits/main.ts @@ -0,0 +1,16 @@ +// esbuild's lazily initialized ES modules: each package is real esbuild +// output (0.25 plain, and whitespace/syntax minified) of a source whose +// load() require()s an ES module. That module and the module it imports +// run behind the __esm helper: once, at the first load(), assigning +// their interop bindings (a builtin and an external CommonJS package) +// and live exports that the namespace's getters read. +import { describe, load } from "gtesm"; +import { describe as dws, load as lws } from "gtesmws"; + +console.log("main start"); +console.log(describe()); +console.log(load()); +console.log(load()); +console.log(dws()); +console.log(lws()); +console.log(lws()); diff --git a/tests/fixtures/npm/node_modules/gtesm/dist/index.d.ts b/tests/fixtures/npm/node_modules/gtesm/dist/index.d.ts new file mode 100644 index 000000000..af4369397 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtesm/dist/index.d.ts @@ -0,0 +1,2 @@ +export declare function describe(): string; +export declare function load(): string; diff --git a/tests/fixtures/npm/node_modules/gtesm/dist/index.js b/tests/fixtures/npm/node_modules/gtesm/dist/index.js new file mode 100644 index 000000000..241c5e048 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtesm/dist/index.js @@ -0,0 +1,90 @@ +var __create = Object.create; +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __getProtoOf = Object.getPrototypeOf; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __esm = (fn, res) => function __init() { + return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res; +}; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( + // If the importer is in node compatibility mode or this is not an ESM + // file that has been converted to a CommonJS file using a Babel- + // compatible transform (i.e. "__esModule" has not been set), then set + // "default" to the CommonJS "module.exports" for node compatibility. + isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, + mod +)); +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); + +// src/helper.ts +function helper(x) { + COUNT++; + return "[" + x + "]"; +} +var COUNT; +var init_helper = __esm({ + "src/helper.ts"() { + console.log("helper init"); + COUNT = 0; + } +}); + +// src/lazy.ts +var lazy_exports = {}; +__export(lazy_exports, { + VALUE: () => VALUE, + value: () => value +}); +function value() { + return VALUE + ":" + import_gtcore.default.tag + ":" + COUNT; +} +var path, import_gtcore, VALUE; +var init_lazy = __esm({ + "src/lazy.ts"() { + path = __toESM(require("path")); + import_gtcore = __toESM(require("gtcore")); + init_helper(); + console.log("lazy init"); + VALUE = helper(path.basename("/a/b.txt")); + } +}); + +// src/index.ts +var index_exports = {}; +__export(index_exports, { + describe: () => describe, + load: () => load +}); +module.exports = __toCommonJS(index_exports); + +// src/greet.ts +function greet(name) { + return "hello " + name; +} + +// src/index.ts +function describe() { + return greet("main"); +} +function load() { + const lazy = (init_lazy(), __toCommonJS(lazy_exports)); + return lazy.value() + " " + lazy.VALUE; +} +// Annotate the CommonJS export names for ESM import in node: +0 && (module.exports = { + describe, + load +}); diff --git a/tests/fixtures/npm/node_modules/gtesm/package.json b/tests/fixtures/npm/node_modules/gtesm/package.json new file mode 100644 index 000000000..3913402f9 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtesm/package.json @@ -0,0 +1,10 @@ +{ + "name": "gtesm", + "version": "1.0.0", + "description": "esbuild 0.25.10 output (--bundle --platform=node --format=cjs --external:gtcore) of a source that require()s an ES module: lazily initialized ES modules behind the __esm helper", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "dependencies": { + "gtcore": "1.0.0" + } +} diff --git a/tests/fixtures/npm/node_modules/gtesmws/dist/index.d.ts b/tests/fixtures/npm/node_modules/gtesmws/dist/index.d.ts new file mode 100644 index 000000000..af4369397 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtesmws/dist/index.d.ts @@ -0,0 +1,2 @@ +export declare function describe(): string; +export declare function load(): string; diff --git a/tests/fixtures/npm/node_modules/gtesmws/dist/index.js b/tests/fixtures/npm/node_modules/gtesmws/dist/index.js new file mode 100644 index 000000000..cef009700 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtesmws/dist/index.js @@ -0,0 +1 @@ +var __create=Object.create;var __defProp=Object.defineProperty;var __getOwnPropDesc=Object.getOwnPropertyDescriptor;var __getOwnPropNames=Object.getOwnPropertyNames;var __getProtoOf=Object.getPrototypeOf,__hasOwnProp=Object.prototype.hasOwnProperty;var __esm=(fn,res)=>function(){return fn&&(res=(0,fn[__getOwnPropNames(fn)[0]])(fn=0)),res};var __export=(target,all)=>{for(var name in all)__defProp(target,name,{get:all[name],enumerable:!0})},__copyProps=(to,from,except,desc)=>{if(from&&typeof from=="object"||typeof from=="function")for(let key of __getOwnPropNames(from))!__hasOwnProp.call(to,key)&&key!==except&&__defProp(to,key,{get:()=>from[key],enumerable:!(desc=__getOwnPropDesc(from,key))||desc.enumerable});return to};var __toESM=(mod,isNodeMode,target)=>(target=mod!=null?__create(__getProtoOf(mod)):{},__copyProps(isNodeMode||!mod||!mod.__esModule?__defProp(target,"default",{value:mod,enumerable:!0}):target,mod)),__toCommonJS=mod=>__copyProps(__defProp({},"__esModule",{value:!0}),mod);function helper(x){return COUNT++,"["+x+"]"}var COUNT,init_helper=__esm({"src/helper.ts"(){console.log("helper init");COUNT=0}});var lazy_exports={};__export(lazy_exports,{VALUE:()=>VALUE,value:()=>value});function value(){return VALUE+":"+import_gtcore.default.tag+":"+COUNT}var path,import_gtcore,VALUE,init_lazy=__esm({"src/lazy.ts"(){path=__toESM(require("path")),import_gtcore=__toESM(require("gtcore"));init_helper();console.log("lazy init");VALUE=helper(path.basename("/a/b.txt"))}});var index_exports={};__export(index_exports,{describe:()=>describe,load:()=>load});module.exports=__toCommonJS(index_exports);function greet(name){return"hello "+name}function describe(){return greet("main")}function load(){let lazy=(init_lazy(),__toCommonJS(lazy_exports));return lazy.value()+" "+lazy.VALUE}0&&(module.exports={describe,load}); diff --git a/tests/fixtures/npm/node_modules/gtesmws/package.json b/tests/fixtures/npm/node_modules/gtesmws/package.json new file mode 100644 index 000000000..3dfe90e90 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtesmws/package.json @@ -0,0 +1,10 @@ +{ + "name": "gtesmws", + "version": "1.0.0", + "description": "the gtesm source bundled by esbuild 0.25.10 with --minify-whitespace --minify-syntax", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "dependencies": { + "gtcore": "1.0.0" + } +} diff --git a/tests/harness/npm-cases.ts b/tests/harness/npm-cases.ts index 4b3b9fdee..f8d346154 100644 --- a/tests/harness/npm-cases.ts +++ b/tests/harness/npm-cases.ts @@ -19,12 +19,12 @@ export function npmCases(fixturesRoot: string): NpmCase[] { return [ ...globSync(join(fixturesRoot, "npm/cases/*/main.ts")) .sort() - // 2465-2469 and 2556-2562 are the --npm-static bundler-emitted-CJS + // 2465-2469 and 2556-2563 are the --npm-static bundler-emitted-CJS // cases (npm-static.test.ts drives them with the opt-in): their // consumers exercise the static rewrite's surface (lexer-visible // names the shipped .d.ts never declares, the __toESM interop // family), so they stay out of the flagless island lane by design. - .filter((entry) => !/\/(246[5-9]|255[6-9]|256[0-2])-[^/]+\/main\.ts$/.test(entry)) + .filter((entry) => !/\/(246[5-9]|255[6-9]|256[0-3])-[^/]+\/main\.ts$/.test(entry)) .map((entry) => ({ name: entry.split("/").at(-2)!, entry, diff --git a/tests/harness/npm-static.test.ts b/tests/harness/npm-static.test.ts index a8df98c52..a1703adf7 100644 --- a/tests/harness/npm-static.test.ts +++ b/tests/harness/npm-static.test.ts @@ -76,7 +76,7 @@ async function buildStatic(entry: string, npmStatic: string[] | "auto"): Promise ...globSync( join(fixturesRoot, "npm/node_modules/cryptozoo/**/*.{js,mjs,cjs,json,d.ts}"), ).sort(), - // the bundler-emitted-CJS mini packages (cases 2465-2469, 2556-2562) + // the bundler-emitted-CJS mini packages (cases 2465-2469, 2556-2563) ...globSync(join(fixturesRoot, "npm/node_modules/gt*/**/*.{js,cjs,json}")).sort(), ]; for (const f of inputs) hash.update(f).update(readFileSync(f)); @@ -828,30 +828,44 @@ describe(`npm-static pilots${sanitize ? " (sanitized)" : ""}`, () => { // Types-first resolution answers @types/twinned, but the refusal names the // package that has the code and the flag that compiles it. - test("an @types-typed package is refused under its runtime name with the opt-in remedy", () => { - const { coverage } = analyze(join(pilotRoot, "twinned-cli.ts")); + // The default selection compiles a package typed by a DefinitelyTyped + // twin that describes its installed release, and the untyped + // dependencies of compiled packages. + test("an @types-typed package and its untyped dependency compile by default", async () => { + const entry = join(pilotRoot, "twinned-cli.ts"); + const { coverage } = analyze(entry); + expect(coverage.npmStatic).toEqual([ + { package: "twinned", status: "static" }, + { package: "ms", status: "static" }, + ]); + expect(coverage.preflightFailed).toBe(false); + const binary = await buildStatic(entry, "auto"); + const [nodeRes, nativeRes] = await Promise.all([ + runBinary("node", [entry]), + runBinary(binary, []), + ]); + expect(nativeRes.stdout.toString("utf8")).toBe(nodeRes.stdout.toString("utf8")); + expect(nativeRes.exitCode).toBe(nodeRes.exitCode); + }, 180_000); + + test("an @types twin describing another release is refused with the opt-in remedy", () => { + const { coverage } = analyze(join(pilotRoot, "twinstale-cli.ts")); expect(coverage.npmStatic).toEqual([ { - package: "twinned", + package: "twinstale", status: "refused", detail: - "its declared types come from the separate @types/twinned package, not the package itself", - hint: "to compile it anyway, name it with --npm-static=twinned (or --npm-static=twinned... to include its dependencies)", + "its declared types come from @types/twinstale@2.0.4, which describes a different release than the installed twinstale@3.1.0", + hint: "to compile it anyway, name it with --npm-static=twinstale (or --npm-static=twinstale... to include its dependencies)", }, ]); const diagnostic = coverage.diagnostics.find((d) => d.code === "SC2013"); - expect(diagnostic?.message).toContain("'twinned'"); - expect(diagnostic?.hint).toContain("--npm-static=twinned"); + expect(diagnostic?.message).toContain("'twinstale'"); }, 120_000); - test("a named package's untyped dependency points at the transitive opt-in", () => { + test("a named package's untyped dependency compiles with it", () => { const { coverage } = analyze(join(pilotRoot, "twinned-cli.ts"), { npmStatic: ["twinned"] }); - expect(coverage.npmStatic).toContainEqual({ - package: "ms", - status: "refused", - detail: "it ships no own .d.ts declaration surface", - hint: "to compile it anyway, name it with --npm-static=ms, or use --npm-static=twinned... to include all of twinned's dependencies", - }); + expect(coverage.npmStatic).toContainEqual({ package: "ms", status: "static" }); }, 120_000); test("--npm-static=pkg... compiles the package's dependency closure", async () => { @@ -1457,6 +1471,16 @@ describe(`npm-static pilots${sanitize ? " (sanitized)" : ""}`, () => { ]); }, 120_000); + // 2563: lazily initialized ES modules (__esm) in esbuild 0.25 plain and + // whitespace/syntax-minified bundles. + test("bundled ES module initializers 2563-bundled-esm-inits compile statically and byte-match Node", async () => { + await expectNodeParity(join(fixturesRoot, "npm/cases/2563-bundled-esm-inits/main.ts"), [ + "gtesm", + "gtesmws", + "gtcore", + ]); + }, 180_000); + // 2562: a file with unverifiable interop that no compiled code can load // (the program imports a leaf data module and only a type from the // main entry) leaves its package static. From cfb50d16e288a4b6b7fb9581d0e3f5d7a306f315 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Mon, 19 Oct 2026 14:03:44 -0700 Subject: [PATCH 017/122] Specialize variadic generic wrappers to the wrapped function's signature MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Higher-order wrappers over `(...args: any) => any` that forward `arguments` with `fn.apply(this, arguments)` and type their options with `Parameters` / `ReturnType`, and their rest-tuple rewrite, did not compile. Generic calls already monomorphize per call site; these pieces now resolve to that site's concrete signature: - Rest parameters typed as fixed tuples (`...args: [A, B]`, `...args: A` bound to a tuple, `...args: Parameters`) flatten to positional parameters, so `(...args: [A, B]) => R` and `(a: A, b: B) => R` share one function type. Trailing optional positions become `T | undefined` slots. - `Parameters`, `ReturnType`, `Awaited>` and the `ReturnType extends PromiseLike ? U : …` spelling resolve through F's binding. - A parameterless function expression reading `arguments` lowers against the fixed signature its value is asserted to (or contextually typed as); `arguments` is that signature's tuple. - `f.apply(thisArg, args)` and `f(...args)` over a forwarded tuple call directly, passing `thisArg` as the receiver. A tuple that is only forwarded is never allocated. - `fn.name` on a function parameter folds to the call site's static name; the instantiation key carries it. Also: optional callback fields narrowed at the call (`o.cb ? o.cb(x) : …`) compile, effect-free surplus arguments to fixed-arity function values are dropped, and `arguments.length` no longer counts a `this` parameter. Co-Authored-By: Claude Opus 5.5 --- .../language-features/generics.mdx | 1 + .../src/frontend/lowering/call-arguments.ts | 64 ++- .../src/frontend/lowering/call-signatures.ts | 94 +++- .../lowering/expressions/optional-chains.ts | 17 + .../frontend/lowering/generic-functions.ts | 31 +- .../src/frontend/lowering/lower-calls.ts | 20 + .../src/frontend/lowering/lower-exprs.ts | 34 +- .../src/frontend/lowering/lower-functions.ts | 3 + .../src/frontend/lowering/lower-stmts.ts | 14 + .../compiler/src/frontend/lowering/lowerer.ts | 96 ++++ .../lowering/static-function-names.ts | 221 ++++++++ .../src/frontend/lowering/typed-arguments.ts | 511 ++++++++++++++++++ packages/compiler/src/frontend/type-mapper.ts | 283 +++++++++- ...0-variadic-wrapper-arguments-apply.ts.json | 6 + .../6381-variadic-wrapper-async.ts.json | 6 + ...6382-variadic-wrapper-methods-this.ts.json | 6 + .../6383-variadic-wrapper-rest-tuple.ts.json | 6 + .../6384-variadic-wrapper-nested.ts.json | 6 + .../variadic-wrapper-fences.ts.json | 6 + .../6380-variadic-wrapper-arguments-apply.ts | 77 +++ tests/corpus/6381-variadic-wrapper-async.ts | 81 +++ .../6382-variadic-wrapper-methods-this.ts | 46 ++ .../6383-variadic-wrapper-rest-tuple.ts | 54 ++ tests/corpus/6384-variadic-wrapper-nested.ts | 37 ++ tests/diagnostics/arrow-bind.ts | 3 +- tests/diagnostics/variadic-wrapper-fences.ts | 28 + tests/harness/__snapshots__/arrow-bind.ts.txt | 17 +- .../variadic-wrapper-fences.ts.txt | 29 + 28 files changed, 1763 insertions(+), 34 deletions(-) create mode 100644 packages/compiler/src/frontend/lowering/static-function-names.ts create mode 100644 packages/compiler/src/frontend/lowering/typed-arguments.ts create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6380-variadic-wrapper-arguments-apply.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6381-variadic-wrapper-async.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6382-variadic-wrapper-methods-this.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6383-variadic-wrapper-rest-tuple.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6384-variadic-wrapper-nested.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/variadic-wrapper-fences.ts.json create mode 100644 tests/corpus/6380-variadic-wrapper-arguments-apply.ts create mode 100644 tests/corpus/6381-variadic-wrapper-async.ts create mode 100644 tests/corpus/6382-variadic-wrapper-methods-this.ts create mode 100644 tests/corpus/6383-variadic-wrapper-rest-tuple.ts create mode 100644 tests/corpus/6384-variadic-wrapper-nested.ts create mode 100644 tests/diagnostics/variadic-wrapper-fences.ts create mode 100644 tests/harness/__snapshots__/variadic-wrapper-fences.ts.txt diff --git a/docs/content/limitations/language-features/generics.mdx b/docs/content/limitations/language-features/generics.mdx index 7668e0d47..5b252ebf0 100644 --- a/docs/content/limitations/language-features/generics.mdx +++ b/docs/content/limitations/language-features/generics.mdx @@ -1 +1,2 @@ - **Generics:** Generic functions declared inside other functions, generic class expressions, and generic methods requiring dynamic dispatch are unsupported. Generic classes can extend generic bases when each specialization has a concrete layout. Independent concrete uses have no specialization-count limit; recursively expanding demands and type layouts produce diagnostics. Other generic calls compile when the target resolves statically. Generic function values require a concrete signature at use and a binding that is never reassigned. +- **Variadic wrappers:** A generic higher-order wrapper is specialized to each wrapped function's exact signature. `Parameters`, `ReturnType`, and rest parameters typed as fixed tuples become that signature's parameter list. A function expression with no parameters that reads `arguments` compiles only when its value is asserted to, or contextually typed as, one fixed-arity signature; `arguments` is then that signature's tuple. Forwarding it with `fn.apply(this, arguments)` passes `this` through. Reading `fn.name` on a wrapped function requires its name to be known where the wrapper is called, such as a function declaration or a `const`-bound function. `Parameters` over a signature with optional or array-typed rest parameters is unsupported, because that tuple has no fixed length. diff --git a/packages/compiler/src/frontend/lowering/call-arguments.ts b/packages/compiler/src/frontend/lowering/call-arguments.ts index f5600b033..db7691a1c 100644 --- a/packages/compiler/src/frontend/lowering/call-arguments.ts +++ b/packages/compiler/src/frontend/lowering/call-arguments.ts @@ -1,3 +1,4 @@ +import { forwardedTupleType, pureForwardedValues, stripTypeWrappers } from "./typed-arguments.js"; import { dynUndefinedExpr } from "../../ir/build.js"; import { InternalCompilerError } from "../../errors.js"; import * as ts from "../ts7/adapter.js"; @@ -26,13 +27,13 @@ import { checkedIterableSpread } from "./checked-iterable-spread.js"; import { type ParamShape } from "./call-signatures.js"; import { arrayValueRead, arrayValueType } from "./array-values.js"; import { buildArrayConversion } from "./coercions/builders.js"; -import { typeKey } from "../type-mapper.js"; +import { restTupleElementTypes, typeKey } from "../type-mapper.js"; function fixedTupleSpreadInfo( lowerer: Lowerer, node: ts.Expression, ): { type: IrType & { kind: "record" }; fields: { name: string; type: IrType }[] } | null { - const type = lowerer.mapTypeOf(lowerer.typeOf(node)); + const type = forwardedTupleType(lowerer, node) ?? lowerer.mapTypeOf(lowerer.typeOf(node)); if (type?.kind !== "record") return null; const shape = lowerer.shapes.get(type.shapeId); if (!shape?.tuple || shape.fields.length === 0) return null; @@ -42,6 +43,18 @@ function fixedTupleSpreadInfo( }; } +/** `...t` over a provably EMPTY tuple bound to a plain name (`[]`, a rest + * tuple whose type parameter or `Parameters` resolved to no positions): + * it contributes no arguments, and reading the name has no effect. */ +function emptyTupleSpread(lowerer: Lowerer, node: ts.Expression): boolean { + let operand = node; + while (ts.isParenthesizedExpression(operand)) operand = operand.expression; + if (!ts.isIdentifier(operand)) return false; + const forwarded = forwardedTupleType(lowerer, operand); + if (forwarded) return forwarded.kind === "array"; + return restTupleElementTypes(lowerer.typeOf(operand), lowerer.typeCtx)?.length === 0; +} + export function completeArgs( lowerer: Lowerer, argNodes: readonly ts.Expression[], @@ -59,7 +72,9 @@ export function completeArgs( // graph. Ordinary calls and fixed tuples never need that analysis. const runtimeSpreads = argNodes.filter( (arg): arg is ts.SpreadElement => - ts.isSpreadElement(arg) && !fixedTupleSpreadInfo(lowerer, arg.expression), + ts.isSpreadElement(arg) && + !fixedTupleSpreadInfo(lowerer, arg.expression) && + !emptyTupleSpread(lowerer, arg.expression), ); // Spread operands lowered while choosing the completion, each exactly once. const loweredSpreads = new Map(); @@ -190,6 +205,12 @@ export function completeArgs( sources.push(arg); continue; } + if (emptyTupleSpread(lowerer, arg.expression)) continue; + const pure = pureForwardedValues(lowerer, arg.expression); + if (pure) { + for (const value of pure) sources.push({ ir: value }); + continue; + } const tuple = fixedTupleSpreadInfo(lowerer, arg.expression); if (!tuple) { sources.push(arg); @@ -204,7 +225,13 @@ export function completeArgs( // values as separately written arguments while the saved tuple stays // alive through the call. const spreadLoc = locOf(arg); - const value = lowerer.coerceInto(arg.expression, lowerer.lowerExpr(arg.expression), tuple.type); + // A forwarded tuple seen through type assertions (`...(arguments as + // unknown as Parameters)`) spreads the tuple itself. + const operand = + forwardedTupleType(lowerer, arg.expression) !== null + ? stripTypeWrappers(arg.expression) + : arg.expression; + const value = lowerer.coerceInto(arg.expression, lowerer.lowerExpr(operand), tuple.type); const saved = lowerer.declareHiddenLocal("%callSpread", tuple.type); const savedRef = (): IrExpr => ({ kind: "varRef", @@ -506,9 +533,38 @@ export function completeFuncValueArgs( shapes.push({ type: rest, mode: "rest" }); return completeArgs(lowerer, call.arguments.nodes, shapes, loc, call); } + // A fixed-tuple spread (`fn(...args)` over a tuple-typed rest binding) + // has a compile-time arity: completion flattens it into the positional + // slots exactly like a direct call's. + if ( + funcType.rest !== true && + call.arguments.nodes.some(ts.isSpreadElement) && + call.arguments.nodes.every( + (arg) => + !ts.isSpreadElement(arg) || + fixedTupleSpreadInfo(lowerer, arg.expression) !== null || + emptyTupleSpread(lowerer, arg.expression), + ) + ) { + const shapes: ParamShape[] = funcType.params.map((type) => ({ + type, + mode: omittedArgFor(lowerer, type, loc) ? "omittable" : "required", + })); + return completeArgs(lowerer, call.arguments.nodes, shapes, loc, call); + } const args = call.arguments.nodes.map((arg, index) => lowerer.lowerExprExpecting(arg, funcType.params[index]), ); + // Effect-free surplus arguments of a fixed-arity value (`(r: void) => + // void` drops its void slot) evaluate to nothing observable: drop them, + // as the callee ignores them. + while ( + funcType.rest !== true && + args.length > funcType.params.length && + isSafeToDiscard(args[args.length - 1]!) + ) { + args.pop(); + } for (let i = args.length; i < funcType.params.length; i++) { const absent = omittedArgFor(lowerer, funcType.params[i]!, loc); if (!absent) { diff --git a/packages/compiler/src/frontend/lowering/call-signatures.ts b/packages/compiler/src/frontend/lowering/call-signatures.ts index 9d9c9668c..ef2900a7c 100644 --- a/packages/compiler/src/frontend/lowering/call-signatures.ts +++ b/packages/compiler/src/frontend/lowering/call-signatures.ts @@ -1,3 +1,8 @@ +import { + typedArgumentsCandidate, + typedArgumentsShapes, + typedArgumentsTarget, +} from "./typed-arguments.js"; import { InternalCompilerError } from "../../errors.js"; import * as ts from "../ts7/adapter.js"; import { bodyReadsArguments } from "../arguments-usage.js"; @@ -15,6 +20,7 @@ import { import { isJsSourceFile, isNodeEsmFile } from "../program.js"; import { genResultRecord, + restTupleParamTypes, isAnyArrayType, isUnitOnlyTsType, jsOpenObjectType, @@ -52,6 +58,10 @@ export interface ParamShape { mode: ParamMode; bodyType?: IrType; callDefault?: IrExpr; + /** One flattened position of a fixed-tuple rest parameter + * (restTupleShapes): the declaration's last parameter owns every shape + * from its index on, and the body binds the tuple (declareParams). */ + restTuple?: true; } export interface FnSig { @@ -369,6 +379,28 @@ export function isThisParameter(param: ts.ParameterDeclaration): boolean { return ts.isIdentifier(param.name) && param.name.text === "this"; } +/** A TypeScript rest parameter bound to a name whose type is a fixed tuple + * (`...args: [A, B]`, `...args: A` with A bound to a tuple, `...args: + * Parameters` with F bound): one required shape per position. Call + * sites and function values see the equivalent fixed signature; the body + * rebuilds the tuple (declareParams) unless it only forwards it. Null for + * every other parameter. */ +export function restTupleShapes( + lowerer: Lowerer, + param: ts.ParameterDeclaration, + tsType?: ts.Type, +): ParamShape[] | null { + if ( + param.dotDotDotToken === undefined || + !ts.isIdentifier(param.name) || + isJsSourceFile(param.getSourceFile()) + ) { + return null; + } + const elems = restTupleParamTypes(tsType ?? lowerer.typeOf(param.name), lowerer.typeCtx); + return elems ? elems.map((type) => ({ type, mode: "required", restTuple: true })) : null; +} + /** ParamShapes for a whole parameter list. */ export function paramShapes( lowerer: Lowerer, @@ -376,16 +408,37 @@ export function paramShapes( signature?: ts.Signature, blameOf?: (param: ts.ParameterDeclaration, index: number) => ts.Node, ): ParamShape[] { - if (!signature) - return params.filter((p) => !isThisParameter(p)).map((param) => lowerer.paramShape(param)); + const out: ParamShape[] = []; + if (!signature) { + for (const param of params) { + if (isThisParameter(param)) continue; + const flattened = restTupleShapes(lowerer, param); + if (flattened) out.push(...flattened); + else out.push(lowerer.paramShape(param)); + } + return out; + } const symbols = signature.getParameters(); - return params.map((declParam, i) => { + const shapeAt = (declParam: ts.ParameterDeclaration, i: number): ParamShape => { const stage = lowerer.stageSourceType(declParam.name); if (stage) return { type: stage, mode: "required" as const }; const symbol = symbols[i]; const tsType = symbol ? lowerer.checker.getTypeOfSymbol(symbol) : lowerer.typeOf(declParam.name); + // A call-site instantiation whose concrete argument type does not map + // is that argument's blocker: name it there, with its concrete type, + // rather than as the declaration's unbound type parameter. + if ( + blameOf !== undefined && + !isJsSourceFile(declParam.getSourceFile()) && + (tsType.flags & ts.TypeFlags.Any) === 0 && + declParam.type !== undefined && + mentionsTypeParameter(lowerer, lowerer.checker.getTypeFromTypeNode(declParam.type)) && + lowerer.mapTypeOf(tsType) === null + ) { + lowerer.badType(blameOf(declParam, i), tsType); + } let mapped = jsOpenObjectType( declParam, lowerer.runtimeOptionalBindingType( @@ -416,7 +469,32 @@ export function paramShapes( ); } return { type: mapped, mode: declParam.questionToken ? "omittable" : "required" }; + }; + params.forEach((declParam, i) => { + const symbol = symbols[i]; + const flattened = restTupleShapes( + lowerer, + declParam, + symbol ? lowerer.checker.getTypeOfSymbol(symbol) : undefined, + ); + if (flattened) out.push(...flattened); + else out.push(shapeAt(declParam, i)); }); + return out; +} + +/** A declared type that is (or is a union over) a bare type parameter + * or an instantiation of a generic alias/interface over type arguments. */ +function mentionsTypeParameter(lowerer: Lowerer, t: ts.Type): boolean { + if (t.flags & ts.TypeFlags.TypeParameter) return true; + const mentions = (part: ts.Type): boolean => mentionsTypeParameter(lowerer, part); + if (t.isUnionType()) return ts.constituentTypes(t).some(mentions); + if (t.getAliasTypeArguments().some(mentions)) return true; + return ( + (t.flags & ts.TypeFlags.Object) !== 0 && + ((t as ts.ObjectType).objectFlags & ts.ObjectFlags.Reference) !== 0 && + lowerer.checker.getTypeArguments(t as ts.TypeReference).some(mentions) + ); } /** A default expression may itself yield undefined. Keep that arm in the @@ -758,6 +836,16 @@ export function lambdaSignature( | ts.SetAccessorDeclaration, ): { shapes: ParamShape[]; funcType: IrType & { kind: "func" } } { if (!node.body) lowerer.unsupported("SC1090", node, "function overload signatures"); + // A parameterless function expression forwarding its `arguments`, + // asserted to a fixed signature: that signature is its ABI. + if (typedArgumentsCandidate(node) && !node.typeParameters?.nodes.length) { + const target = typedArgumentsTarget(lowerer, node); + if (target) { + const shapes = typedArgumentsShapes(target); + lowerer.typedArgumentsLambdas.add(node); + return { shapes, funcType: funcTypeFromParamShapes(shapes, target.ret) }; + } + } // A JS replacement of a native stream's write method receives the // actual call tuple, including overloads and omitted arguments. Node's // contextual overload signature cannot define this closure's ABI. diff --git a/packages/compiler/src/frontend/lowering/expressions/optional-chains.ts b/packages/compiler/src/frontend/lowering/expressions/optional-chains.ts index 930e1a7cb..995514a4d 100644 --- a/packages/compiler/src/frontend/lowering/expressions/optional-chains.ts +++ b/packages/compiler/src/frontend/lowering/expressions/optional-chains.ts @@ -1,3 +1,5 @@ +import { isSafeToDiscard } from "./evaluation-safety.js"; +import { completeFuncValueArgs } from "../call-arguments.js"; import { dynUndefinedExpr } from "../../../ir/build.js"; import * as ts from "../../ts7/adapter.js"; import { DYN, STRING, UNDEFINED_T, VOID, isUnitType, typeEquals } from "../../../ir/ir.js"; @@ -429,7 +431,22 @@ export function lowerOptionalChain( if (dotNode === expr && ts.isCallExpression(expr)) { if (narrowed.kind !== "func") lowerer.badType(recvNode, lowerer.typeOf(recvNode)); const params = narrowed.params; + // Forwarded fixed tuples (`f?.(...args)`) complete into the slots. + if (expr.arguments.nodes.some(ts.isSpreadElement)) { + const completed = completeFuncValueArgs(lowerer, expr, narrowed, loc); + const call: IrExpr = { + kind: "callValue", + callee: recvRef, + args: completed, + type: narrowed.ret, + loc, + }; + return lowerer.finishOptionalChain(expr, id, receiver, call, loc); + } const args = expr.arguments.nodes.map((a, i) => lowerer.lowerExprExpecting(a, params[i])); + // Effect-free surplus arguments (a dropped `void` slot) are ignored. + while (narrowed.rest !== true && args.length > params.length && isSafeToDiscard(args.at(-1)!)) + args.pop(); const body: IrExpr = { kind: "callValue", callee: recvRef, args, type: narrowed.ret, loc }; return lowerer.finishOptionalChain(expr, id, receiver, body, loc); } diff --git a/packages/compiler/src/frontend/lowering/generic-functions.ts b/packages/compiler/src/frontend/lowering/generic-functions.ts index bf5f2563d..e1fa5994d 100644 --- a/packages/compiler/src/frontend/lowering/generic-functions.ts +++ b/packages/compiler/src/frontend/lowering/generic-functions.ts @@ -1,3 +1,4 @@ +import { staticNameBindings } from "./static-function-names.js"; import * as ts from "../ts7/adapter.js"; import { bodyReadsArguments } from "../arguments-usage.js"; import type { Lowerer } from "./lowerer.js"; @@ -100,6 +101,9 @@ export interface GenericFnInfo { * names where the slot binds the call site's object type. Computed on * first use. */ implicitPatterns?: (ImplicitPattern | null)[]; + /** Parameter index → symbol for the function parameters whose `.name` + * the body reads (static-function-names.ts); computed on first use. */ + nameReadParams?: Map; } export interface GenericInstance { @@ -140,6 +144,9 @@ export interface GenericInstance { implicitInferReturn?: true; /** An erased instance (every type parameter bound to `unknown`). */ erased?: true; + /** Name-keyed instances: function parameter → its call site's static + * name (null when only known at run time), folded at `.name` reads. */ + staticFnNames?: Map; } /** Defaults bind during inference; collection only resolves symbols in @@ -317,6 +324,7 @@ export function genericCallInstance( // different fields). Non-literal bindings (a key union, plain string) // share one runtime-keyed instance per IR signature, exactly the // widened discipline. + const named = staticNameBindings(lowerer, expr, info); const keyofTps = keyofConstrainedTypeParams(info); if (keyofTps.size > 0) { const tsBindings = new Map(); @@ -333,12 +341,19 @@ export function genericCallInstance( }) .join(","); return internGenericInstance(lowerer, expr, info, params, returnType, () => bindings, { - extraKey: `@${litKey}`, + extraKey: `@${litKey}${named?.key ?? ""}`, tsBindings, + ...(named ? { staticFnNames: named.names } : {}), }); } - return internGenericInstance(lowerer, expr, info, params, returnType, (tsBindings) => - lowerer.inferTypeParamBindings(expr, info, rsig, tsBindings), + return internGenericInstance( + lowerer, + expr, + info, + params, + returnType, + (tsBindings) => lowerer.inferTypeParamBindings(expr, info, rsig, tsBindings), + named ? { extraKey: named.key, staticFnNames: named.names } : undefined, ); } @@ -498,7 +513,11 @@ function internGenericInstance( params: ParamShape[], returnType: IrType, makeBindings: (tsBindings: Map) => Map, - opts?: { extraKey?: string; tsBindings?: Map }, + opts?: { + extraKey?: string; + tsBindings?: Map; + staticFnNames?: Map; + }, ): GenericInstance { // keyof-constrained instantiations append their literal keys // (extraKey): the IR signature alone under-discriminates there — two @@ -541,6 +560,7 @@ function internGenericInstance( bindings, tsBindings, typeArgsText, + ...(opts?.staticFnNames ? { staticFnNames: opts.staticFnNames } : {}), }; info.instances.set(key, inst); lowerer.instantiationQueue.push({ info, inst }); @@ -824,6 +844,8 @@ export function lowerGenericInstance( const prevSuppress = lowerer.suppressStats; const prevClass = lowerer.currentClass; const prevImplicit = lowerer.implicitParamTypes; + const prevStaticFnNames = lowerer.staticFnNames; + lowerer.staticFnNames = inst.staticFnNames ?? null; // A generic METHOD of a generic-class INSTANTIATION lowers under BOTH // binding sets: the receiver instantiation's class type parameters // underneath, the method instantiation's own on top (disjoint symbol @@ -984,6 +1006,7 @@ export function lowerGenericInstance( lowerer.typeParamBindings = prevBindings; lowerer.typeParamTsBindings = prevTsBindings; lowerer.implicitParamTypes = prevImplicit; + lowerer.staticFnNames = prevStaticFnNames; lowerer.instantiationContext = prevContext; lowerer.localClassInstantiations.pop(); lowerer.suppressStats = prevSuppress; diff --git a/packages/compiler/src/frontend/lowering/lower-calls.ts b/packages/compiler/src/frontend/lowering/lower-calls.ts index 67ca01b3f..3bebfd2af 100644 --- a/packages/compiler/src/frontend/lowering/lower-calls.ts +++ b/packages/compiler/src/frontend/lowering/lower-calls.ts @@ -1,3 +1,4 @@ +import { isForwardedTupleSpread, lowerForwardingApply } from "./typed-arguments.js"; import { guardNullProto, nullProtoRecordShape } from "./null-prototype.js"; import { lowerUnionEquality, tagEqualityMayMissAlias } from "./strict-equality.js"; import { lowerHashStreamCall } from "./builtins/hash-stream.js"; @@ -1364,6 +1365,9 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { if (ts.isPropertyAccessExpression(expr.expression)) { const intrinsic = fenceNodeModuleMutationCall(lowerer, expr, expr.expression) ?? + // `f.apply(thisArg, args)` forwarding a fixed argument tuple (typed + // `arguments`, a tuple rest parameter): a direct call. + lowerForwardingApply(lowerer, expr, expr.expression) ?? // Builtin namespace imports first (`fs.readFileSync(...)` where fs // is `import * as fs from "node:fs"`): the same tables and fences // as named builtin imports — before anything below tries to lower @@ -1854,6 +1858,19 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { // or marshals the callee and applies through a runtime-built argument // list (lowerSpreadArgsCall). Shapes outside its lanes fall through // to the historical fences. + // Forwarded fixed tuples (`fn(...args)` over a tuple rest parameter or a + // typed `arguments`) have a compile-time arity: complete them into the + // callee's positional slots directly, without the runtime-arity lane. + if ( + callee.type.rest !== true && + expr.arguments.nodes.some(ts.isSpreadElement) && + expr.arguments.nodes.every( + (a) => !ts.isSpreadElement(a) || isForwardedTupleSpread(lowerer, a.expression), + ) + ) { + const args = completeFuncValueArgs(lowerer, expr, callee.type, loc); + return { kind: "callValue", callee, args, type: callee.type.ret, loc }; + } if (expr.arguments.nodes.some((a) => ts.isSpreadElement(a))) { const spreadServed = lowerSpreadArgsCall(lowerer, expr, callee, loc); if (spreadServed) return spreadServed; @@ -8504,6 +8521,9 @@ export function lowerRecordFieldCall( }; return init ? { kind: "seqExpr", stmts: [init], result, type: result.type, loc } : result; } + // An optional callback field the checker narrowed at this call + // (`o.cb ? o.cb(x) : …`, `if (o.cb) o.cb(x)`): extract the func arm. + if (callee.type.kind === "union") callee = lowerer.maybeNarrow(callee, access); if (callee.type.kind !== "func") lowerer.badType(access, lowerer.typeOf(access)); // JavaScript passes the record as `this`. A record with no dynamic form // cannot be that receiver; the library's own function fields (the diff --git a/packages/compiler/src/frontend/lowering/lower-exprs.ts b/packages/compiler/src/frontend/lowering/lower-exprs.ts index 09407f71f..bc970957d 100644 --- a/packages/compiler/src/frontend/lowering/lower-exprs.ts +++ b/packages/compiler/src/frontend/lowering/lower-exprs.ts @@ -1,3 +1,5 @@ +import { staticFunctionNameRead } from "./static-function-names.js"; +import { argumentsOwner, typedArgumentsArity, typedArgumentsCandidate } from "./typed-arguments.js"; import { dateJsonType, isDateJsonSource } from "./builtins/dates.js"; import { guardNullProto, nullProtoRecordShape, recordNullProtoTest } from "./null-prototype.js"; import { lowerProtocolPropertyRead } from "./class-protocol-dispatch.js"; @@ -191,7 +193,7 @@ import { } from "./lower-classes.js"; import { hasRuntimeStatics } from "./class-runtime-statics.js"; import { bindingNeverReassigned, nullishGenericBindingUnitOf } from "./binding-analysis.js"; -import { funcTypeFromParamShapes } from "./call-signatures.js"; +import { funcTypeFromParamShapes, isThisParameter } from "./call-signatures.js"; import { implicitMonoFile, objLitGenericFnInfoOf, @@ -906,7 +908,18 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr { // type and lowerLambda declared the local. if (expr.text === "arguments") { const binding = captureContextArguments(lowerer.fnStack); - if (binding) return { kind: "varRef", localId: binding.id, type: DYN, loc }; + if (binding && lowerer.pureTuples.has(binding.id)) { + throw new InternalCompilerError("lowerer bug: forwarded-only 'arguments' read as a value"); + } + if (binding) return { kind: "varRef", localId: binding.id, type: binding.type, loc }; + const owner = argumentsOwner(expr); + if (owner && typedArgumentsCandidate(owner)) { + lowerer.unsupported( + "SC1090", + expr, + "'arguments' in a function expression whose value is not asserted to one fixed-arity signature (assert it, as in `function () { … } as F`, and the function lowers against that signature)", + ); + } } // A compiler-projected adapter as a VALUE: the adapter has no // runtime function object (its calls rewrite directly) — call it @@ -1929,6 +1942,18 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr { if (ts.isConditionalExpression(expr)) return lowerTernary(lowerer, expr); if (ts.isPropertyAccessExpression(expr)) { + // `fn.name` on a function parameter of a generic instance keyed by + // its argument's static name (call-site specialization). + { + const staticName = staticFunctionNameRead(lowerer, expr); + if (staticName !== null) return { kind: "strLit", value: staticName, type: STRING, loc }; + } + // `.length` of a forwarded fixed tuple (a typed `arguments`, a tuple + // rest parameter, an alias of either): the signature's arity. + if (expr.name.text === "length" && !expr.questionDotToken) { + const arity = typedArgumentsArity(lowerer, expr.expression); + if (arity !== null) return { kind: "numLit", value: arity, type: F64, loc }; + } // `require("./x").member` over a program module's export table: the // module's %init at this position, then the member read exactly like // a `const m = require("./x")` binding's (cjs-inline-require.ts). @@ -2233,9 +2258,12 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr { p.dotDotDotToken === undefined, ); if (fixedArity) { + // A `this` parameter is type-only: callers never pass it. return { kind: "numLit", - value: (fn as ts.FunctionLikeDeclaration).parameters.nodes.length, + value: (fn as ts.FunctionLikeDeclaration).parameters.nodes.filter( + (p) => !isThisParameter(p), + ).length, type: F64, loc, }; diff --git a/packages/compiler/src/frontend/lowering/lower-functions.ts b/packages/compiler/src/frontend/lowering/lower-functions.ts index 7cc448994..74dd362e5 100644 --- a/packages/compiler/src/frontend/lowering/lower-functions.ts +++ b/packages/compiler/src/frontend/lowering/lower-functions.ts @@ -1,3 +1,4 @@ +import { declareTypedArguments } from "./typed-arguments.js"; import { varRef } from "../../ir/build.js"; import { InternalCompilerError } from "../../errors.js"; import * as ts from "../ts7/adapter.js"; @@ -309,6 +310,8 @@ export function lowerLambda( : null; const { params, prologue } = lowerer.declareParams(node.parameters.nodes, shapes); if (receiver) prologue.unshift(receiver); + if (ts.isFunctionExpression(node) && lowerer.typedArgumentsLambdas.has(node)) + declareTypedArguments(lowerer, node, shapes, params, prologue); declareFunctionArguments( lowerer, node, diff --git a/packages/compiler/src/frontend/lowering/lower-stmts.ts b/packages/compiler/src/frontend/lowering/lower-stmts.ts index 36abb9130..01ff5f5c1 100644 --- a/packages/compiler/src/frontend/lowering/lower-stmts.ts +++ b/packages/compiler/src/frontend/lowering/lower-stmts.ts @@ -1,3 +1,4 @@ +import { lowerTupleAliasDecl, stripTypeWrappers } from "./typed-arguments.js"; import { matchAllRowsReadByElements, storedMatchAllRowsReadByElements, @@ -4463,6 +4464,10 @@ export function lowerVarDecl( } } if (!ts.isIdentifier(decl.name)) lowerer.unsupported("SC1031", decl.name); + { + const alias = lowerTupleAliasDecl(lowerer, decl); + if (alias) return alias; + } // A NULLISH generic binding (`const i: I = null as any` — the // declared type has no mapping, the value is provably null/undefined @@ -4876,6 +4881,15 @@ export function lowerVarDecl( init.type.kind === "func" ) type = init.type; + // A typed-arguments function expression (typed-arguments.ts): the + // checker types the binding variadic, but its value has the fixed + // signature every use asserts. + if ( + !decl.type && + init.type.kind === "func" && + lowerer.typedArgumentsLambdas.has(stripTypeWrappers(decl.initializer)) + ) + type = init.type; // A TypeScript lambda whose closure returns the runtime result (a missing // read, a `??` default of another type) instead of the checker's bare // return type: the binding keeps the checker's parameters and takes the diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index d252af250..ccc140e9e 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -1,3 +1,4 @@ +import { onlyForwarded } from "./typed-arguments.js"; import { jsDynamicDeclarations } from "./js-dynamic-objects.js"; import { lifetimeRegionType, @@ -1910,6 +1911,19 @@ export class Lowerer { * type parameters are bound to `unknown`, so lambdaSignature admits * them. */ readonly erasingLambdas = new Set(); + /** Function expressions lowered against a fixed target signature with a + * tuple-typed `arguments` (typed-arguments.ts). */ + readonly typedArgumentsLambdas = new Set(); + /** Const bindings holding a forwarded fixed tuple (`const args = + * arguments as any` in a typed-arguments function) → the tuple type. */ + readonly tupleAliasSymbols = new Map(); + /** Tuple bindings that are only forwarded (typed-arguments.ts + * onlyForwarded): binding local id → the native slots forwarding reads. + * Such a binding is never materialized. */ + readonly pureTuples = new Map(); + /** The static names of the current generic instance's name-keyed + * function parameters (static-function-names.ts). */ + staticFnNames: Map | null = null; /** Nesting depth of erased generic bodies being lowered (erased lambdas * and erased instances). */ erasingDepth = 0; @@ -10681,6 +10695,70 @@ export class Lowerer { return declareContextLocal(this.ctx, name, type, false, undefined, undefined); } + /** The body binding of a fixed-tuple rest parameter: one native slot per + * position (the flattened ABI), rebuilt into the tuple the source names. + * The empty tuple rides the empty-array representation. */ + declareRestTupleParam( + decl: ts.ParameterDeclaration, + nameNode: ts.Identifier, + elemShapes: readonly ParamShape[], + params: IrParam[], + prologue: IrStmt[], + ): void { + const loc = locOf(decl); + const name = nameNode.text; + const tupleType = this.irTypeOf(nameNode); + const slots = elemShapes.map((shape, i) => { + const slot = this.declareHiddenLocal(`%${name}.${i}`, shape.type); + params.push({ localId: slot.id, name: `%${name}.${i}`, type: shape.type }); + return slot; + }); + const body = this.declareLocal(nameNode, name, tupleType, true); + const fn = decl.parent; + const symbol = this.checker.getSymbolAtLocation(nameNode); + if ( + fn !== undefined && + symbol !== undefined && + onlyForwarded( + this, + fn, + (id) => id !== nameNode && this.checker.getSymbolAtLocation(id) === symbol, + ) + ) { + this.pureTuples.set(body.id, slots); + return; + } + const init = this.tupleOfSlots(slots, tupleType, nameNode, loc); + prologue.push({ kind: "varDecl", localId: body.id, init, loc }); + } + + /** A fresh tuple holding the given positional slots, typed `tupleType` + * (a tuple record, or the empty-array form of `[]`). */ + tupleOfSlots(slots: readonly IrLocal[], tupleType: IrType, blame: ts.Node, loc: SrcLoc): IrExpr { + if (slots.length === 0) { + if (tupleType.kind !== "array") this.badType(blame, this.typeOf(blame)); + return { kind: "arrayLit", elems: [], type: tupleType, loc }; + } + const shape = tupleType.kind === "record" ? this.shapes.get(tupleType.shapeId) : undefined; + if (!shape?.tuple || shape.fields.length !== slots.length) + this.badType(blame, this.typeOf(blame)); + return { + kind: "recordLit", + fields: slots.map((slot, i) => { + const field = shape.fields.find((f) => f.name === String(i))!; + return { + name: field.name, + value: this.coerceToExpected( + { kind: "varRef", localId: slot.id, type: slot.type, loc }, + field.type, + ), + }; + }), + type: tupleType, + loc, + }; + } + /** Declares a callee's parameter locals from its ParamShapes and builds * the DEFAULT-PARAM PROLOGUE. Required/optional/rest params bind their * symbol directly (one local of the ABI type). A defaulted param `x: T = e` @@ -10717,6 +10795,17 @@ export class Lowerer { return defaultAfterUndefined(undefinedValue, this.coerceInto(node, absent, expected)); }; decls.forEach((decl, i) => { + // A fixed-tuple rest parameter owns every remaining (flattened) + // shape, possibly none for `[]` (restTupleShapes). + if ( + decl.dotDotDotToken !== undefined && + ts.isIdentifier(decl.name) && + i === decls.length - 1 && + (shapes[i] === undefined ? shapes.length === i : shapes[i]!.restTuple === true) + ) { + this.declareRestTupleParam(decl, decl.name, shapes.slice(i), params, prologue); + return; + } const shape = shapes[i]!; if (ts.isArrayBindingPattern(decl.name) || ts.isObjectBindingPattern(decl.name)) { // Pattern parameter: one hidden ABI slot carries the source value; @@ -11033,6 +11122,13 @@ export class Lowerer { } if (!symbol) return null; const direct = this.resolveKey(symbol, ident); + if (direct && this.pureTuples.has(direct.id)) { + // onlyForwarded proved every read forwards; a plain read here means + // the analysis and the lowering disagree. + throw new InternalCompilerError( + `lowerer bug: forwarded-only tuple '${ident.text}' read as a value`, + ); + } if (direct) return direct; // A PARAMETER PROPERTY declares two symbols: references resolve to the // PARAMETER symbol, while the declaration's name binds the PROPERTY diff --git a/packages/compiler/src/frontend/lowering/static-function-names.ts b/packages/compiler/src/frontend/lowering/static-function-names.ts new file mode 100644 index 000000000..1fcab901b --- /dev/null +++ b/packages/compiler/src/frontend/lowering/static-function-names.ts @@ -0,0 +1,221 @@ +import * as ts from "../ts7/adapter.js"; +import { isJsSourceFile } from "../program.js"; +import type { Lowerer } from "./lowerer.js"; +import type { GenericFnInfo } from "./generic-functions.js"; +import { stripTypeWrappers } from "./typed-arguments.js"; + +/** `fn.name` inside generic higher-order functions. + * + * Native closures carry no name, but a generic wrapper is instantiated per + * call site anyway: when its body reads `.name` of a function parameter, + * the instantiation key also carries the static name of that call's + * argument (the function declaration's name, a const binding's inferred + * name, the name of the function a wrapper call returns), and the read + * folds to that string. A call whose argument's name is only known at run + * time keeps a precise refusal at the read. */ + +/** Parameter index → symbol for the parameters whose `.name` the generic + * body reads (and never reassigns). Computed once per generic. */ +export function nameReadParams(lowerer: Lowerer, info: GenericFnInfo): Map { + if (info.nameReadParams) return info.nameReadParams; + const out = new Map(); + // JavaScript generics read function names through the checked-value + // box, which carries the runtime name already. + if (isJsSourceFile(info.decl.getSourceFile())) { + info.nameReadParams = out; + return out; + } + const bySymbol = new Map(); + info.decl.parameters.nodes.forEach((param, i) => { + if (!ts.isIdentifier(param.name) || param.dotDotDotToken) return; + if (!functionTypedParameter(lowerer, param)) return; + const symbol = lowerer.checker.getSymbolAtLocation(param.name); + if (symbol) bySymbol.set(symbol, i); + }); + if (bySymbol.size > 0) { + const written = new Set(); + const read = new Set(); + ts.walkPreorder(info.decl, (node) => { + if (ts.isTypeNode(node)) return "skip"; + if ( + ts.isPropertyAccessExpression(node) && + node.name.text === "name" && + ts.isIdentifier(node.expression) + ) { + const symbol = lowerer.checker.getSymbolAtLocation(node.expression); + if (symbol && bySymbol.has(symbol)) read.add(symbol); + } + if ( + ts.isBinaryExpression(node) && + node.operatorToken.kind >= ts.SyntaxKind.FirstAssignment && + node.operatorToken.kind <= ts.SyntaxKind.LastAssignment && + ts.isIdentifier(node.left) + ) { + const symbol = lowerer.checker.getSymbolAtLocation(node.left); + if (symbol) written.add(symbol); + } + return undefined; + }); + for (const symbol of read) { + if (!written.has(symbol)) out.set(bySymbol.get(symbol)!, symbol); + } + } + info.nameReadParams = out; + return out; +} + +/** A parameter declared with a function type, or with a type parameter + * constrained to one (`fn: F` where `F extends (...args: any) => any`). */ +function functionTypedParameter(lowerer: Lowerer, param: ts.ParameterDeclaration): boolean { + const t = lowerer.typeOf(param.name); + if (lowerer.checker.getCallSignatures(t).length > 0) return true; + if (!(t.flags & ts.TypeFlags.TypeParameter)) return false; + const symbol = t.getSymbol(); + const decl = symbol + ? lowerer.checker.declarationsOf(symbol).find(ts.isTypeParameterDeclaration) + : undefined; + if (!decl?.constraint) return false; + const constraint = lowerer.checker.getTypeFromTypeNode(decl.constraint); + return lowerer.checker.getCallSignatures(constraint).length > 0; +} + +/** The instantiation key suffix and name bindings of one call of `info`, + * or null when the body reads no parameter's name. */ +export function staticNameBindings( + lowerer: Lowerer, + call: ts.CallExpression, + info: GenericFnInfo, +): { key: string; names: Map } | null { + const params = nameReadParams(lowerer, info); + if (params.size === 0) return null; + const names = new Map(); + const parts: string[] = []; + for (const [index, symbol] of params) { + const arg = call.arguments.nodes[index]; + const name = arg && !ts.isSpreadElement(arg) ? staticFunctionName(lowerer, arg) : null; + names.set(symbol, name); + parts.push(name === null ? "?" : JSON.stringify(name)); + } + return { key: `@name(${parts.join(",")})`, names }; +} + +/** The `name` JavaScript gives the function `expr` evaluates to, when it is + * statically known; null otherwise. */ +export function staticFunctionName(lowerer: Lowerer, expr: ts.Expression): string | null { + return staticNameOf(lowerer, expr, new Set()); +} + +function staticNameOf(lowerer: Lowerer, expr: ts.Expression, seen: Set): string | null { + const e = stripTypeWrappers(expr); + if (seen.has(e)) return null; + seen.add(e); + // An anonymous function as a call argument gets no inferred name. + if (ts.isFunctionExpression(e) || ts.isArrowFunction(e)) { + return !ts.isArrowFunction(e) && e.name ? e.name.text : ""; + } + if (ts.isIdentifier(e)) { + const symbol = lowerer.resolveValueSymbol(e); + if (!symbol) return null; + const bound = lowerer.staticFnNames?.get(symbol); + if (bound !== undefined) return bound; + const decl = lowerer.checker.valueDeclarationOf(symbol); + if (decl === undefined) return null; + if (ts.isFunctionDeclaration(decl)) return decl.name ? decl.name.text : "default"; + if ( + ts.isVariableDeclaration(decl) && + ts.isIdentifier(decl.name) && + decl.initializer !== undefined && + (ts.getCombinedNodeFlags(decl) & ts.NodeFlags.Const) !== 0 + ) { + const init = stripTypeWrappers(decl.initializer); + // `const f = () => …` / `const f = function () {}`: the binding names + // an anonymous function definition. + if (ts.isArrowFunction(init)) return decl.name.text; + if (ts.isFunctionExpression(init)) return init.name ? init.name.text : decl.name.text; + return staticNameOf(lowerer, init, seen); + } + return null; + } + if (ts.isCallExpression(e) && ts.isIdentifier(e.expression)) { + // A call of a generic higher-order function returns the function its + // body returns: every `return` names the same anonymous definition or + // the same local const binding. + const symbol = lowerer.resolveValueSymbol(e.expression); + const info = symbol ? lowerer.genericFnsBySymbol.get(symbol) : undefined; + if (!info) return null; + return returnedFunctionName(lowerer, info.decl, seen); + } + if (ts.isPropertyAccessExpression(e)) { + const symbol = lowerer.checker.getSymbolAtLocation(e.name); + const decl = symbol ? lowerer.checker.valueDeclarationOf(symbol) : undefined; + if (decl && ts.isMethodDeclaration(decl) && ts.isIdentifier(decl.name)) return decl.name.text; + return null; + } + return null; +} + +function returnedFunctionName( + lowerer: Lowerer, + decl: GenericFnInfo["decl"], + seen: Set, +): string | null { + const body = decl.body; + if (body === undefined) return null; + if (!ts.isBlock(body)) return functionValueName(lowerer, body, seen); + let name: string | null | undefined; + let ok = true; + ts.walkPreorder(body, (node) => { + if (!ok) return "stop"; + if (ts.isFunctionLike(node)) return "skip"; + if (!ts.isReturnStatement(node)) return undefined; + const here = node.expression ? functionValueName(lowerer, node.expression, seen) : null; + if (here === null || (name !== undefined && name !== here)) ok = false; + else name = here; + return undefined; + }); + return ok && name !== undefined ? name : null; +} + +/** The name of a returned function value: an anonymous definition returned + * directly is nameless; a local const binding names its definition. */ +function functionValueName( + lowerer: Lowerer, + expr: ts.Expression, + seen: Set, +): string | null { + const e = stripTypeWrappers(expr); + if (ts.isFunctionExpression(e) || ts.isArrowFunction(e)) { + return !ts.isArrowFunction(e) && e.name ? e.name.text : ""; + } + if (ts.isIdentifier(e)) return staticNameOf(lowerer, e, seen); + return null; +} + +/** `fn.name` where `fn` is a name-keyed parameter of the generic instance + * being lowered: the folded name, or a precise refusal when this call + * site's argument has no static name. Null for every other read. */ +export function staticFunctionNameRead( + lowerer: Lowerer, + expr: ts.PropertyAccessExpression, +): string | null { + const names = lowerer.staticFnNames; + if ( + names === null || + expr.name.text !== "name" || + expr.questionDotToken !== undefined || + !ts.isIdentifier(expr.expression) + ) { + return null; + } + const symbol = lowerer.checker.getSymbolAtLocation(expr.expression); + if (!symbol || !names.has(symbol)) return null; + const name = names.get(symbol); + if (name === null || name === undefined) { + lowerer.unsupported( + "SC1090", + expr, + `reading '${expr.expression.text}.name' when the call passes a function whose name is only known at run time (generic wrappers fold the name of a function declaration, a const-bound function, or a wrapper call's result)`, + ); + } + return name; +} diff --git a/packages/compiler/src/frontend/lowering/typed-arguments.ts b/packages/compiler/src/frontend/lowering/typed-arguments.ts new file mode 100644 index 000000000..751d0d6be --- /dev/null +++ b/packages/compiler/src/frontend/lowering/typed-arguments.ts @@ -0,0 +1,511 @@ +import * as ts from "../ts7/adapter.js"; +import { bodyReadsArguments } from "../arguments-usage.js"; +import { isJsSourceFile, locOf } from "../program.js"; +import { unitOnlyUnion } from "../type-mapper.js"; +import { + DYN, + type IrExpr, + type IrLocal, + type IrStmt, + type IrType, + arrayOf, + typeEquals, +} from "../../ir/ir.js"; +import type { Lowerer } from "./lowerer.js"; +import { captureContextArguments } from "./function-context.js"; +import { isThisParameter, type ParamShape } from "./call-signatures.js"; +import { tryLowerExpression } from "./expressions/try-lower-expression.js"; +import { omittedArgFor } from "./call-arguments.js"; + +/** Variadic forwarding through a FIXED signature. + * + * A TypeScript function expression that declares no parameters and reads + * `arguments` (`function (this: unknown) { return fn.apply(this, arguments) } + * as F`) is lowered against the fixed function type its value is asserted + * to: one native slot per position of that type, and `arguments` is the + * tuple of those slots. Inside a generic instantiation F is the call + * site's concrete signature, so a higher-order wrapper specializes to the + * wrapped function's exact ABI. Forwarding (`f.apply(thisArg, args)`, + * `f(...args)`) then calls with the tuple's positions directly. */ + +/** `e`, with the type-only wrappers that never change its value removed. */ +export function stripTypeWrappers(e: ts.Expression): ts.Expression { + let cur = e; + for (;;) { + if (ts.isParenthesizedExpression(cur)) cur = cur.expression; + else if (ts.isAsExpression(cur) || ts.isTypeAssertion(cur) || ts.isSatisfiesExpression(cur)) + cur = cur.expression; + else if (ts.isNonNullExpression(cur)) cur = cur.expression; + else return cur; + } +} + +/** The outermost type assertion around `node` (`node as unknown as F` → F), + * looking through parentheses; null when the value is not asserted. */ +function assertedTypeNode(node: ts.Node): ts.TypeNode | null { + let cur: ts.Node = node; + let found: ts.TypeNode | null = null; + for (let parent = cur.parent; parent !== undefined; parent = cur.parent) { + if (ts.isParenthesizedExpression(parent)) { + cur = parent; + continue; + } + if ((ts.isAsExpression(parent) || ts.isTypeAssertion(parent)) && parent.expression === cur) { + found = parent.type; + cur = parent; + continue; + } + break; + } + return found; +} + +/** The fixed function type a value is asserted to, or null. */ +function assertedFuncType(lowerer: Lowerer, node: ts.Node): (IrType & { kind: "func" }) | null { + const typeNode = assertedTypeNode(node); + if (!typeNode) return null; + const mapped = lowerer.mapTypeOf(lowerer.checker.getTypeFromTypeNode(typeNode)); + return mapped?.kind === "func" ? mapped : null; +} + +function sameFuncType(a: IrType & { kind: "func" }, b: IrType & { kind: "func" }): boolean { + return JSON.stringify(a) === JSON.stringify(b); +} + +/** A function expression whose `arguments` can be typed: TypeScript source, + * no runtime parameters (a `this` parameter is type-only), an ordinary + * (non-generator) function that reads its own `arguments`. */ +export function typedArgumentsCandidate(node: ts.Node): node is ts.FunctionExpression { + return ( + ts.isFunctionExpression(node) && + node.asteriskToken === undefined && + !isJsSourceFile(node.getSourceFile()) && + node.parameters.nodes.every(isThisParameter) && + bodyReadsArguments(node) + ); +} + +/** The fixed signature a typed-arguments function expression is lowered + * against: the type its value is asserted to (`function () {…} as F`), the + * one type every use of its const binding asserts it to (`const w = + * function () {…}; return w as unknown as F`), or its contextual function + * type. Null when none names one fixed-arity signature. */ +export function typedArgumentsTarget( + lowerer: Lowerer, + node: ts.FunctionExpression, +): (IrType & { kind: "func" }) | null { + const fixed = (t: (IrType & { kind: "func" }) | null) => (t && t.rest !== true ? t : null); + const direct = assertedFuncType(lowerer, node); + if (direct) return fixed(direct); + let top: ts.Node = node; + while (top.parent && ts.isParenthesizedExpression(top.parent)) top = top.parent; + const decl = top.parent; + if ( + decl && + ts.isVariableDeclaration(decl) && + decl.initializer === top && + decl.type === undefined && + ts.isIdentifier(decl.name) && + (ts.getCombinedNodeFlags(decl) & ts.NodeFlags.Const) !== 0 + ) { + const symbol = lowerer.checker.getSymbolAtLocation(decl.name); + const scope = enclosingScope(decl); + if (symbol && scope) { + let target: (IrType & { kind: "func" }) | null = null; + let ok = true; + let uses = 0; + ts.walkPreorder(scope, (n) => { + if (!ok) return "stop"; + if (n === decl.name || !ts.isIdentifier(n) || n.text !== decl.name.text) return undefined; + if (lowerer.checker.getSymbolAtLocation(n) !== symbol) return undefined; + uses++; + const asserted = assertedFuncType(lowerer, n); + if (!asserted || (target && !sameFuncType(target, asserted))) ok = false; + else target = asserted; + return undefined; + }); + if (ok && uses > 0) return fixed(target); + } + } + const ctxType = lowerer.checker.getContextualType(node); + if (!ctxType) return null; + const sigs = lowerer.checker.getCallSignatures(lowerer.checker.getNonNullableType(ctxType)); + if (sigs.length !== 1) return null; + const mapped = lowerer.mapTypeOf(lowerer.checker.getNonNullableType(ctxType)); + return mapped?.kind === "func" ? fixed(mapped) : null; +} + +function enclosingScope(node: ts.Node): ts.Node | null { + for (let cur = node.parent; cur !== undefined; cur = cur.parent) { + if (ts.isSourceFile(cur) || (ts.isFunctionLike(cur) && cur.body !== undefined)) return cur; + } + return null; +} + +/** The tuple type of a fixed parameter list, interned exactly as the + * spelled tuple (and `Parameters`) map: positional fields, or the + * empty-array form for no parameters. */ +export function tupleTypeOfParams(lowerer: Lowerer, params: readonly IrType[]): IrType { + if (params.length === 0) return arrayOf(unitOnlyUnion(lowerer.unions)); + const fields = params.map((type, i) => ({ name: String(i), type })); + fields.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0)); + return { kind: "record", shapeId: lowerer.shapes.intern(fields, true) }; +} + +/** The ABI shapes of a typed-arguments function: one required slot per + * position of its target signature. */ +export function typedArgumentsShapes(target: IrType & { kind: "func" }): ParamShape[] { + return target.params.map((type) => ({ type, mode: "required" })); +} + +/** Declares the hidden positional slots of a typed-arguments function and + * binds its `arguments` to their tuple. */ +export function declareTypedArguments( + lowerer: Lowerer, + node: ts.FunctionExpression, + shapes: readonly ParamShape[], + params: { localId: string; name: string; type: IrType }[], + prologue: IrStmt[], +): void { + const loc = locOf(node); + const slots = shapes.map((shape, i) => { + const slot = lowerer.declareHiddenLocal(`%arguments.${i}`, shape.type); + params.push({ localId: slot.id, name: `%arguments.${i}`, type: shape.type }); + return slot; + }); + const tupleType = tupleTypeOfParams( + lowerer, + shapes.map((s) => s.type), + ); + const local = lowerer.declareHiddenLocal("%arguments", tupleType); + lowerer.ctx.argumentsLocal = local; + // Pure forwarding (every read is forwarded, counted, or aliased by a + // binding that is itself only forwarded): no tuple exists at all. + if ( + onlyForwarded(lowerer, node, (id) => id.text === "arguments" && argumentsOwner(id) === node) + ) { + lowerer.pureTuples.set(local.id, slots); + return; + } + prologue.push({ + kind: "varDecl", + localId: local.id, + init: lowerer.tupleOfSlots(slots, tupleType, node, loc), + loc, + }); +} + +/** Whether every read of a tuple binding in `fn` (identified by `isRoot`) + * only forwards it: `f.apply(t, x)`, `g(...x)` into a program function, + * `x.length`, or a const alias whose own reads only forward. Reads inside + * nested functions (arrows included) capture the binding and need the + * tuple. Type assertions around a read never matter. */ +export function onlyForwarded( + lowerer: Lowerer, + fn: ts.Node, + isRoot: (id: ts.Identifier) => boolean, +): boolean { + const body = (fn as ts.Node & { body?: ts.Node }).body; + if (body === undefined) return false; + const roots: ((id: ts.Identifier) => boolean)[] = [isRoot]; + let ok = true; + const visit = (scope: ts.Node, test: (id: ts.Identifier) => boolean): void => { + ts.walkPreorder(scope, (n) => { + if (!ok) return "stop"; + if (ts.isTypeNode(n)) return "skip"; + if (!ts.isIdentifier(n) || !test(n)) return undefined; + for (let cur = n.parent; cur !== undefined && cur !== fn; cur = cur.parent) { + if (ts.isFunctionLike(cur)) { + ok = false; + return "stop"; + } + } + let top: ts.Node = n; + while ( + top.parent && + (ts.isParenthesizedExpression(top.parent) || + ts.isAsExpression(top.parent) || + ts.isTypeAssertion(top.parent) || + ts.isSatisfiesExpression(top.parent) || + ts.isNonNullExpression(top.parent)) + ) + top = top.parent; + const p = top.parent; + if (!p) { + ok = false; + return "stop"; + } + if (ts.isPropertyAccessExpression(p) && p.expression === top && p.name.text === "length") + return undefined; + if ( + ts.isCallExpression(p) && + p.arguments.nodes.length === 2 && + p.arguments.nodes[1] === top && + ts.isPropertyAccessExpression(p.expression) && + p.expression.name.text === "apply" && + p.expression.questionDotToken === undefined + ) + return undefined; + if (ts.isSpreadElement(p) && p.parent && ts.isCallExpression(p.parent)) { + const sigs = lowerer.checker.getCallSignatures( + lowerer.checker.getNonNullableType(lowerer.typeOf(p.parent.expression)), + ); + const decl = sigs.length === 1 ? lowerer.checker.signatureDeclaration(sigs[0]!) : undefined; + if (decl && !decl.getSourceFile().isDeclarationFile) return undefined; + } + if ( + ts.isVariableDeclaration(p) && + p.initializer === top && + ts.isIdentifier(p.name) && + (ts.getCombinedNodeFlags(p) & ts.NodeFlags.Const) !== 0 + ) { + const alias = lowerer.checker.getSymbolAtLocation(p.name); + const aliasName = p.name; + if (alias) { + roots.push((id) => id !== aliasName && lowerer.checker.getSymbolAtLocation(id) === alias); + return undefined; + } + } + ok = false; + return "stop"; + }); + }; + for (let i = 0; i < roots.length && ok; i++) visit(body, roots[i]!); + return ok; +} + +/** The native slots of a pure-forwarded tuple binding read at `node` in + * the current function, or null when the tuple is materialized. */ +function pureSlotsOf(lowerer: Lowerer, node: ts.Expression): IrLocal[] | null { + const operand = stripTypeWrappers(node); + if (!ts.isIdentifier(operand)) return null; + const local = + operand.text === "arguments" ? lowerer.ctx.argumentsLocal : lowerer.peekLocal(operand); + return local ? (lowerer.pureTuples.get(local.id) ?? null) : null; +} + +/** The positional values `node` forwards when it is a pure-forwarded tuple + * (slot reads, in order), or null. */ +export function pureForwardedValues(lowerer: Lowerer, node: ts.Expression): IrExpr[] | null { + const slots = pureSlotsOf(lowerer, node); + if (!slots) return null; + const loc = locOf(node); + return slots.map((slot) => ({ kind: "varRef", localId: slot.id, type: slot.type, loc })); +} + +/** Whether `node` reads a tuple-typed `arguments` binding: the nearest + * ordinary function (arrows inherit it) is a typed-arguments function. + * Looks the binding up without allocating captures. */ +function isTypedArgumentsRead(lowerer: Lowerer, node: ts.Expression): boolean { + if (!ts.isIdentifier(node) || node.text !== "arguments") return false; + const stack = lowerer.fnStack; + let owner = stack.length - 1; + while (owner > 0 && stack[owner]!.inheritsArguments) owner--; + const own = owner >= 0 ? stack[owner]!.argumentsLocal : undefined; + return own !== undefined && own !== null && own.type.kind !== "dyn"; +} + +/** The fixed tuple `node` forwards, or null: the typed `arguments`, a + * fixed-tuple rest parameter, or a const alias of either — seen through + * type assertions, which never change the value. */ +export function forwardedTupleType(lowerer: Lowerer, node: ts.Expression): IrType | null { + const operand = stripTypeWrappers(node); + if (!ts.isIdentifier(operand)) return null; + if (operand.text === "arguments") { + if (!isTypedArgumentsRead(lowerer, operand)) return null; + return captureContextArguments(lowerer.fnStack)!.type; + } + const symbol = lowerer.resolveValueSymbol(operand); + if (!symbol) return null; + const alias = lowerer.tupleAliasSymbols.get(symbol); + if (alias) return alias; + const decl = lowerer.checker.valueDeclarationOf(symbol); + if ( + decl !== undefined && + ts.isParameter(decl) && + decl.dotDotDotToken !== undefined && + ts.isIdentifier(decl.name) && + !isJsSourceFile(decl.getSourceFile()) + ) { + const t = lowerer.mapTypeOf(lowerer.typeOf(operand)); + if (t && isTupleValueType(lowerer, t)) return t; + } + return null; +} + +function isTupleValueType(lowerer: Lowerer, t: IrType): boolean { + if (t.kind === "record") return lowerer.shapes.get(t.shapeId)?.tuple === true; + // The empty tuple's representation (mapType's `[]` rule). + return typeEquals(t, arrayOf(unitOnlyUnion(lowerer.unions))); +} + +/** The positional reads of a forwarded tuple value (already saved in a + * local), in order. */ +function tupleReads( + lowerer: Lowerer, + ref: () => IrExpr, + tupleType: IrType, + loc: IrExpr["loc"], +): IrExpr[] { + if (tupleType.kind !== "record") return []; + const shape = lowerer.shapes.get(tupleType.shapeId)!; + return [...shape.fields] + .sort((a, b) => Number(a.name) - Number(b.name)) + .map((field) => ({ + kind: "recordGet" as const, + obj: ref(), + shapeId: tupleType.shapeId, + field: field.name, + type: field.type, + loc, + })); +} + +/** `f.apply(thisArg, args)` where `args` forwards a fixed tuple and `f` is a + * fixed-arity function value: a direct call of `f` with the tuple's + * positions and `thisArg` as the call-time receiver. Null for every other + * form (the existing Function.prototype paths keep them). */ +export function lowerForwardingApply( + lowerer: Lowerer, + call: ts.CallExpression, + access: ts.PropertyAccessExpression, +): IrExpr | null { + if (access.name.text !== "apply" || access.questionDotToken !== undefined) return null; + const argNodes = call.arguments.nodes; + if (argNodes.length !== 2 || argNodes.some(ts.isSpreadElement)) return null; + const tupleType = forwardedTupleType(lowerer, argNodes[1]!); + if (!tupleType) return null; + const callee = tryLowerExpression(lowerer, access.expression); + if (callee?.type.kind !== "func") return null; + const fnType = callee.type; + const loc = locOf(call); + if (fnType.rest === true) { + lowerer.unsupported( + "SC1090", + call, + "forwarding a fixed argument tuple through 'apply' to a variadic function value", + ); + } + const savedFn = lowerer.declareHiddenLocal("%applyFn", fnType); + const savedThis = lowerer.declareHiddenLocal("%applyThis", DYN); + const stmts: IrStmt[] = [ + { kind: "varDecl", localId: savedFn.id, init: callee, loc }, + { + kind: "varDecl", + localId: savedThis.id, + init: lowerer.lowerExprExpecting(argNodes[0]!, DYN), + loc, + }, + ]; + let reads = pureForwardedValues(lowerer, argNodes[1]!); + if (!reads) { + const savedArgs = lowerer.declareHiddenLocal("%applyArgs", tupleType); + stmts.push({ + kind: "varDecl", + localId: savedArgs.id, + init: lowerer.lowerExpr(stripTypeWrappers(argNodes[1]!)), + loc, + }); + reads = tupleReads( + lowerer, + () => ({ kind: "varRef", localId: savedArgs.id, type: tupleType, loc }), + tupleType, + loc, + ); + } + // JS drops surplus positions and fills missing ones with undefined. + const args = fnType.params.map((param, i): IrExpr => { + const read = reads[i]; + if (read) return lowerer.coerceInto(call, read, param); + const absent = omittedArgFor(lowerer, param, loc); + if (!absent) { + lowerer.unsupported( + "SC1090", + call, + "forwarding fewer arguments through 'apply' than the callee's required parameters", + ); + } + return absent; + }); + const invoked: IrExpr = { + kind: "callValue", + callee: { kind: "varRef", localId: savedFn.id, type: fnType, loc }, + receiver: { kind: "varRef", localId: savedThis.id, type: DYN, loc }, + args, + type: fnType.ret, + loc, + }; + return { kind: "seqExpr", stmts, result: invoked, type: fnType.ret, loc }; +} + +/** `const args = arguments as …` (or an alias of a rest tuple) inside a + * typed-arguments function: the binding holds the tuple itself — the + * assertion only re-describes the value — and stays a forwarding source. */ +export function lowerTupleAliasDecl(lowerer: Lowerer, decl: ts.VariableDeclaration): IrStmt | null { + if ( + !decl.initializer || + !ts.isIdentifier(decl.name) || + (ts.getCombinedNodeFlags(decl) & ts.NodeFlags.Const) === 0 || + lowerer.fnStack.length === 0 + ) { + return null; + } + const tupleType = forwardedTupleType(lowerer, decl.initializer); + if (!tupleType) return null; + const symbol = lowerer.checker.getSymbolAtLocation(decl.name); + if (!symbol) return null; + const declared = lowerer.mapTypeOf(lowerer.typeOf(decl.name)); + if (declared && declared.kind !== "dyn" && JSON.stringify(declared) !== JSON.stringify(tupleType)) + return null; + lowerer.tupleAliasSymbols.set(symbol, tupleType); + const slots = pureSlotsOf(lowerer, decl.initializer); + if (slots) { + // An alias of a pure-forwarded tuple is itself only forwarded + // (onlyForwarded followed it): it names the same slots. + const local = lowerer.declareLocal(decl.name, decl.name.text, tupleType, false); + lowerer.pureTuples.set(local.id, slots); + return { kind: "block", body: [], loc: locOf(decl) }; + } + const init = lowerer.lowerExpr(stripTypeWrappers(decl.initializer)); + const local = lowerer.declareLocal(decl.name, decl.name.text, tupleType, false); + return { kind: "varDecl", localId: local.id, init, loc: locOf(decl) }; +} + +/** A spread operand forwarding a fixed tuple (forwardedTupleType), + * including one that resolved to no positions at all. */ +export function isForwardedTupleSpread(lowerer: Lowerer, node: ts.Expression): boolean { + return forwardedTupleType(lowerer, node) !== null; +} + +/** `arguments` read inside a typed-arguments function: its fixed arity. */ +export function typedArgumentsArity(lowerer: Lowerer, node: ts.Expression): number | null { + if (!ts.isIdentifier(node)) return null; + const t = forwardedTupleType(lowerer, node); + if (!t) return null; + if (t.kind !== "record") return 0; + const fields = lowerer.shapes.get(t.shapeId)?.fields ?? []; + // A position that may be undefined may also be absent at the call: the + // native signature always passes it, so the count is not static. + if ( + fields.some( + (f) => f.type.kind === "union" && lowerer.armTag(f.type.unionId, { kind: "undefinedT" }) >= 0, + ) + ) { + lowerer.unsupported( + "SC1090", + node.parent ?? node, + "'length' of an argument tuple whose positions may be omitted (the native signature always passes every position, so the count is not static)", + ); + } + return fields.length; +} + +/** The function whose `arguments` an identifier reads: the nearest + * enclosing non-arrow function. */ +export function argumentsOwner(node: ts.Node): ts.Node | null { + for (let cur = node.parent; cur !== undefined; cur = cur.parent) { + if (ts.isFunctionLike(cur) && !ts.isArrowFunction(cur)) return cur; + if (ts.isSourceFile(cur)) return null; + } + return null; +} diff --git a/packages/compiler/src/frontend/type-mapper.ts b/packages/compiler/src/frontend/type-mapper.ts index 123a5d1a0..19e7775cc 100644 --- a/packages/compiler/src/frontend/type-mapper.ts +++ b/packages/compiler/src/frontend/type-mapper.ts @@ -1552,6 +1552,15 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { contextResolutions++; return viaIndexed; } + // `Parameters` / `ReturnType` over a bound function-typed + // parameter (variadic higher-order wrappers): the checker keeps both + // conditionals symbolic inside the body, and their `infer` positions + // match a call signature, which matchInferReference cannot decide. + const viaSignature = mapBoundSignatureAlias(type, ctx); + if (viaSignature !== null) { + contextResolutions++; + return viaSignature; + } // A conditional type over a bound parameter (`Infer`, a // mapped type's member): evaluate it against the bindings. if (type.isConditionalType()) { @@ -4324,6 +4333,16 @@ function mapCallSignature(sig: ts.Signature, ctx: TypeMapperCtx): IrType | null // declaration as a typed packed-array closure would bypass its boxed // spread/error semantics and change coverage classification. if (rest && decl !== undefined && isJsSourceFile(decl.getSourceFile())) return null; + // A fixed-tuple rest (`...args: [A, B]`, `...args: Parameters` over + // a bound F) flattens to its positional parameters. Pattern rests keep + // their packed-record ABI and stay unmapped as values. + if (rest && decl !== undefined && ts.isParameter(decl) && ts.isIdentifier(decl.name)) { + const elems = restTupleParamTypes(checker.getTypeOfSymbol(p), ctx); + if (elems) { + params.push(...elems); + continue; + } + } const optional = decl !== undefined && ts.isParameter(decl) && @@ -4814,6 +4833,167 @@ function mapBoundIndexedAccess(type: ts.Type, ctx: TypeMapperCtx): IrType | null return mapType(checker.getTypeOfSymbol(prop), ctx); } +/** The standard library's `Parameters` / `ReturnType` over a type + * parameter the current instantiation binds to a callable checker type: + * that type's one call signature. Concrete arguments never reach here (the + * checker evaluates those conditionals itself); generic bodies keep them + * symbolic because their `infer` sits in a call signature. */ +function boundSignatureAlias( + type: ts.Type, + ctx: TypeMapperCtx, +): { name: "Parameters" | "ReturnType"; sig: ts.Signature } | null { + const { checker, resolveTypeParamTs } = ctx; + if (!resolveTypeParamTs) return null; + const alias = type.getAliasSymbol(); + if (!alias || (alias.name !== "Parameters" && alias.name !== "ReturnType")) return null; + const aliasArgs = type.getAliasTypeArguments(); + const arg = aliasArgs[0]; + if (aliasArgs.length !== 1 || !arg || !(arg.flags & ts.TypeFlags.TypeParameter)) return null; + const isLibAlias = checker + .declarationsOf(alias) + .some((d) => ts.isTypeAliasDeclaration(d) && ctx.isStdlibFile(d.getSourceFile())); + if (!isLibAlias) return null; + const bound = resolveTypeParamTs(arg); + if (!bound || bound.flags & ts.TypeFlags.TypeParameter) return null; + const sigs = checker.getCallSignatures(bound); + const sig = sigs[0]; + if (sigs.length !== 1 || !sig || sig.getTypeParameters().length > 0) return null; + return { name: alias.name, sig }; +} + +/** One position of a fixed-arity parameter tuple. An optional position + * still has a native slot (the function type's `T | undefined` slot). */ +interface TupleElement { + type: ts.Type; + optional: boolean; +} + +/** The positional parameters of a signature as a fixed tuple: a trailing + * rest flattened when its own type is a fixed tuple. Null for a variadic + * position, which has no fixed native arity. */ +function signatureParameters(sig: ts.Signature, ctx: TypeMapperCtx): TupleElement[] | null { + const { checker } = ctx; + const out: TupleElement[] = []; + for (const p of sig.getParameters()) { + const decl = checker.valueDeclarationOf(p); + const type = checker.getTypeOfSymbol(p); + if (decl !== undefined && ts.isParameter(decl)) { + if (decl.dotDotDotToken !== undefined) { + const inner = restTupleElements(type, ctx); + if (!inner) return null; + out.push(...inner); + continue; + } + out.push({ + type, + optional: decl.questionToken !== undefined || decl.initializer !== undefined, + }); + continue; + } + out.push({ type, optional: (p.flags & ts.SymbolFlags.Optional) !== 0 }); + } + return out; +} + +/** The required-only positional parameter types of a signature (the + * value-level `Parameters` tuple), null when a position is optional + * or variadic: such a tuple's length varies per call. */ +function signatureParameterTypes(sig: ts.Signature, ctx: TypeMapperCtx): ts.Type[] | null { + const elems = signatureParameters(sig, ctx); + if (!elems || elems.some((e) => e.optional)) return null; + return elems.map((e) => e.type); +} + +/** The element types of a REST parameter's type when it is a fixed tuple: + * a tuple of required elements (`[number, string]`, the empty `[]`), a type + * parameter bound to one, or `Parameters` over a bound function type. + * Such a rest parameter has a compile-time arity, so its native ABI is the + * flattened positional list (exactly the equivalent fixed signature — + * TypeScript treats `(...a: [A, B]) => R` and `(a: A, b: B) => R` alike). */ +export function restTupleElementTypes(type: ts.Type, ctx: TypeMapperCtx): ts.Type[] | null { + return restTupleElements(type, ctx)?.map((e) => e.type) ?? null; +} + +/** restTupleElementTypes with each position's optionality: a rest tuple + * may end in optional positions (`[A, B?]`, `Parameters<(a: A, b?: B) => + * R>`), which flatten to `T | undefined` slots exactly as the function + * type's optional parameters do. */ +function restTupleElements(type: ts.Type, ctx: TypeMapperCtx): TupleElement[] | null { + const { checker } = ctx; + if (type.flags & ts.TypeFlags.TypeParameter) { + const bound = ctx.resolveTypeParamTs?.(type); + return bound && bound !== type ? restTupleElements(bound, ctx) : null; + } + const viaAlias = boundSignatureAlias(type, ctx); + if (viaAlias) { + return viaAlias.name === "Parameters" ? signatureParameters(viaAlias.sig, ctx) : null; + } + if (!checker.isTupleType(type)) return null; + const ref = type as ts.TupleTypeReference; + const tupleShape = + (ref.elementFlags as ts.ElementFlags[] | undefined) !== undefined + ? ref + : (ref.getTarget() as ts.TupleType | undefined); + const args = checker.getTypeArguments(ref); + if (args.length === 0) return []; + const flags = tupleShape?.elementFlags; + if (flags === undefined) return null; + if (flags.some((f) => !(f & (ts.ElementFlags.Required | ts.ElementFlags.Optional)))) return null; + return args.map((t, i) => ({ + type: t, + optional: (flags[i]! & ts.ElementFlags.Optional) !== 0, + })); +} + +/** One tuple element's IR type under the tuple mapping's rules. */ +function mapTupleElement(t: ts.Type, ctx: TypeMapperCtx): IrType | null { + let et = (t.flags & ts.TypeFlags.Any) !== 0 ? DYN : mapType(t, ctx); + if (et?.kind === "void" && isUnitOnlyTsType(t, ctx.resolveTypeParam)) + et = unitOnlyUnion(ctx.unions); + return !et || et.kind === "void" ? null : et; +} + +/** The mapped element types of a fixed-tuple rest parameter type + * (restTupleElementTypes), null when it is not one or an element does not + * map. */ +export function restTupleParamTypes(type: ts.Type, ctx: TypeMapperCtx): IrType[] | null { + const elems = restTupleElements(type, ctx); + if (!elems) return null; + const out: IrType[] = []; + for (const e of elems) { + let et = mapTupleElement(e.type, ctx); + // An optional position's slot holds undefined when the caller omits + // it, as an optional parameter's does (mapCallSignature). + if (et && e.optional && et.kind !== "dyn") et = withUndefinedArm(et, ctx.unions); + if (!et) return null; + out.push(et); + } + return out; +} + +/** `Parameters` (the fixed tuple of F's parameters, interned exactly as + * the spelled tuple maps) and `ReturnType` (F's mapped result) over a + * bound function-typed type parameter. */ +function mapBoundSignatureAlias(type: ts.Type, ctx: TypeMapperCtx): IrType | null { + const target = boundSignatureAlias(type, ctx); + if (!target) return null; + if (target.name === "ReturnType") { + const ret = ctx.checker.getReturnTypeOfSignature(target.sig); + return ret.flags & ts.TypeFlags.Any ? DYN : mapType(ret, ctx); + } + const elems = signatureParameterTypes(target.sig, ctx); + if (!elems) return null; + if (elems.length === 0) return arrayOf(unitOnlyUnion(ctx.unions)); + const fields: { name: string; type: IrType }[] = []; + for (let i = 0; i < elems.length; i++) { + const et = mapTupleElement(elems[i]!, ctx); + if (!et) return null; + fields.push({ name: String(i), type: et }); + } + fields.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0)); + return { kind: "record", shapeId: ctx.shapes.intern(fields, true) }; +} + /** `Partial` / `Readonly` where T is a STILL-GENERIC type parameter — * the one mapped-type spelling a monomorphized body can contain that the * checker cannot resolve for us (`keyof T` is unknown inside the body, so @@ -4885,13 +5065,17 @@ function mapGenericAwaitedAlias(type: ts.Type, ctx: TypeMapperCtx): IrType | nul const aliasArgs = type.getAliasTypeArguments(); const arg = aliasArgs[0]; if (aliasArgs.length !== 1 || !arg) return null; - if (!(arg.flags & ts.TypeFlags.TypeParameter)) return null; + // `Awaited>` (an async wrapper's settled result) resolves + // through F's bound signature like a bare type parameter. + const viaSignature = + arg.flags & ts.TypeFlags.TypeParameter ? null : boundSignatureAlias(arg, ctx); + if (!(arg.flags & ts.TypeFlags.TypeParameter) && viaSignature?.name !== "ReturnType") return null; // Provenance: the STANDARD LIBRARY's alias, not a user's shadowing one. const isLibAlias = ctx.checker .declarationsOf(alias) .some((d) => ts.isTypeAliasDeclaration(d) && ctx.isStdlibFile(d.getSourceFile())); if (!isLibAlias) return null; - const bound = resolveTypeParam(arg); + const bound = viaSignature ? mapBoundSignatureAlias(arg, ctx) : resolveTypeParam(arg); if (!bound) return null; let r = bound; while (r.kind === "promise") r = r.inner; @@ -5083,10 +5267,13 @@ function matchInferReference( const target = (t: ts.Type): ts.Type | undefined => t.isTypeReference?.() ? (t as ts.TypeReference).getTarget?.() : undefined; const wantTarget = target(extendsType) ?? extendsType.getAliasSymbol(); - if (!wantTarget) return undefined; + if (!wantTarget) return matchInferSignature(checked, extendsType, ctx); const gotTarget = target(checked) ?? checked.getAliasSymbol(); - if (!gotTarget) return checked.flags & ts.TypeFlags.Object ? undefined : false; - if (gotTarget !== wantTarget) return undefined; + if (!gotTarget) + return checked.flags & ts.TypeFlags.Object + ? matchInferThenable(checked, extendsType, ctx) + : false; + if (gotTarget !== wantTarget) return matchInferThenable(checked, extendsType, ctx); const wantArgs = target(extendsType) ? checker.getTypeArguments(extendsType as ts.TypeReference) : extendsType.getAliasTypeArguments(); @@ -5109,6 +5296,78 @@ function matchInferReference( return inferred; } +/** The type parameter an `infer X` declares, when `t` is one. */ +function inferSymbolOf(t: ts.Type, ctx: TypeMapperCtx): ts.Symbol | undefined { + if (!(t.flags & ts.TypeFlags.TypeParameter)) return undefined; + const symbol = t.getSymbol(); + return symbol && + ctx.checker + .declarationsOf(symbol) + .some((d) => d.parent !== undefined && ts.isInferTypeNode(d.parent)) + ? symbol + : undefined; +} + +/** `checked extends (...args: any) => infer R` — the shape of the standard + * library's ReturnType, over a concrete function type: R binds to its + * result. Parameters must be `any` (they constrain nothing); anything else + * stays undecided. A primitive never matches a function type. */ +function matchInferSignature( + checked: ts.Type, + extendsType: ts.Type, + ctx: TypeMapperCtx, +): Map | false | undefined { + const { checker } = ctx; + const wantSigs = checker.getCallSignatures(extendsType); + const want = wantSigs[0]; + if (wantSigs.length !== 1 || !want || want.getTypeParameters().length > 0) return undefined; + const anyParams = want.getParameters().every((p) => { + const t = checker.getTypeOfSymbol(p); + return ( + (t.flags & ts.TypeFlags.Any) !== 0 || + (checker.isArrayType(t) && + (checker.getTypeArguments(t as ts.TypeReference)[0]!.flags & ts.TypeFlags.Any) !== 0) + ); + }); + if (!anyParams) return undefined; + if (!(checked.flags & ts.TypeFlags.Object)) return false; + const gotSigs = checker.getCallSignatures(checked); + const got = gotSigs[0]; + if (gotSigs.length !== 1 || !got || got.getTypeParameters().length > 0) return undefined; + const wantRet = checker.getReturnTypeOfSignature(want); + const infer = inferSymbolOf(wantRet, ctx); + if (infer) return new Map([[infer, checker.getReturnTypeOfSignature(got)]]); + return wantRet.flags & ts.TypeFlags.Any ? new Map() : undefined; +} + +/** `checked extends PromiseLike` (or `Promise`) over the + * standard library's Promise: U binds to the settled type. An object with + * no `then` member is no thenable. Everything else stays undecided. */ +function matchInferThenable( + checked: ts.Type, + extendsType: ts.Type, + ctx: TypeMapperCtx, +): Map | false | undefined { + const { checker } = ctx; + const stdlibNamed = (t: ts.Type, names: readonly string[]): boolean => { + const symbol = t.getSymbol(); + return ( + symbol !== undefined && + names.includes(symbol.name) && + checker.declarationsOf(symbol).some((d) => ctx.isStdlibFile(d.getSourceFile())) + ); + }; + if (!stdlibNamed(extendsType, ["PromiseLike", "Promise"])) return undefined; + const wantArgs = checker.getTypeArguments(extendsType as ts.TypeReference); + const infer = wantArgs.length === 1 ? inferSymbolOf(wantArgs[0]!, ctx) : undefined; + if (!infer) return undefined; + if (stdlibNamed(checked, ["Promise", "PromiseLike"])) { + const gotArgs = checker.getTypeArguments(checked as ts.TypeReference); + return gotArgs.length === 1 ? new Map([[infer, gotArgs[0]!]]) : undefined; + } + return checker.getPropertyOfType(checked, "then") === undefined ? false : undefined; +} + /** The type mentions a mapped key or bound alias parameter (a template * shape evalMappedTemplate does not reduce). */ function mentionsMappedParams( @@ -5259,10 +5518,22 @@ export function isUnitOnlyTsType(t: ts.Type, resolveTypeParam?: TypeParamResolve return parts.every( (p) => (p.flags & UNIT) !== 0 || - ((p.flags & ts.TypeFlags.TypeParameter) !== 0 && resolveTypeParam?.(p)?.kind === "void"), + ((p.flags & ts.TypeFlags.TypeParameter) !== 0 && resolveTypeParam?.(p)?.kind === "void") || + boundVoidReturnType(p, resolveTypeParam), ); } +/** `ReturnType` over a type parameter bound to a void-returning + * function (a variadic wrapper's result slot): a unit-only value. */ +function boundVoidReturnType(p: ts.Type, resolveTypeParam?: TypeParamResolver): boolean { + if (!resolveTypeParam || p.getAliasSymbol()?.name !== "ReturnType") return false; + const args = p.getAliasTypeArguments(); + const arg = args[0]; + if (args.length !== 1 || !arg || !(arg.flags & ts.TypeFlags.TypeParameter)) return false; + const bound = resolveTypeParam(arg); + return bound?.kind === "func" && bound.ret.kind === "void"; +} + /** IR-level `t | undefined`, canonicalized and fenced exactly like the * ts-union branch of mapType (typeKey-sorted arms, deduplicated; * Date/dyn/void arms unrepresentable; a func arm IS representable — the diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6380-variadic-wrapper-arguments-apply.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6380-variadic-wrapper-arguments-apply.ts.json new file mode 100644 index 000000000..df0124219 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6380-variadic-wrapper-arguments-apply.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6380-variadic-wrapper-arguments-apply.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6381-variadic-wrapper-async.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6381-variadic-wrapper-async.ts.json new file mode 100644 index 000000000..2138df43d --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6381-variadic-wrapper-async.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6381-variadic-wrapper-async.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6382-variadic-wrapper-methods-this.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6382-variadic-wrapper-methods-this.ts.json new file mode 100644 index 000000000..2135f8b6f --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6382-variadic-wrapper-methods-this.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6382-variadic-wrapper-methods-this.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6383-variadic-wrapper-rest-tuple.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6383-variadic-wrapper-rest-tuple.ts.json new file mode 100644 index 000000000..a2f7f32c4 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6383-variadic-wrapper-rest-tuple.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6383-variadic-wrapper-rest-tuple.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6384-variadic-wrapper-nested.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6384-variadic-wrapper-nested.ts.json new file mode 100644 index 000000000..d69618e3a --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6384-variadic-wrapper-nested.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6384-variadic-wrapper-nested.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/variadic-wrapper-fences.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/variadic-wrapper-fences.ts.json new file mode 100644 index 000000000..c3e44b2e5 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/variadic-wrapper-fences.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/diagnostics/variadic-wrapper-fences.ts" + ], + "diags": [] +} diff --git a/tests/corpus/6380-variadic-wrapper-arguments-apply.ts b/tests/corpus/6380-variadic-wrapper-arguments-apply.ts new file mode 100644 index 000000000..1a14b6e2f --- /dev/null +++ b/tests/corpus/6380-variadic-wrapper-arguments-apply.ts @@ -0,0 +1,77 @@ +// A generic higher-order wrapper over `(...args: any) => any` that forwards +// `arguments` with `fn.apply(this, arguments)`, and types its options with +// Parameters / ReturnType. Each call site specializes the wrapper to +// the wrapped function's exact signature. +type AnyFn = (...args: any) => any; + +interface TraceOptions { + name?: string; + tags?: (...args: Parameters) => Record; + onResult?: (result: ReturnType) => void; +} + +function trace(fn: F, opts: TraceOptions = {}): F { + return function (this: unknown) { + const args = arguments as unknown as Parameters; + const tags = opts.tags ? opts.tags(...args) : {}; + console.log("enter", opts.name ?? fn.name, JSON.stringify(tags)); + const result: ReturnType = fn.apply(this, arguments as any); + opts.onResult?.(result); + console.log("exit", opts.name ?? fn.name); + return result; + } as F; +} + +function add(a: number, b: number): number { + return a + b; +} +const tracedAdd = trace(add, { + tags: (a, b) => ({ a: String(a), b: String(b) }), + onResult: (r) => console.log("result", r), +}); +console.log(tracedAdd(2, 3)); +console.log(tracedAdd(10, -4)); + +// A wrapped function with an optional parameter: the omitted position +// arrives as undefined, in the wrapper and its tags callback alike. +function titled(name: string, title?: string): string { + return title ? `${title} ${name}` : name; +} +const tracedTitled = trace(titled, { tags: (name, title) => ({ name, title: title ?? "-" }) }); +console.log(tracedTitled("ada")); +console.log(tracedTitled("ada", "dr")); + +const greet = (who: string, punct: string): string => `hi ${who}${punct}`; +const tracedGreet = trace(greet); +console.log(tracedGreet("ada", "!")); + +const tick = trace(function tick(): number { + return 42; +}); +console.log(tick()); + +interface Point { + x: number; + y: number; +} +const norm = trace((p: Point, scale: number): Point => ({ x: p.x * scale, y: p.y * scale }), { + name: "norm", + tags: (p) => ({ x: String(p.x) }), +}); +console.log(JSON.stringify(norm({ x: 1, y: 2 }, 3))); + +// The arguments tuple spread straight into an optional callback. +function audit(fn: F, check?: (...args: Parameters) => boolean): F { + return function (this: unknown) { + console.log("audit", check?.(...(arguments as unknown as Parameters))); + return fn.apply(this, arguments as any); + } as F; +} +const audited = audit(add, (a, b) => a < b); +console.log(audited(1, 2), audited(5, 3)); +console.log(audit(add)(7, 8)); + +const logOnly = trace((msg: string): void => { + console.log("log:", msg); +}); +logOnly("done"); diff --git a/tests/corpus/6381-variadic-wrapper-async.ts b/tests/corpus/6381-variadic-wrapper-async.ts new file mode 100644 index 000000000..200454a5e --- /dev/null +++ b/tests/corpus/6381-variadic-wrapper-async.ts @@ -0,0 +1,81 @@ +// Variadic generic wrappers around async functions: the wrapper forwards +// `arguments`, awaits the wrapped promise and reports its settled value +// through an Awaited> callback; rejections propagate. +type AnyFn = (...args: any) => any; + +interface Opts { + name?: string; + tags?: (...args: Parameters) => Record; + onSettled?: (value: Awaited>) => void; +} + +function traceAsync Promise>(fn: F, opts: Opts = {}): F { + return async function (this: unknown) { + const args = arguments as unknown as Parameters; + const tags = opts.tags ? opts.tags(...args) : {}; + console.log("start", opts.name ?? fn.name, JSON.stringify(tags)); + try { + const value: Awaited> = await fn.apply(this, arguments as any); + opts.onSettled?.(value); + console.log("ok", opts.name ?? fn.name); + return value; + } catch (e) { + console.log("failed", opts.name ?? fn.name, (e as Error).message); + throw e; + } + } as F; +} + +// The settled result spelled as a conditional over ReturnType. +type Settled = ReturnType extends PromiseLike ? U : ReturnType; + +function summarized Promise>( + fn: F, + summarize: (value: Settled) => string, +): F { + return async function (this: unknown) { + const value: Settled = await fn.apply(this, arguments as any); + console.log("summary", summarize(value)); + return value; + } as F; +} + +async function fetchUser(id: number, prefix: string): Promise { + await Promise.resolve(); + if (id < 0) throw new Error(`bad id ${id}`); + return `${prefix}${id}`; +} + +const getUser = traceAsync(fetchUser, { + tags: (id) => ({ id: String(id) }), + onSettled: (v) => console.log("settled", v.toUpperCase()), +}); + +const sleepless = traceAsync(async (): Promise => 7, { name: "seven" }); + +interface Report { + ok: boolean; + items: string[]; +} +const report = summarized( + async (count: number): Promise => ({ ok: count > 0, items: ["a", "b"].slice(0, count) }), + (r) => `${r.ok}:${r.items.join("+")}`, +); +const counted = summarized(async (n: number): Promise => n * 2, (n) => `n=${n + 1}`); + +async function main(): Promise { + console.log(await getUser(1, "u-")); + console.log(await sleepless()); + console.log(JSON.stringify(await report(1))); + console.log(await counted(4)); + try { + await getUser(-2, "u-"); + } catch (e) { + console.log("caught", (e as Error).message); + } + const pending = getUser(3, "a-"); + const second = await getUser(4, "b-"); + console.log(second, await pending); +} + +main().then(() => console.log("main done")); diff --git a/tests/corpus/6382-variadic-wrapper-methods-this.ts b/tests/corpus/6382-variadic-wrapper-methods-this.ts new file mode 100644 index 000000000..54e043207 --- /dev/null +++ b/tests/corpus/6382-variadic-wrapper-methods-this.ts @@ -0,0 +1,46 @@ +// Variadic generic wrappers on methods: the wrapper passes its own `this` +// through `fn.apply(this, arguments)`, so a wrapped `function (this: C)` +// stored as a class property sees the receiver it was called on. +type AnyFn = (...args: any) => any; + +function logged(fn: F, label: string): F { + return function (this: unknown) { + console.log(`[${label}] call with ${arguments.length} args`); + return fn.apply(this, arguments as any); + } as F; +} + +class Counter { + count = 0; + step: number; + constructor(step: number) { + this.step = step; + } + bump = logged(function (this: Counter, times: number): number { + this.count += this.step * times; + return this.count; + }, "bump"); + reset = logged((): void => { + this.count = 0; + }, "reset"); +} + +const c = new Counter(5); +console.log(c.bump(1)); +console.log(c.bump(2)); +c.reset(); +console.log(c.count); + +class Repo { + base: string; + constructor(base: string) { + this.base = base; + } + load = logged(async function (this: Repo, id: string): Promise { + await Promise.resolve(); + return `${this.base}/${id}`; + }, "load"); +} + +const repo = new Repo("https://x.test"); +repo.load("7").then((url) => console.log("loaded", url)); diff --git a/tests/corpus/6383-variadic-wrapper-rest-tuple.ts b/tests/corpus/6383-variadic-wrapper-rest-tuple.ts new file mode 100644 index 000000000..64f1cbacc --- /dev/null +++ b/tests/corpus/6383-variadic-wrapper-rest-tuple.ts @@ -0,0 +1,54 @@ +// The type-safe variadic wrapper: rest tuples instead of `arguments`. +// `...args: A` with A bound to the wrapped function's parameter tuple +// flattens to that fixed signature; `fn.apply(this, args)`, `fn(...args)` +// and `opts.tags(...args)` forward the tuple's positions. +interface Opts { + name?: string; + tags?: (...args: A) => Record; + onResult?: (r: R) => void; +} + +function wrap(fn: (...args: A) => R, opts: Opts = {}): (...args: A) => R { + return function (this: unknown, ...args: A): R { + const tags = opts.tags ? opts.tags(...args) : {}; + console.log("enter", opts.name ?? fn.name, JSON.stringify(tags)); + const r = fn.apply(this, args); + opts.onResult?.(r); + return r; + }; +} + +function viaSpread(fn: (...args: A) => R): (...args: A) => R { + return (...args: A): R => { + console.log("spread", args.length); + return fn(...args); + }; +} + +function mul(a: number, b: number): number { + return a * b; +} +const m = wrap(mul, { tags: (a, b) => ({ a: String(a), b: String(b) }), onResult: (r) => console.log("r", r) }); +console.log(m(6, 7)); + +const concat = wrap((s: string, n: number, flag: boolean) => `${s}:${n}:${flag}`, { name: "concat" }); +console.log(concat("x", 1, true)); + +const opt = wrap((s: string, n?: number) => `${s}:${n ?? "none"}`, { tags: (s, n) => ({ s, n: String(n) }) }); +console.log(opt("a", undefined), opt("b", 2)); + +const zero = wrap(() => "nothing"); +console.log(zero()); + +const spreadMul = viaSpread(mul); +console.log(spreadMul(3, 3)); + +// A concrete tuple rest parameter has the same fixed signature. +function pair(...args: [string, number]): string { + return `${args[0]}=${args[1]}`; +} +console.log(pair("k", 9)); +const asValue: (k: string, v: number) => string = pair; +console.log(asValue("v", 1)); +const t: [string, number] = ["t", 2]; +console.log(pair(...t)); diff --git a/tests/corpus/6384-variadic-wrapper-nested.ts b/tests/corpus/6384-variadic-wrapper-nested.ts new file mode 100644 index 000000000..76f714402 --- /dev/null +++ b/tests/corpus/6384-variadic-wrapper-nested.ts @@ -0,0 +1,37 @@ +// Nested variadic wrappers: wrapping an already wrapped function, mixing +// the `arguments` form and the rest-tuple form. The inner wrapper's +// function binding gives the outer wrapper its `fn.name`. +type AnyFn = (...args: any) => any; + +function trace(fn: F, label?: string): F { + const wrapped = function (this: unknown) { + console.log("trace>", label ?? fn.name); + const out = fn.apply(this, arguments as any); + console.log("trace<", label ?? fn.name); + return out; + }; + return wrapped as unknown as F; +} + +function timed(fn: (...args: A) => R): (...args: A) => R { + return function (this: unknown, ...args: A): R { + console.log("timed>", fn.name, args.length); + return fn.apply(this, args); + }; +} + +function area(w: number, h: number): number { + return w * h; +} + +const twice = trace(trace(area)); +console.log(twice(2, 3)); + +const labelled = trace(trace(area, "inner"), "outer"); +console.log(labelled(4, 5)); + +const mixed = timed(trace(area)); +console.log(mixed(6, 7)); + +const mixed2 = trace(timed(area)); +console.log(mixed2(1, 9)); diff --git a/tests/diagnostics/arrow-bind.ts b/tests/diagnostics/arrow-bind.ts index c78db514b..0b677c5ef 100644 --- a/tests/diagnostics/arrow-bind.ts +++ b/tests/diagnostics/arrow-bind.ts @@ -1,4 +1,5 @@ -// Typed preset and rest signatures still exceed the callable adapter. +// Typed rest signatures still exceed the callable adapter. (A typed preset +// binds to a fixed signature and compiles.) const preset = ((value: number) => value).bind(null, 1); const rest = ((...values: number[]) => values.length).bind(null); export {}; diff --git a/tests/diagnostics/variadic-wrapper-fences.ts b/tests/diagnostics/variadic-wrapper-fences.ts new file mode 100644 index 000000000..d2ad65646 --- /dev/null +++ b/tests/diagnostics/variadic-wrapper-fences.ts @@ -0,0 +1,28 @@ +// Variadic generic wrappers specialize per call site; the forms that have +// no fixed signature or no static name keep precise refusals. +type AnyFn = (...args: any) => any; + +// `arguments` with no fixed signature to lower against. +const loose = function () { + return arguments[0]; +}; + +// `fn.name` where the argument's name is only known at run time. +function named(fn: F): string { + return fn.name; +} +function one(): number { + return 1; +} +function two(): number { + return 2; +} +const pick = (flag: boolean) => (flag ? one : two); +console.log(named(pick(true))); + +// Forwarding a fixed tuple to a variadic function value. +function forward(sink: (...xs: number[]) => number, ...args: A): number { + return sink.apply(null, args); +} +console.log(forward((...xs: number[]) => xs.length, 1, 2)); +export { loose }; diff --git a/tests/harness/__snapshots__/arrow-bind.ts.txt b/tests/harness/__snapshots__/arrow-bind.ts.txt index 4618bdfe2..d63201d59 100644 --- a/tests/harness/__snapshots__/arrow-bind.ts.txt +++ b/tests/harness/__snapshots__/arrow-bind.ts.txt @@ -1,17 +1,8 @@ -arrow-bind.ts:2:16 - error SC1100: passing 'unknown' values where '((null | undefined)[]) => number' is expected is not supported yet +arrow-bind.ts:4:14 - error SC1100: passing 'unknown' values where '(number[]) => number' is expected is not supported yet - 1 | // Typed preset and rest signatures still exceed the callable adapter. - 2 | const preset = ((value: number) => value).bind(null, 1); - | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - 3 | const rest = ((...values: number[]) => values.length).bind(null); - - hint: validate with 'as ' first — the cast checks the dynamic value at runtime and throws on mismatch - -arrow-bind.ts:3:14 - error SC1100: passing 'unknown' values where '(number[]) => number' is expected is not supported yet - - 2 | const preset = ((value: number) => value).bind(null, 1); - 3 | const rest = ((...values: number[]) => values.length).bind(null); + 3 | const preset = ((value: number) => value).bind(null, 1); + 4 | const rest = ((...values: number[]) => values.length).bind(null); | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - 4 | export {}; + 5 | export {}; hint: validate with 'as ' first — the cast checks the dynamic value at runtime and throws on mismatch \ No newline at end of file diff --git a/tests/harness/__snapshots__/variadic-wrapper-fences.ts.txt b/tests/harness/__snapshots__/variadic-wrapper-fences.ts.txt new file mode 100644 index 000000000..0cb2774d2 --- /dev/null +++ b/tests/harness/__snapshots__/variadic-wrapper-fences.ts.txt @@ -0,0 +1,29 @@ +variadic-wrapper-fences.ts:6:7 - error SC2009: values of type '() => any' cannot be compiled: the function shape is supported, but its signature is variadic ('arguments'-reading), and a compiled signature is fixed-arity + + 5 | // `arguments` with no fixed signature to lower against. + 6 | const loose = function () { + | ^~~~~ + 7 | return arguments[0]; + + hint: the outer shape is supported — the named component is the blocker; restate it inside the compilable set (number, string, boolean, arrays, Maps, Sets, RegExp, functions, classes, records, unions of those, and 'unknown') + +variadic-wrapper-fences.ts:7:10 - error SC1090: 'arguments' in a function expression whose value is not asserted to one fixed-arity signature (assert it, as in `function () { … } as F`, and the function lowers against that signature) is not supported yet + + 6 | const loose = function () { + 7 | return arguments[0]; + | ^~~~~~~~~ + 8 | }; + +variadic-wrapper-fences.ts:12:10 - error SC1090: reading 'fn.name' when the call passes a function whose name is only known at run time (generic wrappers fold the name of a function declaration, a const-bound function, or a wrapper call's result) is not supported yet (instantiating 'named' with <() => number>) + + 11 | function named(fn: F): string { + 12 | return fn.name; + | ^~~~~~~ + 13 | } + +variadic-wrapper-fences.ts:25:10 - error SC1090: forwarding a fixed argument tuple through 'apply' to a variadic function value is not supported yet (instantiating 'forward' with <[number, number]>) + + 24 | function forward(sink: (...xs: number[]) => number, ...args: A): number { + 25 | return sink.apply(null, args); + | ^~~~~~~~~~~~~~~~~~~~~~ + 26 | } \ No newline at end of file From 0d9a6da8c96556f042690d0d8a294bd19e964f27 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Mon, 19 Oct 2026 14:19:07 -0700 Subject: [PATCH 018/122] Select untyped dependencies and @types twins by identity An untyped package another package's JavaScript imports is selected whether resolution answers no declarations or its JavaScript. A DefinitelyTyped twin qualifies by identity (@types/): the declarations only check the program, and the JavaScript compiles with its inferred surface, so a twin that disagrees with the shipped JavaScript fails the build at the use rather than compiling against it. Co-Authored-By: Claude Opus 5.5 --- packages/compiler/src/frontend/npm-static.ts | 65 ++++++------------- packages/compiler/src/frontend/pipeline.ts | 14 ++-- .../node_modules/@types/twinstale/index.d.ts | 2 +- .../node_modules/twinstale/index.js | 5 +- tests/fixtures/npm-static/twinstale-cli.ts | 7 +- tests/harness/npm-static.test.ts | 21 +++--- 6 files changed, 46 insertions(+), 68 deletions(-) diff --git a/packages/compiler/src/frontend/npm-static.ts b/packages/compiler/src/frontend/npm-static.ts index 7c9b7f6a1..4c1bc9ae6 100644 --- a/packages/compiler/src/frontend/npm-static.ts +++ b/packages/compiler/src/frontend/npm-static.ts @@ -501,46 +501,18 @@ function hasTransformMarkers(source: string): boolean { return TRANSFORM_MARKERS.some((m) => source.includes(m)); } -/** The installed version of the package owning `file` (the innermost - * node_modules segment), or null when no package.json names one. */ -function installedVersionOf(file: string): string | null { - const norm = file.split("\\").join("/"); - const idx = norm.lastIndexOf("/node_modules/"); - if (idx < 0) return null; - const pkg = npmPackageNameOf(norm); - if (pkg === null) return null; - const text = trackedReadFile(`${norm.slice(0, idx)}/node_modules/${pkg}/package.json`); - if (text === null) return null; - try { - const version = (JSON.parse(text) as { version?: unknown }).version; - return typeof version === "string" ? version : null; - } catch { - return null; - } -} - -/** Why a DefinitelyTyped twin cannot vouch for the runtime package `pkg`, - * or null when it can. DefinitelyTyped versions an @types package by the - * library version it describes (major.minor; 0.x minors break): the twin - * must describe the release that is installed, read from both package - * manifests next to the resolved declaration and JavaScript files. */ -export function definitelyTypedMismatch( - pkg: string, - typesFile: string, - jsFile: string, -): string | null { - const typesVersion = installedVersionOf(typesFile); - const version = installedVersionOf(jsFile); - const twin = npmPackageNameOf(typesFile.split("\\").join("/")) ?? "its @types package"; - if (typesVersion === null || version === null) - return `its declared types come from the separate ${twin} package, whose version cannot be matched against the installed ${pkg}`; - const line = (v: string): string => { - const [major = "", minor = ""] = v.split("."); - return major === "0" ? `0.${minor}` : major; - }; - return line(typesVersion) === line(version) +/** Why the declarations at `typesFile` cannot stand for the runtime + * package `pkg`, or null when they are its DefinitelyTyped twin + * (`@types/`). The declarations only check the importing + * program; the JavaScript compiles with its inferred types, and an + * inferred surface the program does not typecheck against refuses the + * package afterwards, whatever release the twin describes. */ +export function definitelyTypedMismatch(pkg: string, typesFile: string): string | null { + const owner = npmPackageNameOf(typesFile.split("\\").join("/")); + const twin = `@types/${mangledTypesName(pkg)}`; + return owner === twin ? null - : `its declared types come from ${twin}@${typesVersion}, which describes a different release than the installed ${pkg}@${version}`; + : `its declared types come from ${owner ?? typesFile}, which is not ${pkg}'s DefinitelyTyped package ${twin}`; } /** Auto-mode eligibility for one candidate package: `typesFile` is what @@ -551,12 +523,17 @@ export function npmStaticIneligibleReason( pkgName: string, typesFile: string, jsEntry: string | null, + /** A dependency another package's JavaScript imports: no declarations + * check that importer, so the package need not ship its own. */ + untypedDependency = false, ): string | null { - if (npmPackageNameOf(typesFile) !== pkgName) { - return "its declared types come from a third-party @types package, not the package itself"; - } - if (!/\.d\.(ts|mts|cts)$/.test(typesFile)) { - return "it ships no own .d.ts declaration surface"; + if (!untypedDependency) { + if (npmPackageNameOf(typesFile) !== pkgName) { + return "its declared types come from a third-party @types package, not the package itself"; + } + if (!/\.d\.(ts|mts|cts)$/.test(typesFile)) { + return "it ships no own .d.ts declaration surface"; + } } if (jsEntry === null) return "no runtime JS entry resolves"; const source = trackedReadFile(jsEntry); diff --git a/packages/compiler/src/frontend/pipeline.ts b/packages/compiler/src/frontend/pipeline.ts index 5c929960a..5081fc7be 100644 --- a/packages/compiler/src/frontend/pipeline.ts +++ b/packages/compiler/src/frontend/pipeline.ts @@ -221,16 +221,17 @@ function detectAutoPackages( continue; } // Both answers compile the JavaScript with inferred types; the - // declarations only check the importer. A DefinitelyTyped twin - // qualifies when it describes the installed release. A package - // without declarations qualifies as a dependency of a compiled - // package, whose JavaScript importer has no declarations to check. + // declarations only check the importer, and an inferred surface + // the program does not typecheck against still refuses the package + // below. A DefinitelyTyped twin qualifies; a package without + // declarations qualifies as a dependency of another package, whose + // JavaScript importer has no declarations to check. const refusal = npm === null - ? owner === null + ? owner === null && !sf.fileName.includes("/node_modules/") ? "it ships no own .d.ts declaration surface" : null - : definitelyTypedMismatch(runtimePkg, npm.typesFile, js!.typesFile); + : definitelyTypedMismatch(runtimePkg, npm.typesFile); if (refusal === null) { chosen.push(runtimePkg); continue; @@ -269,6 +270,7 @@ function detectAutoPackages( pkg, typesFile, jsEntry !== null && isJsSourceFileName(jsEntry.typesFile) ? jsEntry.typesFile : null, + mode === "lib" && isJsSourceFileName(typesFile) && fromFile.includes("/node_modules/"), ); if (reason === null) chosen.push(pkg); else diff --git a/tests/fixtures/npm-static/node_modules/@types/twinstale/index.d.ts b/tests/fixtures/npm-static/node_modules/@types/twinstale/index.d.ts index 0017b82be..47f003c12 100644 --- a/tests/fixtures/npm-static/node_modules/@types/twinstale/index.d.ts +++ b/tests/fixtures/npm-static/node_modules/@types/twinstale/index.d.ts @@ -1 +1 @@ -export default function stale(text: string): string; +export function stale(text: string): string; diff --git a/tests/fixtures/npm-static/node_modules/twinstale/index.js b/tests/fixtures/npm-static/node_modules/twinstale/index.js index 129abfcce..6efa90723 100644 --- a/tests/fixtures/npm-static/node_modules/twinstale/index.js +++ b/tests/fixtures/npm-static/node_modules/twinstale/index.js @@ -1,6 +1,7 @@ -// The 3.x release changed the export: its @types twin still describes 2.x. +// The 3.x release renamed its export: its @types twin still describes the +// 2.x name. 'use strict'; -module.exports = function stale(text) { +exports.render = function render(text) { return 'stale:' + text; }; diff --git a/tests/fixtures/npm-static/twinstale-cli.ts b/tests/fixtures/npm-static/twinstale-cli.ts index 1938cbf8b..f6c8a679c 100644 --- a/tests/fixtures/npm-static/twinstale-cli.ts +++ b/tests/fixtures/npm-static/twinstale-cli.ts @@ -1,5 +1,6 @@ -// twinstale's @types twin describes an older major release than the -// installed package, so it cannot vouch for the JavaScript. -import stale from "twinstale"; +// twinstale's @types twin describes an older release than the installed +// package: the program typechecks against the twin, but not against the +// JavaScript's inferred surface (under Node the import is undefined). +import { stale } from "twinstale"; console.log(stale("ready")); diff --git a/tests/harness/npm-static.test.ts b/tests/harness/npm-static.test.ts index a1703adf7..8560973cd 100644 --- a/tests/harness/npm-static.test.ts +++ b/tests/harness/npm-static.test.ts @@ -848,19 +848,16 @@ describe(`npm-static pilots${sanitize ? " (sanitized)" : ""}`, () => { expect(nativeRes.exitCode).toBe(nodeRes.exitCode); }, 180_000); - test("an @types twin describing another release is refused with the opt-in remedy", () => { + // The twin only checks the program; the JavaScript compiles with its + // inferred surface, so a twin naming an export the JavaScript lacks + // (Node's link error) fails the build at the import's use instead of + // compiling against the declaration. + test("an @types twin that disagrees with the shipped JavaScript does not build", () => { const { coverage } = analyze(join(pilotRoot, "twinstale-cli.ts")); - expect(coverage.npmStatic).toEqual([ - { - package: "twinstale", - status: "refused", - detail: - "its declared types come from @types/twinstale@2.0.4, which describes a different release than the installed twinstale@3.1.0", - hint: "to compile it anyway, name it with --npm-static=twinstale (or --npm-static=twinstale... to include its dependencies)", - }, - ]); - const diagnostic = coverage.diagnostics.find((d) => d.code === "SC2013"); - expect(diagnostic?.message).toContain("'twinstale'"); + expect(coverage.npmStatic).toEqual([{ package: "twinstale", status: "static" }]); + expect(coverage.diagnostics).toContainEqual( + expect.objectContaining({ message: expect.stringContaining("'stale'") as string }), + ); }, 120_000); test("a named package's untyped dependency compiles with it", () => { From 4e0f71a7e5dae2f2b266049d6ce8d8473cbd5760 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Mon, 19 Oct 2026 14:21:57 -0700 Subject: [PATCH 019/122] Document the new package selection; record the 2563 order baseline Co-Authored-By: Claude Opus 5.5 --- docs/content/docs/dependencies.mdx | 14 ++++++++------ .../src/frontend/npm-static-bundled-esm.test.ts | 9 ++++----- .../src/frontend/npm-static-bundled-esm.ts | 13 +++++++++---- .../npm/cases/2563-bundled-esm-inits/main.ts.json | 6 ++++++ 4 files changed, 27 insertions(+), 15 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2563-bundled-esm-inits/main.ts.json diff --git a/docs/content/docs/dependencies.mdx b/docs/content/docs/dependencies.mdx index 3c6958841..e6020fe54 100644 --- a/docs/content/docs/dependencies.mdx +++ b/docs/content/docs/dependencies.mdx @@ -52,10 +52,12 @@ Type-only imports follow Node.js's type stripping. `import type` and `export typ scriptc compiles a package automatically when it: -- ships its own type declarations, and -- ships readable, unminified JavaScript. +- ships its own type declarations, or is typed by its DefinitelyTyped `@types/` package, or is a dependency of another package that has no declarations at all, and +- ships readable JavaScript: unminified, or minified by a bundler whose helpers keep their names. -A package that doesn't meet these requirements, or that uses something scriptc can't compile, gets a package-specific diagnostic that explains why. The usual fixes are to switch to a version or alternative package that compiles, or to replace the call with code you write yourself. A package whose declarations come from a separate `@types/` package doesn't ship its own; its diagnostic names the package and the `--npm-static` option that compiles it anyway. +The declarations check your program; the package's JavaScript compiles with the types scriptc infers from it. A program that doesn't typecheck against the inferred types, for example because an `@types/` package describes another release, fails the build at that use. + +A package that doesn't meet these requirements, or that uses something scriptc can't compile, gets a package-specific diagnostic that explains why. The usual fixes are to switch to a version or alternative package that compiles, or to replace the call with code you write yourself. @@ -63,17 +65,17 @@ A package that doesn't meet these requirements, or that uses something scriptc c ## Explicit package selection -Some packages rely on separately installed declarations, such as an `@types/` package, or on types inferred from their JavaScript. To try compiling one of them, name it with `--npm-static`: +Some packages your program imports directly ship no declarations, so their types can only be inferred from their JavaScript. To try compiling one of them, name it with `--npm-static`: ```console $ scriptc build app.ts --npm-static=three -o app >/dev/null ``` -Naming a package skips the declaration requirement above, but the package still has to compile. The option selects only the packages it names: a dependency of a named package that doesn't ship its own declarations is still refused. Add a trailing `...` to select a package together with every package it depends on, for example `--npm-static=fs-extra...`. `--npm-static=auto` uses the default automatic selection. The option accepts a comma-separated list and can be repeated. +Naming a package skips the declaration requirement above, but the package still has to compile. Add a trailing `...` to select a package together with every package it depends on, for example `--npm-static=fs-extra...`. `--npm-static=auto` uses the default automatic selection. The option accepts a comma-separated list and can be repeated. ## CommonJS packages -CommonJS packages compile with Node.js's module semantics. Each `require()` evaluates its module at the call, once, and later calls return the cached exports. A package can assign `module.exports` and run code before it requires its dependencies. If that code reads a `const` or `let` require binding before its `require` runs, the read throws the same `ReferenceError` as Node.js. A `var` binding that earlier code can reach is reported at build time. A `require()` of a JSON file, such as a package's own `package.json`, returns one object parsed at build time, shared by every module that requires it. A require cycle compiles when each of its modules assigns its exports before requiring anything, as semver's classes do, and uses the other modules only inside functions that its top-level code can't call. Other require cycles are reported at build time. The [limitations](/docs/limitations) page lists the supported `require` forms. +CommonJS packages compile with Node.js's module semantics. Each `require()` evaluates its module at the call, once, and later calls return the cached exports. A package can assign `module.exports` and run code before it requires its dependencies. If that code reads a `const` or `let` require binding before its `require` runs, the read throws the same `ReferenceError` as Node.js. A `var` binding that earlier code can reach is reported at build time. A `require()` of a JSON file, such as a package's own `package.json`, returns one object parsed at build time, shared by every module that requires it. A require cycle compiles when each of its modules assigns its exports before requiring anything, as semver's classes and esbuild's per-file output do, and uses the other modules only inside functions that its top-level code can't call. Bundles built by esbuild compile with their bundled CommonJS modules and lazily initialized ES modules, each evaluated once, at its first use, as under Node.js. Other require cycles are reported at build time. The [limitations](/docs/limitations) page lists the supported `require` forms. ## Unsupported code inside a package diff --git a/packages/compiler/src/frontend/npm-static-bundled-esm.test.ts b/packages/compiler/src/frontend/npm-static-bundled-esm.test.ts index 9c064cb55..0b9ecd042 100644 --- a/packages/compiler/src/frontend/npm-static-bundled-esm.test.ts +++ b/packages/compiler/src/frontend/npm-static-bundled-esm.test.ts @@ -117,10 +117,7 @@ test("minified initializers sharing a var statement lower the same way", () => { }); test.each([ - [ - "is asynchronous", - `var init_a = __esm({ async "a.ts"() { await 0; } });\ninit_a();`, - ], + ["is asynchronous", `var init_a = __esm({ async "a.ts"() { await 0; } });\ninit_a();`], [ "is used other than as a statement call", `var init_a = __esm({ "a.ts"() {} });\nvar r = init_a();`, @@ -145,5 +142,7 @@ test.each([ test("files without __esm initializers and ES-module bundles are left alone", () => { expect(lower("exports.x = 1;\n")).toBeNull(); - expect(lower(`${helpers}var init_a = __esm({ "a.ts"() {} });\ninit_a();\nexport {};\n`)).toBeNull(); + expect( + lower(`${helpers}var init_a = __esm({ "a.ts"() {} });\ninit_a();\nexport {};\n`), + ).toBeNull(); }); diff --git a/packages/compiler/src/frontend/npm-static-bundled-esm.ts b/packages/compiler/src/frontend/npm-static-bundled-esm.ts index ae5985e51..cf4f6280b 100644 --- a/packages/compiler/src/frontend/npm-static-bundled-esm.ts +++ b/packages/compiler/src/frontend/npm-static-bundled-esm.ts @@ -252,7 +252,8 @@ export function lowerBundledEsmInits( const stable = (name: string): boolean => !duplicate.has(name) && !writes.has(name); const ownNames = (name: string): boolean => { const t = top.get(name); - const init = t !== undefined && ts.isVariableDeclaration(t.node) ? t.node.initializer : undefined; + const init = + t !== undefined && ts.isVariableDeclaration(t.node) ? t.node.initializer : undefined; return ( init !== undefined && stable(name) && @@ -308,8 +309,7 @@ export function lowerBundledEsmInits( ) return { degrade: ESM_SHAPE_DEGRADE }; const frame = factoryFrameUse(fn.body, false); - if (frame !== null) - return { degrade: `its bundled ES module initializer '${name}' ${frame}` }; + if (frame !== null) return { degrade: `its bundled ES module initializer '${name}' ${frame}` }; inits.set(name, { name, call, body: fn.body }); } if (inits.size === 0) return null; @@ -531,7 +531,12 @@ export function lowerBundledEsmInits( const read = new Set(); for (const [, value] of table.entries) for (const x of references(value)) read.add(x.text); for (let a = p.parent; a !== undefined && !ts.isSourceFile(a); a = a.parent) { - if (ts.isFunctionLike(a) || ts.isBlock(a) || ts.isClassDeclaration(a) || ts.isClassExpression(a)) { + if ( + ts.isFunctionLike(a) || + ts.isBlock(a) || + ts.isClassDeclaration(a) || + ts.isClassExpression(a) + ) { for (const n of declaredNames(a)) { if (read.has(n)) return { degrade: `its shipped JS ${why}` }; } diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2563-bundled-esm-inits/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2563-bundled-esm-inits/main.ts.json new file mode 100644 index 000000000..953a75d7d --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2563-bundled-esm-inits/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/npm/cases/2563-bundled-esm-inits/main.ts" + ], + "diags": [] +} From 769e9e58cf768ced8e26510d7e42f35deee02355 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Mon, 19 Oct 2026 14:29:23 -0700 Subject: [PATCH 020/122] Run the two npm cases the default selection now compiles cjs-lexer-invisible and hoisted-workspace compiled only once their untyped dependencies were selected; both now match Node in both lanes. Co-Authored-By: Claude Opus 5.5 --- tests/harness/npm.test.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/harness/npm.test.ts b/tests/harness/npm.test.ts index b6af0dbb6..1f231b3bc 100644 --- a/tests/harness/npm.test.ts +++ b/tests/harness/npm.test.ts @@ -40,7 +40,6 @@ const KNOWN: Record = { "bytes-exit": { build: "SC2004" }, "cjs-esm-named": { build: "SC2013" }, "cjs-lexer-aligned": { build: "SC2013" }, - "cjs-lexer-invisible": { build: "SC2013" }, "commander-calc": { run: "SC1090", argvs: [["--help"]] }, "commander-calc-async": { run: "SC1090", argvs: [["--help"]] }, "create-require": { run: "SC1090" }, @@ -53,7 +52,6 @@ const KNOWN: Record = { "esm-missing-export": { build: "SC1090" }, "fetch-destructure": { run: "SC1031" }, "fs-shims": { run: "SC2020" }, - "hoisted-workspace": { build: "SC2013" }, "import-refusal": { build: "SC2020" }, "island-registry": { build: "SC2011" }, "island-timers": { From 1711c0110e1727323d17241eccf3275ef88c3e6e Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Mon, 19 Oct 2026 17:05:27 -0500 Subject: [PATCH 021/122] Inspect WebAssembly objects as Node does and declare the wasm entry points in scr_runtime.h util.inspect prints a handle whose Symbol.toStringTag ends with "." and its class name in place of the name, as Node does: WebAssembly.Module {} rather than Module [WebAssembly.Module] {}. The same holds for Instance, Memory, Table and Global. The WebAssembly API entry points move to scr_wasm.h, which scr_runtime.h includes with scr_wasm_abi.h, so every scr_* symbol the backend can emit has a header declaration. The emitted registry array is renamed sc_wasm_registry_modules, following the compiler-emitted symbol prefix. --- .../compiler/src/backend/llvm/wasm-modules.ts | 4 ++-- packages/runtime/src/scr_dyn.h | 3 ++- packages/runtime/src/scr_inspect.c | 22 ++++++++++++++---- packages/runtime/src/scr_runtime.h | 2 ++ packages/runtime/src/scr_wasm.c | 11 --------- packages/runtime/src/scr_wasm.h | 23 +++++++++++++++++++ 6 files changed, 47 insertions(+), 18 deletions(-) create mode 100644 packages/runtime/src/scr_wasm.h diff --git a/packages/compiler/src/backend/llvm/wasm-modules.ts b/packages/compiler/src/backend/llvm/wasm-modules.ts index c37dc36c4..6a79ff5fe 100644 --- a/packages/compiler/src/backend/llvm/wasm-modules.ts +++ b/packages/compiler/src/backend/llvm/wasm-modules.ts @@ -65,8 +65,8 @@ export function emitWasmModules( out.push(`@scr_wasm_registry = constant { i64, ptr } { i64 0, ptr null }`); } else { out.push( - `@scr_wasm_registry_modules = private constant [${descriptors.length} x ptr] [${descriptors.map((d) => `ptr ${d}`).join(", ")}]`, - `@scr_wasm_registry = constant { i64, ptr } { i64 ${descriptors.length}, ptr @scr_wasm_registry_modules }`, + `@sc_wasm_registry_modules = private constant [${descriptors.length} x ptr] [${descriptors.map((d) => `ptr ${d}`).join(", ")}]`, + `@scr_wasm_registry = constant { i64, ptr } { i64 ${descriptors.length}, ptr @sc_wasm_registry_modules }`, ); } out.push(""); diff --git a/packages/runtime/src/scr_dyn.h b/packages/runtime/src/scr_dyn.h index b7960ba12..e46fb28d4 100644 --- a/packages/runtime/src/scr_dyn.h +++ b/packages/runtime/src/scr_dyn.h @@ -1171,7 +1171,8 @@ typedef struct ScrDynHandleOps { * inspector returns `this`) instead of the bare class name. */ bool inspect_bracketed_past_depth; /* The @@toStringTag when it differs from cls: String() answers - * "[object ]" and util.inspect prints ` [] `. */ + * "[object ]" and util.inspect prints ` [] `, or + * ` ` when the tag ends with "." and cls, as Node does. */ const char *to_string_tag; /* The view has no properties, so util.inspect prints it in full at any * depth, as Node prints `{}`. */ diff --git a/packages/runtime/src/scr_inspect.c b/packages/runtime/src/scr_inspect.c index 34438353f..eccd404fc 100644 --- a/packages/runtime/src/scr_inspect.c +++ b/packages/runtime/src/scr_inspect.c @@ -975,6 +975,16 @@ static ScrStr *scr_insp_class_tag(const char *cls, const char *tail) { return ib_take(&out); } +/* Node prints a @@toStringTag that ends with "." and the constructor name + * (WebAssembly.Module for Module) in place of the name, without the + * bracketed tag. */ +static bool insp_tag_names_class(const char *tag, const char *cls) { + if (!tag) return false; + size_t tag_len = strlen(tag), cls_len = strlen(cls); + return tag_len > cls_len + 1 && tag[tag_len - cls_len - 1] == '.' && + memcmp(tag + tag_len - cls_len, cls, cls_len) == 0; +} + /* A native Set or Map held as a checked-dynamic value: `Set(n) { a, b }`, * `Map(n) { k => v }`, at most 100 entries like Node's maxArrayLength. */ /* Node's formatWeakCollection: the entries of a WeakMap or WeakSet are not @@ -1261,11 +1271,15 @@ ScrStr *scr_insp_dyn(ScrDyn *d, double recurse, double depth) { ScrStr *body = scr_insp_dyn(view, recurse, depth); scr_dyn_release(view); InspBuf out = {0}; - ib_cstr(&out, ops->cls); - if (ops->to_string_tag) { - ib_cstr(&out, " ["); + if (insp_tag_names_class(ops->to_string_tag, ops->cls)) { ib_cstr(&out, ops->to_string_tag); - ib_char(&out, ']'); + } else { + ib_cstr(&out, ops->cls); + if (ops->to_string_tag) { + ib_cstr(&out, " ["); + ib_cstr(&out, ops->to_string_tag); + ib_char(&out, ']'); + } } ib_char(&out, ' '); ib_bytes(&out, body->data, body->len); diff --git a/packages/runtime/src/scr_runtime.h b/packages/runtime/src/scr_runtime.h index a66d1c554..f52e63088 100644 --- a/packages/runtime/src/scr_runtime.h +++ b/packages/runtime/src/scr_runtime.h @@ -74,5 +74,7 @@ #include "scr_assert.h" #include "scr_console.h" #include "scr_fs_cp.h" +#include "scr_wasm_abi.h" +#include "scr_wasm.h" #endif /* SCR_RUNTIME_H */ diff --git a/packages/runtime/src/scr_wasm.c b/packages/runtime/src/scr_wasm.c index 03d59604f..f9e96ab76 100644 --- a/packages/runtime/src/scr_wasm.c +++ b/packages/runtime/src/scr_wasm.c @@ -76,17 +76,6 @@ #define SCR_WASM_ASAN 1 #endif -ScrPromise *scr_wasm_compile(ScrDyn *bytes); -bool scr_wasm_validate(ScrDyn *bytes); -ScrPromise *scr_wasm_instantiate(ScrDyn *source, ScrDyn *imports); -ScrDyn *scr_wasm_module_new(ScrDyn *bytes); -ScrDyn *scr_wasm_instance_new(ScrDyn *module, ScrDyn *imports); -bool scr_wasm_is(const ScrDyn *value, double kind); -ScrDyn *scr_wasm_error_new(double kind, ScrDyn *message, ScrDyn *options); -ScrDyn *scr_wasm_function(double kind); -ScrDyn *scr_wasm_namespace(void); -ScrDyn *scr_wasm_global_view(void); - /* Entry points of translated code. */ /* Sanitized builds do not declare it noreturn: AddressSanitizer would * call __asan_handle_no_return before every call, which warns on the diff --git a/packages/runtime/src/scr_wasm.h b/packages/runtime/src/scr_wasm.h new file mode 100644 index 000000000..7b0a29689 --- /dev/null +++ b/packages/runtime/src/scr_wasm.h @@ -0,0 +1,23 @@ +/* scriptc runtime — the WebAssembly JavaScript API (scr_wasm.c): the entry + * points the LLVM backend calls for the wasm.* lib functions. Included by + * scr_runtime.h, which says where new declarations go. The contract with + * translated modules, including scr_wasm_registry, is in scr_wasm_abi.h. */ +#ifndef SCR_WASM_H +#define SCR_WASM_H + +#include "scr_async.h" +#include "scr_dyn.h" +#include "scr_wasm_abi.h" + +ScrPromise *scr_wasm_compile(ScrDyn *bytes); +bool scr_wasm_validate(ScrDyn *bytes); +ScrPromise *scr_wasm_instantiate(ScrDyn *source, ScrDyn *imports); +ScrDyn *scr_wasm_module_new(ScrDyn *bytes); +ScrDyn *scr_wasm_instance_new(ScrDyn *module, ScrDyn *imports); +bool scr_wasm_is(const ScrDyn *value, double kind); +ScrDyn *scr_wasm_error_new(double kind, ScrDyn *message, ScrDyn *options); +ScrDyn *scr_wasm_function(double kind); +ScrDyn *scr_wasm_namespace(void); +ScrDyn *scr_wasm_global_view(void); + +#endif /* SCR_WASM_H */ From 46d12eb1e1a1981a9bfcf9faf45a262fcc9a1bc9 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Mon, 19 Oct 2026 15:15:02 -0700 Subject: [PATCH 022/122] Probe forwarded tuples without interning types Recognizing the empty-tuple representation interned the unit-only union, and a rest parameter's IR type was mapped before checking that it is a tuple at all. Both shifted union ids in programs with ordinary array rest parameters that read `.length`. Probe through the checker and registry lookups only. Co-Authored-By: Claude Opus 5.5 --- .../src/frontend/lowering/typed-arguments.ts | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/packages/compiler/src/frontend/lowering/typed-arguments.ts b/packages/compiler/src/frontend/lowering/typed-arguments.ts index 751d0d6be..90fef0e86 100644 --- a/packages/compiler/src/frontend/lowering/typed-arguments.ts +++ b/packages/compiler/src/frontend/lowering/typed-arguments.ts @@ -1,16 +1,8 @@ import * as ts from "../ts7/adapter.js"; import { bodyReadsArguments } from "../arguments-usage.js"; import { isJsSourceFile, locOf } from "../program.js"; -import { unitOnlyUnion } from "../type-mapper.js"; -import { - DYN, - type IrExpr, - type IrLocal, - type IrStmt, - type IrType, - arrayOf, - typeEquals, -} from "../../ir/ir.js"; +import { restTupleElementTypes, unitOnlyUnion } from "../type-mapper.js"; +import { DYN, type IrExpr, type IrLocal, type IrStmt, type IrType, arrayOf } from "../../ir/ir.js"; import type { Lowerer } from "./lowerer.js"; import { captureContextArguments } from "./function-context.js"; import { isThisParameter, type ParamShape } from "./call-signatures.js"; @@ -325,7 +317,9 @@ export function forwardedTupleType(lowerer: Lowerer, node: ts.Expression): IrTyp ts.isParameter(decl) && decl.dotDotDotToken !== undefined && ts.isIdentifier(decl.name) && - !isJsSourceFile(decl.getSourceFile()) + !isJsSourceFile(decl.getSourceFile()) && + // Checker-only probe first: an ordinary array rest never maps here. + restTupleElementTypes(lowerer.typeOf(operand), lowerer.typeCtx) !== null ) { const t = lowerer.mapTypeOf(lowerer.typeOf(operand)); if (t && isTupleValueType(lowerer, t)) return t; @@ -335,8 +329,17 @@ export function forwardedTupleType(lowerer: Lowerer, node: ts.Expression): IrTyp function isTupleValueType(lowerer: Lowerer, t: IrType): boolean { if (t.kind === "record") return lowerer.shapes.get(t.shapeId)?.tuple === true; - // The empty tuple's representation (mapType's `[]` rule). - return typeEquals(t, arrayOf(unitOnlyUnion(lowerer.unions))); + // The empty tuple's representation (mapType's `[]` rule): an array over + // the unit-only union. Looked up, never interned, so probing a value + // leaves the registries untouched. + if (t.kind !== "array" || t.elem.kind !== "union") return false; + const arms = lowerer.unions.get(t.elem.unionId)?.arms; + return ( + arms !== undefined && + arms.length === 2 && + arms.some((a) => a.kind === "nullT") && + arms.some((a) => a.kind === "undefinedT") + ); } /** The positional reads of a forwarded tuple value (already saved in a From f7f239ad531fbc32b84b99187d421b8152bca71b Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Mon, 19 Oct 2026 15:55:37 -0700 Subject: [PATCH 023/122] Lower memoized lazy loaders of bundled ES-module namespaces A loader `L = () => M ??= NS` over a lazily initialized module's namespace (optionally through __toESM) is respelled at every `L().name` as the module's initializer call followed by the export's live read; loaders whose result escapes as a value stay refused. The canonical export table keeps a getter it cannot chase as a live accessor, so a lazy re-export still initializes its module at the first read. An ES-module importer copies those accessors once when it links the CommonJS module, in the lexer's order, exactly like Node's loader; require() keeps reading them live. Co-Authored-By: Claude Opus 5.5 --- .../src/frontend/lowering/lower-exprs.ts | 102 ++++++++++- .../src/frontend/lowering/lower-modules.ts | 10 +- .../compiler/src/frontend/lowering/lowerer.ts | 4 + .../src/frontend/npm-static-bundled-esm.ts | 173 ++++++++++++++++-- .../src/frontend/npm-static-rewrite-host.ts | 2 + .../src/frontend/npm-static-rewrite-syntax.ts | 28 ++- packages/compiler/src/frontend/npm-static.ts | 10 + packages/compiler/src/frontend/program.ts | 9 +- packages/compiler/src/frontend/services.ts | 9 +- .../npm/cases/2564-lazy-barrel/main.ts | 19 ++ .../cases/2565-lazy-barrel-require/main.ts | 9 + .../npm/node_modules/gtlazy/dist/index.d.ts | 7 + .../npm/node_modules/gtlazy/dist/index.js | 116 ++++++++++++ .../npm/node_modules/gtlazy/package.json | 7 + .../npm/node_modules/gtlazyuser/index.d.ts | 2 + .../npm/node_modules/gtlazyuser/index.js | 10 + .../npm/node_modules/gtlazyuser/package.json | 10 + .../npm/node_modules/gtlazyws/dist/index.d.ts | 7 + .../npm/node_modules/gtlazyws/dist/index.js | 3 + .../npm/node_modules/gtlazyws/package.json | 7 + tests/harness/npm-cases.ts | 4 +- tests/harness/npm-static.test.ts | 13 +- 22 files changed, 537 insertions(+), 24 deletions(-) create mode 100644 tests/fixtures/npm/cases/2564-lazy-barrel/main.ts create mode 100644 tests/fixtures/npm/cases/2565-lazy-barrel-require/main.ts create mode 100644 tests/fixtures/npm/node_modules/gtlazy/dist/index.d.ts create mode 100644 tests/fixtures/npm/node_modules/gtlazy/dist/index.js create mode 100644 tests/fixtures/npm/node_modules/gtlazy/package.json create mode 100644 tests/fixtures/npm/node_modules/gtlazyuser/index.d.ts create mode 100644 tests/fixtures/npm/node_modules/gtlazyuser/index.js create mode 100644 tests/fixtures/npm/node_modules/gtlazyuser/package.json create mode 100644 tests/fixtures/npm/node_modules/gtlazyws/dist/index.d.ts create mode 100644 tests/fixtures/npm/node_modules/gtlazyws/dist/index.js create mode 100644 tests/fixtures/npm/node_modules/gtlazyws/package.json diff --git a/packages/compiler/src/frontend/lowering/lower-exprs.ts b/packages/compiler/src/frontend/lowering/lower-exprs.ts index 09407f71f..cabbc31f5 100644 --- a/packages/compiler/src/frontend/lowering/lower-exprs.ts +++ b/packages/compiler/src/frontend/lowering/lower-exprs.ts @@ -114,6 +114,7 @@ import { cjsClassExprWholeExportOf, cjsExportAssignmentOf, cjsExportDiscardReason, + cjsExportTargetLiteral, isCjsExportTableLiteral, isCjsJsFile, isJsSourceFile, @@ -164,7 +165,7 @@ import { import { strCharsCall } from "./containers/array-construction.js"; import { lowerOptionalStringIndex, lowerUnionStringIndex } from "./string-index.js"; import { tryLowerIndexedComparison } from "./indexed-comparison.js"; -import { npmStaticPackageOfPath } from "../npm-static.js"; +import { npmStaticPackageOfPath, npmStaticLexerVisibleNames } from "../npm-static.js"; import { unsupportedModuleFeatureOf } from "../builtin-modules.js"; import { fenceEnumObjectValue, lowerEnumAccess } from "./lower-enums.js"; import { @@ -12541,9 +12542,108 @@ function cjsExportAccessorRead(lowerer: Lowerer, ident: ts.Identifier): IrExpr | if (!ts.isObjectLiteralExpression(getter.parent) || !isCjsExportTableLiteral(getter.parent)) { return null; } + // An ES-module import binding reads the value Node copied when the + // importer linked the CommonJS module (cjsAccessorSnapshotStores). + const raw = lowerer.checker.getSymbolAtLocation(ident); + const binding = raw ? lowerer.checker.declarationsOf(raw)[0] : undefined; + if ( + binding !== undefined && + (ts.isImportSpecifier(binding) || ts.isExportSpecifier(binding)) && + !isCjsJsFile(binding.getSourceFile(), lowerer.program) + ) { + const snapshot = cjsAccessorSnapshotOf(lowerer, getter); + if (snapshot !== null) + return { kind: "varRef", localId: snapshot.id, type: snapshot.type, loc: locOf(ident) }; + } return cjsAccessorCall(lowerer, getter, locOf(ident)); } +/** The names Node's ES-module loader copies off a CommonJS module that a + * bundler rewrite spells as accessors (npmStaticLexerVisibleNames), with + * their getters in that order; empty for every other module. */ +function lexerVisibleAccessors(dep: ts.SourceFile): ts.GetAccessorDeclaration[] { + const names = npmStaticLexerVisibleNames(dep.fileName); + if (names.length === 0) return []; + let table: ts.ObjectLiteralExpression | null = null; + for (const stmt of dep.statements.nodes) { + const cjs = cjsExportAssignmentOf(stmt); + if (cjs?.kind === "table" && cjsExportDiscardReason(stmt) === null) { + table = cjsExportTargetLiteral(cjs.expr.right, dep); + } + } + if (table === null) return []; + const getters = new Map(); + for (const p of table.properties.nodes) { + if (ts.isGetAccessorDeclaration(p) && (ts.isIdentifier(p.name) || ts.isStringLiteral(p.name))) + getters.set(p.name.text, p); + } + return names.flatMap((n) => { + const g = getters.get(n); + return g === undefined ? [] : [g]; + }); +} + +/** The module global holding the value an accessor export had when an + * ES-module importer first linked its CommonJS module. */ +function cjsAccessorSnapshotOf( + lowerer: Lowerer, + getter: ts.GetAccessorDeclaration, +): { id: string; type: IrType } | null { + const hit = lowerer.cjsAccessorSnapshots.get(getter); + if (hit !== undefined) return hit; + if (!lexerVisibleAccessors(getter.getSourceFile()).includes(getter)) return null; + const call = cjsAccessorCall(lowerer, getter, locOf(getter)); + if (call === null) return null; + const snapshot = { id: `%g.cjsSnapshot.${lowerer.cjsAccessorSnapshots.size}`, type: call.type }; + lowerer.globalsList.push({ id: snapshot.id, name: "%cjsSnapshot", type: call.type, mutable: true }); + lowerer.cjsAccessorSnapshots.set(getter, snapshot); + return snapshot; +} + +/** Node's ES-module loader reads every lexer-visible export of a CommonJS + * module once, when an importer first links it, and the import bindings + * keep those values. For the accessors a bundler rewrite introduced, the + * importer's link point runs each getter once, in the lexer's order, and + * stores the values its bindings read. Empty when the module has none. */ +export function cjsAccessorSnapshotStores( + lowerer: Lowerer, + dep: ts.SourceFile, + loc: SrcLoc, +): IrStmt[] { + const getters = lexerVisibleAccessors(dep); + if (getters.length === 0) return []; + let readyId = lowerer.cjsSnapshotReady.get(dep); + if (readyId === undefined) { + readyId = `%g.cjsSnapshotReady.${lowerer.cjsSnapshotReady.size}`; + lowerer.globalsList.push({ id: readyId, name: "%cjsSnapshotReady", type: BOOL, mutable: true }); + lowerer.cjsSnapshotReady.set(dep, readyId); + } + const stores: IrStmt[] = [ + { kind: "assign", localId: readyId, value: { kind: "boolLit", value: true, type: BOOL, loc }, loc }, + ]; + for (const getter of getters) { + const snapshot = cjsAccessorSnapshotOf(lowerer, getter); + const call = cjsAccessorCall(lowerer, getter, loc); + if (snapshot === null || call === null) return []; + stores.push({ kind: "assign", localId: snapshot.id, value: call, loc }); + } + return [ + { + kind: "if", + cond: { + kind: "unary", + op: "!", + operand: { kind: "varRef", localId: readyId, type: BOOL, loc }, + type: BOOL, + loc, + }, + then: stores, + else_: null, + loc, + }, + ]; +} + /** The interned lifted-getter call both accessor-read paths share. */ function cjsAccessorCall( lowerer: Lowerer, diff --git a/packages/compiler/src/frontend/lowering/lower-modules.ts b/packages/compiler/src/frontend/lowering/lower-modules.ts index 96e22f208..86fab82d4 100644 --- a/packages/compiler/src/frontend/lowering/lower-modules.ts +++ b/packages/compiler/src/frontend/lowering/lower-modules.ts @@ -143,7 +143,7 @@ import { forkTargetPaths } from "../fork-target.js"; import { workerTargetPaths } from "../worker-target.js"; import { isNativeProxyInitializer } from "./expressions/native-proxy.js"; import { handleEventReaderPrelude } from "./lower-event-target.js"; -import { jsHoldsDynObject } from "./lower-exprs.js"; +import { cjsAccessorSnapshotStores, jsHoldsDynObject } from "./lower-exprs.js"; /** One file's declarations, split for collection and init-body lowering. */ export interface FileParts { @@ -2823,6 +2823,14 @@ export function lowerFileInit( }); } else { header.push({ kind: "exprStmt", expr: call, loc }); + // An ES-module importer links a CommonJS dependency: Node + // copies its lexer-visible exports here. + if ( + (ts.isImportDeclaration(stmt) || ts.isExportDeclaration(stmt)) && + !isCjsJsFile(sf, lowerer.program) && + isCjsJsFile(dep, lowerer.program) + ) + header.push(...cjsAccessorSnapshotStores(lowerer, dep, loc)); } } } diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index d252af250..ce2390d95 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -2507,6 +2507,10 @@ export class Lowerer { * lifted lazily as module-level functions and interned per accessor * declaration: member reads call the getter (lower-exprs). */ readonly cjsAccessorFns = new Map(); + /** Accessor exports' link-time values for ES-module importers, and the + * per-module flag that the copy already ran (cjsAccessorSnapshotStores). */ + readonly cjsAccessorSnapshots = new Map(); + readonly cjsSnapshotReady = new Map(); /** Interned `%iter.drain.` helpers (classIteratorDrainCall): one per * receiver class — the eager drain of a class iterable's protocol into * a fresh element array, behind array/call spreads. */ diff --git a/packages/compiler/src/frontend/npm-static-bundled-esm.ts b/packages/compiler/src/frontend/npm-static-bundled-esm.ts index cf4f6280b..cf3664264 100644 --- a/packages/compiler/src/frontend/npm-static-bundled-esm.ts +++ b/packages/compiler/src/frontend/npm-static-bundled-esm.ts @@ -443,9 +443,154 @@ export function lowerBundledEsmInits( } } const lowered = new Set(); + /** Some binding between the use site and the top level rebinds a name + * the table's getters read. */ + const shadowed = (site: ts.Node, entries: readonly [string, ts.Expression][]): boolean => { + const read = new Set(); + for (const [, value] of entries) for (const x of references(value)) read.add(x.text); + for (let a = site.parent; a !== undefined && !ts.isSourceFile(a); a = a.parent) { + if ( + ts.isFunctionLike(a) || + ts.isBlock(a) || + ts.isClassDeclaration(a) || + ts.isClassExpression(a) + ) { + for (const n of declaredNames(a)) if (read.has(n)) return true; + } + } + return false; + }; + const getterText = (value: ts.Expression): string => + applyEdits(source, value.getStart(sf), value.getEnd(), edits); + + /* Memoized lazy loaders of those namespaces, `L = () => M ??= NS` (a + * bundle plugin's lazy re-exports): every `L().name` becomes the + * initializer call followed by the export's live read, so the first + * access still initializes the module, once, and later accesses read + * the binding. */ + const loaderSpans: { start: number; end: number }[] = []; + for (const [name, t] of top) { + const d = t.node; + if (!ts.isVariableDeclaration(d) || d.initializer === undefined) continue; + const fn = unparen(d.initializer); + if (!ts.isArrowFunction(fn) || fn.parameters.nodes.length !== 0 || ts.isBlock(fn.body)) + continue; + const memoize = unparen(fn.body); + if ( + !ts.isBinaryExpression(memoize) || + (memoize.operatorToken.kind !== ts.SyntaxKind.QuestionQuestionEqualsToken && + memoize.operatorToken.kind !== ts.SyntaxKind.BarBarEqualsToken) || + !ts.isIdentifier(memoize.left) + ) + continue; + let w = unparen(memoize.right); + let interop = false; + let nodeMode = false; + if ( + ts.isCallExpression(w) && + ts.isIdentifier(w.expression) && + helperBase(w.expression.text) === "__toESM" + ) { + const [arg, mode] = w.arguments.nodes; + if (!arg || w.arguments.nodes.length > 2) continue; + if (mode !== undefined) { + if (ts.isNumericLiteral(mode)) nodeMode = Number(mode.text) !== 0; + else if (mode.kind === ts.SyntaxKind.TrueKeyword) nodeMode = true; + else if (mode.kind !== ts.SyntaxKind.FalseKeyword) continue; + } + interop = true; + w = unparen(arg); + } + let initCall: ts.CallExpression | null = null; + if (ts.isBinaryExpression(w) && w.operatorToken.kind === ts.SyntaxKind.CommaToken) { + const left = unparen(w.left); + if ( + !ts.isCallExpression(left) || + !ts.isIdentifier(left.expression) || + !inits.has(left.expression.text) || + left.arguments.nodes.length !== 0 + ) + continue; + initCall = left; + w = unparen(w.right); + } + if ( + !ts.isCallExpression(w) || + !ts.isIdentifier(w.expression) || + helperBase(w.expression.text) !== "__toCommonJS" || + w.arguments.nodes.length !== 1 || + !ts.isIdentifier(w.arguments.nodes[0]!) + ) + continue; + const target = (w.arguments.nodes[0] as ts.Identifier).text; + const table = tables.get(target); + if (table === undefined) continue; + const loaderWhy = { + degrade: `its memoized lazy loader '${name}' returns the namespace of a bundled ES module as a value, a module object the static rewrite cannot represent (only member reads of its result compile)`, + }; + const memo = memoize.left.text; + const memoDecl = top.get(memo)?.node; + if ( + memoDecl === undefined || + !ts.isVariableDeclaration(memoDecl) || + memoDecl.initializer !== undefined || + writes.get(memo) !== 1 || + countDeclarations(sf, memo) !== 1 || + (refsByName.get(memo) ?? []).some((x) => x !== memoize.left) || + writes.has(name) || + countDeclarations(sf, name) !== 1 + ) + return loaderWhy; + const entries = new Map(table.entries); + for (const ref of refsByName.get(name) ?? []) { + const call = ref.parent; + if ( + call === undefined || + !ts.isCallExpression(call) || + call.expression !== ref || + call.arguments.nodes.length !== 0 + ) + return loaderWhy; + let access: ts.Node | undefined = call.parent; + if ( + access === undefined || + !ts.isPropertyAccessExpression(access) || + access.expression !== call || + !plainRead(access) + ) + return loaderWhy; + // Node mode binds `default` to the namespace object itself. + if (interop && nodeMode && access.name.text === "default") { + const outer: ts.Node | undefined = access.parent; + if ( + outer === undefined || + !ts.isPropertyAccessExpression(outer) || + outer.expression !== access || + !plainRead(outer) + ) + return loaderWhy; + access = outer; + } + const member = (access as ts.PropertyAccessExpression).name.text; + const value = entries.get(member); + if (value === undefined || shadowed(access, table.entries)) return loaderWhy; + const init = initCall === null ? "" : `${initCall.getText(sf)}, `; + edits.push({ + start: access.getStart(sf), + end: access.getEnd(), + text: `(${init}${getterText(value)})`, + }); + } + edits.push(...removeDeclarators(sf, new Set([d, memoDecl]))); + loaderSpans.push({ start: d.getStart(sf), end: d.getEnd() }); + lowered.add(target); + } + for (const r of allRefs) { const table = tables.get(r.text); if (table === undefined) continue; + if (loaderSpans.some((span) => r.getStart(sf) >= span.start && r.getEnd() <= span.end)) + continue; const p = r.parent; // The table's own declaration and __export call are handled below. if (p !== undefined && ts.isVariableDeclaration(p) && p.name === r) continue; @@ -528,29 +673,27 @@ export function lowerBundledEsmInits( } // The getters read top-level bindings: nothing between the use site // and the top level may rebind one of their names. - const read = new Set(); - for (const [, value] of table.entries) for (const x of references(value)) read.add(x.text); - for (let a = p.parent; a !== undefined && !ts.isSourceFile(a); a = a.parent) { - if ( - ts.isFunctionLike(a) || - ts.isBlock(a) || - ts.isClassDeclaration(a) || - ts.isClassExpression(a) - ) { - for (const n of declaredNames(a)) { - if (read.has(n)) return { degrade: `its shipped JS ${why}` }; - } - } - } + if (shadowed(p, table.entries)) return { degrade: `its shipped JS ${why}` }; const getters = table.entries .map( ([n, value]) => - `get ${/^[A-Za-z_$][\w$]*$/.test(n) ? n : JSON.stringify(n)}() { return ${applyEdits(source, value.getStart(sf), value.getEnd(), edits)}; }`, + `get ${/^[A-Za-z_$][\w$]*$/.test(n) ? n : JSON.stringify(n)}() { return ${getterText(value)}; }`, ) .join(", "); edits.push({ start: p.getStart(sf), end: p.getEnd(), text: `{ ${getters} }` }); lowered.add(r.text); } + // A table nothing reads (its module is never required) goes too: its + // names are no exports of the bundle. + for (const [target, table] of tables) { + if (lowered.has(target)) continue; + const live = (refsByName.get(target) ?? []).some((x) => { + const p = x.parent; + if (p !== undefined && ts.isVariableDeclaration(p) && p.name === x) return false; + return !(x.getStart(sf) >= table.stmt.getStart(sf) && x.getEnd() <= table.stmt.getEnd()); + }); + if (!live) lowered.add(target); + } for (const target of lowered) { const table = tables.get(target)!; const decl = top.get(target)?.node; diff --git a/packages/compiler/src/frontend/npm-static-rewrite-host.ts b/packages/compiler/src/frontend/npm-static-rewrite-host.ts index 9ff14c455..4e0c92e31 100644 --- a/packages/compiler/src/frontend/npm-static-rewrite-host.ts +++ b/packages/compiler/src/frontend/npm-static-rewrite-host.ts @@ -181,10 +181,12 @@ export function rewriteBundlerCjsWithParser( source: string, filePath: string, liveExportTable?: (filePath: string) => void, + lexerVisibleNames?: (filePath: string, names: readonly string[]) => void, ): string | { degrade: string } | null { if (!isBundlerCjsCandidate(source)) return null; return rewriteBundlerCjsSyntax(parser.parse(filePath, source, "js"), filePath, { liveExportTable, + lexerVisibleNames, requireOnly: isVirtualSource, requireTargetEsModuleStamped: (from, spec) => requireTargetEsModuleStamped(parser, from, spec), starTargetNames: (from, spec) => { diff --git a/packages/compiler/src/frontend/npm-static-rewrite-syntax.ts b/packages/compiler/src/frontend/npm-static-rewrite-syntax.ts index 08dbc0db5..3b553c3f2 100644 --- a/packages/compiler/src/frontend/npm-static-rewrite-syntax.ts +++ b/packages/compiler/src/frontend/npm-static-rewrite-syntax.ts @@ -22,6 +22,9 @@ export interface CjsRewriteHost { * exists, so Node's lexer-visible name set bounds nothing and every * name the export plumbing defines is part of its surface. */ requireOnly?: ((filePath: string) => boolean) | undefined; + /** Told the names Node's lexer detects in the original file that the + * rewritten text spells only as accessors (which the lexer skips). */ + lexerVisibleNames?: ((filePath: string, names: readonly string[]) => void) | undefined; } /** True when esbuild's export object (`module.exports = __toCommonJS(T)` @@ -119,8 +122,12 @@ interface EntrySpec { name: string; /** "ident": VALUE is a plain identifier spelled directly in the table. * "hoist": VALUE is an expression text hoisted to a tail const first. + * "getter": VALUE is a getter body's returned expression the table + * keeps as a live accessor (read on every access, like the original + * getter, so a lazily initialized module still initializes at the + * first read). * "any": the checked-dynamic fallback (`__scriptc_any`). */ - kind: "ident" | "hoist" | "any"; + kind: "ident" | "hoist" | "getter" | "any"; value?: string; } @@ -760,7 +767,14 @@ export function rewriteBundlerCjsSyntax( ...exportTables.filter((t) => t.target !== esbuildMainTarget), ]; for (const t of tablesOrdered) { - for (const [name, value] of t.entries) claim(name, classify(name, value)); + for (const [name, value] of t.entries) { + const spec = classify(name, value); + // The table feeding module.exports keeps a getter it cannot chase + // as a live accessor rather than a snapshot. + if (spec.kind === "any" && value !== null && t.target === esbuildMainTarget) + claim(name, { name, kind: "getter", value: value.getText() }); + else claim(name, spec); + } } for (const d of definePropEntries) claim(d.name, classify(d.name, d.value)); @@ -1039,6 +1053,8 @@ export function rewriteBundlerCjsSyntax( const v = `__scriptc_e${hoistN++}`; hoists.push(`const ${v} = ${dropInteropDefault(spec.value!)};`); plainParts.push(`${keyOf(spec.name)}: ${v},`); + } else if (spec.kind === "getter") { + plainParts.push(`get ${keyOf(spec.name)}() { return ${dropInteropDefault(spec.value!)}; },`); } else { plainParts.push(`${keyOf(spec.name)}: __scriptc_any,`); } @@ -1064,5 +1080,13 @@ export function rewriteBundlerCjsSyntax( lines.push( `module.exports = {${spreadParts.join("")}${plainParts.join("")}${esModulePart}${memberParts.join("")}};`, ); + /* Node's lexer sees the original's names for these entries (esbuild's + * dead annotation), but no accessor of the canonical table: the host + * keeps those names lexer-visible, so ES-module importers link exactly + * as they do against the original. */ + const accessorNames = [...entries.values()] + .filter((spec) => spec.kind === "getter" && lexed.exports.has(spec.name)) + .map((spec) => spec.name); + if (accessorNames.length > 0) host.lexerVisibleNames?.(filePath, accessorNames); return text + lines.join("\n") + "\n"; } diff --git a/packages/compiler/src/frontend/npm-static.ts b/packages/compiler/src/frontend/npm-static.ts index 4c1bc9ae6..77eab1d3a 100644 --- a/packages/compiler/src/frontend/npm-static.ts +++ b/packages/compiler/src/frontend/npm-static.ts @@ -43,6 +43,14 @@ export function npmStaticLiveExportTable(path: string): boolean { return liveExportFiles.has(path.split("\\").join("/")); } +/** Names Node's lexer detects in a rewritten file's original text that + * its rewrite spells only as accessors (the lexer skips those). */ +const lexerNames = new Map(); + +export function npmStaticLexerVisibleNames(path: string): readonly string[] { + return lexerNames.get(path.split("\\").join("/")) ?? []; +} + export function setNpmStaticPackages>(packages: T): void { activePackages = new Set(packages); declarationOverloads = new Map(); @@ -51,6 +59,7 @@ export function setNpmStaticPackages>(packages: T): v fileDegrades.clear(); rewriteCache.clear(); liveExportFiles.clear(); + lexerNames.clear(); clearVirtualSources(); untypedPkgCache.clear(); realpathProbed.clear(); @@ -439,6 +448,7 @@ export function npmStaticFsShadow(services: FrontendServices): NpmStaticFsShadow source, path, (live) => liveExportFiles.add(live.split("\\").join("/")), + (file, names) => lexerNames.set(file.split("\\").join("/"), names), ); if (answer !== null && typeof answer === "object") { reportFileDegrade(path, target.pkg, answer.degrade); diff --git a/packages/compiler/src/frontend/program.ts b/packages/compiler/src/frontend/program.ts index 908a587ea..5c824c8ee 100644 --- a/packages/compiler/src/frontend/program.ts +++ b/packages/compiler/src/frontend/program.ts @@ -68,6 +68,7 @@ import { isNpmStaticPackage, npmStaticActive, npmStaticFsShadow, + npmStaticLexerVisibleNames, npmStaticLiveExportTable, npmStaticPackageOfPath, reportNpmStaticOffender, @@ -5440,12 +5441,18 @@ function cjsNamedImportLinkCheck( npmStaticDepSf7(program, from, spec, "require"); return dep !== null && isCjsJsFile7(dep, program) ? dep : null; }; + // A bundler rewrite spells some lexer-visible names as accessors. + const lexWithAccessorNames = (sf: ts.SourceFile): ReturnType => { + const lexed = cjsLexedExportsOfFile(sf); + for (const name of npmStaticLexerVisibleNames(sf.fileName)) lexed.exports.add(name); + return lexed; + }; const visible = (dep: ts.SourceFile, name: string): boolean => // `default` is the module.exports binding itself — always provided. // The program already owns native source ASTs; reuse them without // starting another syntax session or reparsing their source text. name === "default" || - cjsVisibleNames(dep, cjsLexedExportsOfFile, resolveCjsDep, lexMemo).has(name); + cjsVisibleNames(dep, lexWithAccessorNames, resolveCjsDep, lexMemo).has(name); /** The statement's resolved LOCAL CommonJS dependency, when it is an * import/re-export from one. */ diff --git a/packages/compiler/src/frontend/services.ts b/packages/compiler/src/frontend/services.ts index aeed52161..c5dcbf513 100644 --- a/packages/compiler/src/frontend/services.ts +++ b/packages/compiler/src/frontend/services.ts @@ -64,10 +64,17 @@ export class FrontendServices { source: string, path: string, liveExportTable?: (path: string) => void, + lexerVisibleNames?: (path: string, names: readonly string[]) => void, ): string | { degrade: string } | null { this.ensureOpen(); if (!isBundlerCjsCandidate(source)) return null; - return rewriteBundlerCjsWithParser(this.sourceParser(), source, path, liveExportTable); + return rewriteBundlerCjsWithParser( + this.sourceParser(), + source, + path, + liveExportTable, + lexerVisibleNames, + ); } /** A CommonJS bundle's __commonJS chunks as their own modules (null: no * chunks; a degrade reason: chunks the recognizer cannot move). */ diff --git a/tests/fixtures/npm/cases/2564-lazy-barrel/main.ts b/tests/fixtures/npm/cases/2564-lazy-barrel/main.ts new file mode 100644 index 000000000..31c88db9b --- /dev/null +++ b/tests/fixtures/npm/cases/2564-lazy-barrel/main.ts @@ -0,0 +1,19 @@ +// A barrel whose re-exports load their modules lazily: a build plugin +// (real esbuild output, plain and whitespace/syntax minified) turns each +// re-export into a getter over a memoized loader of a lazily initialized +// ES module. Node's ES-module import of a CommonJS module reads every +// lexer-visible export when it links, so each module initializes once, +// before main runs, and the imports are snapshots (greet reassigns +// GREETING afterwards). +import { greet, GREETING, Counter, describe } from "gtlazy"; +import { describe as wsDescribe, GREETING as wsGreeting } from "gtlazyws"; + +console.log("main start"); +console.log(GREETING); +console.log(greet("a")); +console.log(GREETING); +const c = new Counter(); +console.log(c.next(), c.next()); +console.log(describe()); +console.log(wsDescribe()); +console.log(wsGreeting); diff --git a/tests/fixtures/npm/cases/2565-lazy-barrel-require/main.ts b/tests/fixtures/npm/cases/2565-lazy-barrel-require/main.ts new file mode 100644 index 000000000..42bed4781 --- /dev/null +++ b/tests/fixtures/npm/cases/2565-lazy-barrel-require/main.ts @@ -0,0 +1,9 @@ +// The lazy barrel (gtlazy) required from CommonJS: require() hands out the +// export object itself, so each re-export's getter runs at its read. A +// module initializes at the first read of one of its exports, once, and +// later reads see its live bindings (greet reassigns GREETING). +import { first, again } from "gtlazyuser"; + +console.log("main start"); +console.log(first()); +console.log(again()); diff --git a/tests/fixtures/npm/node_modules/gtlazy/dist/index.d.ts b/tests/fixtures/npm/node_modules/gtlazy/dist/index.d.ts new file mode 100644 index 000000000..d3b4bac73 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtlazy/dist/index.d.ts @@ -0,0 +1,7 @@ +export declare let GREETING: string; +export declare function greet(name: string): string; +export declare class Counter { + n: number; + next(): number; +} +export declare function describe(): string; diff --git a/tests/fixtures/npm/node_modules/gtlazy/dist/index.js b/tests/fixtures/npm/node_modules/gtlazy/dist/index.js new file mode 100644 index 000000000..9a94faa11 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtlazy/dist/index.js @@ -0,0 +1,116 @@ +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __esm = (fn, res) => function __init() { + return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res; +}; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); + +// src/greet.ts +var greet_exports = {}; +__export(greet_exports, { + GREETING: () => GREETING, + greet: () => greet +}); +function greet(name) { + GREETING = "hi"; + return GREETING + " " + name; +} +var GREETING; +var init_greet = __esm({ + "src/greet.ts"() { + console.log("greet init"); + GREETING = "hello"; + } +}); + +// src/counter.ts +var counter_exports = {}; +__export(counter_exports, { + default: () => Counter +}); +var Counter; +var init_counter = __esm({ + "src/counter.ts"() { + console.log("counter init"); + Counter = class { + n = 0; + next() { + return ++this.n; + } + }; + } +}); + +// src/describe.ts +var describe_exports = {}; +__export(describe_exports, { + describe: () => describe +}); +function describe() { + return "described: " + greet("d"); +} +var init_describe = __esm({ + "src/describe.ts"() { + init_greet(); + console.log("describe init"); + } +}); + +// src/index.ts +var __create = Object.create; +var __defProp2 = Object.defineProperty; +var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor; +var __getOwnPropNames2 = Object.getOwnPropertyNames; +var __getProtoOf = Object.getPrototypeOf; +var __hasOwnProp2 = Object.prototype.hasOwnProperty; +var __export2 = (target, all) => { + for (var name in all) + __defProp2(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps2 = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames2(from)) + if (!__hasOwnProp2.call(to, key) && key !== except) + __defProp2(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc2(from, key)) || desc.enumerable }); + } + return to; +}; +var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps2( + // If the importer is in node compatibility mode or this is not an ESM + // file that has been converted to a CommonJS file using a Babel- + // compatible transform (i.e. "__esModule" has not been set), then set + // "default" to the CommonJS "module.exports" for node compatibility. + isNodeMode || !mod || !mod.__esModule ? __defProp2(target, "default", { value: mod, enumerable: true }) : target, + mod +)); +var __toCommonJS2 = (mod) => __copyProps2(__defProp2({}, "__esModule", { value: true }), mod); +var stdin_exports = {}; +__export2(stdin_exports, { + Counter: () => load_import_counter().default, + GREETING: () => load_import_greet().GREETING, + describe: () => load_import_describe().describe, + greet: () => load_import_greet().greet +}); +module.exports = __toCommonJS2(stdin_exports); +var import_greet2; +var load_import_greet = () => import_greet2 ??= (init_greet(), __toCommonJS(greet_exports)); +var import_counter; +var load_import_counter = () => import_counter ??= __toESM((init_counter(), __toCommonJS(counter_exports))); +var import_describe; +var load_import_describe = () => import_describe ??= (init_describe(), __toCommonJS(describe_exports)); + +0 && (module.exports = {"Counter": null,"GREETING": null,"describe": null,"greet": null}); diff --git a/tests/fixtures/npm/node_modules/gtlazy/package.json b/tests/fixtures/npm/node_modules/gtlazy/package.json new file mode 100644 index 000000000..e63f696a6 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtlazy/package.json @@ -0,0 +1,7 @@ +{ + "name": "gtlazy", + "version": "1.0.0", + "description": "esbuild 0.25.10 output (--bundle --platform=node --format=cjs) of a barrel whose re-exports a build plugin turns into memoized lazy loaders: `load_x = () => x ??= (init_x(), __toCommonJS(x_exports))`", + "main": "./dist/index.js", + "types": "./dist/index.d.ts" +} diff --git a/tests/fixtures/npm/node_modules/gtlazyuser/index.d.ts b/tests/fixtures/npm/node_modules/gtlazyuser/index.d.ts new file mode 100644 index 000000000..948fec847 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtlazyuser/index.d.ts @@ -0,0 +1,2 @@ +export declare function first(): string; +export declare function again(): string; diff --git a/tests/fixtures/npm/node_modules/gtlazyuser/index.js b/tests/fixtures/npm/node_modules/gtlazyuser/index.js new file mode 100644 index 000000000..d5666d6e1 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtlazyuser/index.js @@ -0,0 +1,10 @@ +"use strict"; +const lazy = require("gtlazy"); + +console.log("user loaded"); +exports.first = function first() { + return lazy.GREETING + "/" + lazy.greet("u"); +}; +exports.again = function again() { + return lazy.GREETING + "/" + lazy.describe(); +}; diff --git a/tests/fixtures/npm/node_modules/gtlazyuser/package.json b/tests/fixtures/npm/node_modules/gtlazyuser/package.json new file mode 100644 index 000000000..841bdc7b2 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtlazyuser/package.json @@ -0,0 +1,10 @@ +{ + "name": "gtlazyuser", + "version": "1.0.0", + "description": "a CommonJS package that require()s the gtlazy barrel: its lazy re-exports initialize at the first read and read live bindings", + "main": "index.js", + "types": "index.d.ts", + "dependencies": { + "gtlazy": "1.0.0" + } +} diff --git a/tests/fixtures/npm/node_modules/gtlazyws/dist/index.d.ts b/tests/fixtures/npm/node_modules/gtlazyws/dist/index.d.ts new file mode 100644 index 000000000..d3b4bac73 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtlazyws/dist/index.d.ts @@ -0,0 +1,7 @@ +export declare let GREETING: string; +export declare function greet(name: string): string; +export declare class Counter { + n: number; + next(): number; +} +export declare function describe(): string; diff --git a/tests/fixtures/npm/node_modules/gtlazyws/dist/index.js b/tests/fixtures/npm/node_modules/gtlazyws/dist/index.js new file mode 100644 index 000000000..a472466b0 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtlazyws/dist/index.js @@ -0,0 +1,3 @@ +var __defProp=Object.defineProperty;var __getOwnPropDesc=Object.getOwnPropertyDescriptor;var __getOwnPropNames=Object.getOwnPropertyNames;var __hasOwnProp=Object.prototype.hasOwnProperty;var __esm=(fn,res)=>function(){return fn&&(res=(0,fn[__getOwnPropNames(fn)[0]])(fn=0)),res};var __export=(target,all)=>{for(var name in all)__defProp(target,name,{get:all[name],enumerable:!0})},__copyProps=(to,from,except,desc)=>{if(from&&typeof from=="object"||typeof from=="function")for(let key of __getOwnPropNames(from))!__hasOwnProp.call(to,key)&&key!==except&&__defProp(to,key,{get:()=>from[key],enumerable:!(desc=__getOwnPropDesc(from,key))||desc.enumerable});return to};var __toCommonJS=mod=>__copyProps(__defProp({},"__esModule",{value:!0}),mod);var greet_exports={};__export(greet_exports,{GREETING:()=>GREETING,greet:()=>greet});function greet(name){return GREETING="hi",GREETING+" "+name}var GREETING,init_greet=__esm({"src/greet.ts"(){console.log("greet init");GREETING="hello"}});var counter_exports={};__export(counter_exports,{default:()=>Counter});var Counter,init_counter=__esm({"src/counter.ts"(){console.log("counter init");Counter=class{n=0;next(){return++this.n}}}});var describe_exports={};__export(describe_exports,{describe:()=>describe});function describe(){return"described: "+greet("d")}var init_describe=__esm({"src/describe.ts"(){init_greet();console.log("describe init")}});var __create=Object.create,__defProp2=Object.defineProperty,__getOwnPropDesc2=Object.getOwnPropertyDescriptor,__getOwnPropNames2=Object.getOwnPropertyNames,__getProtoOf=Object.getPrototypeOf,__hasOwnProp2=Object.prototype.hasOwnProperty,__export2=(target,all)=>{for(var name in all)__defProp2(target,name,{get:all[name],enumerable:!0})},__copyProps2=(to,from,except,desc)=>{if(from&&typeof from=="object"||typeof from=="function")for(let key of __getOwnPropNames2(from))!__hasOwnProp2.call(to,key)&&key!==except&&__defProp2(to,key,{get:()=>from[key],enumerable:!(desc=__getOwnPropDesc2(from,key))||desc.enumerable});return to},__toESM=(mod,isNodeMode,target)=>(target=mod!=null?__create(__getProtoOf(mod)):{},__copyProps2(isNodeMode||!mod||!mod.__esModule?__defProp2(target,"default",{value:mod,enumerable:!0}):target,mod)),__toCommonJS2=mod=>__copyProps2(__defProp2({},"__esModule",{value:!0}),mod),stdin_exports={};__export2(stdin_exports,{Counter:()=>load_import_counter().default,GREETING:()=>load_import_greet().GREETING,describe:()=>load_import_describe().describe,greet:()=>load_import_greet().greet});module.exports=__toCommonJS2(stdin_exports);var import_greet2,load_import_greet=()=>import_greet2??=(init_greet(),__toCommonJS(greet_exports)),import_counter,load_import_counter=()=>import_counter??=__toESM((init_counter(),__toCommonJS(counter_exports))),import_describe,load_import_describe=()=>import_describe??=(init_describe(),__toCommonJS(describe_exports)); + +0 && (module.exports = {"Counter": null,"GREETING": null,"describe": null,"greet": null}); diff --git a/tests/fixtures/npm/node_modules/gtlazyws/package.json b/tests/fixtures/npm/node_modules/gtlazyws/package.json new file mode 100644 index 000000000..bf5ec5b93 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtlazyws/package.json @@ -0,0 +1,7 @@ +{ + "name": "gtlazyws", + "version": "1.0.0", + "description": "the gtlazy source and plugin bundled by esbuild 0.25.10 with --minify-whitespace --minify-syntax", + "main": "./dist/index.js", + "types": "./dist/index.d.ts" +} diff --git a/tests/harness/npm-cases.ts b/tests/harness/npm-cases.ts index f8d346154..87a6de2a8 100644 --- a/tests/harness/npm-cases.ts +++ b/tests/harness/npm-cases.ts @@ -19,12 +19,12 @@ export function npmCases(fixturesRoot: string): NpmCase[] { return [ ...globSync(join(fixturesRoot, "npm/cases/*/main.ts")) .sort() - // 2465-2469 and 2556-2563 are the --npm-static bundler-emitted-CJS + // 2465-2469 and 2556-2565 are the --npm-static bundler-emitted-CJS // cases (npm-static.test.ts drives them with the opt-in): their // consumers exercise the static rewrite's surface (lexer-visible // names the shipped .d.ts never declares, the __toESM interop // family), so they stay out of the flagless island lane by design. - .filter((entry) => !/\/(246[5-9]|255[6-9]|256[0-3])-[^/]+\/main\.ts$/.test(entry)) + .filter((entry) => !/\/(246[5-9]|255[6-9]|256[0-5])-[^/]+\/main\.ts$/.test(entry)) .map((entry) => ({ name: entry.split("/").at(-2)!, entry, diff --git a/tests/harness/npm-static.test.ts b/tests/harness/npm-static.test.ts index 8560973cd..943b81695 100644 --- a/tests/harness/npm-static.test.ts +++ b/tests/harness/npm-static.test.ts @@ -76,7 +76,7 @@ async function buildStatic(entry: string, npmStatic: string[] | "auto"): Promise ...globSync( join(fixturesRoot, "npm/node_modules/cryptozoo/**/*.{js,mjs,cjs,json,d.ts}"), ).sort(), - // the bundler-emitted-CJS mini packages (cases 2465-2469, 2556-2563) + // the bundler-emitted-CJS mini packages (cases 2465-2469, 2556-2565) ...globSync(join(fixturesRoot, "npm/node_modules/gt*/**/*.{js,cjs,json}")).sort(), ]; for (const f of inputs) hash.update(f).update(readFileSync(f)); @@ -1478,6 +1478,17 @@ describe(`npm-static pilots${sanitize ? " (sanitized)" : ""}`, () => { ]); }, 180_000); + // 2564-2565: a barrel whose re-exports are memoized lazy loaders of + // lazily initialized ES modules (a bundle plugin's shape): ES-module + // importers copy every export when they link (Node's CommonJS import), + // require() reads each export's getter at the read. + test.each([ + ["2564-lazy-barrel", ["gtlazy", "gtlazyws"]], + ["2565-lazy-barrel-require", ["gtlazyuser", "gtlazy"]], + ])("the lazy barrel %s compiles statically and byte-matches Node", async (name, packages) => { + await expectNodeParity(join(fixturesRoot, `npm/cases/${name}/main.ts`), packages); + }, 180_000); + // 2562: a file with unverifiable interop that no compiled code can load // (the program imports a leaf data module and only a type from the // main entry) leaves its package static. From bc32d97339b1b0f82618e4d38feb4b78babef6f0 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Mon, 19 Oct 2026 16:52:01 -0700 Subject: [PATCH 024/122] Compile build-utils' bundled code: star re-exports, aliases, three lowering fixes - A star re-export of a lazily initialized module into the bundle's exports (__reExport(main, (init_x(), __toCommonJS(x_exports)))) initializes the module at the statement and adds its names to the export table after the explicit ones, without default. - The rewrite keeps the original's lexer answer whenever its export table holds accessors (Node's lexer stops at the first one). - An npm alias directory of a selected package ("x-updated": "npm:x@6.3.0") resolves to that package's JavaScript in both resolvers. - "use strict" code may read arguments in functions with parameters. - A comma expression ending in null/undefined coerces its result. - A void conditional return lowers as control flow. - A detached method of a local class drops its empty capture list. Co-Authored-By: Claude Opus 5.5 --- .../src/frontend/lowering/call-signatures.ts | 27 +++++++++++++- .../frontend/lowering/class-method-values.ts | 5 ++- .../compiler/src/frontend/lowering/lowerer.ts | 20 +++++++++++ .../src/frontend/npm-static-bundled-esm.ts | 35 +++++++++++++++++++ .../src/frontend/npm-static-rewrite-syntax.ts | 15 ++++---- packages/compiler/src/frontend/npm-static.ts | 24 +++++++++++++ packages/compiler/src/frontend/resolve.ts | 24 ++++++++++++- .../6260-local-class-detached-method.ts | 16 +++++++++ tests/corpus/6262-comma-null-union-return.cjs | 15 ++++++++ .../6263-strict-arguments-parameters.cjs | 15 ++++++++ .../cases/2566-npm-alias-two-releases/main.ts | 7 ++++ .../node_modules/gtroute | 1 + .../node_modules/gtpattern/lib/index.d.ts | 2 ++ .../node_modules/gtpattern/lib/index.js | 5 +++ .../node_modules/gtpattern/package.json | 6 ++++ .../node_modules/gtpattern/lib/index.d.ts | 2 ++ .../node_modules/gtpattern/lib/index.js | 5 +++ .../node_modules/gtpattern/package.json | 6 ++++ .../gtroute@1.0.0/node_modules/gtpattern | 1 + .../gtroute@1.0.0/node_modules/gtpattern-next | 1 + .../node_modules/gtroute/index.d.ts | 1 + .../node_modules/gtroute/index.js | 7 ++++ .../node_modules/gtroute/package.json | 11 ++++++ tests/harness/npm-cases.ts | 4 +-- tests/harness/npm-static.test.ts | 23 +++++++++++- 25 files changed, 264 insertions(+), 14 deletions(-) create mode 100644 tests/corpus/6260-local-class-detached-method.ts create mode 100644 tests/corpus/6262-comma-null-union-return.cjs create mode 100644 tests/corpus/6263-strict-arguments-parameters.cjs create mode 100644 tests/fixtures/npm/cases/2566-npm-alias-two-releases/main.ts create mode 120000 tests/fixtures/npm/cases/2566-npm-alias-two-releases/node_modules/gtroute create mode 100644 tests/fixtures/npm/store/gtpattern@1.0.0/node_modules/gtpattern/lib/index.d.ts create mode 100644 tests/fixtures/npm/store/gtpattern@1.0.0/node_modules/gtpattern/lib/index.js create mode 100644 tests/fixtures/npm/store/gtpattern@1.0.0/node_modules/gtpattern/package.json create mode 100644 tests/fixtures/npm/store/gtpattern@2.0.0/node_modules/gtpattern/lib/index.d.ts create mode 100644 tests/fixtures/npm/store/gtpattern@2.0.0/node_modules/gtpattern/lib/index.js create mode 100644 tests/fixtures/npm/store/gtpattern@2.0.0/node_modules/gtpattern/package.json create mode 120000 tests/fixtures/npm/store/gtroute@1.0.0/node_modules/gtpattern create mode 120000 tests/fixtures/npm/store/gtroute@1.0.0/node_modules/gtpattern-next create mode 100644 tests/fixtures/npm/store/gtroute@1.0.0/node_modules/gtroute/index.d.ts create mode 100644 tests/fixtures/npm/store/gtroute@1.0.0/node_modules/gtroute/index.js create mode 100644 tests/fixtures/npm/store/gtroute@1.0.0/node_modules/gtroute/package.json diff --git a/packages/compiler/src/frontend/lowering/call-signatures.ts b/packages/compiler/src/frontend/lowering/call-signatures.ts index 9d9c9668c..9de7cd026 100644 --- a/packages/compiler/src/frontend/lowering/call-signatures.ts +++ b/packages/compiler/src/frontend/lowering/call-signatures.ts @@ -707,6 +707,27 @@ export function collectSignatureInner(lowerer: Lowerer, decl: ts.FunctionDeclara }); } +/** True when a "use strict" directive prologue covers the node: its + * file's or an enclosing function's (strict code's arguments object never + * aliases the named parameters). */ +function inStrictCode(node: ts.Node): boolean { + const strict = (stmts: readonly ts.Statement[]): boolean => { + for (const st of stmts) { + if (!ts.isExpressionStatement(st) || !ts.isStringLiteral(st.expression)) return false; + if (st.expression.text === "use strict") return true; + } + return false; + }; + for (let n: ts.Node | undefined = node; n !== undefined; n = n.parent) { + if (ts.isSourceFile(n)) return strict(n.statements.nodes); + if (ts.isFunctionLike(n)) { + const body = (n as { body?: ts.Node }).body; + if (body !== undefined && ts.isBlock(body) && strict(body.statements.nodes)) return true; + } + } + return false; +} + /** Arrows inherit arguments. Ordinary JS functions receive a hidden * array unless a declared rest parameter already owns that contract. * Class methods are strict; other parameterized forms require ES modules @@ -732,7 +753,11 @@ function appendArgumentsParameter( const classMethod = ts.isMethodDeclaration(node) && (ts.isClassDeclaration(node.parent) || ts.isClassExpression(node.parent)); - if (!classMethod && !isNodeEsmFile(node.getSourceFile(), lowerer.program)) { + if ( + !classMethod && + !isNodeEsmFile(node.getSourceFile(), lowerer.program) && + !inStrictCode(node) + ) { lowerer.unsupported( "SC1090", node, diff --git a/packages/compiler/src/frontend/lowering/class-method-values.ts b/packages/compiler/src/frontend/lowering/class-method-values.ts index 10572377e..ff732efff 100644 --- a/packages/compiler/src/frontend/lowering/class-method-values.ts +++ b/packages/compiler/src/frontend/lowering/class-method-values.ts @@ -539,8 +539,11 @@ export function finalizeClassMethodValues(lowerer: Lowerer, functions: IrFunctio if (usesThis) continue; // A method that never uses this remains callable when detached. const name = `%method.receiverless:${target.name}`; + // A local class's method keeps an (empty) class-capture list; the + // detached copy has no receiver to read captures through. + const { classCaptures: _none, ...detached } = target; functions.push({ - ...target, + ...detached, name, params: target.params.slice(1), locals: target.locals.filter((local) => local.id !== thisParam.localId), diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index ce2390d95..1f905cae4 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -8478,6 +8478,17 @@ export class Lowerer { * else (including a DIFFERENT union) is left for requireExactShape, which * rejects union mismatches with SC2003. */ coerceToExpected(expr: IrExpr, expected: IrType): IrExpr { + // A comma expression ending in a unit literal (`return log(), null`): + // its effects run first, unchanged, and the unit value takes the + // destination's representation (a bare unit literal has none). + if ( + expr.kind === "seqExpr" && + expr.result.kind === "unitLit" && + !typeEquals(expr.type, expected) + ) { + const result = this.coerceToExpected(expr.result, expected); + return { ...expr, result, type: result.type }; + } // A destination that holds undefined takes an unassigned deferred-init // field's slot as is (Node passes the undefined along). if (expected.kind === "union" && this.armTag(expected.unionId, UNDEFINED_T) >= 0) { @@ -9905,6 +9916,15 @@ export class Lowerer { e = this.returnedPromiseAwait(e); } if (e.kind === "unitLit") return { kind: "return", value: null, loc }; + // `return c ? f() : g()` over void calls: a void conditional has no + // value representation, so its branch selection becomes control flow. + if (e.kind === "ternary" && e.type.kind === "void") { + const branch = (x: IrExpr): IrStmt => + x.kind === "ternary" && x.type.kind === "void" + ? { kind: "if", cond: x.cond, then: [branch(x.then)], else_: [branch(x.else_)], loc } + : { kind: "exprStmt", expr: x, loc }; + return { kind: "block", body: [branch(e), { kind: "return", value: null, loc }], loc }; + } if (e.type.kind === "void") return { kind: "return", value: e, loc }; return { kind: "block", diff --git a/packages/compiler/src/frontend/npm-static-bundled-esm.ts b/packages/compiler/src/frontend/npm-static-bundled-esm.ts index cf3664264..10177ccb9 100644 --- a/packages/compiler/src/frontend/npm-static-bundled-esm.ts +++ b/packages/compiler/src/frontend/npm-static-bundled-esm.ts @@ -633,6 +633,41 @@ export function lowerBundledEsmInits( names.has(access.name.text) && plainRead(access); const holder = ns.parent; + // `__reExport(main, ns, module.exports)`: a star re-export of the + // module into the bundle's own exports (`export * from` in its entry). + // The initializer runs at the statement; the names become live getter + // entries of the export table, after its explicit ones (first wins) + // and without `default`, exactly the names __reExport copies. + if ( + holder !== undefined && + ts.isCallExpression(holder) && + ts.isIdentifier(holder.expression) && + helperBase(holder.expression.text) === "__reExport" && + holder.arguments.nodes[1] === ns && + holder.arguments.nodes.length <= 3 && + ts.isExpressionStatement(holder.parent) && + mainTarget !== null && + ts.isIdentifier(holder.arguments.nodes[0]!) && + (holder.arguments.nodes[0] as ts.Identifier).text === mainTarget + ) { + const inner = unparen(ns); + const init = + ts.isBinaryExpression(inner) && inner.operatorToken.kind === ts.SyntaxKind.CommaToken + ? `${unparen(inner.left).getText(sf)}; ` + : ""; + if (shadowed(holder, table.entries)) return { degrade: `its shipped JS ${why}` }; + const entries = table.entries + .filter(([n]) => n !== "default") + .map(([n, value]) => `${JSON.stringify(n)}: () => ${getterText(value)}`) + .join(", "); + edits.push({ + start: holder.parent.getStart(sf), + end: holder.parent.getEnd(), + text: `${init}__export(${mainTarget}, { ${entries} });`, + }); + lowered.add(r.text); + continue; + } let readable = memberRead(holder, ns); if ( !readable && diff --git a/packages/compiler/src/frontend/npm-static-rewrite-syntax.ts b/packages/compiler/src/frontend/npm-static-rewrite-syntax.ts index 3b553c3f2..5963cf7b5 100644 --- a/packages/compiler/src/frontend/npm-static-rewrite-syntax.ts +++ b/packages/compiler/src/frontend/npm-static-rewrite-syntax.ts @@ -1080,13 +1080,12 @@ export function rewriteBundlerCjsSyntax( lines.push( `module.exports = {${spreadParts.join("")}${plainParts.join("")}${esModulePart}${memberParts.join("")}};`, ); - /* Node's lexer sees the original's names for these entries (esbuild's - * dead annotation), but no accessor of the canonical table: the host - * keeps those names lexer-visible, so ES-module importers link exactly - * as they do against the original. */ - const accessorNames = [...entries.values()] - .filter((spec) => spec.kind === "getter" && lexed.exports.has(spec.name)) - .map((spec) => spec.name); - if (accessorNames.length > 0) host.lexerVisibleNames?.(filePath, accessorNames); + /* Node's lexer skips accessors (and stops reading an object literal at + * the first one), so a canonical table with accessor entries hides + * names the original showed it (esbuild's dead annotation): the host + * keeps the original's lexer answer, so ES-module importers link + * exactly as they do against the original. */ + if ([...entries.values()].some((spec) => spec.kind === "getter")) + host.lexerVisibleNames?.(filePath, [...lexed.exports]); return text + lines.join("\n") + "\n"; } diff --git a/packages/compiler/src/frontend/npm-static.ts b/packages/compiler/src/frontend/npm-static.ts index 77eab1d3a..e00122238 100644 --- a/packages/compiler/src/frontend/npm-static.ts +++ b/packages/compiler/src/frontend/npm-static.ts @@ -63,6 +63,7 @@ export function setNpmStaticPackages>(packages: T): v clearVirtualSources(); untypedPkgCache.clear(); realpathProbed.clear(); + aliasNames.clear(); } export function setNpmStaticDeclarationOverloads( @@ -177,9 +178,32 @@ function shadowTargetOf(path: string): { pkg: string; viaTypes: boolean } | null if (mangledTypesName(pkg) === mangled) return { pkg, viaTypes: true }; } } + // An npm alias (`"x-updated": "npm:x@6.3.0"`) installs the package under + // another directory name: its own manifest names the package. + const aliased = aliasedPackageName(`${norm.slice(0, idx)}/node_modules/${dirName}`); + if (aliased !== null && aliased !== dirName && activePackages.has(aliased)) + return { pkg: aliased, viaTypes: false }; return null; } +/** The "name" a node_modules directory's own package.json declares, or + * null (cached per load). */ +const aliasNames = new Map(); +function aliasedPackageName(pkgDir: string): string | null { + const hit = aliasNames.get(pkgDir); + if (hit !== undefined) return hit; + let name: string | null = null; + try { + const text = trackedReadFile(`${pkgDir}/package.json`); + const declared = text === null ? undefined : (JSON.parse(text) as { name?: unknown }).name; + if (typeof declared === "string") name = declared; + } catch { + name = null; + } + aliasNames.set(pkgDir, name); + return name; +} + /** The opted-in package.json transform, applied identically in BOTH * resolution worlds (the tsgo host's fs shadow and resolve.ts): * 1. every types claim goes — the top-level "types"/"typings" fields diff --git a/packages/compiler/src/frontend/resolve.ts b/packages/compiler/src/frontend/resolve.ts index 463400475..f061ca065 100644 --- a/packages/compiler/src/frontend/resolve.ts +++ b/packages/compiler/src/frontend/resolve.ts @@ -18,6 +18,7 @@ import { dirname, isAbsolute, join, resolve } from "node:path"; import { isNpmStaticPackage, + npmStaticActive, npmStaticPackageOfPath, npmStaticTransformPkgJson, } from "./npm-static.js"; @@ -868,7 +869,11 @@ export function resolveBareModule( // never consulted — mirroring the shadowed world the tsgo host serves. const runtimeImport = mode === "runtime-js" || mode === "runtime-source"; const npmStatic = - mode === "js-only" || (!runtimeImport && mode !== "types-only" && isNpmStaticPackage(pkgName)); + mode === "js-only" || + (!runtimeImport && + mode !== "types-only" && + (isNpmStaticPackage(pkgName) || + (npmStaticActive() && isNpmStaticPackage(aliasedPackageOf(fromFile, pkgName))))); const conditions = runtimeImport ? RUNTIME_IMPORT_CONDITIONS : npmStatic @@ -970,6 +975,23 @@ export function resolveBareModule( return npmStatic ? passOnce("js") : (passOnce("types") ?? passOnce("js")); } +/** The package an npm alias directory installs (`"x-updated": + * "npm:x@6.3.0"` puts package x under node_modules/x-updated): the name + * the nearest node_modules//package.json declares when it + * differs from the directory name, else null. */ +function aliasedPackageOf(fromFile: string, dirName: string): string | null { + for (let dir = dirname(resolve(fromFile)); ; ) { + const pkgDir = join(dir, "node_modules", dirName); + if (isDirectory(pkgDir)) { + const name = pkgJsonOf(pkgDir)?.name; + return typeof name === "string" && name !== dirName ? name : null; + } + const parent = dirname(dir); + if (parent === dir) return null; + dir = parent; + } +} + export function resolveWorkspaceSourceModule( fromFile: string, specifier: string, diff --git a/tests/corpus/6260-local-class-detached-method.ts b/tests/corpus/6260-local-class-detached-method.ts new file mode 100644 index 000000000..528539c48 --- /dev/null +++ b/tests/corpus/6260-local-class-detached-method.ts @@ -0,0 +1,16 @@ +// A method that never reads `this`, taken off an instance of a class +// declared inside a function: the detached method runs without a receiver. +function helper(): string { + return "helper"; +} +function make() { + return class Local { + tag = "t"; + plain(): string { + return helper(); + } + }; +} +const C = make(); +const detached = new C().plain; +console.log(detached()); diff --git a/tests/corpus/6262-comma-null-union-return.cjs b/tests/corpus/6262-comma-null-union-return.cjs new file mode 100644 index 000000000..52cef027e --- /dev/null +++ b/tests/corpus/6262-comma-null-union-return.cjs @@ -0,0 +1,15 @@ +// `return log(), null` from a function that otherwise returns a list: the +// comma expression's effects run, then null takes the result's +// representation. +"use strict"; +async function promote(list) { + if (list.length === 0) return console.log("no static mounts"), null; + return list.map((x) => x * 2); +} +function first(list) { + if (list.length === 0) return console.log("empty"), undefined; + return list[0]; +} +promote([]).then((v) => console.log("promoted", v)); +promote([1, 2]).then((v) => console.log("promoted", v)); +console.log(first([]), first([7])); diff --git a/tests/corpus/6263-strict-arguments-parameters.cjs b/tests/corpus/6263-strict-arguments-parameters.cjs new file mode 100644 index 000000000..463cb316a --- /dev/null +++ b/tests/corpus/6263-strict-arguments-parameters.cjs @@ -0,0 +1,15 @@ +// A CommonJS file under "use strict": a function with named parameters +// reads `arguments`, which in strict code never aliases those parameters. +"use strict"; +function wrap(fn, label) { + label = label + "!"; + return [label, arguments.length, arguments[1], typeof arguments[0]].join(" "); +} +function sum(a, b) { + a = 100; + let total = 0; + for (let i = 0; i < arguments.length; i++) total += arguments[i]; + return total; +} +console.log(wrap(() => 1, "tag")); +console.log(sum(1, 2, 3)); diff --git a/tests/fixtures/npm/cases/2566-npm-alias-two-releases/main.ts b/tests/fixtures/npm/cases/2566-npm-alias-two-releases/main.ts new file mode 100644 index 000000000..6ccf96cdb --- /dev/null +++ b/tests/fixtures/npm/cases/2566-npm-alias-two-releases/main.ts @@ -0,0 +1,7 @@ +// gtroute requires two releases of one package: gtpattern 1.0.0 under its +// own name and 2.0.0 through an npm alias directory (gtpattern-next), +// whose manifest still names gtpattern. Both compile from their own +// JavaScript, as Node loads them. +import { compile } from "gtroute"; + +console.log(compile("/users/:id")); diff --git a/tests/fixtures/npm/cases/2566-npm-alias-two-releases/node_modules/gtroute b/tests/fixtures/npm/cases/2566-npm-alias-two-releases/node_modules/gtroute new file mode 120000 index 000000000..e0ddd1565 --- /dev/null +++ b/tests/fixtures/npm/cases/2566-npm-alias-two-releases/node_modules/gtroute @@ -0,0 +1 @@ +../../../store/gtroute@1.0.0/node_modules/gtroute \ No newline at end of file diff --git a/tests/fixtures/npm/store/gtpattern@1.0.0/node_modules/gtpattern/lib/index.d.ts b/tests/fixtures/npm/store/gtpattern@1.0.0/node_modules/gtpattern/lib/index.d.ts new file mode 100644 index 000000000..1ba7851fa --- /dev/null +++ b/tests/fixtures/npm/store/gtpattern@1.0.0/node_modules/gtpattern/lib/index.d.ts @@ -0,0 +1,2 @@ +export declare const version: string; +export declare function compile(path: string): string; diff --git a/tests/fixtures/npm/store/gtpattern@1.0.0/node_modules/gtpattern/lib/index.js b/tests/fixtures/npm/store/gtpattern@1.0.0/node_modules/gtpattern/lib/index.js new file mode 100644 index 000000000..c76f7222f --- /dev/null +++ b/tests/fixtures/npm/store/gtpattern@1.0.0/node_modules/gtpattern/lib/index.js @@ -0,0 +1,5 @@ +"use strict"; +exports.version = "v1"; +exports.compile = function compile(path) { + return path.split("/").filter((part) => part.length > 0).join("."); +}; diff --git a/tests/fixtures/npm/store/gtpattern@1.0.0/node_modules/gtpattern/package.json b/tests/fixtures/npm/store/gtpattern@1.0.0/node_modules/gtpattern/package.json new file mode 100644 index 000000000..fd67b847e --- /dev/null +++ b/tests/fixtures/npm/store/gtpattern@1.0.0/node_modules/gtpattern/package.json @@ -0,0 +1,6 @@ +{ + "name": "gtpattern", + "version": "1.0.0", + "main": "lib/index.js", + "typings": "lib/index.d.ts" +} diff --git a/tests/fixtures/npm/store/gtpattern@2.0.0/node_modules/gtpattern/lib/index.d.ts b/tests/fixtures/npm/store/gtpattern@2.0.0/node_modules/gtpattern/lib/index.d.ts new file mode 100644 index 000000000..1ba7851fa --- /dev/null +++ b/tests/fixtures/npm/store/gtpattern@2.0.0/node_modules/gtpattern/lib/index.d.ts @@ -0,0 +1,2 @@ +export declare const version: string; +export declare function compile(path: string): string; diff --git a/tests/fixtures/npm/store/gtpattern@2.0.0/node_modules/gtpattern/lib/index.js b/tests/fixtures/npm/store/gtpattern@2.0.0/node_modules/gtpattern/lib/index.js new file mode 100644 index 000000000..a192ea3ab --- /dev/null +++ b/tests/fixtures/npm/store/gtpattern@2.0.0/node_modules/gtpattern/lib/index.js @@ -0,0 +1,5 @@ +"use strict"; +exports.version = "v2"; +exports.compile = function compile(path) { + return path.split("/").filter((part) => part.length > 0).map((part) => (part.startsWith(":") ? "<" + part.slice(1) + ">" : part)).join("/"); +}; diff --git a/tests/fixtures/npm/store/gtpattern@2.0.0/node_modules/gtpattern/package.json b/tests/fixtures/npm/store/gtpattern@2.0.0/node_modules/gtpattern/package.json new file mode 100644 index 000000000..6eb6c6d78 --- /dev/null +++ b/tests/fixtures/npm/store/gtpattern@2.0.0/node_modules/gtpattern/package.json @@ -0,0 +1,6 @@ +{ + "name": "gtpattern", + "version": "2.0.0", + "main": "lib/index.js", + "typings": "lib/index.d.ts" +} diff --git a/tests/fixtures/npm/store/gtroute@1.0.0/node_modules/gtpattern b/tests/fixtures/npm/store/gtroute@1.0.0/node_modules/gtpattern new file mode 120000 index 000000000..25c5c39d5 --- /dev/null +++ b/tests/fixtures/npm/store/gtroute@1.0.0/node_modules/gtpattern @@ -0,0 +1 @@ +../../gtpattern@1.0.0/node_modules/gtpattern \ No newline at end of file diff --git a/tests/fixtures/npm/store/gtroute@1.0.0/node_modules/gtpattern-next b/tests/fixtures/npm/store/gtroute@1.0.0/node_modules/gtpattern-next new file mode 120000 index 000000000..916b8c35f --- /dev/null +++ b/tests/fixtures/npm/store/gtroute@1.0.0/node_modules/gtpattern-next @@ -0,0 +1 @@ +../../gtpattern@2.0.0/node_modules/gtpattern \ No newline at end of file diff --git a/tests/fixtures/npm/store/gtroute@1.0.0/node_modules/gtroute/index.d.ts b/tests/fixtures/npm/store/gtroute@1.0.0/node_modules/gtroute/index.d.ts new file mode 100644 index 000000000..2d203bf4e --- /dev/null +++ b/tests/fixtures/npm/store/gtroute@1.0.0/node_modules/gtroute/index.d.ts @@ -0,0 +1 @@ +export declare function compile(path: string): string; diff --git a/tests/fixtures/npm/store/gtroute@1.0.0/node_modules/gtroute/index.js b/tests/fixtures/npm/store/gtroute@1.0.0/node_modules/gtroute/index.js new file mode 100644 index 000000000..da52d5e12 --- /dev/null +++ b/tests/fixtures/npm/store/gtroute@1.0.0/node_modules/gtroute/index.js @@ -0,0 +1,7 @@ +"use strict"; +const legacy = require("gtpattern"); +const next = require("gtpattern-next"); + +exports.compile = function compile(path) { + return legacy.version + ":" + legacy.compile(path) + " " + next.version + ":" + next.compile(path); +}; diff --git a/tests/fixtures/npm/store/gtroute@1.0.0/node_modules/gtroute/package.json b/tests/fixtures/npm/store/gtroute@1.0.0/node_modules/gtroute/package.json new file mode 100644 index 000000000..10bee06e7 --- /dev/null +++ b/tests/fixtures/npm/store/gtroute@1.0.0/node_modules/gtroute/package.json @@ -0,0 +1,11 @@ +{ + "name": "gtroute", + "version": "1.0.0", + "description": "depends on two releases of gtpattern, the newer one through an npm alias (\"gtpattern-next\": \"npm:gtpattern@2.0.0\"), in a pnpm store layout", + "main": "index.js", + "types": "index.d.ts", + "dependencies": { + "gtpattern": "1.0.0", + "gtpattern-next": "npm:gtpattern@2.0.0" + } +} diff --git a/tests/harness/npm-cases.ts b/tests/harness/npm-cases.ts index 87a6de2a8..69ce3917f 100644 --- a/tests/harness/npm-cases.ts +++ b/tests/harness/npm-cases.ts @@ -19,12 +19,12 @@ export function npmCases(fixturesRoot: string): NpmCase[] { return [ ...globSync(join(fixturesRoot, "npm/cases/*/main.ts")) .sort() - // 2465-2469 and 2556-2565 are the --npm-static bundler-emitted-CJS + // 2465-2469 and 2556-2566 are the --npm-static bundler-emitted-CJS // cases (npm-static.test.ts drives them with the opt-in): their // consumers exercise the static rewrite's surface (lexer-visible // names the shipped .d.ts never declares, the __toESM interop // family), so they stay out of the flagless island lane by design. - .filter((entry) => !/\/(246[5-9]|255[6-9]|256[0-5])-[^/]+\/main\.ts$/.test(entry)) + .filter((entry) => !/\/(246[5-9]|255[6-9]|256[0-6])-[^/]+\/main\.ts$/.test(entry)) .map((entry) => ({ name: entry.split("/").at(-2)!, entry, diff --git a/tests/harness/npm-static.test.ts b/tests/harness/npm-static.test.ts index 943b81695..1741c7470 100644 --- a/tests/harness/npm-static.test.ts +++ b/tests/harness/npm-static.test.ts @@ -76,8 +76,9 @@ async function buildStatic(entry: string, npmStatic: string[] | "auto"): Promise ...globSync( join(fixturesRoot, "npm/node_modules/cryptozoo/**/*.{js,mjs,cjs,json,d.ts}"), ).sort(), - // the bundler-emitted-CJS mini packages (cases 2465-2469, 2556-2565) + // the bundler-emitted-CJS mini packages (cases 2465-2469, 2556-2566) ...globSync(join(fixturesRoot, "npm/node_modules/gt*/**/*.{js,cjs,json}")).sort(), + ...globSync(join(fixturesRoot, "npm/store/gt*/**/*.{js,cjs,json}")).sort(), ]; for (const f of inputs) hash.update(f).update(readFileSync(f)); const key = hash @@ -1489,6 +1490,26 @@ describe(`npm-static pilots${sanitize ? " (sanitized)" : ""}`, () => { await expectNodeParity(join(fixturesRoot, `npm/cases/${name}/main.ts`), packages); }, 180_000); + // 2566: two releases of one package in a pnpm store, the newer through + // an npm alias directory; the default selection compiles both copies. + test("an npm alias of a selected package compiles from its JavaScript", async () => { + const entry = join(fixturesRoot, "npm/cases/2566-npm-alias-two-releases/main.ts"); + const { coverage } = analyze(entry); + expect(coverage.npmStatic).toEqual( + expect.arrayContaining([ + { package: "gtroute", status: "static" }, + { package: "gtpattern", status: "static" }, + ]), + ); + const binary = await buildStatic(entry, "auto"); + const [nodeRes, nativeRes] = await Promise.all([ + runBinary("node", [entry]), + runBinary(binary, []), + ]); + expect(nativeRes.stdout.toString("utf8")).toBe(nodeRes.stdout.toString("utf8")); + expect(nativeRes.exitCode).toBe(nodeRes.exitCode); + }, 180_000); + // 2562: a file with unverifiable interop that no compiled code can load // (the program imports a leaf data module and only a type from the // main entry) leaves its package static. From 02dba55958f66d22f02e621a1d572e7fe09db367 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Mon, 19 Oct 2026 16:55:55 -0700 Subject: [PATCH 025/122] Record order baselines for the new cases and corpus programs; format Co-Authored-By: Claude Opus 5.5 --- .../compiler/src/frontend/lowering/lower-exprs.ts | 14 ++++++++++++-- packages/compiler/src/frontend/resolve.ts | 2 +- .../6260-local-class-detached-method.ts.json | 6 ++++++ .../corpus/6262-comma-null-union-return.cjs.json | 6 ++++++ .../6263-strict-arguments-parameters.cjs.json | 6 ++++++ .../npm/cases/2564-lazy-barrel/main.ts.json | 6 ++++++ .../cases/2565-lazy-barrel-require/main.ts.json | 6 ++++++ .../cases/2566-npm-alias-two-releases/main.ts.json | 6 ++++++ tests/harness/npm-static.test.ts | 10 +++++++--- 9 files changed, 56 insertions(+), 6 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6260-local-class-detached-method.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6262-comma-null-union-return.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6263-strict-arguments-parameters.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2564-lazy-barrel/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2565-lazy-barrel-require/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2566-npm-alias-two-releases/main.ts.json diff --git a/packages/compiler/src/frontend/lowering/lower-exprs.ts b/packages/compiler/src/frontend/lowering/lower-exprs.ts index cabbc31f5..afcecb2c5 100644 --- a/packages/compiler/src/frontend/lowering/lower-exprs.ts +++ b/packages/compiler/src/frontend/lowering/lower-exprs.ts @@ -12595,7 +12595,12 @@ function cjsAccessorSnapshotOf( const call = cjsAccessorCall(lowerer, getter, locOf(getter)); if (call === null) return null; const snapshot = { id: `%g.cjsSnapshot.${lowerer.cjsAccessorSnapshots.size}`, type: call.type }; - lowerer.globalsList.push({ id: snapshot.id, name: "%cjsSnapshot", type: call.type, mutable: true }); + lowerer.globalsList.push({ + id: snapshot.id, + name: "%cjsSnapshot", + type: call.type, + mutable: true, + }); lowerer.cjsAccessorSnapshots.set(getter, snapshot); return snapshot; } @@ -12619,7 +12624,12 @@ export function cjsAccessorSnapshotStores( lowerer.cjsSnapshotReady.set(dep, readyId); } const stores: IrStmt[] = [ - { kind: "assign", localId: readyId, value: { kind: "boolLit", value: true, type: BOOL, loc }, loc }, + { + kind: "assign", + localId: readyId, + value: { kind: "boolLit", value: true, type: BOOL, loc }, + loc, + }, ]; for (const getter of getters) { const snapshot = cjsAccessorSnapshotOf(lowerer, getter); diff --git a/packages/compiler/src/frontend/resolve.ts b/packages/compiler/src/frontend/resolve.ts index f061ca065..36470a643 100644 --- a/packages/compiler/src/frontend/resolve.ts +++ b/packages/compiler/src/frontend/resolve.ts @@ -980,7 +980,7 @@ export function resolveBareModule( * the nearest node_modules//package.json declares when it * differs from the directory name, else null. */ function aliasedPackageOf(fromFile: string, dirName: string): string | null { - for (let dir = dirname(resolve(fromFile)); ; ) { + for (let dir = dirname(resolve(fromFile)); ;) { const pkgDir = join(dir, "node_modules", dirName); if (isDirectory(pkgDir)) { const name = pkgJsonOf(pkgDir)?.name; diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6260-local-class-detached-method.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6260-local-class-detached-method.ts.json new file mode 100644 index 000000000..d73c701d7 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6260-local-class-detached-method.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6260-local-class-detached-method.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6262-comma-null-union-return.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6262-comma-null-union-return.cjs.json new file mode 100644 index 000000000..e167cc977 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6262-comma-null-union-return.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6262-comma-null-union-return.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6263-strict-arguments-parameters.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6263-strict-arguments-parameters.cjs.json new file mode 100644 index 000000000..b7ff6a960 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6263-strict-arguments-parameters.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6263-strict-arguments-parameters.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2564-lazy-barrel/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2564-lazy-barrel/main.ts.json new file mode 100644 index 000000000..b14560cea --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2564-lazy-barrel/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/npm/cases/2564-lazy-barrel/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2565-lazy-barrel-require/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2565-lazy-barrel-require/main.ts.json new file mode 100644 index 000000000..77cf9e52c --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2565-lazy-barrel-require/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/npm/cases/2565-lazy-barrel-require/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2566-npm-alias-two-releases/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2566-npm-alias-two-releases/main.ts.json new file mode 100644 index 000000000..a412bc875 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2566-npm-alias-two-releases/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/npm/cases/2566-npm-alias-two-releases/main.ts" + ], + "diags": [] +} diff --git a/tests/harness/npm-static.test.ts b/tests/harness/npm-static.test.ts index 1741c7470..3543aeb0a 100644 --- a/tests/harness/npm-static.test.ts +++ b/tests/harness/npm-static.test.ts @@ -1486,9 +1486,13 @@ describe(`npm-static pilots${sanitize ? " (sanitized)" : ""}`, () => { test.each([ ["2564-lazy-barrel", ["gtlazy", "gtlazyws"]], ["2565-lazy-barrel-require", ["gtlazyuser", "gtlazy"]], - ])("the lazy barrel %s compiles statically and byte-matches Node", async (name, packages) => { - await expectNodeParity(join(fixturesRoot, `npm/cases/${name}/main.ts`), packages); - }, 180_000); + ])( + "the lazy barrel %s compiles statically and byte-matches Node", + async (name, packages) => { + await expectNodeParity(join(fixturesRoot, `npm/cases/${name}/main.ts`), packages); + }, + 180_000, + ); // 2566: two releases of one package in a pnpm store, the newer through // an npm alias directory; the default selection compiles both copies. From 4f7539103c553f46a0cdfdf8bdafc7e5f611dd6d Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Mon, 19 Oct 2026 19:10:11 -0500 Subject: [PATCH 026/122] Keep a lowered ES module namespace unobservable through calls that pass it as `this` A call through a bundled ES module's namespace (`ns.fn()`, `(ns.fn)()`, a tagged template) passes the namespace as `this`. Node's namespace is the __toCommonJS object (`__esModule` first and non-enumerable, non-configurable getters); the lowered object of getters differs. The namespace lowers only when every such call targets a top-level function, never reassigned, that reads no `this` (an arrow inside it included); otherwise the package stays refused. --- .../frontend/npm-static-bundled-esm.test.ts | 36 +++++++ .../src/frontend/npm-static-bundled-esm.ts | 58 +++++++++++- .../2567-esm-namespace-this/main.ts.json | 6 ++ .../npm/cases/2567-esm-namespace-this/main.ts | 7 ++ .../node_modules/gtesmthis/dist/index.d.ts | 2 + .../npm/node_modules/gtesmthis/dist/index.js | 94 +++++++++++++++++++ .../npm/node_modules/gtesmthis/package.json | 10 ++ tests/harness/npm-cases.ts | 4 +- tests/harness/npm-static.test.ts | 17 +++- 9 files changed, 227 insertions(+), 7 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2567-esm-namespace-this/main.ts.json create mode 100644 tests/fixtures/npm/cases/2567-esm-namespace-this/main.ts create mode 100644 tests/fixtures/npm/node_modules/gtesmthis/dist/index.d.ts create mode 100644 tests/fixtures/npm/node_modules/gtesmthis/dist/index.js create mode 100644 tests/fixtures/npm/node_modules/gtesmthis/package.json diff --git a/packages/compiler/src/frontend/npm-static-bundled-esm.test.ts b/packages/compiler/src/frontend/npm-static-bundled-esm.test.ts index 0b9ecd042..46ebfad4f 100644 --- a/packages/compiler/src/frontend/npm-static-bundled-esm.test.ts +++ b/packages/compiler/src/frontend/npm-static-bundled-esm.test.ts @@ -108,6 +108,42 @@ console.log(load(), (init_lazy(), __toCommonJS(lazy_exports)).value()); expect(lowered).toContain('(require("os")).EOL'); }); +test("calls of exported functions that never read their own `this` still lower", () => { + const lowered = expectSameBehavior(`${helpers}var t_exports = {}; +__export(t_exports, { plain: () => plain, inner: () => inner }); +function plain(x) { return "plain " + x; } +function inner() { + function own() { return typeof this; } + return "inner " + own.call(1); +} +var init_t = __esm({ "t.ts"() {} }); +const t = (init_t(), __toCommonJS(t_exports)); +console.log(t.plain(1), (t.plain)(2), t.inner()); +`); + expect(lowered).not.toContain("__toCommonJS(t_exports)"); +}); + +test.each([ + ["a direct call", `NS.who()`], + ["a parenthesized callee", `(NS.who)()`], + ["a tagged template", "NS.who`x`"], + ["a call through a local", `(() => { const m = NS; return m.who(); })()`], + ["an arrow inside the function", `NS.arrow()()`], +])("%s that passes the namespace as `this` keeps the package refused", (_, use) => { + const result = lower(`${helpers}var w_exports = {}; +__export(w_exports, { who: () => who, arrow: () => arrow }); +function who() { return this.__esModule; } +function arrow() { return () => this; } +var init_w = __esm({ "w.ts"() {} }); +console.log(${use.replaceAll("NS", "(init_w(), __toCommonJS(w_exports))")}); +`); + expect(result).toEqual({ + degrade: expect.stringContaining( + "calls only of exported functions that do not read 'this'", + ) as string, + }); +}); + test("minified initializers sharing a var statement lower the same way", () => { expectSameBehavior( helpers + diff --git a/packages/compiler/src/frontend/npm-static-bundled-esm.ts b/packages/compiler/src/frontend/npm-static-bundled-esm.ts index cf4f6280b..37d2d7b1d 100644 --- a/packages/compiler/src/frontend/npm-static-bundled-esm.ts +++ b/packages/compiler/src/frontend/npm-static-bundled-esm.ts @@ -185,6 +185,44 @@ function plainRead(access: ts.Expression): boolean { return true; } +/** True when a member read is the callee of a call or a tag, which receives + * the object it was read from as `this`. */ +function calledWithReceiver(access: ts.Expression): boolean { + let callee: ts.Node = access; + while (callee.parent !== undefined && ts.isParenthesizedExpression(callee.parent)) + callee = callee.parent; + const p = callee.parent; + return ( + p !== undefined && + ((ts.isCallExpression(p) && p.expression === callee) || + (ts.isTaggedTemplateExpression(p) && p.tag === callee)) + ); +} + +/** True when a function reads its own `this`: in its body, or in an arrow + * function inside it. */ +function readsThis(fn: ts.FunctionDeclaration): boolean { + let found = false; + if (fn.body === undefined) return false; + ts.walkPreorder(fn.body, (n) => { + if (found) return "stop"; + if ( + ts.isFunctionDeclaration(n) || + ts.isFunctionExpression(n) || + ts.isMethodDeclaration(n) || + ts.isGetAccessorDeclaration(n) || + ts.isSetAccessorDeclaration(n) || + ts.isConstructorDeclaration(n) || + ts.isClassDeclaration(n) || + ts.isClassExpression(n) + ) + return "skip"; + if (n.kind === ts.SyntaxKind.ThisKeyword) found = true; + return undefined; + }); + return found; +} + /** The `__export(T, { name: () => value, … })` getter table of T, or null. */ function exportTableOf( call: ts.CallExpression, @@ -457,7 +495,7 @@ export function lowerBundledEsmInits( p.parent === table.stmt ) continue; - const why = `reads the namespace of a bundled ES module ('${r.text}') in a way the static rewrite cannot follow (only member reads of its exports compile)`; + const why = `reads the namespace of a bundled ES module ('${r.text}') in a way the static rewrite cannot follow (only member reads of its exports compile, and calls only of exported functions that do not read 'this')`; if ( p === undefined || !ts.isCallExpression(p) || @@ -480,13 +518,25 @@ export function lowerBundledEsmInits( ns = up; else break; } - const names = new Set(table.entries.map(([n]) => n)); + const values = new Map(table.entries); + // A call through the namespace (`ns.fn()`) passes the object the read + // came from as `this`; the lowered object differs from Node's module + // object (`__esModule`, property attributes), so it must stay + // unobservable: the callee is a top-level function that never reads + // `this`. + const receiverHidden = (name: string): boolean => { + const value = values.get(name); + if (value === undefined || !ts.isIdentifier(value) || !stable(value.text)) return false; + const decl = top.get(value.text)?.node; + return decl !== undefined && ts.isFunctionDeclaration(decl) && !readsThis(decl); + }; const memberRead = (access: ts.Node | undefined, of: ts.Node): boolean => access !== undefined && ts.isPropertyAccessExpression(access) && access.expression === of && - names.has(access.name.text) && - plainRead(access); + values.has(access.name.text) && + plainRead(access) && + (!calledWithReceiver(access) || receiverHidden(access.name.text)); const holder = ns.parent; let readable = memberRead(holder, ns); if ( diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2567-esm-namespace-this/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2567-esm-namespace-this/main.ts.json new file mode 100644 index 000000000..0739a6f0c --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2567-esm-namespace-this/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/npm/cases/2567-esm-namespace-this/main.ts" + ], + "diags": [] +} diff --git a/tests/fixtures/npm/cases/2567-esm-namespace-this/main.ts b/tests/fixtures/npm/cases/2567-esm-namespace-this/main.ts new file mode 100644 index 000000000..d6f084bc0 --- /dev/null +++ b/tests/fixtures/npm/cases/2567-esm-namespace-this/main.ts @@ -0,0 +1,7 @@ +// A lazily initialized ES module export that reads `this`, called through +// the module's namespace (`lazy.who()`): Node passes the namespace object, +// whose `__esModule` is true. A lowered namespace object would differ, so +// the package stays refused. +import { load } from "gtesmthis"; + +console.log(load()); diff --git a/tests/fixtures/npm/node_modules/gtesmthis/dist/index.d.ts b/tests/fixtures/npm/node_modules/gtesmthis/dist/index.d.ts new file mode 100644 index 000000000..af4369397 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtesmthis/dist/index.d.ts @@ -0,0 +1,2 @@ +export declare function describe(): string; +export declare function load(): string; diff --git a/tests/fixtures/npm/node_modules/gtesmthis/dist/index.js b/tests/fixtures/npm/node_modules/gtesmthis/dist/index.js new file mode 100644 index 000000000..46b81aa7d --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtesmthis/dist/index.js @@ -0,0 +1,94 @@ +var __create = Object.create; +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __getProtoOf = Object.getPrototypeOf; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __esm = (fn, res) => function __init() { + return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res; +}; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( + // If the importer is in node compatibility mode or this is not an ESM + // file that has been converted to a CommonJS file using a Babel- + // compatible transform (i.e. "__esModule" has not been set), then set + // "default" to the CommonJS "module.exports" for node compatibility. + isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, + mod +)); +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); + +// src/helper.ts +function helper(x) { + COUNT++; + return "[" + x + "]"; +} +var COUNT; +var init_helper = __esm({ + "src/helper.ts"() { + console.log("helper init"); + COUNT = 0; + } +}); + +// src/lazy.ts +var lazy_exports = {}; +__export(lazy_exports, { + VALUE: () => VALUE, + value: () => value, + who: () => who +}); +function who() { + return [typeof this, this.__esModule, Object.getOwnPropertyNames(this).join(","), Object.keys(this).join(",")].join(" "); +} +function value() { + return VALUE + ":" + import_gtcore.default.tag + ":" + COUNT; +} +var path, import_gtcore, VALUE; +var init_lazy = __esm({ + "src/lazy.ts"() { + path = __toESM(require("path")); + import_gtcore = __toESM(require("gtcore")); + init_helper(); + console.log("lazy init"); + VALUE = helper(path.basename("/a/b.txt")); + } +}); + +// src/index.ts +var index_exports = {}; +__export(index_exports, { + describe: () => describe, + load: () => load +}); +module.exports = __toCommonJS(index_exports); + +// src/greet.ts +function greet(name) { + return "hello " + name; +} + +// src/index.ts +function describe() { + return greet("main"); +} +function load() { + const lazy = (init_lazy(), __toCommonJS(lazy_exports)); + return lazy.value() + " " + lazy.VALUE + " | " + lazy.who() + " | " + (init_lazy(), __toCommonJS(lazy_exports)).who(); +} +// Annotate the CommonJS export names for ESM import in node: +0 && (module.exports = { + describe, + load +}); diff --git a/tests/fixtures/npm/node_modules/gtesmthis/package.json b/tests/fixtures/npm/node_modules/gtesmthis/package.json new file mode 100644 index 000000000..c6344fa8c --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtesmthis/package.json @@ -0,0 +1,10 @@ +{ + "name": "gtesmthis", + "version": "1.0.0", + "description": "gtesm's esbuild 0.25.10 output shape with a lazily initialized ES module export that reads `this`, called through the module's namespace", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "dependencies": { + "gtcore": "1.0.0" + } +} diff --git a/tests/harness/npm-cases.ts b/tests/harness/npm-cases.ts index f8d346154..35a9df4a6 100644 --- a/tests/harness/npm-cases.ts +++ b/tests/harness/npm-cases.ts @@ -19,12 +19,12 @@ export function npmCases(fixturesRoot: string): NpmCase[] { return [ ...globSync(join(fixturesRoot, "npm/cases/*/main.ts")) .sort() - // 2465-2469 and 2556-2563 are the --npm-static bundler-emitted-CJS + // 2465-2469, 2556-2563 and 2567 are the --npm-static bundler-emitted-CJS // cases (npm-static.test.ts drives them with the opt-in): their // consumers exercise the static rewrite's surface (lexer-visible // names the shipped .d.ts never declares, the __toESM interop // family), so they stay out of the flagless island lane by design. - .filter((entry) => !/\/(246[5-9]|255[6-9]|256[0-3])-[^/]+\/main\.ts$/.test(entry)) + .filter((entry) => !/\/(246[5-9]|255[6-9]|256[0-37])-[^/]+\/main\.ts$/.test(entry)) .map((entry) => ({ name: entry.split("/").at(-2)!, entry, diff --git a/tests/harness/npm-static.test.ts b/tests/harness/npm-static.test.ts index 8560973cd..443c0c72d 100644 --- a/tests/harness/npm-static.test.ts +++ b/tests/harness/npm-static.test.ts @@ -76,7 +76,7 @@ async function buildStatic(entry: string, npmStatic: string[] | "auto"): Promise ...globSync( join(fixturesRoot, "npm/node_modules/cryptozoo/**/*.{js,mjs,cjs,json,d.ts}"), ).sort(), - // the bundler-emitted-CJS mini packages (cases 2465-2469, 2556-2563) + // the bundler-emitted-CJS mini packages (cases 2465-2469, 2556-2563, 2567) ...globSync(join(fixturesRoot, "npm/node_modules/gt*/**/*.{js,cjs,json}")).sort(), ]; for (const f of inputs) hash.update(f).update(readFileSync(f)); @@ -1478,6 +1478,21 @@ describe(`npm-static pilots${sanitize ? " (sanitized)" : ""}`, () => { ]); }, 180_000); + // 2567: an export that reads `this`, called through its namespace, sees + // Node's module object; the lowered namespace would differ, so the + // package stays refused. + test("a namespace call to an export that reads `this` stays refused", () => { + const entry = join(fixturesRoot, "npm/cases/2567-esm-namespace-this/main.ts"); + const { coverage } = analyze(entry, { npmStatic: ["gtesmthis", "gtcore"] }); + expect(coverage.npmStatic).toContainEqual({ + package: "gtesmthis", + status: "refused", + detail: expect.stringMatching( + /calls only of exported functions that do not read 'this'/, + ) as string, + }); + }, 120_000); + // 2562: a file with unverifiable interop that no compiled code can load // (the program imports a leaf data module and only a type from the // main entry) leaves its package static. From 44bd0a7b1427616f705425e423e529256bebc1db Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Mon, 19 Oct 2026 19:24:17 -0500 Subject: [PATCH 027/122] Keep forwarded-only tuple bindings per function The lowerer kept the tuple bindings that are only forwarded in one map for the whole program, keyed by local id. Local ids are unique within one function only: two wrappers that both bind `arguments` (or a `const args = arguments as any` alias) declare the same id, so a wrapper whose tuple exists found the other wrapper's entry, and reading an element of its `arguments` failed with "forwarded-only 'arguments' read as a value". Each function's context now keeps its own map. The corpus program wraps functions with a forwarding-only wrapper and with one that also reads an element of `arguments`, directly and through an alias, in both orders. --- .../src/frontend/lowering/function-context.ts | 7 +++ .../src/frontend/lowering/lower-exprs.ts | 2 +- .../compiler/src/frontend/lowering/lowerer.ts | 8 +-- .../src/frontend/lowering/typed-arguments.ts | 6 +- ...ariadic-wrapper-shared-local-names.ts.json | 6 ++ ...385-variadic-wrapper-shared-local-names.ts | 56 +++++++++++++++++++ ...ariadic-wrapper-shared-local-names.ts.json | 1 + 7 files changed, 76 insertions(+), 10 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6385-variadic-wrapper-shared-local-names.ts.json create mode 100644 tests/corpus/6385-variadic-wrapper-shared-local-names.ts create mode 100644 tests/harness/corpus-costs/6385-variadic-wrapper-shared-local-names.ts.json diff --git a/packages/compiler/src/frontend/lowering/function-context.ts b/packages/compiler/src/frontend/lowering/function-context.ts index e31e194c5..55d8ed0b0 100644 --- a/packages/compiler/src/frontend/lowering/function-context.ts +++ b/packages/compiler/src/frontend/lowering/function-context.ts @@ -48,6 +48,12 @@ export interface FnCtx { argumentsLocal?: IrLocal | null; /** Arrows inherit the nearest ordinary function's arguments binding. */ inheritsArguments?: boolean; + /** Tuple bindings of this function that are only forwarded + * (typed-arguments.ts onlyForwarded): binding local id → the native + * slots forwarding reads. Such a binding is never materialized. Local ids + * are unique within one function only, and nested functions never read + * these bindings (a read there needs the tuple). */ + pureTuples: Map; /** Declared return type — lets `return` detect record-shape mismatches * (SC2002) before the validator would ICE on them. */ returnType: IrType; @@ -81,6 +87,7 @@ export function newFnCtx( captures: lifted ? [] : null, captureSources: [], captureBySymbol: new Map(), + pureTuples: new Map(), selfSymbol, selfType, returnType, diff --git a/packages/compiler/src/frontend/lowering/lower-exprs.ts b/packages/compiler/src/frontend/lowering/lower-exprs.ts index bc970957d..80b5bb333 100644 --- a/packages/compiler/src/frontend/lowering/lower-exprs.ts +++ b/packages/compiler/src/frontend/lowering/lower-exprs.ts @@ -908,7 +908,7 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr { // type and lowerLambda declared the local. if (expr.text === "arguments") { const binding = captureContextArguments(lowerer.fnStack); - if (binding && lowerer.pureTuples.has(binding.id)) { + if (binding && lowerer.ctx.pureTuples.has(binding.id)) { throw new InternalCompilerError("lowerer bug: forwarded-only 'arguments' read as a value"); } if (binding) return { kind: "varRef", localId: binding.id, type: binding.type, loc }; diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index ccc140e9e..26405865c 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -1917,10 +1917,6 @@ export class Lowerer { /** Const bindings holding a forwarded fixed tuple (`const args = * arguments as any` in a typed-arguments function) → the tuple type. */ readonly tupleAliasSymbols = new Map(); - /** Tuple bindings that are only forwarded (typed-arguments.ts - * onlyForwarded): binding local id → the native slots forwarding reads. - * Such a binding is never materialized. */ - readonly pureTuples = new Map(); /** The static names of the current generic instance's name-keyed * function parameters (static-function-names.ts). */ staticFnNames: Map | null = null; @@ -10725,7 +10721,7 @@ export class Lowerer { (id) => id !== nameNode && this.checker.getSymbolAtLocation(id) === symbol, ) ) { - this.pureTuples.set(body.id, slots); + this.ctx.pureTuples.set(body.id, slots); return; } const init = this.tupleOfSlots(slots, tupleType, nameNode, loc); @@ -11122,7 +11118,7 @@ export class Lowerer { } if (!symbol) return null; const direct = this.resolveKey(symbol, ident); - if (direct && this.pureTuples.has(direct.id)) { + if (direct && this.ctx.pureTuples.has(direct.id)) { // onlyForwarded proved every read forwards; a plain read here means // the analysis and the lowering disagree. throw new InternalCompilerError( diff --git a/packages/compiler/src/frontend/lowering/typed-arguments.ts b/packages/compiler/src/frontend/lowering/typed-arguments.ts index 90fef0e86..82b7f23c3 100644 --- a/packages/compiler/src/frontend/lowering/typed-arguments.ts +++ b/packages/compiler/src/frontend/lowering/typed-arguments.ts @@ -176,7 +176,7 @@ export function declareTypedArguments( if ( onlyForwarded(lowerer, node, (id) => id.text === "arguments" && argumentsOwner(id) === node) ) { - lowerer.pureTuples.set(local.id, slots); + lowerer.ctx.pureTuples.set(local.id, slots); return; } prologue.push({ @@ -273,7 +273,7 @@ function pureSlotsOf(lowerer: Lowerer, node: ts.Expression): IrLocal[] | null { if (!ts.isIdentifier(operand)) return null; const local = operand.text === "arguments" ? lowerer.ctx.argumentsLocal : lowerer.peekLocal(operand); - return local ? (lowerer.pureTuples.get(local.id) ?? null) : null; + return local ? (lowerer.ctx.pureTuples.get(local.id) ?? null) : null; } /** The positional values `node` forwards when it is a pure-forwarded tuple @@ -466,7 +466,7 @@ export function lowerTupleAliasDecl(lowerer: Lowerer, decl: ts.VariableDeclarati // An alias of a pure-forwarded tuple is itself only forwarded // (onlyForwarded followed it): it names the same slots. const local = lowerer.declareLocal(decl.name, decl.name.text, tupleType, false); - lowerer.pureTuples.set(local.id, slots); + lowerer.ctx.pureTuples.set(local.id, slots); return { kind: "block", body: [], loc: locOf(decl) }; } const init = lowerer.lowerExpr(stripTypeWrappers(decl.initializer)); diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6385-variadic-wrapper-shared-local-names.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6385-variadic-wrapper-shared-local-names.ts.json new file mode 100644 index 000000000..5791565f2 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6385-variadic-wrapper-shared-local-names.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6385-variadic-wrapper-shared-local-names.ts" + ], + "diags": [] +} diff --git a/tests/corpus/6385-variadic-wrapper-shared-local-names.ts b/tests/corpus/6385-variadic-wrapper-shared-local-names.ts new file mode 100644 index 000000000..2d4272b55 --- /dev/null +++ b/tests/corpus/6385-variadic-wrapper-shared-local-names.ts @@ -0,0 +1,56 @@ +// Variadic wrappers whose bodies declare the same local names: one only +// forwards its `arguments` (or its alias of them), the other also reads an +// element, so its tuple exists. Each wrapper forwards its own arguments. +type AnyFn = (...args: any) => any; + +function forward(fn: F): F { + return function (this: unknown) { + return fn.apply(this, arguments as any); + } as F; +} + +function logFirst(fn: F): F { + return function (this: unknown) { + console.log("first:", arguments[0], "of", arguments.length); + return fn.apply(this, arguments as any); + } as F; +} + +function forwardAlias(fn: F): F { + return function (this: unknown) { + const args = arguments as any; + return fn.apply(this, args); + } as F; +} + +function logFirstAlias(fn: F): F { + return function (this: unknown) { + const args = arguments as any; + console.log("alias first:", args[0]); + return fn.apply(this, args); + } as F; +} + +function add3(a: number, b: number, c: number): number { + return a + b + c; +} + +function greet(name: string): string { + return "hi " + name; +} + +const sum = forward(add3); +console.log(sum(1, 2, 3)); +const hello = logFirst(greet); +console.log(hello("ada")); + +const sumAlias = forwardAlias(add3); +console.log(sumAlias(4, 5, 6)); +const helloAlias = logFirstAlias(greet); +console.log(helloAlias("grace")); + +// The other order: the wrapper that reads an element first. +const hello2 = logFirst(greet); +console.log(hello2("linus")); +const sum2 = forward(add3); +console.log(sum2(7, 8, 9)); diff --git a/tests/harness/corpus-costs/6385-variadic-wrapper-shared-local-names.ts.json b/tests/harness/corpus-costs/6385-variadic-wrapper-shared-local-names.ts.json new file mode 100644 index 000000000..187f1764f --- /dev/null +++ b/tests/harness/corpus-costs/6385-variadic-wrapper-shared-local-names.ts.json @@ -0,0 +1 @@ +{ "plain": 2200, "san": 3300 } From 1c3a405277793908948e99fe1c70720ac18ab1ec Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Mon, 19 Oct 2026 17:31:49 -0700 Subject: [PATCH 028/122] Expect the lazy-loader refusal for a namespace kept by a loader Co-Authored-By: Claude Opus 5.5 --- packages/compiler/src/frontend/npm-static-bundled-esm.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/compiler/src/frontend/npm-static-bundled-esm.test.ts b/packages/compiler/src/frontend/npm-static-bundled-esm.test.ts index 0b9ecd042..0a144036c 100644 --- a/packages/compiler/src/frontend/npm-static-bundled-esm.test.ts +++ b/packages/compiler/src/frontend/npm-static-bundled-esm.test.ts @@ -127,7 +127,7 @@ test.each([ `var init_a = __esm({ "a.ts"() { console.log(this); } });\ninit_a();`, ], [ - "stores the namespace of a bundled ES module ('a_exports') in a variable", + "memoized lazy loader 'load' returns the namespace of a bundled ES module as a value", `var a_exports = {};\n__export(a_exports, { v: () => v });\nvar v, init_a = __esm({ "a.ts"() { v = 1; } });\nvar ns, load = () => ns ??= __toESM((init_a(), __toCommonJS(a_exports)));\nload();`, ], [ From 5670bc182e53e2c0544c1038b12b0e5db01d5d9d Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Mon, 19 Oct 2026 18:21:52 -0700 Subject: [PATCH 029/122] Respell members of a function export object; name conditional exports A CommonJS module whose export object is one of its own top-level functions (semver 5/6's `exports = module.exports = SemVer`) reaches its members through that function after the statement: member reads and writes through module.exports (and, after the rebinding, every `exports` reference) become members of the function, the expando form both resolvers model, and the rebinding itself drops. Constructor functions exported whole with members attached afterwards no longer lose those members at runtime. Replacing module.exports or rebinding exports inside a branch (an environment-dependent export object, readable-stream 2.x) now names the construct instead of the generic assignment fence, and a required builtin module object is truthy in the LLVM backend. Co-Authored-By: Claude Opus 5.5 --- packages/compiler/src/backend/llvm/emitter.ts | 3 + .../src/frontend/lowering/lower-stmts.ts | 20 ++ .../frontend/npm-static-cjs-exports-alias.ts | 179 ++++++++++++++++++ packages/compiler/src/frontend/npm-static.ts | 6 +- packages/compiler/src/frontend/services.ts | 8 + .../6264-required-module-truthiness.cjs | 10 + .../cases/2567-cjs-function-exports/main.ts | 12 ++ .../2568-cjs-conditional-exports/main.ts | 7 + .../npm/node_modules/gtcondexports/index.d.ts | 2 + .../npm/node_modules/gtcondexports/index.js | 14 ++ .../node_modules/gtcondexports/package.json | 7 + .../npm/node_modules/gtfnexports/index.d.ts | 13 ++ .../npm/node_modules/gtfnexports/index.js | 25 +++ .../npm/node_modules/gtfnexports/package.json | 7 + .../npm/node_modules/gtfnmembers/index.d.ts | 11 ++ .../npm/node_modules/gtfnmembers/index.js | 13 ++ .../npm/node_modules/gtfnmembers/package.json | 7 + tests/harness/npm-cases.ts | 4 +- tests/harness/npm-static.test.ts | 28 ++- 19 files changed, 372 insertions(+), 4 deletions(-) create mode 100644 packages/compiler/src/frontend/npm-static-cjs-exports-alias.ts create mode 100644 tests/corpus/6264-required-module-truthiness.cjs create mode 100644 tests/fixtures/npm/cases/2567-cjs-function-exports/main.ts create mode 100644 tests/fixtures/npm/cases/2568-cjs-conditional-exports/main.ts create mode 100644 tests/fixtures/npm/node_modules/gtcondexports/index.d.ts create mode 100644 tests/fixtures/npm/node_modules/gtcondexports/index.js create mode 100644 tests/fixtures/npm/node_modules/gtcondexports/package.json create mode 100644 tests/fixtures/npm/node_modules/gtfnexports/index.d.ts create mode 100644 tests/fixtures/npm/node_modules/gtfnexports/index.js create mode 100644 tests/fixtures/npm/node_modules/gtfnexports/package.json create mode 100644 tests/fixtures/npm/node_modules/gtfnmembers/index.d.ts create mode 100644 tests/fixtures/npm/node_modules/gtfnmembers/index.js create mode 100644 tests/fixtures/npm/node_modules/gtfnmembers/package.json diff --git a/packages/compiler/src/backend/llvm/emitter.ts b/packages/compiler/src/backend/llvm/emitter.ts index 9e2d3524e..f20c63b1d 100644 --- a/packages/compiler/src/backend/llvm/emitter.ts +++ b/packages/compiler/src/backend/llvm/emitter.ts @@ -5882,6 +5882,9 @@ export class LlEmitter { // for stdin. They remain JavaScript objects in boolean contexts. case "procStream": return "true"; + // A module namespace (a required builtin module object) is an object. + case "moduleNs": + return "true"; case "array": case "record": case "object": diff --git a/packages/compiler/src/frontend/lowering/lower-stmts.ts b/packages/compiler/src/frontend/lowering/lower-stmts.ts index 36abb9130..4a6fb15a3 100644 --- a/packages/compiler/src/frontend/lowering/lower-stmts.ts +++ b/packages/compiler/src/frontend/lowering/lower-stmts.ts @@ -30,6 +30,7 @@ import { import { cjsExportMemberWriteGlobalOf, isCjsExportsIdentifier, + isCjsModuleExports, isFreshExportsReset, } from "./cjs-exports-object.js"; import { isDeadTscHelperDecl, tscInteropBindingOfDecl } from "./cjs-tsc-interop.js"; @@ -6627,6 +6628,25 @@ export function lowerExprStatement(lowerer: Lowerer, expr: ts.Expression): IrStm "require() outside the module's top level (move it to the top of the file)", ); } + // Replacing the export object (or rebinding `exports`) anywhere but a + // top-level statement makes the module's exports depend on which path + // ran (`cond ? module.exports = a : module.exports = b`, an + // environment-dependent branch); the static export table has one shape. + if ( + !topLevelJs && + isJsSourceFile(expr.getSourceFile()) && + ts.isBinaryExpression(expr) && + expr.operatorToken.kind === ts.SyntaxKind.EqualsToken && + (isCjsModuleExports(lowerer, expr.left) || isCjsExportsIdentifier(lowerer, expr.left)) + ) { + lowerer.unsupported( + "SC1090", + expr.left, + isCjsModuleExports(lowerer, expr.left) + ? "replacing module.exports outside a top-level statement (a conditional or environment-dependent export object)" + : "rebinding 'exports' outside a top-level statement (a conditional or environment-dependent export object)", + ); + } if (topLevelJs && ts.isExpressionStatement(stmtNode)) { if (isFreshExportsReset(lowerer, stmtNode)) return { kind: "block", body: [], loc: locOf(expr) }; diff --git a/packages/compiler/src/frontend/npm-static-cjs-exports-alias.ts b/packages/compiler/src/frontend/npm-static-cjs-exports-alias.ts new file mode 100644 index 000000000..c88c9949e --- /dev/null +++ b/packages/compiler/src/frontend/npm-static-cjs-exports-alias.ts @@ -0,0 +1,179 @@ +/* CommonJS modules whose export object is one of their own functions: + * + * exports = module.exports = SemVer; // or module.exports = SemVer; + * exports.parse = parse; // or module.exports.parse = … + * function SemVer(version) { … } + * + * After that statement `module.exports` (and, after the rebinding form, + * `exports`) IS the function, so every member read or write through them + * is a member of the function itself. The pass respells those accesses + * through the function's name, the expando form both resolvers model + * (members on a function declaration), and drops the `exports =` + * rebinding the static export protocol has no storage for. + * + * Exact only while nothing can observe the module before the statement + * runs and nothing replaces the export object again: the statement is a + * top-level statement preceded only by inert declarations, it is the + * file's only write of `module.exports` and `exports`, the function is a + * top-level declaration nothing reassigns, and `exports`/`module` are the + * CommonJS globals (never declared in the file). Other shapes answer + * null and keep their existing treatment. */ +import * as ts from "./ts7/syntax.js"; +import { + applyEdits, + declaredNames, + isWritten, + references, + unparen, + type Edit, +} from "./npm-static-bundled-chunks.js"; + +function isModuleExports(e: ts.Expression): e is ts.PropertyAccessExpression { + const x = unparen(e); + return ( + ts.isPropertyAccessExpression(x) && + !x.questionDotToken && + ts.isIdentifier(x.expression) && + x.expression.text === "module" && + x.name.text === "exports" + ); +} + +/** True for a statement whose evaluation runs no code that could observe + * the export object: declarations and member writes (`F.prototype.m = + * function …`) without calls, and directives. */ +function inert(stmt: ts.Statement): boolean { + if (ts.isFunctionDeclaration(stmt) || ts.isEmptyStatement(stmt)) return true; + if (ts.isExpressionStatement(stmt) && ts.isStringLiteral(stmt.expression)) return true; + if (ts.isExpressionStatement(stmt)) { + const e = unparen(stmt.expression); + if ( + !ts.isBinaryExpression(e) || + e.operatorToken.kind !== ts.SyntaxKind.EqualsToken || + !(ts.isPropertyAccessExpression(e.left) || ts.isElementAccessExpression(e.left)) + ) + return false; + let root: ts.Expression = e.left; + while (ts.isPropertyAccessExpression(root) || ts.isElementAccessExpression(root)) + root = root.expression; + if (!ts.isIdentifier(root) || root.text === "module" || root.text === "exports") return false; + } else if (!ts.isVariableStatement(stmt)) return false; + let calls = false; + ts.walkPreorder(stmt, (n) => { + if (calls) return "stop"; + if (ts.isArrowFunction(n) || ts.isFunctionExpression(n)) return "skip"; + if (ts.isCallExpression(n) || ts.isNewExpression(n) || ts.isTaggedTemplateExpression(n)) + calls = true; + return undefined; + }); + return !calls; +} + +export function normalizeCjsFunctionExports(sf: ts.SourceFile): string | null { + const source = sf.text; + if (!source.includes("module.exports")) return null; + if (!sf.fileName.endsWith(".cjs") && ts.isExternalModule(sf)) return null; + const declared = declaredNames(sf); + if (declared.has("exports") || declared.has("module")) return null; + + // The whole-export statement: `[exports =] module.exports = W;`. + let whole: { stmt: ts.ExpressionStatement; fn: string; rebinds: boolean } | null = null; + const stmts = sf.statements.nodes; + for (let i = 0; i < stmts.length; i++) { + const stmt = stmts[i]!; + if (!ts.isExpressionStatement(stmt)) continue; + const top = unparen(stmt.expression); + if (!ts.isBinaryExpression(top) || top.operatorToken.kind !== ts.SyntaxKind.EqualsToken) + continue; + let e: ts.BinaryExpression = top; + let rebinds = false; + if (ts.isIdentifier(top.left) && top.left.text === "exports") { + const inner = unparen(top.right); + if (!ts.isBinaryExpression(inner) || inner.operatorToken.kind !== ts.SyntaxKind.EqualsToken) + continue; + rebinds = true; + e = inner; + } + if (!isModuleExports(e.left)) continue; + const value = unparen(e.right); + if (!ts.isIdentifier(value)) return null; + if (whole !== null || !stmts.slice(0, i).every(inert)) return null; + whole = { stmt, fn: value.text, rebinds }; + } + if (whole === null) return null; + const fnName = whole.fn; + const fnDecls = stmts.filter( + (st) => ts.isFunctionDeclaration(st) && st.name?.text === fnName && st.body !== undefined, + ); + if (fnDecls.length !== 1) return null; + + const edits: Edit[] = []; + let unsafe = false; + const refs = references(sf); + for (const r of refs) { + if (r.text === fnName && isWritten(r)) unsafe = true; + if (r.text === "exports") { + if (isWritten(r)) { + // Only the whole statement's own rebinding. + const assign = r.parent; + if ( + !whole.rebinds || + assign === undefined || + !ts.isBinaryExpression(assign) || + assign.parent !== whole.stmt || + assign.left !== r + ) + unsafe = true; + continue; + } + // Without the rebinding, `exports` keeps the replaced object. + if (!whole.rebinds || r.getStart(sf) < whole.stmt.getEnd()) { + unsafe = true; + continue; + } + edits.push({ start: r.getStart(sf), end: r.getEnd(), text: fnName }); + continue; + } + if (r.text === "module") { + const access = r.parent; + if (access === undefined || !isModuleExports(access as ts.Expression)) continue; + const outer = access.parent; + if (outer !== undefined && ts.isBinaryExpression(outer) && outer.left === access) { + if ( + outer.operatorToken.kind >= ts.SyntaxKind.FirstAssignment && + outer.operatorToken.kind <= ts.SyntaxKind.LastAssignment && + !( + (outer.parent === whole.stmt && !whole.rebinds) || + (whole.rebinds && outer.parent?.parent === whole.stmt) + ) + ) + unsafe = true; + continue; + } + // A member access through module.exports: a member of the function. + if ( + outer !== undefined && + (ts.isPropertyAccessExpression(outer) || ts.isElementAccessExpression(outer)) && + outer.expression === access + ) { + if (access.getStart(sf) < whole.stmt.getEnd() && !insideFunction(access)) unsafe = true; + edits.push({ start: access.getStart(sf), end: access.getEnd(), text: fnName }); + } + } + } + if (unsafe) return null; + if (whole.rebinds) { + // `exports = module.exports = W` → `module.exports = W`. + const e = unparen(whole.stmt.expression) as ts.BinaryExpression; + edits.push({ start: e.getStart(sf), end: unparen(e.right).getStart(sf), text: "" }); + } + if (edits.length === 0) return null; + return applyEdits(source, 0, source.length, edits); +} + +function insideFunction(node: ts.Node): boolean { + for (let p = node.parent; p !== undefined && !ts.isSourceFile(p); p = p.parent) { + if (ts.isFunctionLike(p)) return true; + } + return false; +} diff --git a/packages/compiler/src/frontend/npm-static.ts b/packages/compiler/src/frontend/npm-static.ts index e00122238..182c5b62b 100644 --- a/packages/compiler/src/frontend/npm-static.ts +++ b/packages/compiler/src/frontend/npm-static.ts @@ -448,7 +448,11 @@ export function npmStaticFsShadow(services: FrontendServices): NpmStaticFsShadow } else if (esm !== null) { original = esm; } - const respelled = split !== null || esm !== null; + // A module exporting one of its functions reaches its members + // through that function. + const fnExports = original === null ? null : services.cjsFunctionExports(original, path); + if (fnExports !== null) original = fnExports; + const respelled = split !== null || esm !== null || fnExports !== null; if (original !== null) { const namepaths = services.jsDocNamepaths(path, original); const source = namepaths ?? original; diff --git a/packages/compiler/src/frontend/services.ts b/packages/compiler/src/frontend/services.ts index c5dcbf513..2e3d686ab 100644 --- a/packages/compiler/src/frontend/services.ts +++ b/packages/compiler/src/frontend/services.ts @@ -2,6 +2,7 @@ import { Ts7SourceParser, type Ts7SourceKind } from "./ts7/source-parser.js"; import { Ts7Host, type Ts7ApiFactory, type Ts7HostOptions } from "./ts7/program-host.js"; import { lowerEsmInitsWithParser, rewriteBundlerCjsWithParser } from "./npm-static-rewrite-host.js"; import { isBundlerCjsCandidate } from "./npm-static-rewrite-syntax.js"; +import { normalizeCjsFunctionExports } from "./npm-static-cjs-exports-alias.js"; import { mayBundleChunks, splitBundledChunks, @@ -91,6 +92,13 @@ export class FrontendServices { if (!source.includes("__esm")) return null; return lowerEsmInitsWithParser(this.sourceParser(), source, path); } + /** A CommonJS module whose export object is one of its functions, with + * its member accesses respelled through that function (null: none). */ + cjsFunctionExports(source: string, path: string): string | null { + this.ensureOpen(); + if (!source.includes("module.exports")) return null; + return normalizeCjsFunctionExports(this.parse(path, source, "js")); + } nullableClassFields(path: string, source: string): syntax.NpmStaticOverloadRewrite | null { return syntax.applyNpmStaticNullableClassFields(this.parse(path, source, "js"), source); } diff --git a/tests/corpus/6264-required-module-truthiness.cjs b/tests/corpus/6264-required-module-truthiness.cjs new file mode 100644 index 000000000..626d1cd4d --- /dev/null +++ b/tests/corpus/6264-required-module-truthiness.cjs @@ -0,0 +1,10 @@ +// A required builtin module object in a boolean context is truthy, as any +// object: readable-stream's `process.env.READABLE_STREAM === "disable" && +// Stream` and `Stream || exports` guards. +"use strict"; +var Stream = require("stream"); +var os = require("os"); +console.log(Stream ? "stream" : "none", !!os, !os); +console.log(process.env.SCRIPTC_UNSET_FLAG === "disable" && Stream ? "disabled" : "default"); +var fallback = Stream || null; +console.log(fallback === Stream); diff --git a/tests/fixtures/npm/cases/2567-cjs-function-exports/main.ts b/tests/fixtures/npm/cases/2567-cjs-function-exports/main.ts new file mode 100644 index 000000000..29e65cbfc --- /dev/null +++ b/tests/fixtures/npm/cases/2567-cjs-function-exports/main.ts @@ -0,0 +1,12 @@ +// CommonJS modules whose export object is one of their own functions: +// semver 5/6's `exports = module.exports = Version` with members attached +// through both `exports` and `module.exports` (and read back through +// `exports` inside the module), and a constructor function exported whole +// with function and value members attached afterwards. +import Version from "gtfnexports"; +import Point from "gtfnmembers"; + +const v = Version.parse("2.4.1"); +console.log(v === null ? "null" : v.major(), String(new Version("3.0.0"))); +console.log(Version.compare("5.0.0", "3.2.0"), Version.gt("1.0.0", "2.0.0"), Version.MAX_LENGTH); +console.log(Point.origin().sum(), new Point(2, 3).sum(), Point.dimensions); diff --git a/tests/fixtures/npm/cases/2568-cjs-conditional-exports/main.ts b/tests/fixtures/npm/cases/2568-cjs-conditional-exports/main.ts new file mode 100644 index 000000000..2c117e914 --- /dev/null +++ b/tests/fixtures/npm/cases/2568-cjs-conditional-exports/main.ts @@ -0,0 +1,7 @@ +// An environment-dependent export object (readable-stream 2.x's +// READABLE_STREAM=disable switch): module.exports is replaced inside an +// if/else, so the module's exports depend on which branch ran. scriptc +// names the construct; Node picks the branch at load. +import rs from "gtcondexports"; + +console.log(rs.Readable.name); diff --git a/tests/fixtures/npm/node_modules/gtcondexports/index.d.ts b/tests/fixtures/npm/node_modules/gtcondexports/index.d.ts new file mode 100644 index 000000000..ba94e150b --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtcondexports/index.d.ts @@ -0,0 +1,2 @@ +declare const exported: { Readable: Function; Stream: unknown; name: string }; +export = exported; diff --git a/tests/fixtures/npm/node_modules/gtcondexports/index.js b/tests/fixtures/npm/node_modules/gtcondexports/index.js new file mode 100644 index 000000000..b4017d055 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtcondexports/index.js @@ -0,0 +1,14 @@ +var Stream = require("stream"); +function Local(name) { + this.name = name; +} +if (process.env.GT_STREAMS === "native" && Stream) { + module.exports = Stream; + exports = module.exports = Stream.Readable; + exports.Readable = Stream.Readable; + exports.Stream = Stream; +} else { + exports = module.exports = Local; + exports.Stream = Stream || exports; + exports.Readable = exports; +} diff --git a/tests/fixtures/npm/node_modules/gtcondexports/package.json b/tests/fixtures/npm/node_modules/gtcondexports/package.json new file mode 100644 index 000000000..eab4647ed --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtcondexports/package.json @@ -0,0 +1,7 @@ +{ + "name": "gtcondexports", + "version": "1.0.0", + "description": "readable-stream 2.x's environment-dependent export object: module.exports is replaced (and exports rebound) inside an if/else on an environment variable", + "main": "index.js", + "types": "index.d.ts" +} diff --git a/tests/fixtures/npm/node_modules/gtfnexports/index.d.ts b/tests/fixtures/npm/node_modules/gtfnexports/index.d.ts new file mode 100644 index 000000000..c2ee082d3 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtfnexports/index.d.ts @@ -0,0 +1,13 @@ +declare class Version { + constructor(raw: string); + raw: string; + major(): number; + toString(): string; +} +declare namespace Version { + const MAX_LENGTH: number; + function parse(raw: string): Version | null; + function compare(a: string, b: string): number; + function gt(a: string, b: string): boolean; +} +export = Version; diff --git a/tests/fixtures/npm/node_modules/gtfnexports/index.js b/tests/fixtures/npm/node_modules/gtfnexports/index.js new file mode 100644 index 000000000..d64b58f0c --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtfnexports/index.js @@ -0,0 +1,25 @@ +exports = module.exports = Version; + +var label = process.env.GT_LABEL ? "[" + process.env.GT_LABEL + "]" : ""; + +function Version(raw) { + this.raw = raw; + this.parts = raw.split("."); +} +Version.prototype.major = function () { + return Number(this.parts[0]); +}; +Version.prototype.toString = function () { + return label + this.raw; +}; + +exports.MAX_LENGTH = 256; +exports.parse = function parse(raw) { + return raw.length > exports.MAX_LENGTH ? null : new Version(raw); +}; +exports.compare = function compare(a, b) { + return exports.parse(a).major() - exports.parse(b).major(); +}; +module.exports.gt = function gt(a, b) { + return exports.compare(a, b) > 0; +}; diff --git a/tests/fixtures/npm/node_modules/gtfnexports/package.json b/tests/fixtures/npm/node_modules/gtfnexports/package.json new file mode 100644 index 000000000..4524cd1bc --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtfnexports/package.json @@ -0,0 +1,7 @@ +{ + "name": "gtfnexports", + "version": "1.0.0", + "description": "semver 5/6's export idiom: exports = module.exports = Version, then members attached through exports and module.exports", + "main": "index.js", + "types": "index.d.ts" +} diff --git a/tests/fixtures/npm/node_modules/gtfnmembers/index.d.ts b/tests/fixtures/npm/node_modules/gtfnmembers/index.d.ts new file mode 100644 index 000000000..ea1888e0b --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtfnmembers/index.d.ts @@ -0,0 +1,11 @@ +declare class Point { + constructor(x: number, y: number); + x: number; + y: number; + sum(): number; +} +declare namespace Point { + function origin(): Point; + const dimensions: number; +} +export = Point; diff --git a/tests/fixtures/npm/node_modules/gtfnmembers/index.js b/tests/fixtures/npm/node_modules/gtfnmembers/index.js new file mode 100644 index 000000000..6eaf003f7 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtfnmembers/index.js @@ -0,0 +1,13 @@ +"use strict"; +function Point(x, y) { + this.x = x; + this.y = y; +} +Point.prototype.sum = function () { + return this.x + this.y; +}; +module.exports = Point; +module.exports.origin = function origin() { + return new Point(0, 0); +}; +module.exports.dimensions = 2; diff --git a/tests/fixtures/npm/node_modules/gtfnmembers/package.json b/tests/fixtures/npm/node_modules/gtfnmembers/package.json new file mode 100644 index 000000000..5179c6caf --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtfnmembers/package.json @@ -0,0 +1,7 @@ +{ + "name": "gtfnmembers", + "version": "1.0.0", + "description": "a constructor function as module.exports with function and value members attached through module.exports afterwards", + "main": "index.js", + "types": "index.d.ts" +} diff --git a/tests/harness/npm-cases.ts b/tests/harness/npm-cases.ts index 69ce3917f..e0f1384bb 100644 --- a/tests/harness/npm-cases.ts +++ b/tests/harness/npm-cases.ts @@ -19,12 +19,12 @@ export function npmCases(fixturesRoot: string): NpmCase[] { return [ ...globSync(join(fixturesRoot, "npm/cases/*/main.ts")) .sort() - // 2465-2469 and 2556-2566 are the --npm-static bundler-emitted-CJS + // 2465-2469 and 2556-2568 are the --npm-static bundler-emitted-CJS // cases (npm-static.test.ts drives them with the opt-in): their // consumers exercise the static rewrite's surface (lexer-visible // names the shipped .d.ts never declares, the __toESM interop // family), so they stay out of the flagless island lane by design. - .filter((entry) => !/\/(246[5-9]|255[6-9]|256[0-6])-[^/]+\/main\.ts$/.test(entry)) + .filter((entry) => !/\/(246[5-9]|255[6-9]|256[0-8])-[^/]+\/main\.ts$/.test(entry)) .map((entry) => ({ name: entry.split("/").at(-2)!, entry, diff --git a/tests/harness/npm-static.test.ts b/tests/harness/npm-static.test.ts index 3543aeb0a..02ddadb0d 100644 --- a/tests/harness/npm-static.test.ts +++ b/tests/harness/npm-static.test.ts @@ -76,7 +76,7 @@ async function buildStatic(entry: string, npmStatic: string[] | "auto"): Promise ...globSync( join(fixturesRoot, "npm/node_modules/cryptozoo/**/*.{js,mjs,cjs,json,d.ts}"), ).sort(), - // the bundler-emitted-CJS mini packages (cases 2465-2469, 2556-2566) + // the bundler-emitted-CJS mini packages (cases 2465-2469, 2556-2568) ...globSync(join(fixturesRoot, "npm/node_modules/gt*/**/*.{js,cjs,json}")).sort(), ...globSync(join(fixturesRoot, "npm/store/gt*/**/*.{js,cjs,json}")).sort(), ]; @@ -1514,6 +1514,32 @@ describe(`npm-static pilots${sanitize ? " (sanitized)" : ""}`, () => { expect(nativeRes.exitCode).toBe(nodeRes.exitCode); }, 180_000); + // 2567: a module whose export object is one of its functions (semver + // 5/6's `exports = module.exports = Version`), under both values of the + // environment variable the module reads. + test("function export objects 2567-cjs-function-exports compile statically and byte-match Node", async () => { + const entry = join(fixturesRoot, "npm/cases/2567-cjs-function-exports/main.ts"); + await expectNodeParity(entry, ["gtfnexports", "gtfnmembers"]); + const binary = await buildStatic(entry, ["gtfnexports", "gtfnmembers"]); + const env = { ...process.env, GT_LABEL: "x" }; + const [nodeRes, nativeRes] = await Promise.all([ + runBinary("node", [entry], env), + runBinary(binary, [], env), + ]); + expect(nativeRes.stdout.toString("utf8")).toBe(nodeRes.stdout.toString("utf8")); + expect(nativeRes.stdout.toString("utf8")).toContain("[x]"); + }, 180_000); + + // 2568: an export object replaced inside an environment-dependent branch + // (readable-stream 2.x) is named precisely. + test("an environment-dependent export object is refused by name", () => { + const entry = join(fixturesRoot, "npm/cases/2568-cjs-conditional-exports/main.ts"); + const { coverage } = analyze(entry, { npmStatic: ["gtcondexports"] }); + expect(JSON.stringify(coverage)).toContain( + "replacing module.exports outside a top-level statement (a conditional or environment-dependent export object)", + ); + }, 120_000); + // 2562: a file with unverifiable interop that no compiled code can load // (the program imports a leaf data module and only a type from the // main entry) leaves its package static. From 3723e05417ec5229afc23cb861da81251e26586c Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Mon, 19 Oct 2026 18:28:45 -0700 Subject: [PATCH 030/122] Resolve bundled chunks that re-export a builtin to the builtin A chunk whose whole body is `module.exports = require("")` (readable-stream's internal/streams/stream.js) is the builtin module: its calls become that require, and no chunk module is emitted. Co-Authored-By: Claude Opus 5.5 --- .../npm-static-bundled-chunks.test.ts | 18 ++++++++ .../src/frontend/npm-static-bundled-chunks.ts | 44 ++++++++++++++++++- 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/packages/compiler/src/frontend/npm-static-bundled-chunks.test.ts b/packages/compiler/src/frontend/npm-static-bundled-chunks.test.ts index 82926d2b8..7ccad33f0 100644 --- a/packages/compiler/src/frontend/npm-static-bundled-chunks.test.ts +++ b/packages/compiler/src/frontend/npm-static-bundled-chunks.test.ts @@ -182,3 +182,21 @@ export const g = import_g.g; degrade: expect.stringContaining("bundled CommonJS modules") as string, }); }); + +test("a chunk that only re-exports a builtin module becomes that module", () => { + const answer = expectSameBehavior(`${keyedHelper}var require_stream = __commonJS({ + "lib/stream.js"(exports2, module2) { + module2.exports = require("stream"); + } +}); +var require_user = __commonJS({ + "lib/user.js"(exports2) { + var Stream = require_stream(); + exports2.same = Stream === require("stream"); + } +}); +console.log(typeof require_stream().PassThrough, require_user().same); +`); + expect(answer.chunks.map((c) => basename(c.path))).toEqual(["index.js.require_user.cjs"]); + expect(answer.text).toContain('typeof require("stream").PassThrough'); +}); diff --git a/packages/compiler/src/frontend/npm-static-bundled-chunks.ts b/packages/compiler/src/frontend/npm-static-bundled-chunks.ts index 28fe4a1b9..03b639cbe 100644 --- a/packages/compiler/src/frontend/npm-static-bundled-chunks.ts +++ b/packages/compiler/src/frontend/npm-static-bundled-chunks.ts @@ -23,6 +23,7 @@ * beside it). Anything else answers a degrade reason. */ import * as ts from "./ts7/syntax.js"; import { helperTokens } from "./helper-tokens.js"; +import { canonicalBuiltinModule } from "./builtin-modules.js"; export interface BundledChunkSplit { /** The bundle with chunk declarations blanked and calls respelled. */ @@ -407,10 +408,11 @@ export function splitBundledChunks( return { degrade: `its bundled CommonJS module factory '${r.text}' is used other than as a direct call`, }; + const forwarded = forwarderSpec(chunk); callEdits.push({ start: call.getStart(sf), end: call.getEnd(), - text: `require(${JSON.stringify(chunk.spec)})`, + text: `require(${JSON.stringify(forwarded ?? chunk.spec)})`, }); } for (const name of chunks.keys()) { @@ -422,6 +424,11 @@ export function splitBundledChunks( const out: BundledChunkSplit["chunks"] = []; const bundleEdits: Edit[] = [...callEdits]; for (const chunk of chunks.values()) { + if (forwarderSpec(chunk) !== null) { + // A chunk that only re-exports a builtin module is that module. + bundleEdits.push({ start: chunk.stmt.getStart(sf), end: chunk.stmt.getEnd(), text: "" }); + continue; + } const result = chunkSource(sf, chunk, chunks, top, stable, callEdits, bundleStrict); if (typeof result !== "string") return result; out.push({ path: chunk.path, source: result }); @@ -472,6 +479,41 @@ export function applyEdits(source: string, from: number, to: number, edits: Edit return text + source.slice(at, to); } +/** The builtin module a chunk forwards, or null: a body that is exactly + * `module.exports = require("")` (readable-stream's + * internal/streams/stream.js). Requiring the chunk is then requiring the + * builtin itself (same object, and loading a builtin runs no code). */ +function forwarderSpec(chunk: Chunk): string | null { + const moduleParam = chunk.params[1]; + if (moduleParam === undefined) return null; + const stmts = chunk.body.statements.nodes.filter( + (st) => !(ts.isExpressionStatement(st) && ts.isStringLiteral(st.expression)), + ); + const only = stmts[0]; + if (stmts.length !== 1 || only === undefined || !ts.isExpressionStatement(only)) return null; + const e = unparen(only.expression); + if ( + !ts.isBinaryExpression(e) || + e.operatorToken.kind !== ts.SyntaxKind.EqualsToken || + !ts.isPropertyAccessExpression(e.left) || + !ts.isIdentifier(e.left.expression) || + e.left.expression.text !== moduleParam || + e.left.name.text !== "exports" + ) + return null; + const call = unparen(e.right); + if ( + !ts.isCallExpression(call) || + !ts.isIdentifier(call.expression) || + call.expression.text !== "require" || + call.arguments.nodes.length !== 1 || + !ts.isStringLiteral(call.arguments.nodes[0]!) + ) + return null; + const spec = (call.arguments.nodes[0] as ts.StringLiteral).text; + return canonicalBuiltinModule(spec) !== null ? spec : null; +} + /** How code in a factory body (a chunk or a lazy ES-module initializer) * observes the factory's own frame, or null: its receiver, its arguments * object, `super`, a meta property, or (when `returns`) a top-level From 2b7c892af3645f871e55aaecd590d42395ca2185 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Mon, 19 Oct 2026 18:29:54 -0700 Subject: [PATCH 031/122] Record order baselines for the new cases and corpus program Co-Authored-By: Claude Opus 5.5 --- .../tests/corpus/6264-required-module-truthiness.cjs.json | 6 ++++++ .../npm/cases/2567-cjs-function-exports/main.ts.json | 6 ++++++ .../npm/cases/2568-cjs-conditional-exports/main.ts.json | 6 ++++++ 3 files changed, 18 insertions(+) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6264-required-module-truthiness.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2567-cjs-function-exports/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2568-cjs-conditional-exports/main.ts.json diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6264-required-module-truthiness.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6264-required-module-truthiness.cjs.json new file mode 100644 index 000000000..2fa691953 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6264-required-module-truthiness.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6264-required-module-truthiness.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2567-cjs-function-exports/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2567-cjs-function-exports/main.ts.json new file mode 100644 index 000000000..63a9b7efc --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2567-cjs-function-exports/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/npm/cases/2567-cjs-function-exports/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2568-cjs-conditional-exports/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2568-cjs-conditional-exports/main.ts.json new file mode 100644 index 000000000..9cad58f88 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2568-cjs-conditional-exports/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/npm/cases/2568-cjs-conditional-exports/main.ts" + ], + "diags": [] +} From c17eec0261ae43502a29e76ba57a3cde02299d86 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Mon, 19 Oct 2026 19:03:28 -0700 Subject: [PATCH 032/122] Compile event-loop metrics, predicate-narrowed catch bindings and other wrapper-reachable gaps Once generic wrappers compile (#1413), the code behind them is reachable. This closes the general gaps found there: - performance.eventLoopUtilization() and monitorEventLoopDelay()'s IntervalHistogram (enable, disable, reset, percentile and the min, max, mean, stddev, count and exceeds readings), under the fallback declarations and @types/node. A new runtime unit, scr_perf_loop.c, links only for programs that read them (the perfLoop feature): an unref'd sampling interval records into HdrHistogram-shaped buckets, and utilization measures active time as the main thread's CPU time since the loop started. - Catch bindings narrowed by a user type predicate or an assertion read their members through the checked-dynamic value. - `??` with a function default of a different signature takes the optional callback's own function type and adapts the default. - Optional calls on interface-typed receivers dispatch like the plain protocol call. - `any` parameters of function declarations and spelled function types are checked-dynamic slots, and a generic instantiated at `any` takes `unknown` there. - `'key' in err` on Error receivers answers from the declaring subclasses. - Object.entries over a generic record resolves `keyof T extends never ? unknown : T[keyof T]` through the binding. Co-Authored-By: Claude Opus 5.5 --- .../builtin-apis/performance-entries.mdx | 1 + .../src/generated/node-v24-compatibility.json | 40 +-- .../generated/node-v24-backlog.json | 94 +++--- .../generated/node-v24-internal.json | 56 +++- internal/compatibility/static-support.json | 4 + .../ambient/scriptc-node-fallback.d.ts | 29 ++ .../src/backend/executable-features.ts | 1 + packages/compiler/src/backend/library-pack.ts | 1 + packages/compiler/src/backend/llvm/emitter.ts | 17 + .../compiler/src/backend/llvm/lib-shared.ts | 8 + .../src/backend/native-link-info-core.ts | 2 + .../compiler/src/backend/native/contracts.ts | 2 + .../compiler/src/backend/native/executable.ts | 2 + .../src/backend/runtime-pack-targets.test.ts | 1 + .../compiler/src/backend/runtime-pack.test.ts | 1 + packages/compiler/src/backend/warm-cache.ts | 1 + .../src/executable/executable-cache.test.ts | 1 + .../src/executable/executable-cache.ts | 2 + .../frontend/lowering/builtins/performance.ts | 313 +++++++++++++++++- .../src/frontend/lowering/call-signatures.ts | 7 +- .../lowering/class-protocol-dispatch.ts | 8 +- .../frontend/lowering/generic-functions.ts | 14 +- .../src/frontend/lowering/lower-calls.ts | 20 +- .../src/frontend/lowering/lower-exprs.ts | 66 ++++ .../src/frontend/lowering/lower-stmts.ts | 3 +- .../compiler/src/frontend/lowering/lowerer.ts | 3 + .../src/frontend/lowering/surfaces.ts | 21 ++ packages/compiler/src/frontend/type-mapper.ts | 86 ++++- packages/compiler/src/index.ts | 1 + .../compiler/src/ir/builtin-signatures.ts | 14 + packages/compiler/src/ir/runtime-features.ts | 6 + packages/compiler/src/native/cli.ts | 1 + packages/compiler/src/native/prepare.ts | 1 + .../compiler/src/native/sanitizer.test.ts | 1 + packages/compiler/surface-manifest.json | 14 + .../6390-catch-binding-predicate.ts.json | 6 + .../6391-nullish-function-default.ts.json | 6 + ...6392-optional-chain-protocol-calls.ts.json | 6 + .../6393-function-types-any-params.ts.json | 6 + .../6394-in-key-error-subclasses.ts.json | 6 + .../6395-generic-object-entries.ts.json | 6 + .../6396-generic-any-instantiation.ts.json | 6 + .../corpus/6397-event-loop-metrics.ts.json | 6 + .../node-types/event-loop-metrics.ts.json | 6 + .../fixtures/node-types/timeout-type.ts.json | 6 + .../runtime-pack-matrix.mjs | 2 + packages/runtime/src/scr_lib.h | 15 + packages/runtime/src/scr_perf_loop.c | 305 +++++++++++++++++ tests/corpus/6390-catch-binding-predicate.ts | 42 +++ tests/corpus/6391-nullish-function-default.ts | 13 + .../6392-optional-chain-protocol-calls.ts | 47 +++ .../corpus/6393-function-types-any-params.ts | 8 + tests/corpus/6394-in-key-error-subclasses.ts | 25 ++ tests/corpus/6395-generic-object-entries.ts | 7 + .../corpus/6396-generic-any-instantiation.ts | 18 + tests/corpus/6397-event-loop-metrics.ts | 49 +++ tests/fixtures/node-types/README.md | 1 + .../fixtures/node-types/event-loop-metrics.ts | 49 +++ tests/fixtures/node-types/timeout-type.ts | 30 ++ tests/harness/project-config.test.ts | 26 ++ .../self-hosting-native-toolchain.test.ts | 1 + .../harness/self-hosting-runtime-pack.test.ts | 1 + 62 files changed, 1457 insertions(+), 84 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6390-catch-binding-predicate.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6391-nullish-function-default.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6392-optional-chain-protocol-calls.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6393-function-types-any-params.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6394-in-key-error-subclasses.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6395-generic-object-entries.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6396-generic-any-instantiation.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6397-event-loop-metrics.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/event-loop-metrics.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/timeout-type.ts.json create mode 100644 packages/runtime/src/scr_perf_loop.c create mode 100644 tests/corpus/6390-catch-binding-predicate.ts create mode 100644 tests/corpus/6391-nullish-function-default.ts create mode 100644 tests/corpus/6392-optional-chain-protocol-calls.ts create mode 100644 tests/corpus/6393-function-types-any-params.ts create mode 100644 tests/corpus/6394-in-key-error-subclasses.ts create mode 100644 tests/corpus/6395-generic-object-entries.ts create mode 100644 tests/corpus/6396-generic-any-instantiation.ts create mode 100644 tests/corpus/6397-event-loop-metrics.ts create mode 100644 tests/fixtures/node-types/event-loop-metrics.ts create mode 100644 tests/fixtures/node-types/timeout-type.ts diff --git a/docs/content/limitations/builtin-apis/performance-entries.mdx b/docs/content/limitations/builtin-apis/performance-entries.mdx index 957781b06..f634bb342 100644 --- a/docs/content/limitations/builtin-apis/performance-entries.mdx +++ b/docs/content/limitations/builtin-apis/performance-entries.mdx @@ -1 +1,2 @@ - **Performance entries:** `performance.mark()`, `measure()`, and the `getEntries` methods return entries with Node's fields, `toJSON()`, inspection, and identity. `Object.keys`, `for...in`, object spread, and `hasOwnProperty` on an entry are unsupported, and an entry stored in an `unknown` slot prints as a plain object. +- **Event-loop metrics:** `performance.eventLoopUtilization()` counts the main thread's CPU time since the event loop started as active time, so blocking system calls count as idle where Node counts them as active. `monitorEventLoopDelay()` supports `enable()`, `disable()`, `reset()`, `percentile()`, and the `min`, `max`, `mean`, `stddev`, `count`, and `exceeds` readings; the BigInt readings, `percentiles`, and non-literal option objects are unsupported. diff --git a/docs/src/generated/node-v24-compatibility.json b/docs/src/generated/node-v24-compatibility.json index e2e4cdd19..b0f0f2450 100644 --- a/docs/src/generated/node-v24-compatibility.json +++ b/docs/src/generated/node-v24-compatibility.json @@ -539,9 +539,9 @@ "apiEntries": 131, "static": { "supported": 0, - "partial": 19, + "partial": 25, "refused": 0, - "not-implemented": 112, + "not-implemented": 106, "by-design": 0, "unreviewed": 0, "not-applicable": 0 @@ -54688,9 +54688,9 @@ "inherited": true }, "static": { - "status": "not-implemented", - "detail": "No static compiler lowering is registered for this API yet.", - "verification": "registry-gap" + "status": "partial", + "detail": "Implemented for the documented scriptc module-loader subset.", + "verification": "test-backed" } }, { @@ -54803,9 +54803,9 @@ "inherited": true }, "static": { - "status": "not-implemented", - "detail": "No static compiler lowering is registered for this API yet.", - "verification": "registry-gap" + "status": "partial", + "detail": "Implemented for the documented scriptc module-loader subset.", + "verification": "test-backed" } }, { @@ -55286,9 +55286,9 @@ "inherited": true }, "static": { - "status": "not-implemented", - "detail": "No static compiler lowering is registered for this API yet.", - "verification": "registry-gap" + "status": "partial", + "detail": "Implemented for the documented scriptc module-loader subset.", + "verification": "test-backed" } }, { @@ -55309,9 +55309,9 @@ "inherited": true }, "static": { - "status": "not-implemented", - "detail": "No static compiler lowering is registered for this API yet.", - "verification": "registry-gap" + "status": "partial", + "detail": "Implemented for the documented scriptc module-loader subset.", + "verification": "test-backed" } }, { @@ -55677,9 +55677,9 @@ "inherited": true }, "static": { - "status": "not-implemented", - "detail": "No static compiler lowering is registered for this API yet.", - "verification": "registry-gap" + "status": "partial", + "detail": "Implemented for the call shapes accepted by the compiler lowering.", + "verification": "test-backed" } }, { @@ -55815,9 +55815,9 @@ "inherited": true }, "static": { - "status": "not-implemented", - "detail": "No static compiler lowering is registered for this API yet.", - "verification": "registry-gap" + "status": "partial", + "detail": "Implemented for the documented scriptc module-loader subset.", + "verification": "test-backed" } }, { diff --git a/internal/compatibility/generated/node-v24-backlog.json b/internal/compatibility/generated/node-v24-backlog.json index 0ad30b783..26a0302c0 100644 --- a/internal/compatibility/generated/node-v24-backlog.json +++ b/internal/compatibility/generated/node-v24-backlog.json @@ -7,8 +7,8 @@ "tierItemCount": 3456, "tiers": { "static": { - "audit-partial": 1163, - "verify-gap": 2165, + "audit-partial": 1169, + "verify-gap": 2159, "replace-refusal": 41, "classify": 2, "implement": 85 @@ -54259,12 +54259,15 @@ "priority": "high", "tiers": { "static": { - "status": "not-implemented", - "action": "verify-gap", - "verification": "registry-gap", - "confidence": "low", - "source": "compiler-unmatched:perf_hooks", - "tests": [] + "status": "partial", + "action": "audit-partial", + "verification": "test-backed", + "confidence": "high", + "source": "compiler-feature:perf_hooks.histogram.percentile", + "tests": [ + "tests/corpus/6397-event-loop-metrics.ts", + "tests/fixtures/node-types/event-loop-metrics.ts" + ] } } }, @@ -54384,12 +54387,15 @@ "priority": "high", "tiers": { "static": { - "status": "not-implemented", - "action": "verify-gap", - "verification": "registry-gap", - "confidence": "low", - "source": "compiler-unmatched:perf_hooks", - "tests": [] + "status": "partial", + "action": "audit-partial", + "verification": "test-backed", + "confidence": "high", + "source": "compiler-feature:perf_hooks.histogram.reset", + "tests": [ + "tests/corpus/6397-event-loop-metrics.ts", + "tests/fixtures/node-types/event-loop-metrics.ts" + ] } } }, @@ -54909,12 +54915,15 @@ "priority": "high", "tiers": { "static": { - "status": "not-implemented", - "action": "verify-gap", - "verification": "registry-gap", - "confidence": "low", - "source": "compiler-unmatched:perf_hooks", - "tests": [] + "status": "partial", + "action": "audit-partial", + "verification": "test-backed", + "confidence": "high", + "source": "compiler-feature:perf_hooks.histogram.disable", + "tests": [ + "tests/corpus/6397-event-loop-metrics.ts", + "tests/fixtures/node-types/event-loop-metrics.ts" + ] } } }, @@ -54934,12 +54943,15 @@ "priority": "high", "tiers": { "static": { - "status": "not-implemented", - "action": "verify-gap", - "verification": "registry-gap", - "confidence": "low", - "source": "compiler-unmatched:perf_hooks", - "tests": [] + "status": "partial", + "action": "audit-partial", + "verification": "test-backed", + "confidence": "high", + "source": "compiler-feature:perf_hooks.histogram.enable", + "tests": [ + "tests/corpus/6397-event-loop-metrics.ts", + "tests/fixtures/node-types/event-loop-metrics.ts" + ] } } }, @@ -55334,12 +55346,15 @@ "priority": "high", "tiers": { "static": { - "status": "not-implemented", - "action": "verify-gap", - "verification": "registry-gap", - "confidence": "low", - "source": "compiler-unmatched:perf_hooks", - "tests": [] + "status": "partial", + "action": "audit-partial", + "verification": "test-backed", + "confidence": "high", + "source": "surface-manifest:node-builtin.perf_hooks.monitorEventLoopDelay", + "tests": [ + "tests/corpus/6397-event-loop-metrics.ts", + "tests/fixtures/node-types/event-loop-metrics.ts" + ] } } }, @@ -55494,12 +55509,15 @@ "priority": "high", "tiers": { "static": { - "status": "not-implemented", - "action": "verify-gap", - "verification": "registry-gap", - "confidence": "low", - "source": "compiler-unmatched:perf_hooks", - "tests": [] + "status": "partial", + "action": "audit-partial", + "verification": "test-backed", + "confidence": "high", + "source": "compiler-feature:perf_hooks.performance.eventLoopUtilization", + "tests": [ + "tests/corpus/6397-event-loop-metrics.ts", + "tests/fixtures/node-types/event-loop-metrics.ts" + ] } } }, diff --git a/internal/compatibility/generated/node-v24-internal.json b/internal/compatibility/generated/node-v24-internal.json index 2f0565f84..9870f7c48 100644 --- a/internal/compatibility/generated/node-v24-internal.json +++ b/internal/compatibility/generated/node-v24-internal.json @@ -566,9 +566,9 @@ "apiEntries": 131, "static": { "supported": 0, - "partial": 19, + "partial": 25, "refused": 0, - "not-implemented": 112, + "not-implemented": 106, "by-design": 0, "unreviewed": 0, "not-applicable": 0 @@ -75524,7 +75524,9 @@ "tests/corpus/5212-perf-marks-measures.ts", "tests/corpus/5213-perf-timing-errors.ts", "tests/corpus/5214-perf-entry-inspect.ts", - "tests/corpus/5215-perf-timing-validation.js" + "tests/corpus/5215-perf-timing-validation.js", + "tests/corpus/6397-event-loop-metrics.ts", + "tests/fixtures/node-types/event-loop-metrics.ts" ] }, "anchorSource": "chapter" @@ -77565,8 +77567,12 @@ "inherited": true }, "static": { - "status": "not-implemented", - "evidence": "compiler-unmatched:perf_hooks" + "status": "partial", + "evidence": "compiler-feature:perf_hooks.histogram.percentile", + "tests": [ + "tests/corpus/6397-event-loop-metrics.ts", + "tests/fixtures/node-types/event-loop-metrics.ts" + ] }, "anchorSource": "exact" }, @@ -77680,8 +77686,12 @@ "inherited": true }, "static": { - "status": "not-implemented", - "evidence": "compiler-unmatched:perf_hooks" + "status": "partial", + "evidence": "compiler-feature:perf_hooks.histogram.reset", + "tests": [ + "tests/corpus/6397-event-loop-metrics.ts", + "tests/fixtures/node-types/event-loop-metrics.ts" + ] }, "anchorSource": "exact" }, @@ -78163,8 +78173,12 @@ "inherited": true }, "static": { - "status": "not-implemented", - "evidence": "compiler-unmatched:perf_hooks" + "status": "partial", + "evidence": "compiler-feature:perf_hooks.histogram.disable", + "tests": [ + "tests/corpus/6397-event-loop-metrics.ts", + "tests/fixtures/node-types/event-loop-metrics.ts" + ] }, "anchorSource": "exact" }, @@ -78186,8 +78200,12 @@ "inherited": true }, "static": { - "status": "not-implemented", - "evidence": "compiler-unmatched:perf_hooks" + "status": "partial", + "evidence": "compiler-feature:perf_hooks.histogram.enable", + "tests": [ + "tests/corpus/6397-event-loop-metrics.ts", + "tests/fixtures/node-types/event-loop-metrics.ts" + ] }, "anchorSource": "exact" }, @@ -78554,8 +78572,12 @@ "inherited": true }, "static": { - "status": "not-implemented", - "evidence": "compiler-unmatched:perf_hooks" + "status": "partial", + "evidence": "surface-manifest:node-builtin.perf_hooks.monitorEventLoopDelay", + "tests": [ + "tests/corpus/6397-event-loop-metrics.ts", + "tests/fixtures/node-types/event-loop-metrics.ts" + ] }, "anchorSource": "exact" }, @@ -78704,8 +78726,12 @@ "inherited": true }, "static": { - "status": "not-implemented", - "evidence": "compiler-unmatched:perf_hooks" + "status": "partial", + "evidence": "compiler-feature:perf_hooks.performance.eventLoopUtilization", + "tests": [ + "tests/corpus/6397-event-loop-metrics.ts", + "tests/fixtures/node-types/event-loop-metrics.ts" + ] }, "anchorSource": "exact" }, diff --git a/internal/compatibility/static-support.json b/internal/compatibility/static-support.json index 5807c1ae9..f1130f9b1 100644 --- a/internal/compatibility/static-support.json +++ b/internal/compatibility/static-support.json @@ -132,6 +132,8 @@ "node-builtin.perf_hooks.performance.getEntries": ["tests/corpus/5212-perf-marks-measures.ts", "tests/corpus/5214-perf-entry-inspect.ts", "tests/corpus/5215-perf-timing-validation.js"], "node-builtin.perf_hooks.performance.clearMarks": ["tests/corpus/5212-perf-marks-measures.ts", "tests/corpus/5213-perf-timing-errors.ts", "tests/corpus/5215-perf-timing-validation.js"], "node-builtin.perf_hooks.performance.clearMeasures": ["tests/corpus/5212-perf-marks-measures.ts"], + "node-builtin.perf_hooks.performance.eventLoopUtilization": ["tests/corpus/6397-event-loop-metrics.ts", "tests/fixtures/node-types/event-loop-metrics.ts"], + "node-builtin.perf_hooks.monitorEventLoopDelay": ["tests/corpus/6397-event-loop-metrics.ts", "tests/fixtures/node-types/event-loop-metrics.ts"], "node-builtin.process": ["tests/corpus/990-process-basics.ts", "tests/corpus/2314-process-introspection.ts"], "node-builtin.process.hrtime": ["tests/corpus/process-hrtime.js"], "node-builtin.process.memoryUsage": ["tests/corpus/5210-process-memory-usage.ts"], @@ -232,6 +234,8 @@ { "chapter": "util", "symbols": ["util.parseEnv"], "status": "partial", "evidence": ["tests/corpus/node24-env-parse.ts", "tests/corpus/node24-env-parse-errors.ts", "tests/corpus/node24-env-imports.cjs", "tests/fixtures/node-types/env-files.ts", "tests/harness/project-config.test.ts"] }, { "chapter": "process", "symbols": ["process.memoryUsage.rss"], "status": "partial", "evidence": ["tests/corpus/5210-process-memory-usage.ts"] }, { "chapter": "perf_hooks", "symbols": ["performance.now"], "status": "partial", "evidence": ["tests/corpus/2427-perf-hooks-now.ts", "tests/corpus/2446-performance-global.ts"] }, + { "chapter": "perf_hooks", "symbols": ["performance.eventLoopUtilization"], "status": "partial", "evidence": ["tests/corpus/6397-event-loop-metrics.ts", "tests/fixtures/node-types/event-loop-metrics.ts"] }, + { "chapter": "perf_hooks", "symbols": ["histogram.enable", "histogram.disable", "histogram.percentile", "histogram.reset"], "status": "partial", "evidence": ["tests/corpus/6397-event-loop-metrics.ts", "tests/fixtures/node-types/event-loop-metrics.ts"] }, { "chapter": "perf_hooks", "symbols": ["performance.mark", "performance.measure", "performance.getEntries", "performance.getEntriesByName", "performance.getEntriesByType", "performance.clearMarks", "performance.clearMeasures"], "status": "partial", "evidence": ["tests/corpus/5212-perf-marks-measures.ts", "tests/corpus/5213-perf-timing-errors.ts", "tests/corpus/5214-perf-entry-inspect.ts", "tests/corpus/5215-perf-timing-validation.js"] }, { "chapter": "perf_hooks", "symbols": ["PerformanceEntry", "PerformanceMark", "PerformanceMeasure"], "status": "partial", "evidence": ["tests/corpus/5212-perf-marks-measures.ts", "tests/corpus/5214-perf-entry-inspect.ts"] }, { "chapter": "perf_hooks", "parent": "Class: `PerformanceEntry`", "symbols": ["duration", "entryType", "name", "startTime"], "status": "partial", "evidence": ["tests/corpus/5212-perf-marks-measures.ts", "tests/corpus/5214-perf-entry-inspect.ts"] }, diff --git a/packages/compiler/ambient/scriptc-node-fallback.d.ts b/packages/compiler/ambient/scriptc-node-fallback.d.ts index 0a0837389..dc9f2be2a 100644 --- a/packages/compiler/ambient/scriptc-node-fallback.d.ts +++ b/packages/compiler/ambient/scriptc-node-fallback.d.ts @@ -1166,8 +1166,37 @@ declare module "node:perf_hooks" { getEntriesByType(type: EntryType): PerformanceEntry[]; clearMarks(name?: string): void; clearMeasures(name?: string): void; + eventLoopUtilization( + utilization1?: EventLoopUtilization, + utilization2?: EventLoopUtilization, + ): EventLoopUtilization; } export const performance: Performance; + /* Event-loop metrics: utilization (idle/active ms since the loop started) + * and monitorEventLoopDelay's sampling histogram (nanoseconds). */ + export interface EventLoopUtilization { + idle: number; + active: number; + utilization: number; + } + export interface EventLoopMonitorOptions { + resolution?: number | undefined; + } + export interface Histogram { + readonly count: number; + readonly exceeds: number; + readonly max: number; + readonly mean: number; + readonly min: number; + readonly stddev: number; + percentile(percentile: number): number; + reset(): void; + } + export interface IntervalHistogram extends Histogram { + enable(): boolean; + disable(): boolean; + } + export function monitorEventLoopDelay(options?: EventLoopMonitorOptions): IntervalHistogram; } declare module "perf_hooks" { export * from "node:perf_hooks"; diff --git a/packages/compiler/src/backend/executable-features.ts b/packages/compiler/src/backend/executable-features.ts index a565965f1..141ee4962 100644 --- a/packages/compiler/src/backend/executable-features.ts +++ b/packages/compiler/src/backend/executable-features.ts @@ -40,5 +40,6 @@ export function executableLinkFeatures(mod: IrModule): NativeLinkFeatures { tls: features.tls, tlsCa: features.tlsCa, cipher: features.cipher, + perfLoop: features.perfLoop, }; } diff --git a/packages/compiler/src/backend/library-pack.ts b/packages/compiler/src/backend/library-pack.ts index 833ef93e7..e260430e3 100644 --- a/packages/compiler/src/backend/library-pack.ts +++ b/packages/compiler/src/backend/library-pack.ts @@ -52,6 +52,7 @@ export async function compilePackedLibrary( nodeTest: false, tls: false, cipher: false, + perfLoop: false, tlsCa: false, }; const selection = await loadRuntimePack({ diff --git a/packages/compiler/src/backend/llvm/emitter.ts b/packages/compiler/src/backend/llvm/emitter.ts index 9e2d3524e..3981c5da0 100644 --- a/packages/compiler/src/backend/llvm/emitter.ts +++ b/packages/compiler/src/backend/llvm/emitter.ts @@ -2285,6 +2285,7 @@ export class LlEmitter { if (runsLoop) { this.declare(`declare zeroext i1 @scr_loop_run(ptr)`); if (this.mod.commonJsEntry === true) this.declare(`declare void @scr_loop_commonjs_entry()`); + if (this.readsLoopMetrics()) this.declare(`declare void @scr_perf_loop_mark()`); this.declare(`declare zeroext i1 @scr_report_unhandled_rejections()`); this.declare(`declare void @scr_discard_unhandled_rejections()`); } @@ -2843,6 +2844,8 @@ export class LlEmitter { ...(runsLoop ? [ ...(this.mod.commonJsEntry === true ? [` call void @scr_loop_commonjs_entry()`] : []), + // performance.eventLoopUtilization measures from here on. + ...(this.readsLoopMetrics() ? [` call void @scr_perf_loop_mark()`] : []), ` %loop_rejection = call zeroext i1 @scr_loop_run(ptr ${asyncEntry ? "%top" : "null"})`, ` %lexc = call zeroext i1 @scr_exc_pending()`, ` br i1 %lexc, label %luncaught, label %lok`, @@ -7858,6 +7861,20 @@ export class LlEmitter { /** Moves need plain slots that nothing else reads behind the binding's * back: no debugger view, no coroutine frame, no borrowed (+0) return. */ + /** Whether the program reads event-loop utilization (perf.loopStart / + * perf.idleTime): main then marks the loop's start before running it. */ + private loopMetricsRead: boolean | undefined; + private readsLoopMetrics(): boolean { + if (this.loopMetricsRead !== undefined) return this.loopMetricsRead; + const visitor = { + expr: (e: IrExpr): boolean => + !(e.kind === "libCall" && (e.fn === "perf.loopStart" || e.fn === "perf.idleTime")), + stmt: (): boolean => true, + }; + this.loopMetricsRead = !this.mod.functions.every((fn) => everyStmtList(fn.body, visitor)); + return this.loopMetricsRead; + } + private lastUseMovesAllowed(): boolean { return ( this.debug === null && diff --git a/packages/compiler/src/backend/llvm/lib-shared.ts b/packages/compiler/src/backend/llvm/lib-shared.ts index be67e1b67..8ab89bd22 100644 --- a/packages/compiler/src/backend/llvm/lib-shared.ts +++ b/packages/compiler/src/backend/llvm/lib-shared.ts @@ -273,6 +273,14 @@ export const LIB_FN_SYMS: Record = { "perf.entryDetail": "scr_perf_entry_detail", "perf.entryKeep": "scr_perf_entry_keep", "perf.entryRef": "scr_perf_entry_ref", + "perf.loopStart": "scr_perf_loop_start", + "perf.idleTime": "scr_perf_idle_time", + "perf.eldNew": "scr_eld_new", + "perf.eldEnable": "scr_eld_enable", + "perf.eldDisable": "scr_eld_disable", + "perf.eldReset": "scr_eld_reset", + "perf.eldPercentile": "scr_eld_percentile", + "perf.eldStat": "scr_eld_stat", "process.memoryUsageTake": "scr_process_memory_usage_take", "process.memoryUsageField": "scr_process_memory_usage_field", "process.rss": "scr_process_rss", diff --git a/packages/compiler/src/backend/native-link-info-core.ts b/packages/compiler/src/backend/native-link-info-core.ts index 3beb84a20..048deecb1 100644 --- a/packages/compiler/src/backend/native-link-info-core.ts +++ b/packages/compiler/src/backend/native-link-info-core.ts @@ -50,6 +50,8 @@ export interface NativeLinkFeatures { /** crypto.createCipheriv/createDecipheriv: scr_cipher.c plus the mbedTLS * archive. */ cipher: boolean; + /** perf_hooks event-loop metrics: scr_perf_loop.c. */ + perfLoop: boolean; tlsCa: boolean; } diff --git a/packages/compiler/src/backend/native/contracts.ts b/packages/compiler/src/backend/native/contracts.ts index 875a723bc..bc9df5618 100644 --- a/packages/compiler/src/backend/native/contracts.ts +++ b/packages/compiler/src/backend/native/contracts.ts @@ -199,6 +199,8 @@ export interface CcOptions { /** crypto.createCipheriv/createDecipheriv: compiles scr_cipher.c and links * the mbedTLS archive (shared with tls). */ cipher?: boolean; + /** perf_hooks event-loop metrics: compiles scr_perf_loop.c. */ + perfLoop?: boolean; /** Internal compiler hook: called only after a strict native artifact hit * or a successful stable build has installed `outPath`. The executable * frontend cache uses it to publish its stamp after native dependencies diff --git a/packages/compiler/src/backend/native/executable.ts b/packages/compiler/src/backend/native/executable.ts index 6c8673fe4..0b6ab945c 100644 --- a/packages/compiler/src/backend/native/executable.ts +++ b/packages/compiler/src/backend/native/executable.ts @@ -510,6 +510,8 @@ export async function compileCInternal( // The performance timeline (every perf_hooks user-timing call selects // dynAsync, whose unit carries the timeline's buffer warning). ...(opts.dynAsync ? [rt(join(rtDir, "scr_perf.c"))] : []), + // perf_hooks event-loop metrics, only for programs that read them. + ...(opts.perfLoop ? [rt(join(rtDir, "scr_perf_loop.c"))] : []), // Blob and File (every blob.* call selects dynAsync, whose unit boxes // their reads as dyn promises). ...(opts.dynAsync ? [rt(join(rtDir, "scr_blob.c"))] : []), diff --git a/packages/compiler/src/backend/runtime-pack-targets.test.ts b/packages/compiler/src/backend/runtime-pack-targets.test.ts index faa335e33..1d500957a 100644 --- a/packages/compiler/src/backend/runtime-pack-targets.test.ts +++ b/packages/compiler/src/backend/runtime-pack-targets.test.ts @@ -119,6 +119,7 @@ const features = { tls: false, tlsCa: false, cipher: false, + perfLoop: false, }; test.each([ diff --git a/packages/compiler/src/backend/runtime-pack.test.ts b/packages/compiler/src/backend/runtime-pack.test.ts index ffe581366..3521b47bd 100644 --- a/packages/compiler/src/backend/runtime-pack.test.ts +++ b/packages/compiler/src/backend/runtime-pack.test.ts @@ -56,6 +56,7 @@ const BASE: NativeLinkFeatures = { tls: false, tlsCa: false, cipher: false, + perfLoop: false, }; async function fixture(target: NativeTargetSpec = MACOS_ARM64_TARGET) { diff --git a/packages/compiler/src/backend/warm-cache.ts b/packages/compiler/src/backend/warm-cache.ts index 67e627c91..232701e6b 100644 --- a/packages/compiler/src/backend/warm-cache.ts +++ b/packages/compiler/src/backend/warm-cache.ts @@ -61,6 +61,7 @@ export async function warmNativeCaches( nodeTest: false, tls: false, cipher: false, + perfLoop: false, tlsCa: false, }, }); diff --git a/packages/compiler/src/executable/executable-cache.test.ts b/packages/compiler/src/executable/executable-cache.test.ts index e5887e855..b6bd90b06 100644 --- a/packages/compiler/src/executable/executable-cache.test.ts +++ b/packages/compiler/src/executable/executable-cache.test.ts @@ -57,6 +57,7 @@ const native: EarlyExecutableNativeFeatures = { tls: false, tlsCa: false, cipher: false, + perfLoop: false, }; async function fixture(): Promise<{ diff --git a/packages/compiler/src/executable/executable-cache.ts b/packages/compiler/src/executable/executable-cache.ts index ee9d84fcf..c020a1e1c 100644 --- a/packages/compiler/src/executable/executable-cache.ts +++ b/packages/compiler/src/executable/executable-cache.ts @@ -79,6 +79,7 @@ export interface EarlyExecutableNativeFeatures { nodeTest: boolean; tls: boolean; cipher: boolean; + perfLoop: boolean; tlsCa: boolean; } @@ -193,6 +194,7 @@ const BOOLEAN_NATIVE_KEYS = [ "tls", "tlsCa", "cipher", + "perfLoop", ] as const satisfies readonly (keyof EarlyExecutableNativeFeatures)[]; function validNativeFeatures(value: unknown): value is EarlyExecutableNativeFeatures { diff --git a/packages/compiler/src/frontend/lowering/builtins/performance.ts b/packages/compiler/src/frontend/lowering/builtins/performance.ts index 2a9962b0f..19b585e37 100644 --- a/packages/compiler/src/frontend/lowering/builtins/performance.ts +++ b/packages/compiler/src/frontend/lowering/builtins/performance.ts @@ -134,6 +134,12 @@ export function lowerPerfHooksCall( loc, }; } + if ( + access.name.text === "eventLoopUtilization" && + isPerfHooksPerformanceExpr(lowerer, access.expression) + ) { + return lowerEventLoopUtilization(lowerer, expr); + } const timeline = PERF_TIMELINE_CALLS.get(access.name.text); if (timeline && isPerfHooksPerformanceExpr(lowerer, access.expression)) { return lowerPerfTimelineCall(lowerer, expr, access.name.text, timeline, loc); @@ -142,7 +148,7 @@ export function lowerPerfHooksCall( lowerer.noLowering( `perf_hooks performance.${access.name.text}`, expr, - "performance.now() (and its .bind(performance) function value), mark, measure, the getEntries family, clearMarks and clearMeasures are the lowered surface", + "performance.now() (and its .bind(performance) function value), mark, measure, the getEntries family, clearMarks, clearMeasures and eventLoopUtilization are the lowered surface", ); } return null; @@ -513,3 +519,308 @@ export function fencePerfEntryKeyWalk( `${what} over performance entries (Node keeps their fields as prototype getters, so own-key walks see none; read the fields or call toJSON())`, ); } + +/* ── event-loop metrics: eventLoopUtilization and monitorEventLoopDelay ── */ + +/** The stdlib IntervalHistogram (monitorEventLoopDelay's result), by the + * receiver's checker type. Its value is the runtime histogram handle. */ +function isIntervalHistogram(lowerer: Lowerer, node: ts.Expression): boolean { + const symbol = lowerer.typeOf(node).getSymbol(); + return symbol?.name === "IntervalHistogram" && lowerer.isStdlibSymbol(symbol); +} + +/** IntervalHistogram's numeric members, as perf.eldStat selectors. */ +const HISTOGRAM_STATS: ReadonlyMap = new Map([ + ["min", 0], + ["max", 1], + ["mean", 2], + ["stddev", 3], + ["count", 4], + ["exceeds", 5], +]); + +/** `h.min`, `h.max`, … on an IntervalHistogram: the runtime's current + * statistic (Node's hdr_histogram values; nanoseconds). Null for every + * other read. */ +export function lowerHistogramProperty( + lowerer: Lowerer, + expr: ts.PropertyAccessExpression, +): IrExpr | null { + const which = HISTOGRAM_STATS.get(expr.name.text); + if ( + which === undefined || + expr.questionDotToken || + !isIntervalHistogram(lowerer, expr.expression) + ) + return null; + const loc = locOf(expr); + return { + kind: "libCall", + fn: "perf.eldStat", + args: [lowerer.lowerExprExpecting(expr.expression, F64), numLit(which, loc)], + type: F64, + loc, + }; +} + +/** enable(), disable(), reset() and percentile(p) on an IntervalHistogram. + * Other members (the BigInt twins, the percentiles Map) fence by name. */ +export function lowerHistogramMethodCall( + lowerer: Lowerer, + expr: ts.CallExpression, + access: ts.PropertyAccessExpression, +): IrExpr | null { + if (access.questionDotToken || !isIntervalHistogram(lowerer, access.expression)) return null; + const loc = locOf(expr); + const name = access.name.text; + const handle = (): IrExpr => lowerer.lowerExprExpecting(access.expression, F64); + const args = expr.arguments.nodes; + if ((name === "enable" || name === "disable" || name === "reset") && args.length === 0) { + const fn = + name === "enable" + ? "perf.eldEnable" + : name === "disable" + ? "perf.eldDisable" + : "perf.eldReset"; + return { kind: "libCall", fn, args: [handle()], type: name === "reset" ? VOID : BOOL, loc }; + } + if (name === "percentile" && args.length === 1 && !ts.isSpreadElement(args[0]!)) { + // Node validates first: NaN, p <= 0 and p > 100 throw ERR_OUT_OF_RANGE. + const h = lowerer.declareHiddenLocal("%histogram", F64); + const p = lowerer.declareHiddenLocal("%percentile", F64); + const pRef = (): IrExpr => varRef(p.id, F64, loc); + const cmp = (op: "!==" | "<=" | ">", left: IrExpr, right: IrExpr): IrExpr => ({ + kind: "bin", + op, + left, + right, + type: BOOL, + loc, + }); + const or = (left: IrExpr, right: IrExpr): IrExpr => ({ + kind: "logical", + op: "||", + left, + right, + type: BOOL, + loc, + }); + const outOfRange = or( + or(cmp("!==", pRef(), pRef()), cmp("<=", pRef(), numLit(0, loc))), + cmp(">", pRef(), numLit(100, loc)), + ); + const message: IrExpr = { + kind: "strConcat", + left: { + kind: "strLit", + value: 'The value of "percentile" is out of range. It must be > 0 && <= 100. Received ', + type: STRING, + loc, + }, + right: { kind: "toString", operand: pRef(), type: STRING, loc }, + type: STRING, + loc, + }; + const thrown: IrExpr = { + kind: "libCall", + fn: "error.nodeThrow", + args: [ + numLit(2, loc), + { kind: "strLit", value: "ERR_OUT_OF_RANGE", type: STRING, loc }, + message, + ], + type: F64, + loc, + }; + return { + kind: "seqExpr", + stmts: [ + { kind: "varDecl", localId: h.id, init: handle(), loc }, + { kind: "varDecl", localId: p.id, init: lowerer.lowerExprExpecting(args[0]!, F64), loc }, + ], + result: { + kind: "ternary", + cond: outOfRange, + then: thrown, + else_: { + kind: "libCall", + fn: "perf.eldPercentile", + args: [varRef(h.id, F64, loc), pRef()], + type: F64, + loc, + }, + type: F64, + loc, + }, + type: F64, + loc, + }; + } + lowerer.noLowering( + `IntervalHistogram.${name}`, + expr, + "enable(), disable(), reset(), percentile(p) and the min, max, mean, stddev, count and exceeds readings are the lowered surface", + ); +} + +/** `monitorEventLoopDelay(options?)` from node:perf_hooks (a named import + * or a namespace member): a new sampling histogram handle. The resolution + * must be a literal-free number expression or an object literal option. */ +export function lowerMonitorEventLoopDelayCall( + lowerer: Lowerer, + expr: ts.CallExpression, +): IrExpr | null { + const callee = expr.expression; + const bi = ts.isIdentifier(callee) + ? builtinImportOf(lowerer, callee) + : ts.isPropertyAccessExpression(callee) && !callee.questionDotToken + ? lowerer.builtinMemberOf(callee) + : null; + if (bi === null || bi.module !== "perf_hooks" || bi.member !== "monitorEventLoopDelay") + return null; + const loc = locOf(expr); + const args = expr.arguments.nodes; + let resolution: IrExpr = numLit(10, loc); + if (args.length > 1 || args.some(ts.isSpreadElement)) { + lowerer.noLowering( + "this monitorEventLoopDelay call form", + expr, + "pass at most one options object", + ); + } + if (args.length === 1) { + const options = args[0]!; + if (!ts.isObjectLiteralExpression(options)) { + lowerer.noLowering( + "monitorEventLoopDelay with a non-literal options object", + options, + "spell the options inline: monitorEventLoopDelay({ resolution: 20 })", + ); + } + for (const property of options.properties.nodes) { + if ( + !ts.isPropertyAssignment(property) || + !ts.isIdentifier(property.name) || + property.name.text !== "resolution" + ) { + lowerer.noLowering( + "monitorEventLoopDelay options other than resolution", + property, + "resolution is the one option", + ); + } + resolution = lowerer.lowerExprExpecting(property.initializer, F64); + } + } + return { kind: "libCall", fn: "perf.eldNew", args: [resolution], type: F64, loc }; +} + +/** performance.eventLoopUtilization(u1?, u2?) — Node's + * lib/internal/perf/event_loop_utilization.js over the runtime's loop + * start and idle time, one lifted helper per arity. */ +function lowerEventLoopUtilization(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { + const loc = locOf(expr); + const args = expr.arguments.nodes; + const resultT = lowerer.mapTypeOf(lowerer.typeOf(expr)); + if (resultT?.kind !== "record" || args.length > 2 || args.some(ts.isSpreadElement)) { + lowerer.noLowering("this performance.eventLoopUtilization call form", expr); + } + const helper = eluHelperOf(lowerer, args.length, resultT); + return { + kind: "call", + callee: helper, + args: args.map((arg) => lowerer.lowerExprExpecting(arg, resultT)), + type: resultT, + loc, + }; +} + +function eluHelperOf(lowerer: Lowerer, arity: number, recT: IrType & { kind: "record" }): string { + const name = `%perf.elu.${arity}`; + if (lowerer.liftedFns.some((f) => f.name === name)) return name; + const loc: SrcLoc = { file: "", start: 0, end: 0 }; + const f64Local = (id: string) => ({ id, name: id, type: F64, mutable: false }); + const params = Array.from({ length: arity }, (_, i) => ({ + localId: `u${i + 1}.0`, + name: `u${i + 1}`, + type: recT as IrType, + })); + const locals = [ + ...params.map((p) => ({ id: p.localId, name: p.name, type: recT as IrType, mutable: false })), + f64Local("ls.0"), + f64Local("idle.0"), + f64Local("active.0"), + ]; + const ref = (id: string): IrExpr => varRef(id, F64, loc); + const field = (param: string, f: string): IrExpr => ({ + kind: "recordGet", + obj: varRef(param, recT, loc), + shapeId: recT.shapeId, + field: f, + type: F64, + loc, + }); + const bin = (op: "+" | "-" | "/" | "<=", left: IrExpr, right: IrExpr): IrExpr => ({ + kind: "bin", + op, + left, + right, + type: op === "<=" ? BOOL : F64, + loc, + }); + const record = (idle: IrExpr, active: IrExpr, utilization: IrExpr): IrExpr => ({ + kind: "recordLit", + fields: [ + { name: "idle", value: idle }, + { name: "active", value: active }, + { name: "utilization", value: utilization }, + ], + type: recT, + loc, + }); + const result = (idle: IrExpr, active: IrExpr): IrStmt => ({ + kind: "return", + value: record(idle, active, bin("/", active, bin("+", idle, active))), + loc, + }); + const decl = (id: string, init: IrExpr): IrStmt => ({ kind: "varDecl", localId: id, init, loc }); + const body: IrStmt[] = [ + decl("ls.0", { kind: "libCall", fn: "perf.loopStart", args: [], type: F64, loc }), + { + kind: "if", + cond: bin("<=", ref("ls.0"), numLit(0, loc)), + then: [ + { kind: "return", value: record(numLit(0, loc), numLit(0, loc), numLit(0, loc)), loc }, + ], + else_: null, + loc, + }, + ]; + if (arity === 2) { + body.push( + decl("idle.0", bin("-", field("u1.0", "idle"), field("u2.0", "idle"))), + decl("active.0", bin("-", field("u1.0", "active"), field("u2.0", "active"))), + result(ref("idle.0"), ref("active.0")), + ); + } else { + body.push( + decl("idle.0", { kind: "libCall", fn: "perf.idleTime", args: [], type: F64, loc }), + decl( + "active.0", + bin( + "-", + bin("-", { kind: "libCall", fn: "perf.now", args: [], type: F64, loc }, ref("ls.0")), + ref("idle.0"), + ), + ), + arity === 0 + ? result(ref("idle.0"), ref("active.0")) + : result( + bin("-", ref("idle.0"), field("u1.0", "idle")), + bin("-", ref("active.0"), field("u1.0", "active")), + ), + ); + } + lowerer.liftedFns.push({ name, params, returnType: recT, locals, body, loc }); + return name; +} diff --git a/packages/compiler/src/frontend/lowering/call-signatures.ts b/packages/compiler/src/frontend/lowering/call-signatures.ts index ef2900a7c..533ff79b0 100644 --- a/packages/compiler/src/frontend/lowering/call-signatures.ts +++ b/packages/compiler/src/frontend/lowering/call-signatures.ts @@ -443,7 +443,12 @@ export function paramShapes( declParam, lowerer.runtimeOptionalBindingType( declParam.name, - lowerer.mapTypeOf(tsType) ?? lowerer.irTypeOf(declParam.name), + lowerer.mapTypeOf(tsType) ?? + // A TypeScript instantiation at `any` (a type argument inferred + // from an untyped value) takes the checked-dynamic slot. + ((tsType.flags & ts.TypeFlags.Any) !== 0 && !isJsSourceFile(declParam.getSourceFile()) + ? DYN + : lowerer.irTypeOf(declParam.name)), ), lowerer.shapes, lowerer.unions, diff --git a/packages/compiler/src/frontend/lowering/class-protocol-dispatch.ts b/packages/compiler/src/frontend/lowering/class-protocol-dispatch.ts index c8e56f3cc..1b881ed43 100644 --- a/packages/compiler/src/frontend/lowering/class-protocol-dispatch.ts +++ b/packages/compiler/src/frontend/lowering/class-protocol-dispatch.ts @@ -188,7 +188,8 @@ function protocolSignature( call: ts.CallExpression, access: ts.PropertyAccessExpression, ): ProtocolSignature | null { - const recvTs = lowerer.typeOf(access.expression); + // A guarded optional step's receiver still carries its nullish arms. + const recvTs = lowerer.checker.getNonNullableType(lowerer.typeOf(access.expression)); const property = lowerer.checker.getPropertyOfType(recvTs, access.name.text); if (!property) return null; const declarations = lowerer.checker.declarationsOf(property); @@ -240,10 +241,11 @@ export function lowerProtocolMethodCall( access: ts.PropertyAccessExpression, recv: IrExpr, ): IrExpr | null { + // An optional chain that already guarded this step (`s?.m(x)`, the chain + // re-dispatching with the narrowed receiver) calls like a plain one. if ( recv.type.kind !== "dyn" || - call.questionDotToken || - access.questionDotToken || + lowerer.chainBlocked(call, access) || call.arguments.nodes.some(ts.isSpreadElement) ) return null; diff --git a/packages/compiler/src/frontend/lowering/generic-functions.ts b/packages/compiler/src/frontend/lowering/generic-functions.ts index e1fa5994d..ac611a7df 100644 --- a/packages/compiler/src/frontend/lowering/generic-functions.ts +++ b/packages/compiler/src/frontend/lowering/generic-functions.ts @@ -302,7 +302,13 @@ export function genericCallInstance( const retTs = lowerer.checker.getReturnTypeOfSignature(rsig); const local = localClassResultInstance(lowerer, expr, info, rsig, params, retTs); if (local) return local; - const mappedReturn = lowerer.mapTypeOf(retTs); + // A result instantiated at `any` (a type argument inferred from an + // untyped value, such as an untyped forwarding call) is checked-dynamic. + const mappedReturn = + lowerer.mapTypeOf(retTs) ?? + ((retTs.flags & ts.TypeFlags.Any) !== 0 && !isJsSourceFile(info.decl.getSourceFile()) + ? DYN + : null); if (!mappedReturn) { fenceGenericSignatureResult(lowerer, expr, retTs); lowerer.badType(expr, retTs); @@ -691,7 +697,11 @@ function unifySignatureBindings( tsBindings.set(sym, lowerer.typeParamTsResolver(inst) ?? inst); } if (!bindings.has(sym)) { - const mapped = lowerer.mapTypeOf(inst); + const mapped = + lowerer.mapTypeOf(inst) ?? + ((inst.flags & ts.TypeFlags.Any) !== 0 && !isJsSourceFile(info.decl.getSourceFile()) + ? DYN + : null); // A visitor's inferred T can be void. It still binds the // return convention of `(node) => T`; discarding it leaves // the callback's return type spuriously uninstantiated. diff --git a/packages/compiler/src/frontend/lowering/lower-calls.ts b/packages/compiler/src/frontend/lowering/lower-calls.ts index 3bebfd2af..23629a7ba 100644 --- a/packages/compiler/src/frontend/lowering/lower-calls.ts +++ b/packages/compiler/src/frontend/lowering/lower-calls.ts @@ -143,6 +143,8 @@ import { import { fencePerfEntryKeyWalk, isPerfEntryType, + lowerHistogramMethodCall, + lowerMonitorEventLoopDelayCall, lowerPerfHooksCall, perfEntryTag, } from "./builtins/performance.js"; @@ -241,6 +243,15 @@ const ZLIB_STREAM_METHODS: ReadonlySet = new Set(["flush", "params", "cl export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { const loc = locOf(expr); + // node:perf_hooks monitorEventLoopDelay (named import or namespace member). + if ( + (ts.isIdentifier(expr.expression) && expr.expression.text === "monitorEventLoopDelay") || + (ts.isPropertyAccessExpression(expr.expression) && + expr.expression.name.text === "monitorEventLoopDelay") + ) { + const histogram = lowerMonitorEventLoopDelayCall(lowerer, expr); + if (histogram) return histogram; + } // Object's checker return type is `any`. Claim known object inputs // before generic checked-dynamic dispatch tries to load the constructor. @@ -1379,6 +1390,7 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { // .bind(performance) function value over the runtime's // process-start-anchored monotonic clock. lowerPerfHooksCall(lowerer, expr, expr.expression) ?? + lowerHistogramMethodCall(lowerer, expr, expr.expression) ?? // The composed crypto pattern (randomBytes(n).toString(enc)) // — its receiver is a Buffer-typed CALL no other lowering claims. lowerer.lowerCryptoComposedCall(expr, expr.expression) ?? @@ -3289,7 +3301,13 @@ function lowerDynReceiverMethodCall( // array — scalar narrowings bridge through maybeNarrow's dynCheck and // take the ordinary typed paths, but there is no static home for an // any-elemented array). Typed receivers keep their own lowerings. - const recvTs = lowerer.typeOf(access.expression); + // An optional step the chain already guarded (`s?.m(x)`) has a receiver + // proven non-nullish. + const rawRecvTs = lowerer.typeOf(access.expression); + const recvTs = + access.questionDotToken !== undefined && lowerer.chainHandled.has(access) + ? lowerer.checker.getNonNullableType(rawRecvTs) + : rawRecvTs; if (lowerer.classImplementedProtocol(recvTs)) { const property = lowerer.checker.getPropertyOfType(recvTs, access.name.text); if ( diff --git a/packages/compiler/src/frontend/lowering/lower-exprs.ts b/packages/compiler/src/frontend/lowering/lower-exprs.ts index 80b5bb333..aa597d8a2 100644 --- a/packages/compiler/src/frontend/lowering/lower-exprs.ts +++ b/packages/compiler/src/frontend/lowering/lower-exprs.ts @@ -1,3 +1,5 @@ +import { lowerHistogramProperty } from "./builtins/performance.js"; +import { cleanFuncAdaptable } from "./coercions/function-adapters.js"; import { staticFunctionNameRead } from "./static-function-names.js"; import { argumentsOwner, typedArgumentsArity, typedArgumentsCandidate } from "./typed-arguments.js"; import { dateJsonType, isDateJsonSource } from "./builtins/dates.js"; @@ -1948,6 +1950,11 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr { const staticName = staticFunctionNameRead(lowerer, expr); if (staticName !== null) return { kind: "strLit", value: staticName, type: STRING, loc }; } + // An IntervalHistogram's readings (min, max, mean, stddev, count, exceeds). + { + const reading = lowerHistogramProperty(lowerer, expr); + if (reading) return reading; + } // `.length` of a forwarded fixed tuple (a typed `arguments`, a tuple // rest parameter, an alias of either): the signature's arity. if (expr.name.text === "length" && !expr.questionDotToken) { @@ -4715,6 +4722,22 @@ function lowerNullishPair( ); } } + // An optional callback defaulted to a function of another signature + // (`o.tags ?? (() => ({}))`, fewer parameters or a narrower result): the + // result is the callback's own function type, and the default adapts into + // it (JavaScript ignores surplus arguments). The checker's merged union + // signature would lose the callback's result shape. + if (present?.kind === "func" && present.rest !== true) { + const raw = lowerer.lowerExpr(expr.right); + if ( + raw.type.kind === "func" && + (typeEquals(raw.type, present) || cleanFuncAdaptable(lowerer, raw.type, present)) + ) { + const right = lowerer.coerceInto(expr.right, raw, present); + lowerer.nullishFuncResults.add(expr); + return { kind: "nullish", left, right, type: present, loc }; + } + } lowerer.unsupported( "SC1090", expr, @@ -12688,6 +12711,19 @@ export function caughtRead( if (narrowed?.kind === "dyn") { return { kind: "caughtToDyn", value: ref, type: DYN, loc }; } + // A binding a user type predicate (or an assertion) narrowed to a + // structural type (`isErrorLike(e)` → `{ message: string; … }`, an + // intersection like `TypeError & { code: unknown }`): the thrown value is + // still whatever was thrown, so it crosses as a checked-dynamic value and + // each member read validates its own result, as JavaScript reads it. + const checked = lowerer.typeOf(node); + if ( + narrowed?.kind === "record" || + narrowed?.kind === "union" || + (narrowed === null && (checked.flags & (ts.TypeFlags.Object | ts.TypeFlags.Intersection)) !== 0) + ) { + return { kind: "caughtToDyn", value: ref, type: DYN, loc }; + } lowerer.unsupported("SC1063", node); } @@ -14000,6 +14036,36 @@ function lowerInExpression(lowerer: Lowerer, expr: ts.BinaryExpression, loc: Src ) { return { kind: "boolLit", value: true, type: BOOL, loc }; } + // Any other key: a member a program class declares. The receiver's + // runtime class is its static class or a descendant; the key is + // present exactly when that class (or an ancestor) declares it. + if (recv.kind === "varRef" || recv.kind === "caughtNarrow") { + const declares = (c: ClassInfo): boolean => + c.fields.has(key) || + lowerer.findMethodOn(c, key) !== null || + lowerer.findMethodOn(c, `get:${key}`) !== null || + lowerer.findMethodOn(c, `set:${key}`) !== null; + const staticName = recv.type.className; + const staticInfo = lowerer.classes.get(staticName); + if (staticInfo && declares(staticInfo)) + return { kind: "boolLit", value: true, type: BOOL, loc }; + const tests: IrExpr[] = []; + for (const c of lowerer.classes.values()) { + const name = c.def.name; + if (name === staticName || !lowerer.isSubclassOf(name, staticName) || !declares(c)) + continue; + // A declaring ancestor below the static class already covers it. + if (c.base && c.base.def.name !== staticName && declares(c.base)) continue; + tests.push({ kind: "instanceOf", value: recv, className: name, type: BOOL, loc }); + } + return tests.reduce( + (acc, test) => + acc.kind === "boolLit" + ? test + : { kind: "logical", op: "||", left: acc, right: test, type: BOOL, loc }, + { kind: "boolLit", value: false, type: BOOL, loc }, + ); + } lowerer.unsupported( "SC1090", expr, diff --git a/packages/compiler/src/frontend/lowering/lower-stmts.ts b/packages/compiler/src/frontend/lowering/lower-stmts.ts index 01ff5f5c1..3af47f614 100644 --- a/packages/compiler/src/frontend/lowering/lower-stmts.ts +++ b/packages/compiler/src/frontend/lowering/lower-stmts.ts @@ -4887,7 +4887,8 @@ export function lowerVarDecl( if ( !decl.type && init.type.kind === "func" && - lowerer.typedArgumentsLambdas.has(stripTypeWrappers(decl.initializer)) + (lowerer.typedArgumentsLambdas.has(stripTypeWrappers(decl.initializer)) || + lowerer.nullishFuncResults.has(stripTypeWrappers(decl.initializer))) ) type = init.type; // A TypeScript lambda whose closure returns the runtime result (a missing diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index 26405865c..8913b9eeb 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -1917,6 +1917,9 @@ export class Lowerer { /** Const bindings holding a forwarded fixed tuple (`const args = * arguments as any` in a typed-arguments function) → the tuple type. */ readonly tupleAliasSymbols = new Map(); + /** `??` expressions whose result is the optional callback's own function + * type (an adapted function default); their bindings take that type. */ + readonly nullishFuncResults = new Set(); /** The static names of the current generic instance's name-keyed * function parameters (static-function-names.ts). */ staticFnNames: Map | null = null; diff --git a/packages/compiler/src/frontend/lowering/surfaces.ts b/packages/compiler/src/frontend/lowering/surfaces.ts index e43748b39..c819bf80c 100644 --- a/packages/compiler/src/frontend/lowering/surfaces.ts +++ b/packages/compiler/src/frontend/lowering/surfaces.ts @@ -2397,6 +2397,27 @@ export const AMBIENT_SURFACE_FNS: readonly AmbientSurfaceRow[] = [ name: "perf_hooks.performance.clearMeasures", fns: ["perf.clearMeasures"], }, + { + id: "node-builtin.perf_hooks.performance.eventLoopUtilization", + kind: "node-builtin", + name: "perf_hooks.performance.eventLoopUtilization", + fns: ["perf.loopStart", "perf.idleTime"], + note: "active time is the main thread's CPU time since the loop started; the one- and two-argument difference forms are supported", + }, + { + id: "node-builtin.perf_hooks.monitorEventLoopDelay", + kind: "node-builtin", + name: "perf_hooks.monitorEventLoopDelay", + fns: [ + "perf.eldNew", + "perf.eldEnable", + "perf.eldDisable", + "perf.eldReset", + "perf.eldPercentile", + "perf.eldStat", + ], + note: "an unref'd sampling interval records the delay histogram; enable, disable, reset, percentile and the min, max, mean, stddev, count and exceeds readings are supported", + }, // ── the process global's ambient reads and authority calls // (lowerProcessProperty/lowerProcessMethodCall — process is a // provenance-checked global here, not an importable module). diff --git a/packages/compiler/src/frontend/type-mapper.ts b/packages/compiler/src/frontend/type-mapper.ts index 19e7775cc..1e3954586 100644 --- a/packages/compiler/src/frontend/type-mapper.ts +++ b/packages/compiler/src/frontend/type-mapper.ts @@ -1556,7 +1556,8 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // parameter (variadic higher-order wrappers): the checker keeps both // conditionals symbolic inside the body, and their `infer` positions // match a call signature, which matchInferReference cannot decide. - const viaSignature = mapBoundSignatureAlias(type, ctx); + const viaSignature = + mapBoundSignatureAlias(type, ctx) ?? mapBoundKeyofNeverConditional(type, ctx); if (viaSignature !== null) { contextResolutions++; return viaSignature; @@ -3809,6 +3810,27 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { return { kind: "record", shapeId: perfEntryShape(ctx.shapes, ctx.unions) }; } // readline's getCursorPos() record, in Node's key order ({ cols, rows }). + // perf_hooks' EventLoopUtilization (performance.eventLoopUtilization's + // result): a plain data record in Node's key order. + if (isStdlibInterface("EventLoopUtilization")) { + return { + kind: "record", + shapeId: ctx.shapes.intern( + [ + { name: "active", type: F64 }, + { name: "idle", type: F64 }, + { name: "utilization", type: F64 }, + ], + false, + undefined, + ["idle", "active", "utilization"], + ), + }; + } + // monitorEventLoopDelay's IntervalHistogram: a numeric handle into the + // runtime's histogram table, like a timer handle. Its members lower over + // the handle (builtins/performance.ts). + if (isStdlibInterface("IntervalHistogram")) return F64; if (isStdlibInterface("CursorPos")) { return { kind: "record", @@ -4348,6 +4370,22 @@ function mapCallSignature(sig: ts.Signature, ctx: TypeMapperCtx): IrType | null ts.isParameter(decl) && (decl.questionToken !== undefined || decl.initializer !== undefined); let pt = mapType(checker.getTypeOfSymbol(p), ctx); + // A parameter a TypeScript program types `any` takes whatever the + // caller passes: the checked-dynamic slot, as an `any`-typed binding + // already is (dynFallbackType). Declaration files keep their paths. + // Only function declarations and spelled function types: lambdas and + // methods in object literals keep their existing checked-dynamic story. + if ( + !pt && + !rest && + decl !== undefined && + ts.isParameter(decl) && + decl.parent !== undefined && + (ts.isFunctionDeclaration(decl.parent) || ts.isFunctionTypeNode(decl.parent)) && + !decl.getSourceFile().isDeclarationFile && + (checker.getTypeOfSymbol(p).flags & ts.TypeFlags.Any) !== 0 + ) + pt = DYN; if ( decl && ts.isParameter(decl) && @@ -5122,7 +5160,51 @@ function mapGenericIndexedAccess(type: ts.Type, ctx: TypeMapperCtx): IrType | nu } if (!covered || covered.length === 0) return null; const first = covered[0]!; - return covered.every((t) => typeEquals(t, first)) ? first : null; + if (covered.every((t) => typeEquals(t, first))) return first; + // Only the `keyof T` index itself covers every field at once; a key + // parameter bound to string keeps its literal-key resolution below. + if (!(idx.isIndexType() && idx.getTarget() === obj)) return null; + // Fields of different scalar types (`Object.entries` over a bound record + // reads `T[keyof T]`): any one of them, the union of their types. + const arms: IrType[] = []; + for (const t of covered) { + const parts = t.kind === "union" ? (ctx.unions.get(t.unionId)?.arms ?? null) : [t]; + if (!parts) return null; + for (const part of parts) { + if ( + part.kind !== "f64" && + part.kind !== "string" && + part.kind !== "bool" && + part.kind !== "undefinedT" && + part.kind !== "nullT" + ) + return null; + if (!arms.some((a) => typeEquals(a, part))) arms.push(part); + } + } + if (arms.length === 1) return arms[0]!; + arms.sort((a, b) => (typeKey(a) < typeKey(b) ? -1 : 1)); + return { kind: "union", unionId: ctx.unions.intern(arms) }; +} + +/** `keyof T extends never ? A : B` over a bound record T (the lib's + * Object.entries element spelling): decided by whether the binding has any + * key. */ +function mapBoundKeyofNeverConditional(type: ts.Type, ctx: TypeMapperCtx): IrType | null { + const { resolveTypeParam, shapes } = ctx; + if (!resolveTypeParam || !type.isConditionalType()) return null; + const check = type.getCheckType(); + const ext = type.getExtendsType(); + if (!check || !ext || !(ext.flags & ts.TypeFlags.Never) || !check.isIndexType()) return null; + const target = check.getTarget(); + if (!target || !(target.flags & ts.TypeFlags.TypeParameter)) return null; + const bound = resolveTypeParam(target); + if (bound?.kind !== "record") return null; + const shape = shapes.get(bound.shapeId); + if (!shape || shape.tuple) return null; + const hasKeys = shape.fields.length > 0 || shape.indexValue !== undefined; + const branch = hasKeys ? type.getFalseType() : type.getTrueType(); + return branch ? mapType(branch, ctx) : null; } /** A mapped type over a type parameter of the instance being lowered (`{ diff --git a/packages/compiler/src/index.ts b/packages/compiler/src/index.ts index ca9dc4d18..92643c7b9 100644 --- a/packages/compiler/src/index.ts +++ b/packages/compiler/src/index.ts @@ -554,6 +554,7 @@ async function compileExecutableNative( tls: features.tls, tlsCa: features.tlsCa, cipher: features.cipher, + perfLoop: features.perfLoop, ...(onArtifactReady === undefined ? {} : { onArtifactReady }), ...(ffi === null ? {} diff --git a/packages/compiler/src/ir/builtin-signatures.ts b/packages/compiler/src/ir/builtin-signatures.ts index f3ad4db28..02943ae1b 100644 --- a/packages/compiler/src/ir/builtin-signatures.ts +++ b/packages/compiler/src/ir/builtin-signatures.ts @@ -361,6 +361,20 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ "perf.entryDetail": { argTypes: [F64], result: DYN }, "perf.entryKeep": { argTypes: [DYN], result: VOID }, "perf.entryRef": { argTypes: [F64], result: DYN }, + /** perf_hooks event-loop metrics (scr_perf_loop.c): loopStart is the + * loop's start in performance.now() ms (0 before it), idleTime the idle + * ms since. eldNew(resolution ms) creates a monitorEventLoopDelay + * histogram handle; enable/disable answer whether the state changed; + * eldPercentile expects Node's validated range; eldStat(which) reads + * 0 min, 1 max, 2 mean, 3 stddev, 4 count, 5 exceeds. */ + "perf.loopStart": { argTypes: [], result: F64 }, + "perf.idleTime": { argTypes: [], result: F64 }, + "perf.eldNew": { argTypes: [F64], result: F64 }, + "perf.eldEnable": { argTypes: [F64], result: BOOL }, + "perf.eldDisable": { argTypes: [F64], result: BOOL }, + "perf.eldReset": { argTypes: [F64], result: VOID }, + "perf.eldPercentile": { argTypes: [F64, F64], result: F64 }, + "perf.eldStat": { argTypes: [F64, F64], result: F64 }, /** process.memoryUsage(): memoryUsageTake samples the resident set and * the runtime heap once (MAY THROW Node's uv_resident_set_memory error) * and memoryUsageField(k) reads that sample (0 rss, 1 heapTotal, 2 diff --git a/packages/compiler/src/ir/runtime-features.ts b/packages/compiler/src/ir/runtime-features.ts index 6e71fe91c..b2628fa10 100644 --- a/packages/compiler/src/ir/runtime-features.ts +++ b/packages/compiler/src/ir/runtime-features.ts @@ -49,6 +49,9 @@ export interface RuntimeFeatures { tls: boolean; tlsCa: boolean; cipher: boolean; + /** perf_hooks event-loop metrics (eventLoopUtilization, + * monitorEventLoopDelay): scr_perf_loop.c. */ + perfLoop: boolean; } const DYN_ASYNC_LIB_FNS: ReadonlySet = new Set([ @@ -407,6 +410,7 @@ function scanRuntimeFeatures(mod: IrModule, stopAt?: keyof RuntimeFeatures): Run tls: false, tlsCa: false, cipher: false, + perfLoop: false, }; const keepGoing = (): boolean => stopAt === undefined || !features[stopAt]; let tables: TypeTables | undefined; @@ -505,6 +509,8 @@ function scanRuntimeFeatures(mod: IrModule, stopAt?: keyof RuntimeFeatures): Run // scr_cipher.c over the mbedTLS archive; DEP0182 reports through the // process-warning unit, and unknown output encodings render through // the inspect unit. + if (fn.startsWith("perf.loop") || fn === "perf.idleTime" || fn.startsWith("perf.eld")) + features.perfLoop = true; if (fn === "crypto.cipherNew") { features.cipher = true; features.dynAsync = true; diff --git a/packages/compiler/src/native/cli.ts b/packages/compiler/src/native/cli.ts index c0d89bbbf..cf0c32ee3 100644 --- a/packages/compiler/src/native/cli.ts +++ b/packages/compiler/src/native/cli.ts @@ -100,6 +100,7 @@ async function main(): Promise { nodeTest: false, tls: false, cipher: false, + perfLoop: false, tlsCa: false, }, options.optimization ?? "release", diff --git a/packages/compiler/src/native/prepare.ts b/packages/compiler/src/native/prepare.ts index bb901a674..90f4ca562 100644 --- a/packages/compiler/src/native/prepare.ts +++ b/packages/compiler/src/native/prepare.ts @@ -155,6 +155,7 @@ function validFeatures(value: unknown): value is NativeLinkFeatures { "tls", "tlsCa", "cipher", + "perfLoop", ].every((name) => typeof features[name] === "boolean"); } diff --git a/packages/compiler/src/native/sanitizer.test.ts b/packages/compiler/src/native/sanitizer.test.ts index 79984d6c5..3f7d96226 100644 --- a/packages/compiler/src/native/sanitizer.test.ts +++ b/packages/compiler/src/native/sanitizer.test.ts @@ -104,6 +104,7 @@ test.skipIf(process.platform !== "linux" && process.platform !== "darwin")( tls: false, tlsCa: false, cipher: false, + perfLoop: false, nativeFetch: false, netEffective: false, httpEffective: false, diff --git a/packages/compiler/surface-manifest.json b/packages/compiler/surface-manifest.json index 5cea78586..dc8ff48f8 100644 --- a/packages/compiler/surface-manifest.json +++ b/packages/compiler/surface-manifest.json @@ -1847,6 +1847,13 @@ "status": "static", "note": "recognized module (bare and node:-prefixed specifiers)" }, + { + "id": "node-builtin.perf_hooks.monitorEventLoopDelay", + "kind": "node-builtin", + "name": "perf_hooks.monitorEventLoopDelay", + "status": "static", + "note": "an unref'd sampling interval records the delay histogram; enable, disable, reset, percentile and the min, max, mean, stddev, count and exceeds readings are supported" + }, { "id": "node-builtin.perf_hooks.performance.clearMarks", "kind": "node-builtin", @@ -1859,6 +1866,13 @@ "name": "perf_hooks.performance.clearMeasures", "status": "static" }, + { + "id": "node-builtin.perf_hooks.performance.eventLoopUtilization", + "kind": "node-builtin", + "name": "perf_hooks.performance.eventLoopUtilization", + "status": "static", + "note": "active time is the main thread's CPU time since the loop started; the one- and two-argument difference forms are supported" + }, { "id": "node-builtin.perf_hooks.performance.getEntries", "kind": "node-builtin", diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6390-catch-binding-predicate.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6390-catch-binding-predicate.ts.json new file mode 100644 index 000000000..fcfb2852f --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6390-catch-binding-predicate.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6390-catch-binding-predicate.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6391-nullish-function-default.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6391-nullish-function-default.ts.json new file mode 100644 index 000000000..8200d301e --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6391-nullish-function-default.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6391-nullish-function-default.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6392-optional-chain-protocol-calls.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6392-optional-chain-protocol-calls.ts.json new file mode 100644 index 000000000..f2343714c --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6392-optional-chain-protocol-calls.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6392-optional-chain-protocol-calls.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6393-function-types-any-params.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6393-function-types-any-params.ts.json new file mode 100644 index 000000000..af0a9abe7 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6393-function-types-any-params.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6393-function-types-any-params.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6394-in-key-error-subclasses.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6394-in-key-error-subclasses.ts.json new file mode 100644 index 000000000..9df1e1014 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6394-in-key-error-subclasses.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6394-in-key-error-subclasses.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6395-generic-object-entries.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6395-generic-object-entries.ts.json new file mode 100644 index 000000000..879a09388 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6395-generic-object-entries.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6395-generic-object-entries.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6396-generic-any-instantiation.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6396-generic-any-instantiation.ts.json new file mode 100644 index 000000000..da41452ad --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6396-generic-any-instantiation.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6396-generic-any-instantiation.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6397-event-loop-metrics.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6397-event-loop-metrics.ts.json new file mode 100644 index 000000000..0a74534b0 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6397-event-loop-metrics.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6397-event-loop-metrics.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/event-loop-metrics.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/event-loop-metrics.ts.json new file mode 100644 index 000000000..66246403d --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/event-loop-metrics.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/node-types/event-loop-metrics.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/timeout-type.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/timeout-type.ts.json new file mode 100644 index 000000000..46f0b6e38 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/timeout-type.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/node-types/timeout-type.ts" + ], + "diags": [] +} diff --git a/packages/runtime-pack-common/runtime-pack-matrix.mjs b/packages/runtime-pack-common/runtime-pack-matrix.mjs index eab680a55..b603f37ee 100644 --- a/packages/runtime-pack-common/runtime-pack-matrix.mjs +++ b/packages/runtime-pack-common/runtime-pack-matrix.mjs @@ -62,6 +62,8 @@ const OPTIONAL = [ ["scr_async_dyn.c", any("dynAsync", "dynInvoke", "dc", "fileHandle", "nativeFetch")], // The performance timeline emits its buffer warning through scr_async_dyn.c. ["scr_perf.c", "dynAsync"], + // perf_hooks event-loop metrics, only for programs that read them. + ["scr_perf_loop.c", "perfLoop"], // Blob and File box their reads as dyn promises through scr_async_dyn.c. ["scr_blob.c", "dynAsync"], ["scr_zlib.c", "zlib"], diff --git a/packages/runtime/src/scr_lib.h b/packages/runtime/src/scr_lib.h index 4c973e3e5..4b1094d54 100644 --- a/packages/runtime/src/scr_lib.h +++ b/packages/runtime/src/scr_lib.h @@ -169,6 +169,21 @@ double scr_process_uptime(void); /* perf_hooks performance.now(): fractional ms since process start (the * uptime anchor — Node's timeOrigin for a compiled program). */ double scr_perf_now(void); +/* perf_hooks event-loop metrics (scr_perf_loop.c). loop_mark records the + * loop's start (the emitted main calls it before the loop runs, only in + * programs that read the metric); loop_start answers it (0 before) and + * idle_time the idle ms since. The eld_* family is monitorEventLoopDelay's + * IntervalHistogram over a handle; eld_percentile expects 0 < p <= 100; eld_stat which: 0 min, 1 max, 2 mean, 3 stddev, + * 4 count, 5 exceeds. */ +void scr_perf_loop_mark(void); +double scr_perf_loop_start(void); +double scr_perf_idle_time(void); +double scr_eld_new(double resolution); +bool scr_eld_enable(double handle); +bool scr_eld_disable(double handle); +void scr_eld_reset(double handle); +double scr_eld_percentile(double handle, double percentile); +double scr_eld_stat(double handle, double which); /* perf_hooks user timing (scr_perf.c): mark, measure, the getEntries * family and the clears take the call's dyn argument vector (borrowed) * and apply Node's validation and errors. mark and measure answer 1 and diff --git a/packages/runtime/src/scr_perf_loop.c b/packages/runtime/src/scr_perf_loop.c new file mode 100644 index 000000000..29d98ea29 --- /dev/null +++ b/packages/runtime/src/scr_perf_loop.c @@ -0,0 +1,305 @@ +/* node:perf_hooks event-loop metrics: performance.eventLoopUtilization() + * and monitorEventLoopDelay()'s IntervalHistogram. + * + * Event-loop utilization. Node measures idle time as the time its loop + * spends blocked in the poll phase. A compiled program's main thread is + * busy exactly while it runs on the CPU (or blocks in a synchronous call), + * so the thread's CPU time stands in for Node's active time: idle is the + * wall time since the loop started minus that CPU time. The loop start is + * marked by the emitted main just before the loop runs, and only in + * programs that ask for the metric; before it, utilization is all zeros, + * like Node during the main module's synchronous evaluation. + * + * Event-loop delay. Each histogram samples on an unref'd interval at its + * resolution and records the time between samples in nanoseconds, like + * Node's ELDHistogram (Histogram::RecordDelta). Values land in an + * HdrHistogram-shaped counts array (lowest 1, three significant figures: + * 2048 sub-buckets per power of two), so min, max, mean, stddev and + * percentiles answer the bucket values Node's hdr_histogram answers. */ + +#include "scr_runtime.h" + +#include +#include +#include +#include +#ifdef _WIN32 +#include +#endif + +/* ── event-loop utilization ─────────────────────────────────────────── */ + +static SCR_TL double scr_perf_loop_start_ms = 0; /* performance.now() at loop start */ +static SCR_TL double scr_perf_loop_cpu0_ms = 0; /* thread CPU time at loop start */ + +/* The calling thread's CPU time in milliseconds; 0 where unavailable. */ +static double scr_perf_thread_cpu_ms(void) { +#if defined(_WIN32) + FILETIME created, exited, kernel, user; + if (!GetThreadTimes(GetCurrentThread(), &created, &exited, &kernel, &user)) return 0; + ULARGE_INTEGER k, u; + k.LowPart = kernel.dwLowDateTime; + k.HighPart = kernel.dwHighDateTime; + u.LowPart = user.dwLowDateTime; + u.HighPart = user.dwHighDateTime; + return (double)(k.QuadPart + u.QuadPart) / 1e4; /* 100ns units */ +#elif defined(CLOCK_THREAD_CPUTIME_ID) && !defined(__wasi__) + struct timespec ts; + if (clock_gettime(CLOCK_THREAD_CPUTIME_ID, &ts) != 0) return 0; + return (double)ts.tv_sec * 1000.0 + (double)ts.tv_nsec / 1e6; +#else + return 0; +#endif +} + +void scr_perf_loop_mark(void) { + if (scr_perf_loop_start_ms > 0) return; + scr_perf_loop_cpu0_ms = scr_perf_thread_cpu_ms(); + double now = scr_perf_now(); + /* Node's loopStart is positive once set; a mark at the very first + * microsecond still counts as started. */ + scr_perf_loop_start_ms = now > 0 ? now : 1e-6; +} + +double scr_perf_loop_start(void) { return scr_perf_loop_start_ms; } + +double scr_perf_idle_time(void) { + if (scr_perf_loop_start_ms <= 0) return 0; + double wall = scr_perf_now() - scr_perf_loop_start_ms; + double busy = scr_perf_thread_cpu_ms() - scr_perf_loop_cpu0_ms; + double idle = wall - busy; + if (idle < 0) idle = 0; + if (idle > wall) idle = wall; + return idle; +} + +/* ── HdrHistogram-shaped counts (lowest 1, 3 significant figures) ───── */ + +#define SCR_HDR_SUB_BUCKET_HALF_MAG 10 +#define SCR_HDR_SUB_BUCKET_HALF (1 << SCR_HDR_SUB_BUCKET_HALF_MAG) /* 1024 */ +#define SCR_HDR_SUB_BUCKET_COUNT (SCR_HDR_SUB_BUCKET_HALF * 2) /* 2048 */ +#define SCR_HDR_SUB_BUCKET_MASK ((int64_t)SCR_HDR_SUB_BUCKET_COUNT - 1) + +typedef struct { + int64_t *counts; /* owned, grown on demand */ + size_t counts_len; + int64_t total; + int64_t min_value; /* INT64_MAX while empty */ + int64_t max_value; /* 0 while empty */ + double count; /* Node's count_ (successful records) */ + uint64_t prev_ns; /* RecordDelta's previous sample; 0 = none */ + double resolution; /* ms */ + double timer; /* the sampling interval's handle while enabled, else 0 */ + ScrClosure *tick; /* owned: the sampling callback, identifies this histogram */ + bool enabled; +} ScrEld; + +static int scr_hdr_bucket_index(int64_t value) { + int pow2ceiling = 64 - __builtin_clzll((uint64_t)(value | SCR_HDR_SUB_BUCKET_MASK)); + return pow2ceiling - (SCR_HDR_SUB_BUCKET_HALF_MAG + 1); +} + +static size_t scr_hdr_counts_index(int64_t value) { + int bucket = scr_hdr_bucket_index(value); + int64_t sub = value >> bucket; + return (size_t)(((int64_t)(bucket + 1) << SCR_HDR_SUB_BUCKET_HALF_MAG) + (sub - SCR_HDR_SUB_BUCKET_HALF)); +} + +static int64_t scr_hdr_value_at_index(size_t index) { + int bucket = (int)(index >> SCR_HDR_SUB_BUCKET_HALF_MAG) - 1; + int64_t sub = (int64_t)(index & (SCR_HDR_SUB_BUCKET_HALF - 1)) + SCR_HDR_SUB_BUCKET_HALF; + if (bucket < 0) { + sub -= SCR_HDR_SUB_BUCKET_HALF; + bucket = 0; + } + return sub << bucket; +} + +static int64_t scr_hdr_range_size(int64_t value) { + int bucket = scr_hdr_bucket_index(value); + int64_t sub = value >> bucket; + int adjusted = sub >= SCR_HDR_SUB_BUCKET_COUNT ? bucket + 1 : bucket; + return (int64_t)1 << adjusted; +} + +static int64_t scr_hdr_lowest_equivalent(int64_t value) { + int bucket = scr_hdr_bucket_index(value); + int64_t sub = value >> bucket; + return sub << bucket; +} + +static int64_t scr_hdr_highest_equivalent(int64_t value) { + return scr_hdr_lowest_equivalent(value) + scr_hdr_range_size(value) - 1; +} + +static int64_t scr_hdr_median_equivalent(int64_t value) { + return scr_hdr_lowest_equivalent(value) + (scr_hdr_range_size(value) >> 1); +} + +/* Newton's square root: keeps this unit free of a libm reference. */ +static double scr_eld_sqrt(double x) { + if (!(x > 0)) return x == 0 ? 0 : NAN; + double r = x >= 1 ? x : 1; + for (int i = 0; i < 64; i++) { + double next = 0.5 * (r + x / r); + if (next == r) break; + r = next; + } + return r; +} + +static void scr_eld_record(ScrEld *h, int64_t value) { + if (value < 0) return; + size_t index = scr_hdr_counts_index(value); + if (index >= h->counts_len) { + size_t len = h->counts_len ? h->counts_len : SCR_HDR_SUB_BUCKET_COUNT; + while (len <= index) len *= 2; + int64_t *grown = realloc(h->counts, len * sizeof(int64_t)); + if (grown == NULL) return; + for (size_t i = h->counts_len; i < len; i++) grown[i] = 0; + h->counts = grown; + h->counts_len = len; + } + h->counts[index]++; + h->total++; + if (value != 0 && value < h->min_value) h->min_value = value; + if (value > h->max_value) h->max_value = value; + h->count += 1; +} + +static void scr_eld_clear(ScrEld *h) { + for (size_t i = 0; i < h->counts_len; i++) h->counts[i] = 0; + h->total = 0; + h->min_value = INT64_MAX; + h->max_value = 0; + h->count = 0; + h->prev_ns = 0; +} + +/* ── monitorEventLoopDelay ──────────────────────────────────────────── */ + +static SCR_TL ScrEld **scr_elds; +static SCR_TL size_t scr_elds_len; + +static ScrEld *scr_eld_of(double handle) { + if (!(handle >= 1) || handle > (double)scr_elds_len) return NULL; + return scr_elds[(size_t)handle - 1]; +} + +static void scr_eld_tick(ScrClosure *self) { + for (size_t i = 0; i < scr_elds_len; i++) { + ScrEld *h = scr_elds[i]; + if (h->tick != self) continue; + uint64_t now = scr_hrtime_ns(); + if (h->prev_ns > 0 && now > h->prev_ns) scr_eld_record(h, (int64_t)(now - h->prev_ns)); + h->prev_ns = now; + return; + } +} + +/* Histograms live for the thread's lifetime (Node frees them with their + * JS object); release them when the script ends so their callbacks and + * counts do not outlive it. */ +static void scr_eld_cleanup(void) { + for (size_t i = 0; i < scr_elds_len; i++) { + ScrEld *h = scr_elds[i]; + scr_closure_release(h->tick); + free(h->counts); + free(h); + } + free(scr_elds); + scr_elds = NULL; + scr_elds_len = 0; +} + +double scr_eld_new(double resolution) { + if (scr_elds_len == 0 && scr_elds == NULL) scr_atexit(scr_eld_cleanup); + ScrEld *h = calloc(1, sizeof(ScrEld)); + ScrEld **grown = realloc(scr_elds, (scr_elds_len + 1) * sizeof(ScrEld *)); + if (h == NULL || grown == NULL) { + free(h); + if (grown != NULL) scr_elds = grown; + return 0; + } + scr_elds = grown; + h->min_value = INT64_MAX; + h->resolution = resolution >= 1 ? resolution : 10; + h->tick = scr_closure_new((void *)scr_eld_tick, 0); + scr_elds[scr_elds_len++] = h; + return (double)scr_elds_len; +} + +bool scr_eld_enable(double handle) { + ScrEld *h = scr_eld_of(handle); + if (h == NULL || h->enabled) return false; + h->enabled = true; + h->timer = scr_set_interval(scr_closure_retain(h->tick), h->resolution); + scr_timer_unref(h->timer); + return true; +} + +bool scr_eld_disable(double handle) { + ScrEld *h = scr_eld_of(handle); + if (h == NULL || !h->enabled) return false; + h->enabled = false; + scr_clear_interval(h->timer); + h->timer = 0; + return true; +} + +void scr_eld_reset(double handle) { + ScrEld *h = scr_eld_of(handle); + if (h != NULL) scr_eld_clear(h); +} + +/* hdr_value_at_percentile; the compiler checked Node's 0 < p <= 100 range. */ +double scr_eld_percentile(double handle, double percentile) { + ScrEld *h = scr_eld_of(handle); + if (h == NULL || h->total == 0) return 0; + double requested = percentile < 100.0 ? percentile : 100.0; + int64_t wanted = (int64_t)((requested / 100.0) * (double)h->total + 0.5); + if (wanted < 1) wanted = 1; + int64_t seen = 0; + for (size_t i = 0; i < h->counts_len; i++) { + seen += h->counts[i]; + if (seen >= wanted) return (double)scr_hdr_highest_equivalent(scr_hdr_value_at_index(i)); + } + return 0; +} + +/* which: 0 min, 1 max, 2 mean, 3 stddev, 4 count, 5 exceeds. */ +double scr_eld_stat(double handle, double which) { + ScrEld *h = scr_eld_of(handle); + int w = (int)which; + if (h == NULL) return w == 0 ? (double)INT64_MAX : w >= 2 && w <= 3 ? NAN : 0; + switch (w) { + case 0: + if (h->counts_len > 0 && h->counts[0] > 0) return 0; + return h->min_value == INT64_MAX ? (double)INT64_MAX + : (double)scr_hdr_lowest_equivalent(h->min_value); + case 1: + return h->max_value == 0 ? 0 : (double)scr_hdr_highest_equivalent(h->max_value); + case 2: + case 3: { + if (h->total == 0) return NAN; + double sum = 0; + for (size_t i = 0; i < h->counts_len; i++) { + if (h->counts[i] == 0) continue; + sum += (double)h->counts[i] * (double)scr_hdr_median_equivalent(scr_hdr_value_at_index(i)); + } + double mean = sum / (double)h->total; + if (w == 2) return mean; + double geo = 0; + for (size_t i = 0; i < h->counts_len; i++) { + if (h->counts[i] == 0) continue; + double dev = (double)scr_hdr_median_equivalent(scr_hdr_value_at_index(i)) - mean; + geo += dev * dev * (double)h->counts[i]; + } + return scr_eld_sqrt(geo / (double)h->total); + } + case 4: + return h->count; + default: + return 0; + } +} diff --git a/tests/corpus/6390-catch-binding-predicate.ts b/tests/corpus/6390-catch-binding-predicate.ts new file mode 100644 index 000000000..82c8797b5 --- /dev/null +++ b/tests/corpus/6390-catch-binding-predicate.ts @@ -0,0 +1,42 @@ +// A catch binding narrowed by a user type predicate reads its fields. +interface ErrorLike { + message: string; + name: string; + code?: string; +} +function isErrorLike(value: unknown): value is ErrorLike { + return typeof value === "object" && value !== null && "message" in value && "name" in value; +} +interface Coded { + code: string; +} +function isCoded(value: unknown): value is Coded { + return typeof value === "object" && value !== null && typeof (value as Coded).code === "string"; +} +function report(fn: () => void): void { + try { + fn(); + } catch (err) { + if (isErrorLike(err)) { + console.log("error", { message: err ? err.message : "none", name: err.name, code: err.code ?? "-" }); + if (err.message.includes("disk")) console.log("disk problem"); + } else { + console.log("not error-like", String(err)); + } + if (isCoded(err) && err.code === "ENOSPC") console.log("coded", err.code); + } +} +report(() => { + throw new Error("disk full"); +}); +report(() => { + const e = new Error("no space") as Error & { code?: string }; + e.code = "ENOSPC"; + throw e; +}); +report(() => { + throw "plain"; +}); +report(() => { + throw { message: "obj", name: "Custom", code: "ENOSPC" }; +}); diff --git a/tests/corpus/6391-nullish-function-default.ts b/tests/corpus/6391-nullish-function-default.ts new file mode 100644 index 000000000..6721d97bf --- /dev/null +++ b/tests/corpus/6391-nullish-function-default.ts @@ -0,0 +1,13 @@ +// `??` with a function fallback whose signature differs from the optional +// callback's (fewer parameters, narrower result). +interface Opts { + tags?: (a: number, b: number) => Record; + label?: (n: number) => string; +} +function run(o: Opts, x: number, y: number): string { + const tags = o.tags ?? (() => ({})); + const label = o.label ?? ((n: number) => `#${n}`); + return `${label(x)} ${JSON.stringify(tags(x, y))}`; +} +console.log(run({}, 1, 2)); +console.log(run({ tags: (a, b) => ({ a, b }), label: (n) => `n${n}` }, 3, 4)); diff --git a/tests/corpus/6392-optional-chain-protocol-calls.ts b/tests/corpus/6392-optional-chain-protocol-calls.ts new file mode 100644 index 000000000..a08a4ee85 --- /dev/null +++ b/tests/corpus/6392-optional-chain-protocol-calls.ts @@ -0,0 +1,47 @@ +// Optional calls on an interface-typed method chain (a do-nothing tracer). +interface Span { + addTags(tags: Record): Span; +} +interface Scope { + active(): Span | null; +} +interface Tracer { + scope(): Scope; +} +class NoopSpan implements Span { + addTags(tags: Record): Span { + console.log("tags", JSON.stringify(tags)); + return this; + } +} +class NoopScope implements Scope { + span: Span | null; + constructor(span: Span | null) { + this.span = span; + } + active(): Span | null { + return this.span; + } +} +class NoopTracer implements Tracer { + s: Scope; + constructor(s: Scope) { + this.s = s; + } + scope(): Scope { + return this.s; + } +} +const quiet: Tracer = new NoopTracer(new NoopScope(null)); +const loud: Tracer = new NoopTracer(new NoopScope(new NoopSpan())); +function tagActiveSpan(tracer: Tracer, tags: Record): void { + tracer.scope().active()?.addTags(tags); +} +tagActiveSpan(quiet, { a: 1 }); +tagActiveSpan(loud, { b: 2 }); + +// Narrowed locals and non-null assertions reach the same protocol calls. +const scope2: Scope = new NoopScope(new NoopSpan()); +const current = scope2.active(); +current?.addTags({ c: 3 }); +scope2.active()!.addTags({ d: 4 }); diff --git a/tests/corpus/6393-function-types-any-params.ts b/tests/corpus/6393-function-types-any-params.ts new file mode 100644 index 000000000..ec7799c9d --- /dev/null +++ b/tests/corpus/6393-function-types-any-params.ts @@ -0,0 +1,8 @@ +// Function values whose parameters are typed any (optional or not). +function build(limit?: any): string { + return `limit=${limit ?? "none"}`; +} +const fns: Array<(limit?: any) => string> = [build]; +console.log(fns[0]!(), fns[0]!(5)); +const take: (v: any) => number = (v) => (typeof v === "number" ? v : -1); +console.log(take(3), take("x")); diff --git a/tests/corpus/6394-in-key-error-subclasses.ts b/tests/corpus/6394-in-key-error-subclasses.ts new file mode 100644 index 000000000..cdb28a5a3 --- /dev/null +++ b/tests/corpus/6394-in-key-error-subclasses.ts @@ -0,0 +1,25 @@ +// `'key' in err` on Error receivers, including subclasses that declare it. +class HttpError extends Error { + status: number; + constructor(message: string, status: number) { + super(message); + this.status = status; + } +} +function describe(err: Error): string { + if ("status" in err) return `status ${(err as HttpError).status}`; + if ("code" in err) return "has code"; + return `plain ${err.message}`; +} +console.log(describe(new Error("x"))); +console.log(describe(new HttpError("nf", 404))); +class Timeout extends HttpError { + get retryable(): boolean { + return true; + } +} +function retryable(err: Error): boolean { + return "retryable" in err; +} +console.log(retryable(new Error("x")), retryable(new HttpError("a", 500)), retryable(new Timeout("t", 504))); +console.log(describe(new Timeout("t", 504))); diff --git a/tests/corpus/6395-generic-object-entries.ts b/tests/corpus/6395-generic-object-entries.ts new file mode 100644 index 000000000..772367a3a --- /dev/null +++ b/tests/corpus/6395-generic-object-entries.ts @@ -0,0 +1,7 @@ +// Object.entries over a generic record parameter. +function maskSecrets>(tags: T): Record { + return Object.fromEntries( + Object.entries(tags).map(([k, v]) => [k, k.includes("secret") ? "[redacted]" : v]), + ); +} +console.log(JSON.stringify(maskSecrets({ user: "ada", secretToken: "x", n: 1 }))); diff --git a/tests/corpus/6396-generic-any-instantiation.ts b/tests/corpus/6396-generic-any-instantiation.ts new file mode 100644 index 000000000..c3c9da060 --- /dev/null +++ b/tests/corpus/6396-generic-any-instantiation.ts @@ -0,0 +1,18 @@ +// A generic method instantiated at `any` from an untyped forwarding call. +type AnyFn = (...args: any) => any; +class Tracer { + trace(name: string, fn: () => T): T { + console.log("span", name); + return fn(); + } +} +const tracer = new Tracer(); +function traced(name: string, fn: F): F { + return function (this: unknown) { + const args = arguments; + const result = tracer.trace(name, () => fn.apply(this, args as any)); + return result; + } as F; +} +const add = traced("add", (a: number, b: number) => a + b); +console.log(add(1, 2)); diff --git a/tests/corpus/6397-event-loop-metrics.ts b/tests/corpus/6397-event-loop-metrics.ts new file mode 100644 index 000000000..e1c3829ac --- /dev/null +++ b/tests/corpus/6397-event-loop-metrics.ts @@ -0,0 +1,49 @@ +// Event-loop monitoring under @types/node: monitorEventLoopDelay's histogram, +// eventLoopUtilization deltas, and an unref'd interval typed NodeJS.Timeout. +import { monitorEventLoopDelay, performance } from "node:perf_hooks"; + +const histogram = monitorEventLoopDelay({ resolution: 10 }); +console.log("enable", histogram.enable(), histogram.enable()); +const first = performance.eventLoopUtilization(); +console.log("before loop", first.idle, first.active, first.utilization); + +let last = performance.eventLoopUtilization(); +let ticks = 0; +// The unref'd interval keeps nothing alive; this timer holds the loop open +// until the third sample. +const keepAlive = setTimeout(() => console.log("never"), 10_000); +const interval = setInterval(() => { + const now = performance.eventLoopUtilization(); + const delta = performance.eventLoopUtilization(now, last); + last = now; + ticks++; + const p50 = histogram.percentile(50); + const p99 = histogram.percentile(99); + console.log( + "tick", + ticks, + delta.utilization >= 0 && delta.utilization <= 1, + delta.idle >= 0, + p50 > 0, + p99 >= p50, + histogram.max >= p99, + histogram.min <= p50, + histogram.count > 0, + ); + histogram.reset(); + console.log("after reset", histogram.count, histogram.max, histogram.min, Number.isNaN(histogram.mean)); + if (ticks === 3) { + clearInterval(interval); + clearTimeout(keepAlive); + console.log("disable", histogram.disable(), histogram.disable()); + const since = performance.eventLoopUtilization(first); + console.log("since", since.utilization > 0 && since.utilization <= 1, since.idle > 0); + try { + histogram.percentile(0); + } catch (e) { + console.log((e as Error).message, (e as { code?: string }).code); + } + } +}, 50); +interval.unref(); +console.log("hasRef", interval.hasRef()); diff --git a/tests/fixtures/node-types/README.md b/tests/fixtures/node-types/README.md index 0784c57fe..243ce0b81 100644 --- a/tests/fixtures/node-types/README.md +++ b/tests/fixtures/node-types/README.md @@ -21,6 +21,7 @@ What the fixture pins (see tests/harness/project-config.test.ts): (process.uptime, Buffer.from, setInterval) reports the SC2020-family fence naming @types/node, instead of typechecking its way into a broken binary — and never a raw "Cannot find name" error. +- `event-loop-metrics.ts` and `timeout-type.ts` — performance.eventLoopUtilization, monitorEventLoopDelay's IntervalHistogram and NodeJS.Timeout as declared types lower under @types/node exactly as under the fallback declarations. - `source-import/` — an imported TypeScript source may use the `RequestInfo` global supplied by TypeScript's default DOM lib while scriptc adopts the project's @types/node declarations. Projects WITHOUT @types/node (every other fixture and the whole corpus) keep diff --git a/tests/fixtures/node-types/event-loop-metrics.ts b/tests/fixtures/node-types/event-loop-metrics.ts new file mode 100644 index 000000000..f5c97c41b --- /dev/null +++ b/tests/fixtures/node-types/event-loop-metrics.ts @@ -0,0 +1,49 @@ +// Event-loop monitoring under @types/node: monitorEventLoopDelay's histogram, +// eventLoopUtilization deltas, and an unref'd interval typed NodeJS.Timeout. +import { monitorEventLoopDelay, performance } from "node:perf_hooks"; + +const histogram = monitorEventLoopDelay({ resolution: 10 }); +console.log("enable", histogram.enable(), histogram.enable()); +const first = performance.eventLoopUtilization(); +console.log("before loop", first.idle, first.active, first.utilization); + +let last = performance.eventLoopUtilization(); +let ticks = 0; +// The unref'd interval keeps nothing alive; this timer holds the loop open +// until the third sample. +const keepAlive = setTimeout(() => console.log("never"), 10_000); +const interval: NodeJS.Timeout = setInterval(() => { + const now = performance.eventLoopUtilization(); + const delta = performance.eventLoopUtilization(now, last); + last = now; + ticks++; + const p50 = histogram.percentile(50); + const p99 = histogram.percentile(99); + console.log( + "tick", + ticks, + delta.utilization >= 0 && delta.utilization <= 1, + delta.idle >= 0, + p50 > 0, + p99 >= p50, + histogram.max >= p99, + histogram.min <= p50, + histogram.count > 0, + ); + histogram.reset(); + console.log("after reset", histogram.count, histogram.max, histogram.min, Number.isNaN(histogram.mean)); + if (ticks === 3) { + clearInterval(interval); + clearTimeout(keepAlive); + console.log("disable", histogram.disable(), histogram.disable()); + const since = performance.eventLoopUtilization(first); + console.log("since", since.utilization > 0 && since.utilization <= 1, since.idle > 0); + try { + histogram.percentile(0); + } catch (e) { + console.log((e as Error).message, (e as { code?: string }).code); + } + } +}, 50); +interval.unref(); +console.log("hasRef", interval.hasRef()); diff --git a/tests/fixtures/node-types/timeout-type.ts b/tests/fixtures/node-types/timeout-type.ts new file mode 100644 index 000000000..42daee86d --- /dev/null +++ b/tests/fixtures/node-types/timeout-type.ts @@ -0,0 +1,30 @@ +// NodeJS.Timeout as a declared type under @types/node: fields, clearTimeout, +// ref/unref/hasRef, refresh. +class Poller { + private timer: NodeJS.Timeout | undefined; + private count = 0; + start(): void { + this.timer = setInterval(() => { + this.count++; + console.log("poll", this.count); + if (this.count === 3) this.stop(); + }, 5); + } + stop(): void { + if (this.timer) { + clearInterval(this.timer); + this.timer = undefined; + console.log("stopped"); + } + } +} +new Poller().start(); +const t: NodeJS.Timeout = setTimeout(() => console.log("never"), 1000); +console.log(t.hasRef()); +t.unref(); +console.log(t.hasRef()); +t.ref(); +console.log(t.hasRef()); +clearTimeout(t); +const timers: NodeJS.Timeout[] = [setTimeout(() => console.log("a"), 30), setTimeout(() => console.log("b"), 31)]; +console.log(timers.length); diff --git a/tests/harness/project-config.test.ts b/tests/harness/project-config.test.ts index bf57a830a..4b307b1d9 100644 --- a/tests/harness/project-config.test.ts +++ b/tests/harness/project-config.test.ts @@ -52,6 +52,32 @@ test("node-types: filesystem capacity supports typed number and BigInt results u expect(comparableStderr(native.stderr)).toBe(node.stderr); }); +for (const [name, title] of [ + [ + "event-loop-metrics", + "eventLoopUtilization and monitorEventLoopDelay follow @types/node's types", + ], + ["timeout-type", "NodeJS.Timeout fields, ref/unref/hasRef and clearInterval under @types/node"], +] as const) { + test(`node-types: ${title}`, async () => { + const outDir = outDirFor(name); + const entry = join(nodeTypesDir, `${name}.ts`); + const result = await compile(entry, { + outPath: join(outDir, process.platform === "win32" ? `${name}.exe` : name), + outDir, + sanitize, + }); + expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true); + if (!result.ok) return; + const [native, node] = await Promise.all([ + execFileAsync(result.binaryPath), + execFileAsync(process.execPath, ["--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", entry]), + ]); + expect(native.stdout).toBe(node.stdout); + expect(comparableStderr(native.stderr)).toBe(node.stderr); + }); +} + test("node-types: http header arrays and server.address() follow @types/node's types", async () => { const outDir = outDirFor("http-headers-address"); const entry = join(nodeTypesDir, "http-headers-address.ts"); diff --git a/tests/harness/self-hosting-native-toolchain.test.ts b/tests/harness/self-hosting-native-toolchain.test.ts index c22e8a839..39edd5567 100644 --- a/tests/harness/self-hosting-native-toolchain.test.ts +++ b/tests/harness/self-hosting-native-toolchain.test.ts @@ -42,6 +42,7 @@ const features: NativeLinkFeatures = { tls: false, tlsCa: false, cipher: false, + perfLoop: false, }; test("native toolchain configuration, staging and helper validation are static", async () => { diff --git a/tests/harness/self-hosting-runtime-pack.test.ts b/tests/harness/self-hosting-runtime-pack.test.ts index 08eb45754..7aee06559 100644 --- a/tests/harness/self-hosting-runtime-pack.test.ts +++ b/tests/harness/self-hosting-runtime-pack.test.ts @@ -43,6 +43,7 @@ const base: NativeLinkFeatures = { tls: false, tlsCa: false, cipher: false, + perfLoop: false, }; test("runtime manifest validation and selection are entirely static", async () => { From fa012f5da3ecc2fd5e818164678bdea271cb0e0f Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Mon, 19 Oct 2026 19:51:26 -0700 Subject: [PATCH 033/122] Name the corpus programs instead of numbering them The numbers collided with other open changes. Co-Authored-By: Claude Opus 5.5 --- .../tests/corpus/6260-local-class-detached-method.ts.json | 6 ------ .../tests/corpus/6262-comma-null-union-return.cjs.json | 6 ------ .../tests/corpus/6263-strict-arguments-parameters.cjs.json | 6 ------ .../tests/corpus/comma-null-union-return.cjs.json | 6 ++++++ .../tests/corpus/local-class-detached-method.ts.json | 6 ++++++ .../tests/corpus/strict-arguments-parameters.cjs.json | 6 ++++++ ...ma-null-union-return.cjs => comma-null-union-return.cjs} | 0 ...ss-detached-method.ts => local-class-detached-method.ts} | 0 ...ments-parameters.cjs => strict-arguments-parameters.cjs} | 0 9 files changed, 18 insertions(+), 18 deletions(-) delete mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6260-local-class-detached-method.ts.json delete mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6262-comma-null-union-return.cjs.json delete mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6263-strict-arguments-parameters.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/comma-null-union-return.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/local-class-detached-method.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/strict-arguments-parameters.cjs.json rename tests/corpus/{6262-comma-null-union-return.cjs => comma-null-union-return.cjs} (100%) rename tests/corpus/{6260-local-class-detached-method.ts => local-class-detached-method.ts} (100%) rename tests/corpus/{6263-strict-arguments-parameters.cjs => strict-arguments-parameters.cjs} (100%) diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6260-local-class-detached-method.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6260-local-class-detached-method.ts.json deleted file mode 100644 index d73c701d7..000000000 --- a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6260-local-class-detached-method.ts.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "order": [ - "/tests/corpus/6260-local-class-detached-method.ts" - ], - "diags": [] -} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6262-comma-null-union-return.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6262-comma-null-union-return.cjs.json deleted file mode 100644 index e167cc977..000000000 --- a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6262-comma-null-union-return.cjs.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "order": [ - "/tests/corpus/6262-comma-null-union-return.cjs" - ], - "diags": [] -} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6263-strict-arguments-parameters.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6263-strict-arguments-parameters.cjs.json deleted file mode 100644 index b7ff6a960..000000000 --- a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6263-strict-arguments-parameters.cjs.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "order": [ - "/tests/corpus/6263-strict-arguments-parameters.cjs" - ], - "diags": [] -} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/comma-null-union-return.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/comma-null-union-return.cjs.json new file mode 100644 index 000000000..1fad3c559 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/comma-null-union-return.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/comma-null-union-return.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/local-class-detached-method.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/local-class-detached-method.ts.json new file mode 100644 index 000000000..6d311fce9 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/local-class-detached-method.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/local-class-detached-method.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/strict-arguments-parameters.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/strict-arguments-parameters.cjs.json new file mode 100644 index 000000000..8c9cc9279 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/strict-arguments-parameters.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/strict-arguments-parameters.cjs" + ], + "diags": [] +} diff --git a/tests/corpus/6262-comma-null-union-return.cjs b/tests/corpus/comma-null-union-return.cjs similarity index 100% rename from tests/corpus/6262-comma-null-union-return.cjs rename to tests/corpus/comma-null-union-return.cjs diff --git a/tests/corpus/6260-local-class-detached-method.ts b/tests/corpus/local-class-detached-method.ts similarity index 100% rename from tests/corpus/6260-local-class-detached-method.ts rename to tests/corpus/local-class-detached-method.ts diff --git a/tests/corpus/6263-strict-arguments-parameters.cjs b/tests/corpus/strict-arguments-parameters.cjs similarity index 100% rename from tests/corpus/6263-strict-arguments-parameters.cjs rename to tests/corpus/strict-arguments-parameters.cjs From c1a9341739c469c1f32ed7eb07b6aa20555c4364 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Mon, 19 Oct 2026 19:51:39 -0700 Subject: [PATCH 034/122] Name the module truthiness corpus program instead of numbering it Its number collided with another open change. Co-Authored-By: Claude Opus 5.5 --- .../tests/corpus/6264-required-module-truthiness.cjs.json | 6 ------ .../tests/corpus/required-module-truthiness.cjs.json | 6 ++++++ ...module-truthiness.cjs => required-module-truthiness.cjs} | 0 3 files changed, 6 insertions(+), 6 deletions(-) delete mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6264-required-module-truthiness.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/required-module-truthiness.cjs.json rename tests/corpus/{6264-required-module-truthiness.cjs => required-module-truthiness.cjs} (100%) diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6264-required-module-truthiness.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6264-required-module-truthiness.cjs.json deleted file mode 100644 index 2fa691953..000000000 --- a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6264-required-module-truthiness.cjs.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "order": [ - "/tests/corpus/6264-required-module-truthiness.cjs" - ], - "diags": [] -} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/required-module-truthiness.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/required-module-truthiness.cjs.json new file mode 100644 index 000000000..101bb2d57 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/required-module-truthiness.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/required-module-truthiness.cjs" + ], + "diags": [] +} diff --git a/tests/corpus/6264-required-module-truthiness.cjs b/tests/corpus/required-module-truthiness.cjs similarity index 100% rename from tests/corpus/6264-required-module-truthiness.cjs rename to tests/corpus/required-module-truthiness.cjs From 33588981ae38c6a7d6380024e014dad5cc07abd0 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Mon, 19 Oct 2026 19:59:40 -0700 Subject: [PATCH 035/122] Bake mixed JSON tables and destructure unions of arrays and tuples JSON documents whose rows mix strings and numbers (`(string | number)[][]`, `(string | number[])[][]`, the character-set and code-point tables of iconv-lite and tr46) now bake: comptime literals take union-typed elements and pick each value's arm from its JSON kind and contents. Array destructuring of a union of array and tuple types (`const [m, a, b] = re.exec(s) || ["", "", ""]`) reads each position from the arm the value holds: a tuple arm answers its field or undefined past its end, an array arm its element or undefined past its end. Unions of tuple types no longer pick the checker's `length` literal as a discriminant: tuple shapes have no such slot. Co-Authored-By: Claude Opus 5.5 --- .../src/frontend/lowering/array-values.ts | 60 ++++++++++++--- .../src/frontend/lowering/lower-comptime.ts | 73 ++++++++++++++++++- .../src/frontend/lowering/lower-stmts.ts | 12 ++- packages/compiler/src/frontend/type-mapper.ts | 3 + .../main.ts.json | 6 ++ ...11-union-array-tuple-destructuring.ts.json | 6 ++ .../6410-json-mixed-scalar-tables/extra.json | 1 + .../6410-json-mixed-scalar-tables/main.ts | 21 ++++++ .../node_modules/code-table/index.d.ts | 2 + .../node_modules/code-table/index.js | 10 +++ .../node_modules/code-table/package.json | 1 + .../node_modules/code-table/table.json | 1 + .../node_modules/range-map/index.d.ts | 2 + .../node_modules/range-map/index.js | 37 ++++++++++ .../node_modules/range-map/package.json | 1 + .../node_modules/range-map/table.json | 1 + .../6411-union-array-tuple-destructuring.ts | 34 +++++++++ 17 files changed, 255 insertions(+), 16 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6410-json-mixed-scalar-tables/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6411-union-array-tuple-destructuring.ts.json create mode 100644 tests/corpus/6410-json-mixed-scalar-tables/extra.json create mode 100644 tests/corpus/6410-json-mixed-scalar-tables/main.ts create mode 100644 tests/corpus/6410-json-mixed-scalar-tables/node_modules/code-table/index.d.ts create mode 100644 tests/corpus/6410-json-mixed-scalar-tables/node_modules/code-table/index.js create mode 100644 tests/corpus/6410-json-mixed-scalar-tables/node_modules/code-table/package.json create mode 100644 tests/corpus/6410-json-mixed-scalar-tables/node_modules/code-table/table.json create mode 100644 tests/corpus/6410-json-mixed-scalar-tables/node_modules/range-map/index.d.ts create mode 100644 tests/corpus/6410-json-mixed-scalar-tables/node_modules/range-map/index.js create mode 100644 tests/corpus/6410-json-mixed-scalar-tables/node_modules/range-map/package.json create mode 100644 tests/corpus/6410-json-mixed-scalar-tables/node_modules/range-map/table.json create mode 100644 tests/corpus/6411-union-array-tuple-destructuring.ts diff --git a/packages/compiler/src/frontend/lowering/array-values.ts b/packages/compiler/src/frontend/lowering/array-values.ts index 8f2b28a3f..e9148b244 100644 --- a/packages/compiler/src/frontend/lowering/array-values.ts +++ b/packages/compiler/src/frontend/lowering/array-values.ts @@ -84,8 +84,11 @@ export function arrayValueRead( }; } -/** Read a stabilized union of native arrays without copying either the - * receiver or its elements. Every arm contributes its missing-value case. */ +/** Read one position of a stabilized union of native arrays and tuples + * (`const [a, b] = re.exec(s) || ["", ""]`) without copying the receiver + * or its elements. Every array arm contributes its missing-value case; a + * tuple arm answers its field, or undefined past its end. Tuple arms need + * a constant position. */ export function unionArrayValueRead( lowerer: Lowerer, value: IrExpr, @@ -96,17 +99,32 @@ export function unionArrayValueRead( const unionId = value.type.unionId; const arms = lowerer.unions.get(unionId)?.arms; if (!arms?.length) return null; + const position = index.kind === "numLit" ? String(index.value) : null; + // A tuple arm's answer at the position: its field, or undefined past + // its end. Null when the arm is neither an array nor a tuple. + const tupleField = (arm: IrType): { name: string; type: IrType } | "past" | null => { + if (arm.kind !== "record" || position === null) return null; + const shape = lowerer.shapes.get(arm.shapeId); + if (!shape?.tuple) return null; + return shape.fields.find((f) => f.name === position) ?? "past"; + }; // Keep the collection's IrType union layout and narrow each element at // its use; an inferred array predicate changes the collection's ABI. - for (const arm of arms) if (arm.kind !== "array") return null; + for (const arm of arms) if (arm.kind !== "array" && tupleField(arm) === null) return null; const answers = new Map(); for (const arm of arms) { - if (arm.kind !== "array") return null; - const answer = arrayValueType(lowerer, arm.elem); + const field = arm.kind === "array" ? null : tupleField(arm); + const answer = + arm.kind === "array" + ? arrayValueType(lowerer, arm.elem) + : field === "past" + ? UNDEFINED_T + : field!.type; const parts = answer.kind === "union" ? lowerer.unions.get(answer.unionId)!.arms : [answer]; for (const part of parts) answers.set(typeKey(part), part); } const joined = [...answers.values()].sort((a, b) => (typeKey(a) < typeKey(b) ? -1 : 1)); + if (joined.length === 1 && joined[0]!.kind === "undefinedT") return null; if ( joined.length > 1 && (!unionContainerArmsOk(joined) || @@ -118,12 +136,34 @@ export function unionArrayValueRead( let result: IrExpr | null = null; for (let tag = arms.length - 1; tag >= 0; tag--) { const arm = arms[tag]!; - if (arm.kind !== "array") return null; const receiver: IrExpr = { kind: "unionNarrow", unionId, tag, value, type: arm, loc }; - const read = lowerer.coerceToExpected( - arrayValueRead(lowerer, receiver, index, arm.elem, loc), - type, - ); + let read: IrExpr; + if (arm.kind === "array") { + read = lowerer.coerceToExpected( + arrayValueRead(lowerer, receiver, index, arm.elem, loc), + type, + ); + } else { + const field = tupleField(arm); + if (field === null) return null; + if (field === "past") { + const missing = lowerer.wrappedUndefined(type, loc); + if (!missing) return null; + read = missing; + } else { + read = lowerer.coerceToExpected( + { + kind: "recordGet", + obj: receiver, + shapeId: (arm as IrType & { kind: "record" }).shapeId, + field: field.name, + type: field.type, + loc, + }, + type, + ); + } + } result = result === null ? read diff --git a/packages/compiler/src/frontend/lowering/lower-comptime.ts b/packages/compiler/src/frontend/lowering/lower-comptime.ts index 41c6704c5..31fa5afe0 100644 --- a/packages/compiler/src/frontend/lowering/lower-comptime.ts +++ b/packages/compiler/src/frontend/lowering/lower-comptime.ts @@ -4,7 +4,7 @@ import { InternalCompilerError } from "../../errors.js"; * literals — records/arrays/unions included, subject to comptimeBakeable. */ import * as ts from "../ts7/adapter.js"; import type { Lowerer } from "./lowerer.js"; -import { type IrExpr, type IrType } from "../../ir/ir.js"; +import { isUnitType, type IrExpr, type IrType } from "../../ir/ir.js"; import { comptimeFailedDiag } from "../../diagnostics/diagnostic.js"; import { locOf } from "../program.js"; import { PoisonError } from "./lowerer.js"; @@ -110,8 +110,9 @@ function comptimeFailureDetail(error: unknown): string { /** True when a comptime result of this type can be written as a literal * expression: number/string/boolean, arrays and record shapes of those, - * nested. Closures, class instances, unions, promises, dyn, and void - * cannot be baked. */ + * and unions of those with null or undefined (the mixed rows of a JSON + * table: `(string | number)[][]`), nested. Closures, class instances, + * promises, dyn, and void cannot be baked. */ export function comptimeBakeable(lowerer: Lowerer, t: IrType): boolean { switch (t.kind) { case "f64": @@ -124,6 +125,52 @@ export function comptimeBakeable(lowerer: Lowerer, t: IrType): boolean { const shape = lowerer.shapes.get(t.shapeId); return !!shape && shape.fields.every((f) => lowerer.comptimeBakeable(f.type)); } + case "union": { + const arms = lowerer.unions.get(t.unionId)?.arms; + return ( + arms !== undefined && + arms.every( + (arm) => isUnitType(arm) || (arm.kind !== "union" && comptimeBakeable(lowerer, arm)), + ) + ); + } + default: + return false; + } +} + +/** Whether `value` bakes into the bakeable type `t` without a mismatch: + * the arm choice of a union literal. Records are exact, as in + * comptimeValueToIr; an absent field reads as undefined. */ +function comptimeFits(lowerer: Lowerer, value: unknown, t: IrType): boolean { + switch (t.kind) { + case "f64": + return typeof value === "number" && Number.isFinite(value); + case "string": + return typeof value === "string"; + case "bool": + return typeof value === "boolean"; + case "nullT": + return value === null; + case "undefinedT": + return value === undefined; + case "array": + return Array.isArray(value) && value.every((el) => comptimeFits(lowerer, el, t.elem)); + case "record": { + if (typeof value !== "object" || value === null || Array.isArray(value)) return false; + const shape = lowerer.shapes.get(t.shapeId); + if (!shape) return false; + const obj = value as Record; + const declared = new Set(shape.fields.map((f) => f.name)); + return ( + Object.keys(obj).every((key) => declared.has(key)) && + shape.fields.every((f) => comptimeFits(lowerer, obj[f.name], f.type)) + ); + } + case "union": + return (lowerer.unions.get(t.unionId)?.arms ?? []).some((arm) => + comptimeFits(lowerer, value, arm), + ); default: return false; } @@ -265,6 +312,26 @@ export function comptimeValueToIr( })); return { kind: "recordLit", fields, type: expected, loc }; } + case "union": { + // The first arm the value fits, in the union's arm order: a JSON + // value fits exactly one scalar arm, and array or record arms are + // told apart by their contents. + const arms = lowerer.unions.get(expected.unionId)!.arms; // bakeable-checked + const tag = arms.findIndex((arm) => comptimeFits(lowerer, value, arm)); + if (tag < 0) fail(describeComptimeValue(value)); + const arm = arms[tag]!; + const payload: IrExpr = isUnitType(arm) + ? { kind: "unitLit", unit: arm.kind === "nullT" ? "null" : "undefined", type: arm, loc } + : lowerer.comptimeValueToIr(value, arm, path, blame); + return { + kind: "unionWrap", + unionId: expected.unionId, + tag, + value: payload, + type: expected, + loc, + }; + } default: // comptimeBakeable already fenced these off. throw new InternalCompilerError(`lowerer bug: unbakeable comptime target '${expected.kind}'`); diff --git a/packages/compiler/src/frontend/lowering/lower-stmts.ts b/packages/compiler/src/frontend/lowering/lower-stmts.ts index 2c36e4973..444fb899c 100644 --- a/packages/compiler/src/frontend/lowering/lower-stmts.ts +++ b/packages/compiler/src/frontend/lowering/lower-stmts.ts @@ -2379,20 +2379,26 @@ export function lowerBindingPattern( lowerer.fenceStaticHeadersIteration(pattern); if ( srcType.kind === "union" && - lowerer.unions.get(srcType.unionId)?.arms.every((arm) => arm.kind === "array") + lowerer.unions + .get(srcType.unionId) + ?.arms.every( + (arm) => + arm.kind === "array" || + (arm.kind === "record" && lowerer.shapes.get(arm.shapeId)?.tuple === true), + ) ) { for (let i = 0; i < pattern.elements.nodes.length; i++) { const el = pattern.elements.nodes[i]!; if (ts.isOmittedExpression(el) || el.name === undefined) continue; if (el.dotDotDotToken) - lowerer.unsupported("SC1031", el, "rest elements over a union of array layouts"); + lowerer.unsupported("SC1031", el, "rest elements over a union of array or tuple layouts"); const loc = locOf(el); let value = unionArrayValueRead(lowerer, srcRef(), numLit(i, loc), loc); if (!value) lowerer.unsupported( "SC1031", el, - "array union elements without a common native representation", + "array or tuple union elements without a common native representation", ); if (el.initializer) value = applyBindingDefault(lowerer, el, value); lowerer.bindPatternTarget(el.name, value, isLet, out); diff --git a/packages/compiler/src/frontend/type-mapper.ts b/packages/compiler/src/frontend/type-mapper.ts index 437aba2b8..eea7dd976 100644 --- a/packages/compiler/src/frontend/type-mapper.ts +++ b/packages/compiler/src/frontend/type-mapper.ts @@ -4411,6 +4411,9 @@ function unionDiscriminant( ): IrUnionDiscriminant | undefined { if (arms.filter((arm) => arm.kind === "record").length < 2 || parts.length < 2) return undefined; const { checker } = ctx; + // Tuple shapes keep positions only: their checker `length` literal has + // no slot to compare (discriminantField refuses tuple shapes too). + if (parts.some((part) => ctx.shapes.get(part.mapped.shapeId)?.tuple)) return undefined; for (const candidate of checker.getPropertiesOfType(parts[0]!.source)) { if (candidate.name.startsWith("__@")) continue; const byTag = new Map(); diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6410-json-mixed-scalar-tables/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6410-json-mixed-scalar-tables/main.ts.json new file mode 100644 index 000000000..c7ec1ab56 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6410-json-mixed-scalar-tables/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6410-json-mixed-scalar-tables/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6411-union-array-tuple-destructuring.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6411-union-array-tuple-destructuring.ts.json new file mode 100644 index 000000000..94d8b78e7 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6411-union-array-tuple-destructuring.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6411-union-array-tuple-destructuring.ts" + ], + "diags": [] +} diff --git a/tests/corpus/6410-json-mixed-scalar-tables/extra.json b/tests/corpus/6410-json-mixed-scalar-tables/extra.json new file mode 100644 index 000000000..927c5d968 --- /dev/null +++ b/tests/corpus/6410-json-mixed-scalar-tables/extra.json @@ -0,0 +1 @@ +[["x", 1, "two", 3], [4, "five"], ["six"]] diff --git a/tests/corpus/6410-json-mixed-scalar-tables/main.ts b/tests/corpus/6410-json-mixed-scalar-tables/main.ts new file mode 100644 index 000000000..54e337b02 --- /dev/null +++ b/tests/corpus/6410-json-mixed-scalar-tables/main.ts @@ -0,0 +1,21 @@ +// JSON tables whose rows mix strings and numbers, the shape of a +// character-set table (`[["8140", 12288, 65292], ...]`) and of a +// code-point range map (`[[[0, 44], "disallowed"], [[65, 65], "mapped", +// [97]], ...]`). A package requires them at load or inside a function, and +// an ES module imports one; each row keeps its strings and numbers. +import { lookup, rowCount } from "code-table"; +import { mapChars, status } from "range-map"; +import extra from "./extra.json" with { type: "json" }; + +console.log(lookup("8150"), lookup("a1"), lookup("zzzz"), rowCount()); +const mapped = mapChars("AB-c9"); +console.log(mapped.text, mapped.error); +console.log(JSON.stringify(mapChars("a,b")), JSON.stringify(mapChars("{"))); +console.log(status(10), status(46), status(66), status(1000)); + +for (const row of extra) { + const head = row[0]; + const rest = row.slice(1); + console.log(typeof head, head, rest.length, rest.map((v) => (typeof v === "number" ? v + 1 : v.toUpperCase())).join(",")); +} +console.log(JSON.stringify(extra)); diff --git a/tests/corpus/6410-json-mixed-scalar-tables/node_modules/code-table/index.d.ts b/tests/corpus/6410-json-mixed-scalar-tables/node_modules/code-table/index.d.ts new file mode 100644 index 000000000..ae7db1d98 --- /dev/null +++ b/tests/corpus/6410-json-mixed-scalar-tables/node_modules/code-table/index.d.ts @@ -0,0 +1,2 @@ +export declare function lookup(code: string): number | string; +export declare function rowCount(): number; diff --git a/tests/corpus/6410-json-mixed-scalar-tables/node_modules/code-table/index.js b/tests/corpus/6410-json-mixed-scalar-tables/node_modules/code-table/index.js new file mode 100644 index 000000000..dc10bb817 --- /dev/null +++ b/tests/corpus/6410-json-mixed-scalar-tables/node_modules/code-table/index.js @@ -0,0 +1,10 @@ +'use strict'; +const table = require('./table.json'); +exports.lookup = function (code) { + for (const row of table) if (row[0] === code) return row[1]; + return -1; +}; +exports.rowCount = function () { + // A second require of the same document answers the same rows. + return require('./table.json').length; +}; diff --git a/tests/corpus/6410-json-mixed-scalar-tables/node_modules/code-table/package.json b/tests/corpus/6410-json-mixed-scalar-tables/node_modules/code-table/package.json new file mode 100644 index 000000000..e15622d87 --- /dev/null +++ b/tests/corpus/6410-json-mixed-scalar-tables/node_modules/code-table/package.json @@ -0,0 +1 @@ +{ "name": "code-table", "version": "1.0.0", "main": "index.js", "types": "index.d.ts" } diff --git a/tests/corpus/6410-json-mixed-scalar-tables/node_modules/code-table/table.json b/tests/corpus/6410-json-mixed-scalar-tables/node_modules/code-table/table.json new file mode 100644 index 000000000..5abaa6772 --- /dev/null +++ b/tests/corpus/6410-json-mixed-scalar-tables/node_modules/code-table/table.json @@ -0,0 +1 @@ +[["8140", 12288, 65292], ["8150", 65507, 65340], ["a1", "。", 62]] diff --git a/tests/corpus/6410-json-mixed-scalar-tables/node_modules/range-map/index.d.ts b/tests/corpus/6410-json-mixed-scalar-tables/node_modules/range-map/index.d.ts new file mode 100644 index 000000000..322bd20e1 --- /dev/null +++ b/tests/corpus/6410-json-mixed-scalar-tables/node_modules/range-map/index.d.ts @@ -0,0 +1,2 @@ +export declare function mapChars(s: string): { text: string; error: boolean }; +export declare function status(cp: number): string; diff --git a/tests/corpus/6410-json-mixed-scalar-tables/node_modules/range-map/index.js b/tests/corpus/6410-json-mixed-scalar-tables/node_modules/range-map/index.js new file mode 100644 index 000000000..481d93ad4 --- /dev/null +++ b/tests/corpus/6410-json-mixed-scalar-tables/node_modules/range-map/index.js @@ -0,0 +1,37 @@ +'use strict'; +var table = require('./table.json'); +function findStatus(val) { + var start = 0; + var end = table.length - 1; + while (start <= end) { + var mid = Math.floor((start + end) / 2); + var target = table[mid]; + if (target[0][0] <= val && target[0][1] >= val) { + return target; + } else if (target[0][0] > val) { + end = mid - 1; + } else { + start = mid + 1; + } + } + return null; +} +exports.status = function (cp) { + var found = findStatus(cp); + return found === null ? 'unknown' : found[1]; +}; +exports.mapChars = function (s) { + var error = false; + var out = ''; + for (var i = 0; i < s.length; ++i) { + var cp = s.codePointAt(i); + var found = findStatus(cp); + if (found === null) { error = true; continue; } + switch (found[1]) { + case 'disallowed': error = true; out += String.fromCodePoint(cp); break; + case 'mapped': out += String.fromCodePoint.apply(String, found[2]); break; + case 'valid': out += String.fromCodePoint(cp); break; + } + } + return { text: out, error: error }; +}; diff --git a/tests/corpus/6410-json-mixed-scalar-tables/node_modules/range-map/package.json b/tests/corpus/6410-json-mixed-scalar-tables/node_modules/range-map/package.json new file mode 100644 index 000000000..bc877e1e8 --- /dev/null +++ b/tests/corpus/6410-json-mixed-scalar-tables/node_modules/range-map/package.json @@ -0,0 +1 @@ +{ "name": "range-map", "version": "1.0.0", "main": "index.js", "types": "index.d.ts" } diff --git a/tests/corpus/6410-json-mixed-scalar-tables/node_modules/range-map/table.json b/tests/corpus/6410-json-mixed-scalar-tables/node_modules/range-map/table.json new file mode 100644 index 000000000..900f9e316 --- /dev/null +++ b/tests/corpus/6410-json-mixed-scalar-tables/node_modules/range-map/table.json @@ -0,0 +1 @@ +[[[0,44],"disallowed"],[[45,46],"valid"],[[47,47],"disallowed"],[[48,57],"valid"],[[65,65],"mapped",[97]],[[66,67],"mapped",[98,99]],[[97,122],"valid"]] diff --git a/tests/corpus/6411-union-array-tuple-destructuring.ts b/tests/corpus/6411-union-array-tuple-destructuring.ts new file mode 100644 index 000000000..1dd88fa92 --- /dev/null +++ b/tests/corpus/6411-union-array-tuple-destructuring.ts @@ -0,0 +1,34 @@ +// Array destructuring of a value whose type is a union of array and tuple +// types: a regex match or a default row (`re.exec(s) || ["", "", ""]`), and +// functions answering either a fixed tuple or a list. Each position reads +// the arm the value holds; positions past a tuple's end and past an array's +// end read undefined, and defaults fire exactly there. +function scan(text: string): string { + const [match, head, word] = /(\w*)-(\w+)/.exec(text) || ["", "", ""]; + return `${match}|${head}|${word.toUpperCase()}`; +} +console.log(scan("ab-cd"), scan("none"), scan("-x")); + +function pick(flag: boolean): [string, number] | string[] { + return flag ? ["a", 1] : ["b", "c"]; +} +const [p, q] = pick(true); +const [r, s = "dflt", t = "z"] = pick(false); +const [u, , w = "w"] = pick(true); +console.log(p, q, r, s, t, u, w); +console.log(typeof q, typeof s); + +function pair(n: number): number[] | [number, number] { + return n > 1 ? [n, n * 2] : [n]; +} +const [x, y] = pair(7); +const [x1, y1 = -1] = pair(1); +console.log(x, y, x1, y1); + +function row(kind: string): [string, boolean] | [string, boolean, number] { + return kind === "long" ? [kind, true, 3] : [kind, false]; +} +for (const k of ["long", "short"]) { + const [name, flag, count = 0] = row(k); + console.log(name, flag, count); +} From d07188d250b987fc65c5ee38f86ab0cc6cb7cf56 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Mon, 19 Oct 2026 22:00:59 -0500 Subject: [PATCH 036/122] Merge base --- .../npm-static-bundled-chunks.test.ts | 92 ++++++++ .../src/frontend/npm-static-bundled-chunks.ts | 200 +++++++++++++++++- packages/compiler/src/frontend/program.ts | 110 ++++++++-- .../2564-chunk-helper-shadowing/main.ts.json | 6 + .../2565-chunk-shared-function/main.ts.json | 6 + .../2566-cjs-cycle-indirect-call/main.ts.json | 6 + .../main.ts.json | 6 + .../cases/2564-chunk-helper-shadowing/main.ts | 7 + .../cases/2565-chunk-shared-function/main.ts | 7 + .../2566-cjs-cycle-indirect-call/main.ts | 7 + .../2571-cjs-cycle-stored-function/main.ts | 8 + .../gtchunkshadow/dist/index.d.ts | 2 + .../node_modules/gtchunkshadow/dist/index.js | 52 +++++ .../node_modules/gtchunkshadow/package.json | 7 + .../gtchunkshared/dist/index.d.ts | 2 + .../node_modules/gtchunkshared/dist/index.js | 56 +++++ .../node_modules/gtchunkshared/package.json | 7 + .../node_modules/gtcyclealias/dist/index.d.ts | 3 + .../node_modules/gtcyclealias/dist/index.js | 37 ++++ .../node_modules/gtcyclealias/dist/reader.js | 34 +++ .../node_modules/gtcyclealias/package.json | 7 + .../node_modules/gtcyclebox/dist/index.d.ts | 3 + .../npm/node_modules/gtcyclebox/dist/index.js | 37 ++++ .../node_modules/gtcyclebox/dist/reader.js | 34 +++ .../npm/node_modules/gtcyclebox/package.json | 7 + .../node_modules/gtcyclecall/dist/index.d.ts | 3 + .../node_modules/gtcyclecall/dist/index.js | 37 ++++ .../node_modules/gtcyclecall/dist/reader.js | 33 +++ .../npm/node_modules/gtcyclecall/package.json | 7 + tests/harness/npm-cases.ts | 4 +- tests/harness/npm-static.test.ts | 57 ++++- 31 files changed, 860 insertions(+), 24 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2564-chunk-helper-shadowing/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2565-chunk-shared-function/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2566-cjs-cycle-indirect-call/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2571-cjs-cycle-stored-function/main.ts.json create mode 100644 tests/fixtures/npm/cases/2564-chunk-helper-shadowing/main.ts create mode 100644 tests/fixtures/npm/cases/2565-chunk-shared-function/main.ts create mode 100644 tests/fixtures/npm/cases/2566-cjs-cycle-indirect-call/main.ts create mode 100644 tests/fixtures/npm/cases/2571-cjs-cycle-stored-function/main.ts create mode 100644 tests/fixtures/npm/node_modules/gtchunkshadow/dist/index.d.ts create mode 100644 tests/fixtures/npm/node_modules/gtchunkshadow/dist/index.js create mode 100644 tests/fixtures/npm/node_modules/gtchunkshadow/package.json create mode 100644 tests/fixtures/npm/node_modules/gtchunkshared/dist/index.d.ts create mode 100644 tests/fixtures/npm/node_modules/gtchunkshared/dist/index.js create mode 100644 tests/fixtures/npm/node_modules/gtchunkshared/package.json create mode 100644 tests/fixtures/npm/node_modules/gtcyclealias/dist/index.d.ts create mode 100644 tests/fixtures/npm/node_modules/gtcyclealias/dist/index.js create mode 100644 tests/fixtures/npm/node_modules/gtcyclealias/dist/reader.js create mode 100644 tests/fixtures/npm/node_modules/gtcyclealias/package.json create mode 100644 tests/fixtures/npm/node_modules/gtcyclebox/dist/index.d.ts create mode 100644 tests/fixtures/npm/node_modules/gtcyclebox/dist/index.js create mode 100644 tests/fixtures/npm/node_modules/gtcyclebox/dist/reader.js create mode 100644 tests/fixtures/npm/node_modules/gtcyclebox/package.json create mode 100644 tests/fixtures/npm/node_modules/gtcyclecall/dist/index.d.ts create mode 100644 tests/fixtures/npm/node_modules/gtcyclecall/dist/index.js create mode 100644 tests/fixtures/npm/node_modules/gtcyclecall/dist/reader.js create mode 100644 tests/fixtures/npm/node_modules/gtcyclecall/package.json diff --git a/packages/compiler/src/frontend/npm-static-bundled-chunks.test.ts b/packages/compiler/src/frontend/npm-static-bundled-chunks.test.ts index 82926d2b8..f9463ea1e 100644 --- a/packages/compiler/src/frontend/npm-static-bundled-chunks.test.ts +++ b/packages/compiler/src/frontend/npm-static-bundled-chunks.test.ts @@ -111,6 +111,98 @@ console.log(require_named().name); expect(chunk).toContain("var __defProp = Object.defineProperty;"); }); +test("a helper travels with a chunk whose nested scopes declare its name", () => { + const answer = expectSameBehavior(`${keyedHelper}function label(n) { + return "label " + n; +} +function wrap(n) { + function inner() { + var label = "inner"; + return label; + } + return label(n) + " " + inner(); +} +var require_e = __commonJS({ + "e.js"(exports2, module2) { + function nested() { + var label = "nested"; + return label; + } + var seen = []; + for (let label of ["loop"]) seen.push(label); + try { + throw "caught"; + } catch (label) { + seen.push(label); + } + { + const label = "block"; + seen.push(label); + } + module2.exports = { outer: label(1), indirect: (0, label)(3), wrapped: wrap(2), nested: nested(), seen }; + } +}); +var e = require_e(); +console.log(e.outer, e.indirect, e.wrapped, e.nested, e.seen.join(",")); +`); + const chunk = answer.chunks[0]!.source; + expect(chunk).toContain("function label(n)"); + expect(chunk).toContain("function wrap(n)"); +}); + +test("a named function expression that only calls itself travels with a chunk", () => { + const answer = expectSameBehavior(`${keyedHelper}var fact = function self(n) { + return n <= 1 ? 1 : n * self(n - 1); +}; +var kind = function self(self) { + return typeof self; +}; +var outer = function self() { + var self = 2; + return self; +}; +var require_g = __commonJS({ + "g.js"(exports2, module2) { + module2.exports = [fact(5), kind(1), outer()]; + } +}); +console.log(require_g().join(",")); +`); + const chunk = answer.chunks[0]!.source; + expect(chunk).toContain("var fact = function self(n)"); +}); + +test.each([ + ["exports it", `function tally() {}`, `module2.exports = { tally };`], + ["reads its properties", `function tally() {}`, `exports2.calls = tally.calls;`], + ["constructs it", `function tally() {}`, `exports2.made = new tally();`], + ["stores an arrow helper", `var tally = () => 1;`, `exports2.fn = tally;`], + [ + "calls a function that reads its own name as a value", + `var tally = function self() { self.n = (self.n || 0) + 1; return self.n; };`, + `exports2.count = tally();`, + ], + [ + "calls a function whose parameter default reads its own name", + `var tally = function self(box = self) { box.n = (box.n || 0) + 1; return box.n; };`, + `exports2.count = tally();`, + ], +])("a chunk that %s keeps the package refused", (_, declaration, statement) => { + const result = split(`${keyedHelper}${declaration} +var require_f = __commonJS({ + "f.js"(exports2, module2) { + ${statement} + } +}); +require_f(); +`); + expect(result).toEqual({ + degrade: expect.stringContaining( + "uses the bundle's top-level function 'tally' as a value", + ) as string, + }); +}); + test.each([ ["reads its factory's receiver", `console.log(this);`], ["reads its factory's arguments object", `console.log(arguments.length);`], diff --git a/packages/compiler/src/frontend/npm-static-bundled-chunks.ts b/packages/compiler/src/frontend/npm-static-bundled-chunks.ts index 28fe4a1b9..2a3ad0b53 100644 --- a/packages/compiler/src/frontend/npm-static-bundled-chunks.ts +++ b/packages/compiler/src/frontend/npm-static-bundled-chunks.ts @@ -105,6 +105,128 @@ export function declaredNames(root: ts.Node): Set { return names; } +/** Adds the identifiers a binding name (an identifier or a destructuring + * pattern) declares. */ +function addBindingNames(name: ts.BindingName, out: Set): void { + if (ts.isIdentifier(name)) { + out.add(name.text); + return; + } + for (const element of name.elements.nodes) { + if (!ts.isOmittedExpression(element) && element.name !== undefined) + addBindingNames(element.name, out); + } +} + +function isFunctionScope(n: ts.Node): n is ts.SignatureDeclaration { + return ( + ts.isFunctionDeclaration(n) || + ts.isFunctionExpression(n) || + ts.isArrowFunction(n) || + ts.isMethodDeclaration(n) || + ts.isGetAccessorDeclaration(n) || + ts.isSetAccessorDeclaration(n) || + ts.isConstructorDeclaration(n) + ); +} + +/** The `var` names a function body declares, without entering nested + * functions. Function declarations in nested blocks count as block-scoped + * here; treating one as free only adds an unused helper copy. */ +function varScopedNames(body: ts.Node): Set { + const out = new Set(); + ts.walkPreorder(body, (n) => { + if (n !== body && (isFunctionScope(n) || ts.isClassDeclaration(n) || ts.isClassExpression(n))) + return "skip"; + if (ts.isVariableDeclarationList(n) && (n.flags & ts.NodeFlags.BlockScoped) === 0) { + for (const d of n.declarations.nodes) addBindingNames(d.name, out); + } + return undefined; + }); + return out; +} + +/** The names one scope node binds for the references inside it. */ +function scopeNames(scope: ts.Node): Set { + const out = new Set(); + if (isFunctionScope(scope)) { + for (const param of scope.parameters.nodes) addBindingNames(param.name, out); + if (ts.isFunctionExpression(scope) && scope.name !== undefined) out.add(scope.name.text); + if (scope.body !== undefined) for (const name of varScopedNames(scope.body)) out.add(name); + } else if (ts.isBlock(scope) || ts.isSourceFile(scope) || ts.isCaseBlock(scope)) { + const statements = ts.isCaseBlock(scope) + ? scope.clauses.nodes.flatMap((clause) => clause.statements.nodes) + : scope.statements.nodes; + for (const stmt of statements) { + if ((ts.isFunctionDeclaration(stmt) || ts.isClassDeclaration(stmt)) && stmt.name) + out.add(stmt.name.text); + else if ( + ts.isVariableStatement(stmt) && + (stmt.declarationList.flags & ts.NodeFlags.BlockScoped) !== 0 + ) { + for (const d of stmt.declarationList.declarations.nodes) addBindingNames(d.name, out); + } + } + } else if (ts.isCatchClause(scope) && scope.variableDeclaration !== undefined) { + addBindingNames(scope.variableDeclaration.name, out); + } else if ( + (ts.isForStatement(scope) || ts.isForInStatement(scope) || ts.isForOfStatement(scope)) && + scope.initializer !== undefined && + ts.isVariableDeclarationList(scope.initializer) && + (scope.initializer.flags & ts.NodeFlags.BlockScoped) !== 0 + ) { + for (const d of scope.initializer.declarations.nodes) addBindingNames(d.name, out); + } else if ( + (ts.isClassDeclaration(scope) || ts.isClassExpression(scope)) && + scope.name !== undefined + ) { + out.add(scope.name.text); + } + return out; +} + +/** Resolves references against one bundle's scopes, keeping each scope's + * names for the bundle's split. */ +class BundleScopes { + private readonly names = new Map>(); + + private of(scope: ts.Node): Set { + let names = this.names.get(scope); + if (names === undefined) { + names = scopeNames(scope); + this.names.set(scope, names); + } + return names; + } + + /** True when a scope between the reference and `root` (inclusive) + * binds its name. */ + boundWithin(id: ts.Identifier, root: ts.Node): boolean { + for (let n = id.parent; n !== undefined; n = n.parent) { + if (this.of(n).has(id.text)) return true; + if (n === root) return false; + } + return false; + } + + /** True when a scope inside `root` (exclusive) binds the reference's + * name. */ + boundInside(id: ts.Identifier, root: ts.Node): boolean { + for (let n = id.parent; n !== undefined && n !== root; n = n.parent) { + if (this.of(n).has(id.text)) return true; + } + return false; + } + + /** True when the reference resolves to a top-level binding of `sf`. */ + topLevel(id: ts.Identifier, sf: ts.SourceFile): boolean { + for (let n = id.parent; n !== undefined && n !== sf; n = n.parent) { + if (this.of(n).has(id.text)) return false; + } + return true; + } +} + /** Every binding reference inside a subtree. */ export function references(root: ts.Node): ts.Identifier[] { const out: ts.Identifier[] = []; @@ -230,6 +352,52 @@ function copyableInitializer(init: ts.Expression | undefined): boolean { return depth > 0 && ts.isIdentifier(walk) && walk.text === "Object"; } +/** A top-level declaration whose value is a function. */ +function isFunctionValued(node: ts.VariableDeclaration | ts.FunctionDeclaration): boolean { + if (ts.isFunctionDeclaration(node)) return true; + const init = node.initializer && unparen(node.initializer); + return init !== undefined && (ts.isArrowFunction(init) || ts.isFunctionExpression(init)); +} + +/** True when a named function expression initializer (`var f = function + * self() {}`) uses its own name only as a callee: `self` is the copied + * function, so it is as observable as the top-level binding. */ +function selfCalledOnly( + node: ts.VariableDeclaration | ts.FunctionDeclaration, + scopes: BundleScopes, +): boolean { + if (!ts.isVariableDeclaration(node) || node.initializer === undefined) return true; + const fn = unparen(node.initializer); + if (!ts.isFunctionExpression(fn) || fn.name === undefined || fn.body === undefined) return true; + const self = fn.name.text; + // A parameter or `var` of the same name hides the function's own name. + const own = varScopedNames(fn.body); + for (const param of fn.parameters.nodes) addBindingNames(param.name, own); + if (own.has(self)) return true; + // Its parameter initializers see the name too. + return references(fn).every((r) => r.text !== self || scopes.boundInside(r, fn) || isCallee(r)); +} + +/** True when a reference is the function a call invokes: `f(...)`, or the + * `(0, f)(...)` form that drops the receiver. */ +function isCallee(id: ts.Identifier): boolean { + let callee: ts.Node = id; + const p = id.parent; + if ( + p !== undefined && + ts.isBinaryExpression(p) && + p.operatorToken.kind === ts.SyntaxKind.CommaToken && + p.right === id && + p.parent !== undefined && + ts.isParenthesizedExpression(p.parent) + ) + callee = p.parent; + while (callee.parent !== undefined && ts.isParenthesizedExpression(callee.parent)) + callee = callee.parent; + const call = callee.parent; + return call !== undefined && ts.isCallExpression(call) && call.expression === callee; +} + /** One recognized chunk declaration. */ interface Chunk { name: string; @@ -302,6 +470,12 @@ export function splitBundledChunks( if (declaredAnywhere.has("Object") || written.has("Object")) return { degrade: CHUNK_SHAPE_DEGRADE }; const stable = (name: string): boolean => !duplicate.has(name) && !written.has(name); + // Names whose every reference in the bundle is a callee: `f(...)` or the + // `(0, f)(...)` form. + const scopes = new BundleScopes(); + const valueUsed = new Set(); + for (const r of allRefs) if (!isCallee(r) && scopes.topLevel(r, sf)) valueUsed.add(r.text); + const calledOnly = (name: string): boolean => !valueUsed.has(name); const ownNames = (name: string): boolean => { const t = top.get(name); if (t === undefined || !stable(name) || !ts.isVariableDeclaration(t.node)) return false; @@ -422,7 +596,17 @@ export function splitBundledChunks( const out: BundledChunkSplit["chunks"] = []; const bundleEdits: Edit[] = [...callEdits]; for (const chunk of chunks.values()) { - const result = chunkSource(sf, chunk, chunks, top, stable, callEdits, bundleStrict); + const result = chunkSource( + sf, + chunk, + chunks, + top, + stable, + calledOnly, + scopes, + callEdits, + bundleStrict, + ); if (typeof result !== "string") return result; out.push({ path: chunk.path, source: result }); bundleEdits.push({ start: chunk.stmt.getStart(sf), end: chunk.stmt.getEnd(), text: "" }); @@ -524,6 +708,8 @@ function chunkSource( chunks: ReadonlyMap, top: ReadonlyMap, stable: (name: string) => boolean, + calledOnly: (name: string) => boolean, + scopes: BundleScopes, callEdits: readonly Edit[], bundleStrict: boolean, ): string | { degrade: string } { @@ -570,7 +756,8 @@ function chunkSource( } continue; } - if (local.has(name)) continue; + // The factory function itself binds the parameters and `var`s. + if (scopes.boundWithin(r, body.parent ?? body)) continue; if (name === "exports" || name === "module") return why(`reads the bundle's own '${name}' binding`); if (name === "__filename") return why("reads __filename, which names the bundle file"); @@ -594,9 +781,14 @@ function chunkSource( (ts.isVariableDeclaration(node) && copyableInitializer(node.initializer))); if (!pure || chunks.has(name)) return why(`reads the bundle's top-level binding '${name}', which it cannot carry along`); - const own = declaredNames(node); + // Each chunk gets its own copy, so a function's identity and its + // properties must stay unobservable: the bundle only ever calls it. + if (isFunctionValued(node) && (!calledOnly(name) || !selfCalledOnly(node, scopes))) + return why( + `uses the bundle's top-level function '${name}' as a value, which its chunks cannot share`, + ); for (const r of references(node)) { - if (own.has(r.text) || !top.has(r.text) || copied.has(r.text)) continue; + if (scopes.boundWithin(r, node) || !top.has(r.text) || copied.has(r.text)) continue; copied.add(r.text); copyQueue.push(r.text); } diff --git a/packages/compiler/src/frontend/program.ts b/packages/compiler/src/frontend/program.ts index 908a587ea..79978abee 100644 --- a/packages/compiler/src/frontend/program.ts +++ b/packages/compiler/src/frontend/program.ts @@ -3199,29 +3199,105 @@ function cjsCycleRefusal7( return null; } -/** True for a plain read `binding.name` (not a call, not a write) of the - * cycle member `dep` whose export `name` is a top-level function - * declaration nothing reassigns. Every member settles its exports before - * entering the cycle, so Node reads that same function whenever the read - * runs, and the compiled read resolves to the declaration itself. */ +/** True when `use` initializes a top-level const whose every other + * reference is a value of an export statement (`module.exports = { … }` + * or `exports.n = …`). */ +function publishedAlias7(use: ts.Node, decl: ts.Node | undefined): boolean { + if ( + decl === undefined || + !ts.isVariableDeclaration(decl) || + decl.initializer !== use || + !ts.isIdentifier(decl.name) + ) + return false; + const list = decl.parent; + const stmt = list?.parent; + const sf = stmt?.parent; + if ( + list === undefined || + (list.flags & ts.NodeFlags.Const) === 0 || + stmt === undefined || + !ts.isVariableStatement(stmt) || + sf === undefined || + !ts.isSourceFile(sf) + ) + return false; + const alias = decl.name; + const published = new Set(); + for (const st of sf.statements.nodes) { + const cjs = cjsExportAssignmentOf(st); + if (cjs === null) continue; + if (cjs.kind === "member") published.add(cjs.value); + else if (cjs.obj !== null) { + for (const p of cjs.obj.properties.nodes) { + if (ts.isPropertyAssignment(p) && !ts.isComputedPropertyName(p.name)) + published.add(p.initializer); + else if (ts.isShorthandPropertyAssignment(p)) published.add(p.name); + } + } + } + let kept = false; + ts.walkPreorder(sf, (n) => { + if (kept) return "stop"; + if (!ts.isIdentifier(n) || n === alias || n.text !== alias.text) return undefined; + const p = n.parent; + if ( + p !== undefined && + ((ts.isPropertyAccessExpression(p) && p.name === n) || + (ts.isPropertyAssignment(p) && p.name === n)) + ) + return undefined; + if (!published.has(n)) kept = true; + return undefined; + }); + return !kept; +} + +/** True for a plain read `binding.name` (not a call, not a write, not a + * value that is stored or passed on) of the cycle member `dep` whose + * export `name` is a top-level function declaration nothing reassigns. + * Every member settles its exports before entering the cycle, so Node + * reads that same function whenever the read runs, and the compiled read + * resolves to the declaration itself. */ function functionExportRead7(id: ts.Identifier, dep: ts.SourceFile): boolean { const access = id.parent; if (!ts.isPropertyAccessExpression(access) || access.expression !== id) return false; + // The value the read yields: through parentheses and the right operand + // of a comma, so `(0, binding.name)(...)` is the call it spells. let use: ts.Node = access; - while (ts.isParenthesizedExpression(use.parent)) use = use.parent; + for (;;) { + const p = use.parent; + if (ts.isParenthesizedExpression(p)) use = p; + else if ( + ts.isBinaryExpression(p) && + p.operatorToken.kind === ts.SyntaxKind.CommaToken && + p.right === use + ) + use = p; + else break; + } const parent = use.parent; - if ( - (ts.isCallExpression(parent) && parent.expression === use) || - ts.isDeleteExpression(parent) || - ((ts.isPrefixUnaryExpression(parent) || ts.isPostfixUnaryExpression(parent)) && - (parent.operator === ts.SyntaxKind.PlusPlusToken || - parent.operator === ts.SyntaxKind.MinusMinusToken)) || + // Only a use that neither runs the function nor keeps it is a read: a + // stored alias (`const f = binding.name`, `{ f: binding.name }`) or an + // argument can be called while the cycle initializes. A top-level const + // that only publishes it (`module.exports = { name: f }`, the static + // rewrite's re-export spelling) runs nothing: a module that reads the + // export back checks that read itself. + const observes = + publishedAlias7(use, parent) || + ts.isExpressionStatement(parent) || + ts.isTypeOfExpression(parent) || + ts.isVoidExpression(parent) || + (ts.isPrefixUnaryExpression(parent) && parent.operator === ts.SyntaxKind.ExclamationToken) || (ts.isBinaryExpression(parent) && - parent.left === use && - parent.operatorToken.kind >= ts.SyntaxKind.FirstAssignment && - parent.operatorToken.kind <= ts.SyntaxKind.LastAssignment) - ) - return false; + (parent.operatorToken.kind === ts.SyntaxKind.EqualsEqualsEqualsToken || + parent.operatorToken.kind === ts.SyntaxKind.ExclamationEqualsEqualsToken || + parent.operatorToken.kind === ts.SyntaxKind.EqualsEqualsToken || + parent.operatorToken.kind === ts.SyntaxKind.ExclamationEqualsToken)) || + ((ts.isIfStatement(parent) || ts.isWhileStatement(parent) || ts.isDoStatement(parent)) && + parent.expression === use) || + (ts.isConditionalExpression(parent) && parent.condition === use); + if (!observes) return false; const name = access.name.text; // The exported value's identifier: a table entry or a member export. const values: ts.Expression[] = []; diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2564-chunk-helper-shadowing/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2564-chunk-helper-shadowing/main.ts.json new file mode 100644 index 000000000..90e304cbe --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2564-chunk-helper-shadowing/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/npm/cases/2564-chunk-helper-shadowing/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2565-chunk-shared-function/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2565-chunk-shared-function/main.ts.json new file mode 100644 index 000000000..6443d170b --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2565-chunk-shared-function/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/npm/cases/2565-chunk-shared-function/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2566-cjs-cycle-indirect-call/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2566-cjs-cycle-indirect-call/main.ts.json new file mode 100644 index 000000000..f51a29951 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2566-cjs-cycle-indirect-call/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/npm/cases/2566-cjs-cycle-indirect-call/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2571-cjs-cycle-stored-function/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2571-cjs-cycle-stored-function/main.ts.json new file mode 100644 index 000000000..c3e0442f9 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2571-cjs-cycle-stored-function/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/npm/cases/2571-cjs-cycle-stored-function/main.ts" + ], + "diags": [] +} diff --git a/tests/fixtures/npm/cases/2564-chunk-helper-shadowing/main.ts b/tests/fixtures/npm/cases/2564-chunk-helper-shadowing/main.ts new file mode 100644 index 000000000..d52b3731d --- /dev/null +++ b/tests/fixtures/npm/cases/2564-chunk-helper-shadowing/main.ts @@ -0,0 +1,7 @@ +// A bundled CommonJS chunk that calls the bundle's top-level `label` while +// a nested function of the chunk declares its own `label`: the chunk +// carries the top-level function along, and each reference keeps its own +// binding. +import { inner, outer } from "gtchunkshadow"; + +console.log(outer(), inner()); diff --git a/tests/fixtures/npm/cases/2565-chunk-shared-function/main.ts b/tests/fixtures/npm/cases/2565-chunk-shared-function/main.ts new file mode 100644 index 000000000..21ca81b40 --- /dev/null +++ b/tests/fixtures/npm/cases/2565-chunk-shared-function/main.ts @@ -0,0 +1,7 @@ +// Two bundled CommonJS chunks export the bundle's top-level `tally` and +// count calls on a property of it. Node prints "1 2 true": both chunks +// share one function. A per-chunk copy would split its identity and its +// properties, so the package stays refused. +import { counts, same } from "gtchunkshared"; + +console.log(counts(), same()); diff --git a/tests/fixtures/npm/cases/2566-cjs-cycle-indirect-call/main.ts b/tests/fixtures/npm/cases/2566-cjs-cycle-indirect-call/main.ts new file mode 100644 index 000000000..3cca145e0 --- /dev/null +++ b/tests/fixtures/npm/cases/2566-cjs-cycle-indirect-call/main.ts @@ -0,0 +1,7 @@ +// A require cycle whose partner calls an export of index.js with esbuild's +// `(0, import_index.peek)()` spelling while index is still initializing: +// peek reads a const in its temporal dead zone, so Node throws a +// ReferenceError, and scriptc refuses the cycle. +import { show } from "gtcyclecall"; + +console.log(show()); diff --git a/tests/fixtures/npm/cases/2571-cjs-cycle-stored-function/main.ts b/tests/fixtures/npm/cases/2571-cjs-cycle-stored-function/main.ts new file mode 100644 index 000000000..ae2be341a --- /dev/null +++ b/tests/fixtures/npm/cases/2571-cjs-cycle-stored-function/main.ts @@ -0,0 +1,8 @@ +// Require cycles whose partner stores an export of index.js (in a const, +// or in an object literal) and calls it while index is still +// initializing: the export reads a const in its temporal dead zone, so +// Node throws a ReferenceError, and scriptc refuses both cycles. +import { show } from "gtcyclealias"; +import { show as showBox } from "gtcyclebox"; + +console.log(show(), showBox()); diff --git a/tests/fixtures/npm/node_modules/gtchunkshadow/dist/index.d.ts b/tests/fixtures/npm/node_modules/gtchunkshadow/dist/index.d.ts new file mode 100644 index 000000000..238cf1744 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtchunkshadow/dist/index.d.ts @@ -0,0 +1,2 @@ +export declare function outer(): string; +export declare function inner(): string; diff --git a/tests/fixtures/npm/node_modules/gtchunkshadow/dist/index.js b/tests/fixtures/npm/node_modules/gtchunkshadow/dist/index.js new file mode 100644 index 000000000..0f89c7ec5 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtchunkshadow/dist/index.js @@ -0,0 +1,52 @@ +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __commonJS = (cb, mod) => function __require() { + return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; +}; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); +function label(n) { + return "label " + n; +} + +// chunk/vendor/shadow.js +var require_shadow = __commonJS({ + "chunk/vendor/shadow.js"(exports2, module2) { + function nested() { + var label = "nested"; + return label; + } + module2.exports = { outer: label(1), inner: nested() }; + } +}); + +// chunk/src/index.ts +var index_exports = {}; +__export(index_exports, { + inner: () => inner, + outer: () => outer +}); +module.exports = __toCommonJS(index_exports); +function outer() { + return require_shadow().outer; +} +function inner() { + return require_shadow().inner; +} +0 && (module.exports = { + inner, + outer +}); diff --git a/tests/fixtures/npm/node_modules/gtchunkshadow/package.json b/tests/fixtures/npm/node_modules/gtchunkshadow/package.json new file mode 100644 index 000000000..6830fbd2c --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtchunkshadow/package.json @@ -0,0 +1,7 @@ +{ + "name": "gtchunkshadow", + "version": "1.0.0", + "description": "esbuild's CommonJS bundle shape: a chunk calls a top-level function whose name a nested scope of the chunk also declares", + "main": "./dist/index.js", + "types": "./dist/index.d.ts" +} diff --git a/tests/fixtures/npm/node_modules/gtchunkshared/dist/index.d.ts b/tests/fixtures/npm/node_modules/gtchunkshared/dist/index.d.ts new file mode 100644 index 000000000..3a47a0495 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtchunkshared/dist/index.d.ts @@ -0,0 +1,2 @@ +export declare function same(): boolean; +export declare function counts(): string; diff --git a/tests/fixtures/npm/node_modules/gtchunkshared/dist/index.js b/tests/fixtures/npm/node_modules/gtchunkshared/dist/index.js new file mode 100644 index 000000000..0d3aacec8 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtchunkshared/dist/index.js @@ -0,0 +1,56 @@ +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __commonJS = (cb, mod) => function __require() { + return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; +}; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); +function tally() { + tally.calls = (tally.calls || 0) + 1; + return tally.calls; +} + +// chunk/vendor/a.js +var require_a = __commonJS({ + "chunk/vendor/a.js"(exports2, module2) { + module2.exports = { tally, first: tally() }; + } +}); + +// chunk/vendor/b.js +var require_b = __commonJS({ + "chunk/vendor/b.js"(exports2, module2) { + module2.exports = { tally, second: tally() }; + } +}); + +// chunk/src/index.ts +var index_exports = {}; +__export(index_exports, { + counts: () => counts, + same: () => same +}); +module.exports = __toCommonJS(index_exports); +function same() { + return require_a().tally === require_b().tally; +} +function counts() { + return require_a().first + " " + require_b().second; +} +0 && (module.exports = { + counts, + same +}); diff --git a/tests/fixtures/npm/node_modules/gtchunkshared/package.json b/tests/fixtures/npm/node_modules/gtchunkshared/package.json new file mode 100644 index 000000000..bfc90c5d2 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtchunkshared/package.json @@ -0,0 +1,7 @@ +{ + "name": "gtchunkshared", + "version": "1.0.0", + "description": "esbuild's CommonJS bundle shape: two chunks export one top-level function and count calls on a property of it", + "main": "./dist/index.js", + "types": "./dist/index.d.ts" +} diff --git a/tests/fixtures/npm/node_modules/gtcyclealias/dist/index.d.ts b/tests/fixtures/npm/node_modules/gtcyclealias/dist/index.d.ts new file mode 100644 index 000000000..5f4373e2f --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtcyclealias/dist/index.d.ts @@ -0,0 +1,3 @@ +export declare const LIMIT = 3; +export declare function show(): string; +export declare function peek(): number; diff --git a/tests/fixtures/npm/node_modules/gtcyclealias/dist/index.js b/tests/fixtures/npm/node_modules/gtcyclealias/dist/index.js new file mode 100644 index 000000000..77aca3c9b --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtcyclealias/dist/index.js @@ -0,0 +1,37 @@ +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); +var index_exports = {}; +__export(index_exports, { + LIMIT: () => LIMIT, + peek: () => peek, + show: () => show +}); +module.exports = __toCommonJS(index_exports); +var import_reader = require("./reader"); +const LIMIT = 3; +function peek() { + return LIMIT; +} +function show() { + return (0, import_reader.describe)(); +} +// Annotate the CommonJS export names for ESM import in node: +0 && (module.exports = { + LIMIT, + show +}); diff --git a/tests/fixtures/npm/node_modules/gtcyclealias/dist/reader.js b/tests/fixtures/npm/node_modules/gtcyclealias/dist/reader.js new file mode 100644 index 000000000..943e61a0a --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtcyclealias/dist/reader.js @@ -0,0 +1,34 @@ +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); +var reader_exports = {}; +__export(reader_exports, { + captured: () => captured, + describe: () => describe +}); +module.exports = __toCommonJS(reader_exports); +var import_index = require("./index"); +const read = import_index.peek; +const captured = read(); +function describe() { + return "limit " + captured; +} +// Annotate the CommonJS export names for ESM import in node: +0 && (module.exports = { + captured, + describe +}); diff --git a/tests/fixtures/npm/node_modules/gtcyclealias/package.json b/tests/fixtures/npm/node_modules/gtcyclealias/package.json new file mode 100644 index 000000000..9ee7441e0 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtcyclealias/package.json @@ -0,0 +1,7 @@ +{ + "name": "gtcyclealias", + "version": "1.0.0", + "description": "esbuild 0.25.10 per-file CommonJS output shape of a cycle whose partner stores an exported function in a const and calls it while that function's const binding is uninitialized", + "main": "./dist/index.js", + "types": "./dist/index.d.ts" +} diff --git a/tests/fixtures/npm/node_modules/gtcyclebox/dist/index.d.ts b/tests/fixtures/npm/node_modules/gtcyclebox/dist/index.d.ts new file mode 100644 index 000000000..5f4373e2f --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtcyclebox/dist/index.d.ts @@ -0,0 +1,3 @@ +export declare const LIMIT = 3; +export declare function show(): string; +export declare function peek(): number; diff --git a/tests/fixtures/npm/node_modules/gtcyclebox/dist/index.js b/tests/fixtures/npm/node_modules/gtcyclebox/dist/index.js new file mode 100644 index 000000000..77aca3c9b --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtcyclebox/dist/index.js @@ -0,0 +1,37 @@ +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); +var index_exports = {}; +__export(index_exports, { + LIMIT: () => LIMIT, + peek: () => peek, + show: () => show +}); +module.exports = __toCommonJS(index_exports); +var import_reader = require("./reader"); +const LIMIT = 3; +function peek() { + return LIMIT; +} +function show() { + return (0, import_reader.describe)(); +} +// Annotate the CommonJS export names for ESM import in node: +0 && (module.exports = { + LIMIT, + show +}); diff --git a/tests/fixtures/npm/node_modules/gtcyclebox/dist/reader.js b/tests/fixtures/npm/node_modules/gtcyclebox/dist/reader.js new file mode 100644 index 000000000..b8f8a927e --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtcyclebox/dist/reader.js @@ -0,0 +1,34 @@ +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); +var reader_exports = {}; +__export(reader_exports, { + captured: () => captured, + describe: () => describe +}); +module.exports = __toCommonJS(reader_exports); +var import_index = require("./index"); +const box = { read: import_index.peek }; +const captured = box.read(); +function describe() { + return "limit " + captured; +} +// Annotate the CommonJS export names for ESM import in node: +0 && (module.exports = { + captured, + describe +}); diff --git a/tests/fixtures/npm/node_modules/gtcyclebox/package.json b/tests/fixtures/npm/node_modules/gtcyclebox/package.json new file mode 100644 index 000000000..67f943195 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtcyclebox/package.json @@ -0,0 +1,7 @@ +{ + "name": "gtcyclebox", + "version": "1.0.0", + "description": "esbuild 0.25.10 per-file CommonJS output shape of a cycle whose partner stores an exported function in an object literal and calls it while that function's const binding is uninitialized", + "main": "./dist/index.js", + "types": "./dist/index.d.ts" +} diff --git a/tests/fixtures/npm/node_modules/gtcyclecall/dist/index.d.ts b/tests/fixtures/npm/node_modules/gtcyclecall/dist/index.d.ts new file mode 100644 index 000000000..5f4373e2f --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtcyclecall/dist/index.d.ts @@ -0,0 +1,3 @@ +export declare const LIMIT = 3; +export declare function show(): string; +export declare function peek(): number; diff --git a/tests/fixtures/npm/node_modules/gtcyclecall/dist/index.js b/tests/fixtures/npm/node_modules/gtcyclecall/dist/index.js new file mode 100644 index 000000000..77aca3c9b --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtcyclecall/dist/index.js @@ -0,0 +1,37 @@ +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); +var index_exports = {}; +__export(index_exports, { + LIMIT: () => LIMIT, + peek: () => peek, + show: () => show +}); +module.exports = __toCommonJS(index_exports); +var import_reader = require("./reader"); +const LIMIT = 3; +function peek() { + return LIMIT; +} +function show() { + return (0, import_reader.describe)(); +} +// Annotate the CommonJS export names for ESM import in node: +0 && (module.exports = { + LIMIT, + show +}); diff --git a/tests/fixtures/npm/node_modules/gtcyclecall/dist/reader.js b/tests/fixtures/npm/node_modules/gtcyclecall/dist/reader.js new file mode 100644 index 000000000..c98d1ae46 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtcyclecall/dist/reader.js @@ -0,0 +1,33 @@ +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); +var reader_exports = {}; +__export(reader_exports, { + captured: () => captured, + describe: () => describe +}); +module.exports = __toCommonJS(reader_exports); +var import_index = require("./index"); +const captured = (0, import_index.peek)(); +function describe() { + return "limit " + captured; +} +// Annotate the CommonJS export names for ESM import in node: +0 && (module.exports = { + captured, + describe +}); diff --git a/tests/fixtures/npm/node_modules/gtcyclecall/package.json b/tests/fixtures/npm/node_modules/gtcyclecall/package.json new file mode 100644 index 000000000..cc1069a6f --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtcyclecall/package.json @@ -0,0 +1,7 @@ +{ + "name": "gtcyclecall", + "version": "1.0.0", + "description": "esbuild 0.25.10 per-file CommonJS output shape of a cycle whose partner calls an exported function through (0, binding.fn)() while that function's const binding is uninitialized", + "main": "./dist/index.js", + "types": "./dist/index.d.ts" +} diff --git a/tests/harness/npm-cases.ts b/tests/harness/npm-cases.ts index 35a9df4a6..9ca3c0df0 100644 --- a/tests/harness/npm-cases.ts +++ b/tests/harness/npm-cases.ts @@ -19,12 +19,12 @@ export function npmCases(fixturesRoot: string): NpmCase[] { return [ ...globSync(join(fixturesRoot, "npm/cases/*/main.ts")) .sort() - // 2465-2469, 2556-2563 and 2567 are the --npm-static bundler-emitted-CJS + // 2465-2469, 2556-2567 and 2571 are the --npm-static bundler-emitted-CJS // cases (npm-static.test.ts drives them with the opt-in): their // consumers exercise the static rewrite's surface (lexer-visible // names the shipped .d.ts never declares, the __toESM interop // family), so they stay out of the flagless island lane by design. - .filter((entry) => !/\/(246[5-9]|255[6-9]|256[0-37])-[^/]+\/main\.ts$/.test(entry)) + .filter((entry) => !/\/(246[5-9]|255[6-9]|256[0-7]|2571)-[^/]+\/main\.ts$/.test(entry)) .map((entry) => ({ name: entry.split("/").at(-2)!, entry, diff --git a/tests/harness/npm-static.test.ts b/tests/harness/npm-static.test.ts index 443c0c72d..32639e0d1 100644 --- a/tests/harness/npm-static.test.ts +++ b/tests/harness/npm-static.test.ts @@ -76,7 +76,7 @@ async function buildStatic(entry: string, npmStatic: string[] | "auto"): Promise ...globSync( join(fixturesRoot, "npm/node_modules/cryptozoo/**/*.{js,mjs,cjs,json,d.ts}"), ).sort(), - // the bundler-emitted-CJS mini packages (cases 2465-2469, 2556-2563, 2567) + // the bundler-emitted-CJS mini packages (cases 2465-2469, 2556-2567, 2571) ...globSync(join(fixturesRoot, "npm/node_modules/gt*/**/*.{js,cjs,json}")).sort(), ]; for (const f of inputs) hash.update(f).update(readFileSync(f)); @@ -1502,6 +1502,61 @@ describe(`npm-static pilots${sanitize ? " (sanitized)" : ""}`, () => { ]); }, 180_000); + // 2566: the same early read through esbuild's `(0, binding.fn)()` call + // spelling also keeps SC1016. + test("a CommonJS cycle that calls an export during initialization stays refused", () => { + const entry = join(fixturesRoot, "npm/cases/2566-cjs-cycle-indirect-call/main.ts"); + const { coverage } = analyze(entry, { npmStatic: ["gtcyclecall"] }); + expect(coverage.npmStatic).toEqual([ + { + package: "gtcyclecall", + status: "refused", + detail: expect.stringMatching( + /circular imports .*can use its binding 'import_index' of a module of the cycle/, + ) as string, + }, + ]); + }, 120_000); + + // 2571: the partner stores the export (in a const, or in an object + // literal) and calls it while the cycle initializes; both keep SC1016. + test("a CommonJS cycle that stores an export and calls it stays refused", () => { + const entry = join(fixturesRoot, "npm/cases/2571-cjs-cycle-stored-function/main.ts"); + const { coverage } = analyze(entry, { npmStatic: ["gtcyclealias", "gtcyclebox"] }); + for (const pkg of ["gtcyclealias", "gtcyclebox"]) + expect(coverage.npmStatic).toContainEqual({ + package: pkg, + status: "refused", + detail: expect.stringMatching( + /circular imports .*can use its binding 'import_index' of a module of the cycle/, + ) as string, + }); + }, 120_000); + + // 2564: a chunk reads a top-level function whose name a nested scope of + // the chunk also declares; the function still travels with the chunk. + test("a chunk carries a top-level function its nested scopes shadow", async () => { + await expectNodeParity(join(fixturesRoot, "npm/cases/2564-chunk-helper-shadowing/main.ts"), [ + "gtchunkshadow", + ]); + }, 180_000); + + // 2565: Node shares one function between the chunks that export it; a + // per-chunk copy would not, so the package stays refused. + test("chunks that export a shared top-level function stay refused", () => { + const entry = join(fixturesRoot, "npm/cases/2565-chunk-shared-function/main.ts"); + const { coverage } = analyze(entry, { npmStatic: ["gtchunkshared"] }); + expect(coverage.npmStatic).toEqual([ + { + package: "gtchunkshared", + status: "refused", + detail: expect.stringMatching( + /uses the bundle's top-level function 'tally' as a value, which its chunks cannot share/, + ) as string, + }, + ]); + }, 120_000); + test("published type-only declarations coexist with a static runtime bundle", async () => { const entry = join(fixturesRoot, "npm/cases/2469-bundle-offender/main.ts"); const { coverage } = analyze(entry, { npmStatic: ["gtghost"] }); From 52f9bf284f7174c3e0f0163aa676f57591ec25b4 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Mon, 19 Oct 2026 20:40:56 -0700 Subject: [PATCH 037/122] Destructure a match-or-empty-string union by code point `const [hit] = re.exec(s) || ""` destructures `RegExpExecArray | string`: the string arm answers its code point at each position, or undefined past its end, as the string iterator does. Co-Authored-By: Claude Opus 5.5 --- .../src/frontend/lowering/array-values.ts | 25 ++++++++++++++----- .../src/frontend/lowering/lower-stmts.ts | 8 ++++-- .../6411-union-array-tuple-destructuring.ts | 8 ++++++ 3 files changed, 33 insertions(+), 8 deletions(-) diff --git a/packages/compiler/src/frontend/lowering/array-values.ts b/packages/compiler/src/frontend/lowering/array-values.ts index e9148b244..0ca8503df 100644 --- a/packages/compiler/src/frontend/lowering/array-values.ts +++ b/packages/compiler/src/frontend/lowering/array-values.ts @@ -8,6 +8,7 @@ import { type IrType, isIntegerBytesElem, type SrcLoc, + STRING, UNDEFINED_T, typeEquals, typeKey, @@ -88,12 +89,15 @@ export function arrayValueRead( * (`const [a, b] = re.exec(s) || ["", ""]`) without copying the receiver * or its elements. Every array arm contributes its missing-value case; a * tuple arm answers its field, or undefined past its end. Tuple arms need - * a constant position. */ + * a constant position. With `stringChars` (the string iterator's code + * points as a fresh string[]), a string arm (`re.exec(s) || ""`) answers + * its code point at the position, or undefined past its end. */ export function unionArrayValueRead( lowerer: Lowerer, value: IrExpr, index: IrExpr, loc: SrcLoc, + stringChars?: (text: IrExpr) => IrExpr, ): IrExpr | null { if (value.type.kind !== "union") return null; const unionId = value.type.unionId; @@ -110,16 +114,20 @@ export function unionArrayValueRead( }; // Keep the collection's IrType union layout and narrow each element at // its use; an inferred array predicate changes the collection's ABI. - for (const arm of arms) if (arm.kind !== "array" && tupleField(arm) === null) return null; + const stringArm = (arm: IrType): boolean => arm.kind === "string" && stringChars !== undefined; + for (const arm of arms) + if (arm.kind !== "array" && !stringArm(arm) && tupleField(arm) === null) return null; const answers = new Map(); for (const arm of arms) { - const field = arm.kind === "array" ? null : tupleField(arm); + const field = arm.kind === "array" || stringArm(arm) ? null : tupleField(arm); const answer = arm.kind === "array" ? arrayValueType(lowerer, arm.elem) - : field === "past" - ? UNDEFINED_T - : field!.type; + : stringArm(arm) + ? arrayValueType(lowerer, STRING) + : field === "past" + ? UNDEFINED_T + : field!.type; const parts = answer.kind === "union" ? lowerer.unions.get(answer.unionId)!.arms : [answer]; for (const part of parts) answers.set(typeKey(part), part); } @@ -143,6 +151,11 @@ export function unionArrayValueRead( arrayValueRead(lowerer, receiver, index, arm.elem, loc), type, ); + } else if (stringArm(arm)) { + read = lowerer.coerceToExpected( + arrayValueRead(lowerer, stringChars!(receiver), index, STRING, loc), + type, + ); } else { const field = tupleField(arm); if (field === null) return null; diff --git a/packages/compiler/src/frontend/lowering/lower-stmts.ts b/packages/compiler/src/frontend/lowering/lower-stmts.ts index 444fb899c..737c2e9ae 100644 --- a/packages/compiler/src/frontend/lowering/lower-stmts.ts +++ b/packages/compiler/src/frontend/lowering/lower-stmts.ts @@ -2384,8 +2384,10 @@ export function lowerBindingPattern( ?.arms.every( (arm) => arm.kind === "array" || + arm.kind === "string" || (arm.kind === "record" && lowerer.shapes.get(arm.shapeId)?.tuple === true), - ) + ) && + lowerer.unions.get(srcType.unionId)!.arms.some((arm) => arm.kind !== "string") ) { for (let i = 0; i < pattern.elements.nodes.length; i++) { const el = pattern.elements.nodes[i]!; @@ -2393,7 +2395,9 @@ export function lowerBindingPattern( if (el.dotDotDotToken) lowerer.unsupported("SC1031", el, "rest elements over a union of array or tuple layouts"); const loc = locOf(el); - let value = unionArrayValueRead(lowerer, srcRef(), numLit(i, loc), loc); + let value = unionArrayValueRead(lowerer, srcRef(), numLit(i, loc), loc, (text) => + strCharsCall(lowerer, text, loc), + ); if (!value) lowerer.unsupported( "SC1031", diff --git a/tests/corpus/6411-union-array-tuple-destructuring.ts b/tests/corpus/6411-union-array-tuple-destructuring.ts index 1dd88fa92..f62eba996 100644 --- a/tests/corpus/6411-union-array-tuple-destructuring.ts +++ b/tests/corpus/6411-union-array-tuple-destructuring.ts @@ -32,3 +32,11 @@ for (const k of ["long", "short"]) { const [name, flag, count = 0] = row(k); console.log(name, flag, count); } + +// A match or an empty string (`re.exec(s) || ""`): a string destructures +// by code point, so the empty string binds undefined. +function firstHit(text: string): string { + const [hit] = /\d+/.exec(text) || ""; + return hit ? `hit ${hit}` : "none"; +} +console.log(firstHit("ab12"), firstHit("ab")); From c208973d752bd72c72b6497358e7e4f2a3860342 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Mon, 19 Oct 2026 20:35:23 -0700 Subject: [PATCH 038/122] Read T[keyof T] as unknown for T bound to an unknown-valued record Inside a generic body Object.entries(t) over T extends Record types its values 'keyof T extends never ? unknown : T[keyof T]'. An instance whose T maps to the checked-dynamic value now reads both branches as unknown. Co-Authored-By: Claude Opus 5.5 --- packages/compiler/src/frontend/type-mapper.ts | 19 +++++++++-- ...468-generic-unknown-record-entries.ts.json | 6 ++++ .../6468-generic-unknown-record-entries.ts | 33 +++++++++++++++++++ 3 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6468-generic-unknown-record-entries.ts.json create mode 100644 tests/corpus/6468-generic-unknown-record-entries.ts diff --git a/packages/compiler/src/frontend/type-mapper.ts b/packages/compiler/src/frontend/type-mapper.ts index 1e3954586..7a40d4e24 100644 --- a/packages/compiler/src/frontend/type-mapper.ts +++ b/packages/compiler/src/frontend/type-mapper.ts @@ -5139,11 +5139,15 @@ function mapGenericIndexedAccess(type: ts.Type, ctx: TypeMapperCtx): IrType | nu const obj = type.getObjectType(); if (obj === undefined || !(obj.flags & ts.TypeFlags.TypeParameter)) return null; const bound = resolveTypeParam(obj); + const idx = type.getIndexType(); + if (idx === undefined) return null; + // `T[keyof T]` over a T bound to an unknown-valued record view + // (`Record` maps to the checked-dynamic value, see + // mapRecordType): every member reads as unknown. + if (bound?.kind === "dyn" && idx.isIndexType() && idx.getTarget() === obj) return DYN; if (!bound || bound.kind !== "record") return null; const shape = shapes.get(bound.shapeId); if (!shape || shape.tuple) return null; - const idx = type.getIndexType(); - if (idx === undefined) return null; let covered: IrType[] | null = null; if (idx.isStringLiteralType()) { const f = shape.fields.find((x) => x.name === idx.value); @@ -5199,6 +5203,17 @@ function mapBoundKeyofNeverConditional(type: ts.Type, ctx: TypeMapperCtx): IrTyp const target = check.getTarget(); if (!target || !(target.flags & ts.TypeFlags.TypeParameter)) return null; const bound = resolveTypeParam(target); + // A T bound to the checked-dynamic value (`Record`, an + // unknown-valued record view) has no static key set to decide the + // branch, but both branches of the lib's `keyof T extends never ? + // unknown : T[keyof T]` read as unknown there: one answer either way. + if (bound?.kind === "dyn") { + const whenEmpty = type.getTrueType(); + const otherwise = type.getFalseType(); + const a = whenEmpty ? mapType(whenEmpty, ctx) : null; + const b = otherwise ? mapType(otherwise, ctx) : null; + return a && b && typeEquals(a, b) ? a : null; + } if (bound?.kind !== "record") return null; const shape = shapes.get(bound.shapeId); if (!shape || shape.tuple) return null; diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6468-generic-unknown-record-entries.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6468-generic-unknown-record-entries.ts.json new file mode 100644 index 000000000..0b4118c70 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6468-generic-unknown-record-entries.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6468-generic-unknown-record-entries.ts" + ], + "diags": [] +} diff --git a/tests/corpus/6468-generic-unknown-record-entries.ts b/tests/corpus/6468-generic-unknown-record-entries.ts new file mode 100644 index 000000000..f0e57756a --- /dev/null +++ b/tests/corpus/6468-generic-unknown-record-entries.ts @@ -0,0 +1,33 @@ +// Inside a generic body, `Object.entries(tags)` over `T extends +// Record` reads `[string, T[keyof T]]` elements. An +// instance whose T is the unknown-valued record view reads every member +// as unknown; a record instance reads the union of its member types. +function upper(text: string): string { + return text.toUpperCase(); +} + +function scrub(text: string): string { + return text.replace(/secret/g, "***"); +} + +function describe>(tags: T): string[] { + return Object.entries(tags).map( + ([key, value]) => `${key}=${typeof value === "string" ? upper(value) : String(value)}`, + ); +} + +function redact>(tags: T): T { + return Object.fromEntries( + Object.entries(tags).map(([key, value]) => [key, typeof value === "string" ? scrub(value) : value]), + ) as T; +} + +function flatten(value: unknown): Record { + return typeof value === "object" && value !== null ? (value as Record) : { value }; +} + +console.log(describe(flatten({ b: 1, a: "x", c: true }))); +console.log(describe(flatten(5))); +console.log(describe({ name: "n", size: 3 })); +const cleaned = redact(flatten({ token: "secret", count: 2 })); +console.log(cleaned, Object.keys(cleaned)); From fe322ae901cbc4914f527c756d8f71235f2454f4 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Mon, 19 Oct 2026 21:16:25 -0700 Subject: [PATCH 039/122] Keep the JSON-shape diagnostics fixture refused now that null fields bake A null-valued JSON field bakes as null, so the fixture's document now compiles. It keeps its purpose with a value no literal spells (1e400 parses to Infinity), and the mixed-table corpus program covers null elements. Co-Authored-By: Claude Opus 5.5 --- tests/corpus/6410-json-mixed-scalar-tables/extra.json | 2 +- tests/corpus/6410-json-mixed-scalar-tables/main.ts | 7 ++++++- tests/diagnostics/json-shape/main.ts | 8 ++++---- tests/diagnostics/json-shape/nulled.json | 2 +- tests/harness/__snapshots__/json-shape/main.ts.txt | 4 ++-- 5 files changed, 14 insertions(+), 9 deletions(-) diff --git a/tests/corpus/6410-json-mixed-scalar-tables/extra.json b/tests/corpus/6410-json-mixed-scalar-tables/extra.json index 927c5d968..7af99c22f 100644 --- a/tests/corpus/6410-json-mixed-scalar-tables/extra.json +++ b/tests/corpus/6410-json-mixed-scalar-tables/extra.json @@ -1 +1 @@ -[["x", 1, "two", 3], [4, "five"], ["six"]] +[["x", 1, "two", 3], [4, "five", null], ["six"], [null]] diff --git a/tests/corpus/6410-json-mixed-scalar-tables/main.ts b/tests/corpus/6410-json-mixed-scalar-tables/main.ts index 54e337b02..c60f962dc 100644 --- a/tests/corpus/6410-json-mixed-scalar-tables/main.ts +++ b/tests/corpus/6410-json-mixed-scalar-tables/main.ts @@ -16,6 +16,11 @@ console.log(status(10), status(46), status(66), status(1000)); for (const row of extra) { const head = row[0]; const rest = row.slice(1); - console.log(typeof head, head, rest.length, rest.map((v) => (typeof v === "number" ? v + 1 : v.toUpperCase())).join(",")); + console.log( + typeof head, + head, + rest.length, + rest.map((v) => (v === null ? "null" : typeof v === "number" ? v + 1 : v.toUpperCase())).join(","), + ); } console.log(JSON.stringify(extra)); diff --git a/tests/diagnostics/json-shape/main.ts b/tests/diagnostics/json-shape/main.ts index 1d9552506..a46338d52 100644 --- a/tests/diagnostics/json-shape/main.ts +++ b/tests/diagnostics/json-shape/main.ts @@ -1,7 +1,7 @@ -/* A JSON document whose shape doesn't bake: a null-valued field has no - * standalone STATIC type, so the import reports the dynamic-family type - * diagnostic (SC2011 at the import site — the shape maps in the embedded - * engine) and uses of the binding poison. */ +/* A JSON document whose value doesn't bake: `1e400` parses to Infinity, + * which no number literal spells, so the import reports the bake failure + * at the import site and uses of the binding poison. (Its null-valued + * field bakes as null.) */ import bad from "./nulled.json" with { type: "json" }; console.log(bad.name); diff --git a/tests/diagnostics/json-shape/nulled.json b/tests/diagnostics/json-shape/nulled.json index fa4738449..5e506a6af 100644 --- a/tests/diagnostics/json-shape/nulled.json +++ b/tests/diagnostics/json-shape/nulled.json @@ -1 +1 @@ -{ "name": "x", "deprecated": null } +{ "name": "x", "deprecated": null, "limit": 1e400 } diff --git a/tests/harness/__snapshots__/json-shape/main.ts.txt b/tests/harness/__snapshots__/json-shape/main.ts.txt index b03f098a6..85f5d2917 100644 --- a/tests/harness/__snapshots__/json-shape/main.ts.txt +++ b/tests/harness/__snapshots__/json-shape/main.ts.txt @@ -1,6 +1,6 @@ -json-shape/main.ts:5:8 - error SC2001: values of type '{ name: string; deprecated: null; }' cannot be compiled yet (supported: number, string, boolean, arrays, Maps, Sets, RegExp, read-only Date values, functions, classes, records, unions of those, and 'unknown') +json-shape/main.ts:5:8 - error SC1110: comptime evaluation failed: expected 'number' at $.limit, got Infinity (only finite numbers can be written as literals) - 4 | * engine) and uses of the binding poison. */ + 4 | * field bakes as null.) */ 5 | import bad from "./nulled.json" with { type: "json" }; | ^~~ 6 | From 72e22ed0edfb0c29d3cc68fef5d53ea338bbae28 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Mon, 19 Oct 2026 23:22:23 -0500 Subject: [PATCH 040/122] Run SHA-384 and SHA-512 blocks with AVX2, AVX-512VL or the arm64 SHA-512 instructions SHA-384, SHA-512, SHA-512/224 and SHA-512/256 (createHash, createHmac, crypto.hash, pbkdf2, hkdf) process their blocks with the fastest kernel the CPU supports: - x86-64 with AVX2 and BMI2: the message schedule is computed two words at a time in vector registers, interleaved with the scalar rounds, which rotate with RORX. - x86-64 with AVX-512VL: the same, with vector rotates and three-input XOR in the schedule. - arm64 with the SHA-512 extension: SHA512H, SHA512H2, SHA512SU0 and SHA512SU1. - Otherwise the portable blocks, now unrolled over a 16-word schedule ring. The CPU and OS checks run once and are cached. The update and final paths pass all their whole blocks to the kernel in one call. test_sha_kernels.c checks the FIPS 180-4 SHA-384 and SHA-512 vectors through every kernel the CPU supports and compares each kernel with the portable blocks over every length from 0 to 1100 bytes and 1 MiB, whole and in 1-, 7-, 64- and 100-byte updates. Corpus 5779 compares SHA-384, SHA-512 and HMAC-SHA512 digests over many lengths and update sizes with Node; tests/harness/corpus-costs/ records its plain and sanitized costs. --- .../tests/corpus/5779-sha512-lengths.ts.json | 6 + packages/runtime/src/scr_lib.c | 344 ++++++++++++++++-- packages/runtime/test/sha-kernels.test.ts | 4 +- packages/runtime/test/test_sha_kernels.c | 100 ++++- tests/corpus/5779-sha512-lengths.ts | 35 ++ .../corpus-costs/5779-sha512-lengths.ts.json | 1 + 6 files changed, 456 insertions(+), 34 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5779-sha512-lengths.ts.json create mode 100644 tests/corpus/5779-sha512-lengths.ts create mode 100644 tests/harness/corpus-costs/5779-sha512-lengths.ts.json diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5779-sha512-lengths.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5779-sha512-lengths.ts.json new file mode 100644 index 000000000..60582ec60 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5779-sha512-lengths.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/5779-sha512-lengths.ts" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_lib.c b/packages/runtime/src/scr_lib.c index 930d44818..99c5a8f63 100644 --- a/packages/runtime/src/scr_lib.c +++ b/packages/runtime/src/scr_lib.c @@ -7405,29 +7405,319 @@ static uint64_t scr_sha512_rotr(uint64_t x, unsigned n) { return (x >> n) | (x << (64 - n)); } -SCR_DIGEST_ALIGNED static void scr_sha512_block(uint64_t h[8], const unsigned char *data) { - uint64_t w[80]; - for (size_t i = 0; i < 16; i++) { - w[i] = 0; - for (size_t b = 0; b < 8; b++) w[i] = (w[i] << 8) | data[i * 8 + b]; +#define SCR_SHA512_BSIG0(x) \ + (scr_sha512_rotr(x, 28) ^ scr_sha512_rotr(x, 34) ^ scr_sha512_rotr(x, 39)) +#define SCR_SHA512_BSIG1(x) \ + (scr_sha512_rotr(x, 14) ^ scr_sha512_rotr(x, 18) ^ scr_sha512_rotr(x, 41)) +#define SCR_SHA512_SSIG0(x) (scr_sha512_rotr(x, 1) ^ scr_sha512_rotr(x, 8) ^ ((x) >> 7)) +#define SCR_SHA512_SSIG1(x) (scr_sha512_rotr(x, 19) ^ scr_sha512_rotr(x, 61) ^ ((x) >> 6)) + +/* One round with the eight working variables passed in rotated order, so + * an unrolled sequence of rounds needs no register moves. */ +#define SCR_SHA512_ROUND(a, b, c, d, e, f, g, h, k, w) \ + do { \ + uint64_t t1 = (h) + SCR_SHA512_BSIG1(e) + ((g) ^ ((e) & ((f) ^ (g)))) + (k) + (w); \ + (d) += t1; \ + (h) = t1 + SCR_SHA512_BSIG0(a) + (((a) & (b)) | ((c) & ((a) | (b)))); \ + } while (0) + +static uint64_t scr_sha512_load(const unsigned char *p) { + uint64_t v = 0; + for (size_t b = 0; b < 8; b++) v = (v << 8) | p[b]; + return v; +} + +/* The message schedule is kept as a 16-word ring and extended 16 words + * at a time between groups of 16 rounds. */ +SCR_DIGEST_ALIGNED static void scr_sha512_blocks_portable(uint64_t state[8], const unsigned char *p, + size_t blocks) { + uint64_t a = state[0], b = state[1], c = state[2], d = state[3]; + uint64_t e = state[4], f = state[5], g = state[6], h = state[7]; + for (; blocks != 0; blocks--, p += 128) { + uint64_t w[16]; + for (size_t i = 0; i < 16; i++) w[i] = scr_sha512_load(p + i * 8); + for (size_t i = 0; i < 80; i += 16) { + if (i != 0) { + for (size_t j = 0; j < 16; j++) { + w[j] += SCR_SHA512_SSIG1(w[(j + 14) & 15]) + w[(j + 9) & 15] + + SCR_SHA512_SSIG0(w[(j + 1) & 15]); + } + } + const uint64_t *k = scr_sha512_k + i; + SCR_SHA512_ROUND(a, b, c, d, e, f, g, h, k[0], w[0]); + SCR_SHA512_ROUND(h, a, b, c, d, e, f, g, k[1], w[1]); + SCR_SHA512_ROUND(g, h, a, b, c, d, e, f, k[2], w[2]); + SCR_SHA512_ROUND(f, g, h, a, b, c, d, e, k[3], w[3]); + SCR_SHA512_ROUND(e, f, g, h, a, b, c, d, k[4], w[4]); + SCR_SHA512_ROUND(d, e, f, g, h, a, b, c, k[5], w[5]); + SCR_SHA512_ROUND(c, d, e, f, g, h, a, b, k[6], w[6]); + SCR_SHA512_ROUND(b, c, d, e, f, g, h, a, k[7], w[7]); + SCR_SHA512_ROUND(a, b, c, d, e, f, g, h, k[8], w[8]); + SCR_SHA512_ROUND(h, a, b, c, d, e, f, g, k[9], w[9]); + SCR_SHA512_ROUND(g, h, a, b, c, d, e, f, k[10], w[10]); + SCR_SHA512_ROUND(f, g, h, a, b, c, d, e, k[11], w[11]); + SCR_SHA512_ROUND(e, f, g, h, a, b, c, d, k[12], w[12]); + SCR_SHA512_ROUND(d, e, f, g, h, a, b, c, k[13], w[13]); + SCR_SHA512_ROUND(c, d, e, f, g, h, a, b, k[14], w[14]); + SCR_SHA512_ROUND(b, c, d, e, f, g, h, a, k[15], w[15]); + } + a = state[0] += a; b = state[1] += b; c = state[2] += c; d = state[3] += d; + e = state[4] += e; f = state[5] += f; g = state[6] += g; h = state[7] += h; } - for (size_t i = 16; i < 80; i++) { - uint64_t s0 = scr_sha512_rotr(w[i - 15], 1) ^ scr_sha512_rotr(w[i - 15], 8) ^ (w[i - 15] >> 7); - uint64_t s1 = scr_sha512_rotr(w[i - 2], 19) ^ scr_sha512_rotr(w[i - 2], 61) ^ (w[i - 2] >> 6); - w[i] = w[i - 16] + s0 + w[i - 7] + s1; +} + +#ifdef SCR_SHA_NI +/* ── SHA-512 kernels (x86-64) ───────────────────────────────────────── + * With AVX2 and BMI2, the message schedule is computed two words at a + * time in vector registers, interleaved with the scalar rounds, and the + * rounds rotate with RORX. With AVX-512VL the schedule uses vector + * rotates and three-input XOR. The CPU and OS state checks run once and + * are cached. */ +static int scr_sha512_x86_state; /* 0 unknown, 1 portable, 2 AVX2, 3 AVX-512VL */ + +static int scr_sha512_x86(void) { + if (scr_sha512_x86_state == 0) { + unsigned a, b, c, d; + int kind = 1; + if (__get_cpuid(1, &a, &b, &c, &d) && (c & bit_OSXSAVE) && (c & bit_AVX)) { + unsigned lo, hi; + __asm__ volatile("xgetbv" : "=a"(lo), "=d"(hi) : "c"(0)); + (void)hi; + /* XMM and YMM state enabled. */ + if ((lo & 6) == 6 && __get_cpuid_count(7, 0, &a, &b, &c, &d) && (b & bit_AVX2) && + (b & bit_BMI2)) { + kind = 2; + /* Opmask, ZMM0-15 upper halves and ZMM16-31 state enabled. */ + if ((b & bit_AVX512F) && (b & bit_AVX512VL) && (lo & 0xe0) == 0xe0) kind = 3; + } + } + scr_sha512_x86_state = kind; } - uint64_t a = h[0], b = h[1], c = h[2], d = h[3]; - uint64_t e = h[4], f = h[5], g = h[6], v = h[7]; - for (size_t i = 0; i < 80; i++) { - uint64_t s1 = scr_sha512_rotr(e, 14) ^ scr_sha512_rotr(e, 18) ^ scr_sha512_rotr(e, 41); - uint64_t t1 = v + s1 + ((e & f) ^ (~e & g)) + scr_sha512_k[i] + w[i]; - uint64_t s0 = scr_sha512_rotr(a, 28) ^ scr_sha512_rotr(a, 34) ^ scr_sha512_rotr(a, 39); - uint64_t t2 = s0 + ((a & b) ^ (a & c) ^ (b & c)); - v = g; g = f; f = e; e = d + t1; - d = c; c = b; b = a; a = t1 + t2; + return scr_sha512_x86_state; +} + +__attribute__((target("avx2,bmi2"))) static inline __m128i scr_sha512_ssig0_avx2(__m128i x) { + return _mm_xor_si128(_mm_xor_si128(_mm_or_si128(_mm_srli_epi64(x, 1), _mm_slli_epi64(x, 63)), + _mm_or_si128(_mm_srli_epi64(x, 8), _mm_slli_epi64(x, 56))), + _mm_srli_epi64(x, 7)); +} + +__attribute__((target("avx2,bmi2"))) static inline __m128i scr_sha512_ssig1_avx2(__m128i x) { + return _mm_xor_si128(_mm_xor_si128(_mm_or_si128(_mm_srli_epi64(x, 19), _mm_slli_epi64(x, 45)), + _mm_or_si128(_mm_srli_epi64(x, 61), _mm_slli_epi64(x, 3))), + _mm_srli_epi64(x, 6)); +} + +__attribute__((target("avx512f,avx512vl,bmi2"))) static inline __m128i +scr_sha512_ssig0_avx512(__m128i x) { + return _mm_ternarylogic_epi64(_mm_ror_epi64(x, 1), _mm_ror_epi64(x, 8), _mm_srli_epi64(x, 7), + 0x96); +} + +__attribute__((target("avx512f,avx512vl,bmi2"))) static inline __m128i +scr_sha512_ssig1_avx512(__m128i x) { + return _mm_ternarylogic_epi64(_mm_ror_epi64(x, 19), _mm_ror_epi64(x, 61), _mm_srli_epi64(x, 6), + 0x96); +} + +/* A round for the vector kernels. wk is the schedule word plus its + * constant. Maj(a, b, c) is ((a ^ b) & (b ^ c)) ^ b, where b ^ c is the + * previous round's a ^ b. The empty asm statements keep the compiler from + * reassociating the additions: adding Sigma1(e) last keeps the sum of h, + * wk and Ch off the dependency chain through e. */ +#define SCR_SHA512_PIN(v) __asm__("" : "+r"(v)) +#define SCR_SHA512_ROUND_WK(a, b, c, d, e, f, g, h, wk, ab, bc) \ + do { \ + uint64_t t1 = (h) + (wk); \ + SCR_SHA512_PIN(t1); \ + t1 += (g) ^ ((e) & ((f) ^ (g))); \ + SCR_SHA512_PIN(t1); \ + t1 += SCR_SHA512_BSIG1(e); \ + SCR_SHA512_PIN(t1); \ + (d) += t1; \ + (ab) = (a) ^ (b); \ + uint64_t m = t1 + (((ab) & (bc)) ^ (b)); \ + SCR_SHA512_PIN(m); \ + (h) = m + SCR_SHA512_BSIG0(a); \ + } while (0) + +/* Words 2j and 2j+1 of the schedule, in the ring slot of words 2j-16 and + * 2j-15, and their sum with the round constants into wk. */ +#define SCR_SHA512_SCHEDULE(j, ssig0, ssig1) \ + do { \ + if ((j) < 40) { \ + __m128i s15 = _mm_alignr_epi8(w[((j) + 1) & 7], w[(j) & 7], 8); \ + __m128i s7 = _mm_alignr_epi8(w[((j) + 5) & 7], w[((j) + 4) & 7], 8); \ + __m128i next = _mm_add_epi64(_mm_add_epi64(w[(j) & 7], ssig0(s15)), \ + _mm_add_epi64(s7, ssig1(w[((j) + 7) & 7]))); \ + w[(j) & 7] = next; \ + _mm_store_si128((__m128i *)&wk[(j) * 2], \ + _mm_add_epi64(next, _mm_loadu_si128((const __m128i *)&scr_sha512_k[(j) * 2]))); \ + } \ + } while (0) + +/* Rounds 2i to 2i+7 compute the schedule words 2i+16 to 2i+23. */ +#define SCR_SHA512_VECTOR_BODY(ssig0, ssig1) \ + const __m128i bswap = _mm_set_epi64x(0x08090a0b0c0d0e0fULL, 0x0001020304050607ULL); \ + uint64_t a = state[0], b = state[1], c = state[2], d = state[3]; \ + uint64_t e = state[4], f = state[5], g = state[6], h = state[7]; \ + for (; blocks != 0; blocks--, p += 128) { \ + __m128i w[8]; \ + uint64_t wk[80] __attribute__((aligned(16))); \ + _Pragma("GCC unroll 8") for (int i = 0; i < 8; i++) { \ + w[i] = _mm_shuffle_epi8(_mm_loadu_si128((const __m128i *)(p + i * 16)), bswap); \ + _mm_store_si128((__m128i *)&wk[i * 2], \ + _mm_add_epi64(w[i], _mm_loadu_si128((const __m128i *)&scr_sha512_k[i * 2]))); \ + } \ + uint64_t x = b ^ c, y; \ + _Pragma("GCC unroll 10") for (int i = 0; i < 40; i += 4) { \ + SCR_SHA512_SCHEDULE(i + 8, ssig0, ssig1); \ + SCR_SHA512_ROUND_WK(a, b, c, d, e, f, g, h, wk[2 * i + 0], y, x); \ + SCR_SHA512_ROUND_WK(h, a, b, c, d, e, f, g, wk[2 * i + 1], x, y); \ + SCR_SHA512_SCHEDULE(i + 9, ssig0, ssig1); \ + SCR_SHA512_ROUND_WK(g, h, a, b, c, d, e, f, wk[2 * i + 2], y, x); \ + SCR_SHA512_ROUND_WK(f, g, h, a, b, c, d, e, wk[2 * i + 3], x, y); \ + SCR_SHA512_SCHEDULE(i + 10, ssig0, ssig1); \ + SCR_SHA512_ROUND_WK(e, f, g, h, a, b, c, d, wk[2 * i + 4], y, x); \ + SCR_SHA512_ROUND_WK(d, e, f, g, h, a, b, c, wk[2 * i + 5], x, y); \ + SCR_SHA512_SCHEDULE(i + 11, ssig0, ssig1); \ + SCR_SHA512_ROUND_WK(c, d, e, f, g, h, a, b, wk[2 * i + 6], y, x); \ + SCR_SHA512_ROUND_WK(b, c, d, e, f, g, h, a, wk[2 * i + 7], x, y); \ + } \ + a = state[0] += a; b = state[1] += b; c = state[2] += c; d = state[3] += d; \ + e = state[4] += e; f = state[5] += f; g = state[6] += g; h = state[7] += h; \ } - h[0] += a; h[1] += b; h[2] += c; h[3] += d; - h[4] += e; h[5] += f; h[6] += g; h[7] += v; + +__attribute__((target("avx2,bmi2"), aligned(64))) static void +scr_sha512_blocks_avx2(uint64_t state[8], const unsigned char *p, size_t blocks) { + SCR_SHA512_VECTOR_BODY(scr_sha512_ssig0_avx2, scr_sha512_ssig1_avx2) +} + +__attribute__((target("avx512f,avx512vl,bmi2"), aligned(64))) static void +scr_sha512_blocks_avx512(uint64_t state[8], const unsigned char *p, size_t blocks) { + SCR_SHA512_VECTOR_BODY(scr_sha512_ssig0_avx512, scr_sha512_ssig1_avx512) +} +#endif + + +#if defined(__aarch64__) && (defined(__clang__) || defined(__GNUC__)) && !defined(_WIN32) +/* ── SHA-512 kernel (arm64) ─────────────────────────────────────────── + * On CPUs with the SHA-512 extension (FEAT_SHA512), the blocks run with + * SHA512H, SHA512H2, SHA512SU0 and SHA512SU1; other CPUs use the portable + * blocks. The CPU check runs once and is cached. */ +#include +#if defined(__APPLE__) +#include +#else +#include +#endif +#define SCR_SHA512_ARM 1 +#if defined(__clang__) +#define SCR_SHA512_ARM_TARGET __attribute__((target("sha3"))) +#else +#define SCR_SHA512_ARM_TARGET __attribute__((target("+sha3"))) +#endif + +static int scr_sha512_arm_state; /* 0 unknown, 1 available, 2 absent */ + +static bool scr_sha512_arm(void) { + if (scr_sha512_arm_state == 0) { + bool ok; +#if defined(__APPLE__) + int value = 0; + size_t size = sizeof value; + ok = sysctlbyname("hw.optional.armv8_2_sha512", &value, &size, NULL, 0) == 0 && value != 0; +#else + ok = (getauxval(AT_HWCAP) & (1ul << 21)) != 0; /* HWCAP_SHA512 */ +#endif + scr_sha512_arm_state = ok ? 1 : 2; + } + return scr_sha512_arm_state == 1; +} + +/* Two rounds per step. With the state pairs in the roles (A, B, C, D) = + * (ab, cd, ef, gh), a step computes I = SHA512H(swap(K + W) + D, ext(C, D), + * ext(B, C)), D = SHA512H2(I, B, A) and B += I; the roles then rotate to + * (D, A, B, C). */ +#define SCR_SHA512_ARM_STEP(A, B, C, D, msg, k) \ + do { \ + uint64x2_t kw = vaddq_u64((msg), vld1q_u64(k)); \ + uint64x2_t sum = vaddq_u64(vextq_u64(kw, kw, 1), (D)); \ + uint64x2_t i = vsha512hq_u64(sum, vextq_u64((C), (D), 1), vextq_u64((B), (C), 1)); \ + (D) = vsha512h2q_u64(i, (B), (A)); \ + (B) = vaddq_u64((B), i); \ + } while (0) + +/* Message words 2j and 2j+1 from the eight pairs before them. */ +#define SCR_SHA512_ARM_SCHEDULE(m0, m1, m4, m5, m7) \ + (m0) = vsha512su1q_u64(vsha512su0q_u64((m0), (m1)), (m7), vextq_u64((m4), (m5), 1)) + +SCR_SHA512_ARM_TARGET static void scr_sha512_blocks_arm(uint64_t state[8], const unsigned char *p, + size_t blocks) { + uint64x2_t ab = vld1q_u64(&state[0]), cd = vld1q_u64(&state[2]); + uint64x2_t ef = vld1q_u64(&state[4]), gh = vld1q_u64(&state[6]); + for (; blocks != 0; blocks--, p += 128) { + uint64x2_t ab0 = ab, cd0 = cd, ef0 = ef, gh0 = gh; + uint64x2_t m0 = vreinterpretq_u64_u8(vrev64q_u8(vld1q_u8(p + 0))); + uint64x2_t m1 = vreinterpretq_u64_u8(vrev64q_u8(vld1q_u8(p + 16))); + uint64x2_t m2 = vreinterpretq_u64_u8(vrev64q_u8(vld1q_u8(p + 32))); + uint64x2_t m3 = vreinterpretq_u64_u8(vrev64q_u8(vld1q_u8(p + 48))); + uint64x2_t m4 = vreinterpretq_u64_u8(vrev64q_u8(vld1q_u8(p + 64))); + uint64x2_t m5 = vreinterpretq_u64_u8(vrev64q_u8(vld1q_u8(p + 80))); + uint64x2_t m6 = vreinterpretq_u64_u8(vrev64q_u8(vld1q_u8(p + 96))); + uint64x2_t m7 = vreinterpretq_u64_u8(vrev64q_u8(vld1q_u8(p + 112))); + const uint64_t *k = scr_sha512_k; + for (int i = 0; i < 5; i++, k += 16) { + SCR_SHA512_ARM_STEP(ab, cd, ef, gh, m0, k + 0); + SCR_SHA512_ARM_STEP(gh, ab, cd, ef, m1, k + 2); + SCR_SHA512_ARM_STEP(ef, gh, ab, cd, m2, k + 4); + SCR_SHA512_ARM_STEP(cd, ef, gh, ab, m3, k + 6); + SCR_SHA512_ARM_STEP(ab, cd, ef, gh, m4, k + 8); + SCR_SHA512_ARM_STEP(gh, ab, cd, ef, m5, k + 10); + SCR_SHA512_ARM_STEP(ef, gh, ab, cd, m6, k + 12); + SCR_SHA512_ARM_STEP(cd, ef, gh, ab, m7, k + 14); + if (i == 4) break; + SCR_SHA512_ARM_SCHEDULE(m0, m1, m4, m5, m7); + SCR_SHA512_ARM_SCHEDULE(m1, m2, m5, m6, m0); + SCR_SHA512_ARM_SCHEDULE(m2, m3, m6, m7, m1); + SCR_SHA512_ARM_SCHEDULE(m3, m4, m7, m0, m2); + SCR_SHA512_ARM_SCHEDULE(m4, m5, m0, m1, m3); + SCR_SHA512_ARM_SCHEDULE(m5, m6, m1, m2, m4); + SCR_SHA512_ARM_SCHEDULE(m6, m7, m2, m3, m5); + SCR_SHA512_ARM_SCHEDULE(m7, m0, m3, m4, m6); + } + ab = vaddq_u64(ab, ab0); + cd = vaddq_u64(cd, cd0); + ef = vaddq_u64(ef, ef0); + gh = vaddq_u64(gh, gh0); + } + vst1q_u64(&state[0], ab); + vst1q_u64(&state[2], cd); + vst1q_u64(&state[4], ef); + vst1q_u64(&state[6], gh); +} +#endif + +static void scr_sha512_blocks(uint64_t h[8], const unsigned char *p, size_t blocks) { +#ifdef SCR_SHA_NI + switch (scr_sha512_x86()) { + case 3: + scr_sha512_blocks_avx512(h, p, blocks); + return; + case 2: + scr_sha512_blocks_avx2(h, p, blocks); + return; + default: + break; + } +#endif +#ifdef SCR_SHA512_ARM + if (scr_sha512_arm()) { + scr_sha512_blocks_arm(h, p, blocks); + return; + } +#endif + scr_sha512_blocks_portable(h, p, blocks); } static void scr_sha512_init(ScrSha512Ctx *ctx, bool sha384) { @@ -7456,14 +7746,15 @@ static void scr_sha512_update(ScrSha512Ctx *ctx, const unsigned char *data, size data += take; len -= take; if (ctx->tail_len == 128) { - scr_sha512_block(ctx->h, ctx->tail); + scr_sha512_blocks(ctx->h, ctx->tail, 1); ctx->tail_len = 0; } } - while (len >= 128) { - scr_sha512_block(ctx->h, data); - data += 128; - len -= 128; + if (len >= 128) { + size_t blocks = len / 128; + scr_sha512_blocks(ctx->h, data, blocks); + data += blocks * 128; + len -= blocks * 128; } if (len != 0) { memcpy(ctx->tail, data, len); @@ -7482,8 +7773,7 @@ static size_t scr_sha512_final(const ScrSha512Ctx *source, unsigned char out[64] tail[pad - 1 - b] = (unsigned char)(low >> (8 * b)); tail[pad - 9 - b] = (unsigned char)(high >> (8 * b)); } - scr_sha512_block(ctx.h, tail); - if (pad == 256) scr_sha512_block(ctx.h, tail + 128); + scr_sha512_blocks(ctx.h, tail, pad / 128); size_t len = sha384 ? 48 : 64; for (size_t i = 0; i < len; i++) out[i] = (unsigned char)(ctx.h[i / 8] >> (56 - 8 * (i % 8))); return len; diff --git a/packages/runtime/test/sha-kernels.test.ts b/packages/runtime/test/sha-kernels.test.ts index 724841fb1..6f7759348 100644 --- a/packages/runtime/test/sha-kernels.test.ts +++ b/packages/runtime/test/sha-kernels.test.ts @@ -42,7 +42,7 @@ const runtimeSources = [ ]; // Compiled once with ASan, UBSan and the RC audit. The test runs the SHA-NI and portable -// SHA-1/SHA-256 blocks over the same inputs and checks known vectors. +// SHA-1/SHA-256 blocks and the SHA-512 kernels over the same inputs and checks known vectors. beforeAll(async () => { await mkdir(join(testDir, "build"), { recursive: true }); await execFileAsync("clang", [ @@ -64,7 +64,7 @@ beforeAll(async () => { ]); }, 300_000); -test("SHA-1 and SHA-256: the SHA-NI blocks match the portable blocks", async () => { +test("SHA-1, SHA-256, SHA-384 and SHA-512: the CPU kernels match the portable blocks", async () => { const { stderr } = await execFileAsync(bin, [], { env: { ...process.env, UBSAN_OPTIONS: "halt_on_error=1" }, }); diff --git a/packages/runtime/test/test_sha_kernels.c b/packages/runtime/test/test_sha_kernels.c index 7f854216e..f9f9b9df7 100644 --- a/packages/runtime/test/test_sha_kernels.c +++ b/packages/runtime/test/test_sha_kernels.c @@ -1,9 +1,11 @@ -/* SHA-1 and SHA-256 block kernels (scr_lib.c), built by +/* SHA-1, SHA-256, SHA-384 and SHA-512 block kernels (scr_lib.c), built by * sha-kernels.test.ts. The unit is included directly so the test can run - * the SHA-NI blocks and the portable blocks over the same inputs: known - * vectors, every length from 0 to 1100 bytes, and 1 MiB, each fed whole - * and in 1-, 7-, 64- and 100-byte updates. On CPUs without the SHA - * extensions only the portable blocks run. */ + * each kernel the CPU supports and the portable blocks over the same + * inputs: known vectors, every length from 0 to 1100 bytes, and 1 MiB, + * each fed whole and in 1-, 7-, 64- and 100-byte updates. The SHA-1 and + * SHA-256 kernels use the SHA extensions; the SHA-512 kernels use AVX2 or + * AVX-512VL on x86-64 and the SHA-512 extension on arm64. On CPUs without + * them only the portable blocks run. */ #include "../src/scr_lib.c" #include @@ -45,6 +47,71 @@ static void sha1_of(const unsigned char *data, size_t len, size_t chunk, unsigne scr_sha1_final(&ctx, out); } +static void sha512_of(const unsigned char *data, size_t len, size_t chunk, bool sha384, + unsigned char out[64]) { + ScrSha512Ctx ctx; + scr_sha512_init(&ctx, sha384); + for (size_t at = 0; at < len;) { + size_t take = chunk == 0 || chunk > len - at ? len - at : chunk; + scr_sha512_update(&ctx, data + at, take); + at += take; + } + scr_sha512_final(&ctx, out, sha384); +} + +/* FIPS 180-4 example vectors for SHA-384 and SHA-512. */ +static void vectors512(const char *path) { + unsigned char d[64]; + char text[129]; + static const char two_block[] = + "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu"; + sha512_of((const unsigned char *)"abc", 3, 0, false, d); + hex(d, 64, text); + check(strcmp(text, "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f") == 0, path, 3, 0); + sha512_of((const unsigned char *)"", 0, 0, false, d); + hex(d, 64, text); + check(strcmp(text, "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e") == 0, path, 0, 0); + sha512_of((const unsigned char *)two_block, sizeof two_block - 1, 0, false, d); + hex(d, 64, text); + check(strcmp(text, "8e959b75dae313da8cf4f72814fc143f8f7779c6eb9f7fa17299aeadb6889018501d289e4900f7e4331b99dec4b5433ac7d329eeb6dd26545e96e55b874be909") == 0, path, sizeof two_block - 1, 0); + sha512_of((const unsigned char *)"abc", 3, 0, true, d); + hex(d, 48, text); + check(strcmp(text, "cb00753f45a35e8bb5a03d699ac65007272c32ab0eded1631a8b605a43ff5bed8086072ba1e7cc2358baeca134c825a7") == 0, path, 3, 0); + sha512_of((const unsigned char *)two_block, sizeof two_block - 1, 0, true, d); + hex(d, 48, text); + check(strcmp(text, "09330c33f71147e83d192fc782cd1b4753111b173b3b05d22fa08086e3b0f712fcc7c71a557e2db966c3e9fa91746039") == 0, path, sizeof two_block - 1, 0); + static unsigned char million[1000000]; + memset(million, 'a', sizeof million); + sha512_of(million, sizeof million, 0, false, d); + hex(d, 64, text); + check(strcmp(text, "e718483d0ce769644e2e42c7bc15b4638e1f98b13b2044285632a803afa973ebde0ff244877ea60a4cb0432ce577c31beb009c5c2c49aa2e4eadb217ad8cc09b") == 0, path, sizeof million, 0); + sha512_of(million, sizeof million, 0, true, d); + hex(d, 48, text); + check(strcmp(text, "9d0e1809716474cb086e834e310a4a1ced149e9c00f248527972cec5704c2a5b07b8b3dc38ecc4ebae97ddd87f3d8985") == 0, path, sizeof million, 0); +} + +/* Selects the SHA-512 kernel: 0 the portable blocks, otherwise the CPU's + * kernel. Returns false when the CPU has none. */ +static bool select512(int kernel) { +#ifdef SCR_SHA_NI + scr_sha512_x86_state = 0; + int best = scr_sha512_x86(); + if (kernel > best - 1) return false; + scr_sha512_x86_state = kernel == 0 ? 1 : kernel + 1; + return true; +#elif defined(SCR_SHA512_ARM) + scr_sha512_arm_state = 0; + bool available = scr_sha512_arm(); + if (kernel > (available ? 1 : 0)) return false; + scr_sha512_arm_state = kernel == 0 ? 2 : 1; + return true; +#else + return kernel == 0; +#endif +} + +static const char *const kernel512_names[] = {"portable", "avx2-or-arm", "avx512"}; + static void vectors(const char *path) { unsigned char d[32]; char text[65]; @@ -108,8 +175,31 @@ int main(void) { vectors("ni"); } #endif + /* SHA-512 and SHA-384: every kernel against the portable blocks, and + * the known vectors through each. */ + int kernels512 = 0; + for (int k = 0; k < 3 && select512(k); k++) { + kernels512 = k + 1; + vectors512(kernel512_names[k]); + } + for (int k = 1; k < kernels512; k++) { + for (size_t len = 0; len <= 1100 + 1; len++) { + size_t n = len == 1101 ? BIG : len; + for (size_t c = 0; c < sizeof chunks / sizeof chunks[0]; c++) { + for (int sha384 = 0; sha384 < 2; sha384++) { + unsigned char a[64], b[64]; + select512(k); + sha512_of(data, n, chunks[c], sha384, a); + select512(0); + sha512_of(data, n, chunks[c], sha384, b); + check(memcmp(a, b, 64) == 0, kernel512_names[k], n, chunks[c]); + } + } + } + } free(data); fprintf(stderr, "sha-ni %s\n", ni ? "available" : "absent"); + fprintf(stderr, "sha-512 kernels %d\n", kernels512); fprintf(stderr, "%ld/%ld cases passed\n", total - failed, total); return failed != 0; } diff --git a/tests/corpus/5779-sha512-lengths.ts b/tests/corpus/5779-sha512-lengths.ts new file mode 100644 index 000000000..277408409 --- /dev/null +++ b/tests/corpus/5779-sha512-lengths.ts @@ -0,0 +1,35 @@ +// SHA-384 and SHA-512 over every length from 0 to 520 bytes, fed whole and +// in 1-, 7-, 100- and 128-byte updates, then over 1 MiB, and HMAC-SHA512 +// over a few lengths. Each length's digests are folded into one running +// SHA-512 so the output stays short. The CPU's SHA-512 kernel (vector or +// SHA-512 instructions) and the portable blocks give the same digests. +import { createHash, createHmac } from "node:crypto"; + +const data = Buffer.alloc(1 << 20); +let x = 0x9e3779b9; +for (let i = 0; i < data.length; i++) { + x ^= x << 13; + x ^= x >>> 17; + x ^= x << 5; + data[i] = x & 0xff; +} + +function digest(algorithm: string, input: Buffer, chunk: number): Buffer { + const hash = createHash(algorithm); + if (chunk === 0) hash.update(input); + else for (let at = 0; at < input.length; at += chunk) hash.update(input.subarray(at, at + chunk)); + return hash.digest(); +} + +for (const algorithm of ["sha384", "sha512"]) { + for (const chunk of [0, 1, 7, 100, 128]) { + const fold = createHash("sha512"); + for (let length = 0; length <= 520; length++) fold.update(digest(algorithm, data.subarray(0, length), chunk)); + console.log(algorithm, "chunk", chunk, fold.digest("hex").slice(0, 32)); + } + console.log(algorithm, "1 MiB", digest(algorithm, data, 0).toString("hex").slice(0, 32), digest(algorithm, data, 4096).toString("hex").slice(0, 32)); +} +console.log("abc", createHash("sha512").update("abc").digest("hex")); +for (const length of [0, 1, 111, 112, 127, 128, 129, 255, 256, 1000]) { + console.log("hmac", length, createHmac("sha512", "key").update(data.subarray(0, length)).digest("hex").slice(0, 32)); +} diff --git a/tests/harness/corpus-costs/5779-sha512-lengths.ts.json b/tests/harness/corpus-costs/5779-sha512-lengths.ts.json new file mode 100644 index 000000000..9e04a316c --- /dev/null +++ b/tests/harness/corpus-costs/5779-sha512-lengths.ts.json @@ -0,0 +1 @@ +{"plain":2610,"san":3540} From d02c85a6119ee2b64b61362a2884157a27f937e2 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Mon, 19 Oct 2026 21:23:53 -0700 Subject: [PATCH 041/122] Use classes that extend EventEmitter or a stream class as values Naming a class that extends EventEmitter, Readable, Writable, Duplex or Transform as a value was refused ("classes extending builtin bases as values"), and so was a class expression of that kind. The refusal also showed up as a deferred runtime fence at the class declaration, because the checked-dynamic tier reflects every class as a value. A user subclass always constructs through its own or synthesized constructor, whose super call runs the runtime initializer, so the class object's construct thunk serves it like any program class. EventTarget subclasses keep the refusal. Co-Authored-By: Claude Opus 5.5 --- .../src/frontend/lowering/lower-classes.ts | 21 ++----- .../6450-builtin-base-class-values.ts.json | 6 ++ ...51-builtin-base-class-expressions.cjs.json | 6 ++ .../corpus/6450-builtin-base-class-values.ts | 63 +++++++++++++++++++ .../6451-builtin-base-class-expressions.cjs | 33 ++++++++++ 5 files changed, 114 insertions(+), 15 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6450-builtin-base-class-values.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6451-builtin-base-class-expressions.cjs.json create mode 100644 tests/corpus/6450-builtin-base-class-values.ts create mode 100644 tests/corpus/6451-builtin-base-class-expressions.cjs diff --git a/packages/compiler/src/frontend/lowering/lower-classes.ts b/packages/compiler/src/frontend/lowering/lower-classes.ts index 8a5170a7a..81cf09e1f 100644 --- a/packages/compiler/src/frontend/lowering/lower-classes.ts +++ b/packages/compiler/src/frontend/lowering/lower-classes.ts @@ -2212,11 +2212,7 @@ export function collectClassShapeInner( } if (lowerer.collectingExprClasses.has(decl)) { for (let parent = base; parent; parent = parent.base) { - if ( - parent.builtinEmitter || - parent.builtinStream !== undefined || - parent.builtinEventTarget !== undefined - ) { + if (parent.builtinEventTarget !== undefined) { lowerer.unsupported( "SC1090", decl, @@ -5132,16 +5128,11 @@ export function classValueRef(lowerer: Lowerer, info: ClassInfo, blame: ts.Node) loc: locOf(blame), }; } - if ( - lowerer.inheritsBuiltinEmitterCtor(info) || - inheritsBuiltinStreamCtor(lowerer, info) || - eventTargetRootOf(info) !== null || - (() => { - for (let c = info.base; c; c = c.base) - if (c.builtinEmitter || c.builtinStream !== undefined) return true; - return false; - })() - ) { + // Classes extending EventEmitter or a stream class construct through + // their own (or synthesized) `%C.constructor`, whose super call runs the + // runtime initializer, so the class object's construct thunk serves them + // like any program class. EventTarget subclasses keep the refusal. + if (eventTargetRootOf(info) !== null) { lowerer.unsupported( "SC1090", blame, diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6450-builtin-base-class-values.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6450-builtin-base-class-values.ts.json new file mode 100644 index 000000000..bdd8fcbc8 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6450-builtin-base-class-values.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6450-builtin-base-class-values.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6451-builtin-base-class-expressions.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6451-builtin-base-class-expressions.cjs.json new file mode 100644 index 000000000..34cde5811 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6451-builtin-base-class-expressions.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6451-builtin-base-class-expressions.cjs" + ], + "diags": [] +} diff --git a/tests/corpus/6450-builtin-base-class-values.ts b/tests/corpus/6450-builtin-base-class-values.ts new file mode 100644 index 000000000..0dafa5d82 --- /dev/null +++ b/tests/corpus/6450-builtin-base-class-values.ts @@ -0,0 +1,63 @@ +// Classes extending Node builtin bases (Writable, Transform, EventEmitter) +// used as values: through a variable, a generic factory, an object table and +// an array, with instanceof against the class and its base. +import { EventEmitter } from "node:events"; +import { Readable, Transform, Writable } from "node:stream"; + +class Sink extends Writable { + readonly lines: string[] = []; + private readonly tag: string; + constructor(tag: string) { + super(); + this.tag = tag; + } + _write(chunk: Buffer, _enc: BufferEncoding, cb: (err?: Error | null) => void): void { + this.lines.push(this.tag + chunk.toString()); + cb(); + } +} + +class Upper extends Transform { + _transform(chunk: Buffer, _enc: BufferEncoding, cb: (err?: Error | null, data?: string) => void): void { + cb(null, chunk.toString().toUpperCase()); + } +} + +class Ticker extends EventEmitter { + count = 0; + tick(): void { + this.count++; + this.emit("tick", this.count); + } +} + +class Plain extends EventEmitter {} + +function build(ctor: new () => T): T { + return new ctor(); +} + +const SinkClass = Sink; +const s = new SinkClass("> "); +s.write("a"); +s.end("b", () => console.log("sink", JSON.stringify(s.lines), s instanceof Sink, s instanceof Writable)); + +const t = build(Ticker); +t.on("tick", (n: number) => console.log("tick", n)); +t.tick(); +t.tick(); +console.log("ticker", t instanceof Ticker, t instanceof EventEmitter, t.count); + +const p = build(Plain); +p.on("x", () => console.log("plain x")); +p.emit("x"); + +const registry = { upper: Upper }; +const u = new registry.upper(); +u.on("data", (d: Buffer) => console.log("upper", d.toString())); +u.end("hello"); + +const kinds = [Ticker, Ticker]; +console.log("names", kinds.map((k) => k.name).join(","), typeof Sink, Sink.name); +const r = Readable.from(["x"]); +console.log("readable", r instanceof Readable); diff --git a/tests/corpus/6451-builtin-base-class-expressions.cjs b/tests/corpus/6451-builtin-base-class-expressions.cjs new file mode 100644 index 000000000..64230a679 --- /dev/null +++ b/tests/corpus/6451-builtin-base-class-expressions.cjs @@ -0,0 +1,33 @@ +// Class expressions extending Node builtin bases (a bundler's output shape). +"use strict"; +const { Readable, Transform } = require("stream"); +const { EventEmitter } = require("events"); + +const Wrap = class extends Readable { + constructor(parts) { + super(); + this.parts = parts; + } + _read() { + const next = this.parts.shift(); + this.push(next === undefined ? null : next); + } +}; +const Bus = class extends EventEmitter {}; +const Lower = class extends Transform { + _transform(chunk, _enc, cb) { + cb(null, chunk.toString().toLowerCase()); + } +}; + +const r = new Wrap(["a", "b"]); +const chunks = []; +r.on("data", (c) => chunks.push(c.toString())); +r.on("end", () => console.log("wrap", chunks.join("+"), r instanceof Readable)); +const bus = new Bus(); +bus.on("ping", (n) => console.log("ping", n)); +bus.emit("ping", 1); +const l = new Lower(); +l.on("data", (d) => console.log("lower", d.toString())); +l.end("HeLLo"); +console.log(Wrap.name, Bus.name, typeof Lower); From 619c857fcd6f73c1339d667c5d0d240d836ac813 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Tue, 20 Oct 2026 00:09:04 -0500 Subject: [PATCH 042/122] List an error's own name where it was created Node lists an object's own properties in creation order. An Error's own `name` is created by an assignment after construction defines `message` and `cause`, or during construction by a `name` field or a constructor's `this.name = ...`. Errors listed an own `name` before `message`, and instances of Error subclasses listed it before every field, so Object.keys, Object.getOwnPropertyNames and JSON.stringify answered a different order than Node's: `{"name":"E","extra":1}` for `{"extra":1, "name":"E"}`. An error's own-property view now lists `name` after the properties its construction defines. For an Error subclass, the class records which field `name` follows: a `name` field takes its place among the fields, parameter properties first, and `this.name = ...` in a constructor follows the fields created before it, including the ones that constructor assigns in JavaScript sources. When no class of the chain sets `name` during construction, an own `name` follows every other own property. --- .../src/backend/llvm/expr-stream-bridges.ts | 12 +++ .../src/frontend/lowering/error-name-order.ts | 86 +++++++++++++++ .../src/frontend/lowering/lower-classes.ts | 7 ++ .../src/ir/function-references.test.ts | 1 + packages/compiler/src/ir/ir.ts | 5 + .../corpus/6403-error-own-name-order.ts.json | 6 ++ .../6404-error-own-name-order-js.js.json | 6 ++ packages/runtime/src/scr_dyn.h | 1 + packages/runtime/src/scr_json.c | 32 +++++- tests/corpus/6403-error-own-name-order.ts | 102 ++++++++++++++++++ tests/corpus/6404-error-own-name-order-js.js | 84 +++++++++++++++ .../6403-error-own-name-order.ts.json | 1 + .../6404-error-own-name-order-js.js.json | 1 + 13 files changed, 340 insertions(+), 4 deletions(-) create mode 100644 packages/compiler/src/frontend/lowering/error-name-order.ts create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6403-error-own-name-order.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6404-error-own-name-order-js.js.json create mode 100644 tests/corpus/6403-error-own-name-order.ts create mode 100644 tests/corpus/6404-error-own-name-order-js.js create mode 100644 tests/harness/corpus-costs/6403-error-own-name-order.ts.json create mode 100644 tests/harness/corpus-costs/6404-error-own-name-order-js.js.json diff --git a/packages/compiler/src/backend/llvm/expr-stream-bridges.ts b/packages/compiler/src/backend/llvm/expr-stream-bridges.ts index 8a53b5fa8..c6e6bf85d 100644 --- a/packages/compiler/src/backend/llvm/expr-stream-bridges.ts +++ b/packages/compiler/src/backend/llvm/expr-stream-bridges.ts @@ -920,6 +920,18 @@ export function streamTypedRefMaterializeAdapter( B.line(`${copied} = call ptr @scr_dyn_copy_property_descriptors(ptr ${out}, ptr ${bag})`); B.line(`call void @scr_dyn_release_v(ptr ${copied})`); } + // An Error subclass instance's own `name` sits where its class's + // construction creates it: after a field, or after every other + // property when no constructor sets it. + const nameAfter = meta.def.errorNameAfter; + if (meta.root.def.name === "%Error" && !meta.def.runtime && nameAfter !== null) { + host.declare(`declare void @scr_error_dyn_place_name(ptr, ptr, ${host.sizeType})`); + B.line( + nameAfter === undefined + ? `call void @scr_error_dyn_place_name(ptr ${out}, ptr null, ${host.sizeType} 0)` + : `call void @scr_error_dyn_place_name(ptr ${out}, ptr ${host.cstr(nameAfter)}, ${host.sizeType} ${Buffer.byteLength(nameAfter, "utf8")})`, + ); + } B.terminate(`ret ptr ${out}`); } else if (t.kind === "record") { const shape = host.recordsById.get(t.shapeId); diff --git a/packages/compiler/src/frontend/lowering/error-name-order.ts b/packages/compiler/src/frontend/lowering/error-name-order.ts new file mode 100644 index 000000000..8523b47cb --- /dev/null +++ b/packages/compiler/src/frontend/lowering/error-name-order.ts @@ -0,0 +1,86 @@ +import * as ts from "../ts7/adapter.js"; +import { isClassOwnEnumerableFieldName } from "../../ir/ir.js"; +import type { ClassInfo } from "./lower-classes.js"; + +type ErrorClassLink = Pick & { + base: ErrorClassLink | null; +}; + +/** Whether `node` is an assignment to `this.`, and the name. */ +function thisAssignmentName(node: ts.Node): string | null { + if (!ts.isBinaryExpression(node) || node.operatorToken.kind !== ts.SyntaxKind.EqualsToken) + return null; + const target = node.left; + if ( + !ts.isPropertyAccessExpression(target) || + target.expression.kind !== ts.SyntaxKind.ThisKeyword + ) + return null; + return ts.isIdentifier(target.name) ? target.name.text : null; +} + +/** Where an Error subclass instance's own `name` sits among its fields when + * a class of its chain gives it one during construction. A property's + * position is fixed when it is created: a `name` field takes its place + * among the fields (parameter properties first, then the declared ones), + * and `this.name = ...` in a constructor follows the fields created so far, + * including the ones that constructor created before it. The answer is the + * field `name` follows, null when it precedes every field, and undefined + * when no class of the chain sets it during construction. `fields` are the + * instance's own enumerable fields. */ +export function errorNameAfterField( + info: ErrorClassLink, + fields: ReadonlySet, +): string | null | undefined { + const chain: ErrorClassLink[] = []; + for (let current: ErrorClassLink | null | undefined = info; current; current = current.base) + if (current.decl) chain.unshift(current); + const created = new Set(); + let last: string | null = null; + const create = (name: string): void => { + if (!fields.has(name) || created.has(name)) return; + created.add(name); + last = name; + }; + for (const link of chain) { + for (const property of link.paramProps ?? []) create(property.name); + for (const member of link.decl!.members.nodes) { + if (!ts.isPropertyDeclaration(member) || !ts.isIdentifier(member.name)) continue; + if ( + member.modifiers?.nodes.some( + (m) => m.kind === ts.SyntaxKind.StaticKeyword || m.kind === ts.SyntaxKind.DeclareKeyword, + ) + ) + continue; + if (member.name.text === "name") return last; + create(member.name.text); + } + const body = link.ctor?.body; + if (!body) continue; + let found = false; + ts.walkPreorder(body, (node) => { + if (ts.isTypeNode(node)) return "skip"; + if (ts.isFunctionLike(node) && !ts.isArrowFunction(node)) return "skip"; + const name = thisAssignmentName(node); + if (name === "name") { + found = true; + return "stop"; + } + if (name !== null) create(name); + return undefined; + }); + if (found) return last; + } + return undefined; +} + +/** The fields an Error subclass's own-property view lists. */ +export function errorViewFields(fields: readonly { name: string }[]): Set { + return new Set( + fields + .map((field) => field.name) + .filter( + (name) => isClassOwnEnumerableFieldName(name) && name !== "name" && name !== "message", + ), + ); +} diff --git a/packages/compiler/src/frontend/lowering/lower-classes.ts b/packages/compiler/src/frontend/lowering/lower-classes.ts index 8a5170a7a..e4ca6d4b6 100644 --- a/packages/compiler/src/frontend/lowering/lower-classes.ts +++ b/packages/compiler/src/frontend/lowering/lower-classes.ts @@ -70,6 +70,7 @@ import { } from "./instantiation-path.js"; import { appendImplicitUndefinedReturn, hasExplicitJsDocReturn } from "./function-returns.js"; import { bodyReadsArguments } from "../arguments-usage.js"; +import { errorNameAfterField, errorViewFields } from "./error-name-order.js"; import { checkedJsDocumentedParameter, documentedParameter, @@ -4209,6 +4210,12 @@ export function collectClassShapeInner( ...(deferredInitFields.size > 0 ? { deferredInitFields } : {}), ...(escape.escapes ? { constructionEscapes: true as const } : {}), }; + let root: ClassInfo | null = info; + while (root?.base) root = root.base; + if (root !== info && root?.def.name === "%Error") { + const after = errorNameAfterField(info, errorViewFields(info.def.fields)); + if (after !== undefined) info.def.errorNameAfter = after; + } // GENERIC members get their declaring-class backlink now that the // info exists (instance lowering reads it for `this` typing and the // generic-class binding merge). diff --git a/packages/compiler/src/ir/function-references.test.ts b/packages/compiler/src/ir/function-references.test.ts index c2d461e09..8c82b42ba 100644 --- a/packages/compiler/src/ir/function-references.test.ts +++ b/packages/compiler/src/ir/function-references.test.ts @@ -154,6 +154,7 @@ const MODULE_FIELDS: Record>, Role> = { "classes[].jsName": "other", "classes[].genericOf": "other", "classes[].baseValueGlobal": "other", + "classes[].errorNameAfter": "other", "classes[].prototypeDataHelper": "reference", "classes[].instancePrototypeHelper": "reference", "classes[].localCaptures[].name": "other", diff --git a/packages/compiler/src/ir/ir.ts b/packages/compiler/src/ir/ir.ts index b84c0594e..890b33426 100644 --- a/packages/compiler/src/ir/ir.ts +++ b/packages/compiler/src/ir/ir.ts @@ -1406,6 +1406,11 @@ export interface IrClassDef { * bag. Its descriptors preserve presence and insertion order independently * of native layout order; field values remain in their native slots. */ tracksOwnFields?: boolean; + /** Error subclasses: the field an instance's own `name` follows among its + * own properties, or null when it precedes every field. Absent when no + * class of the chain sets `name` during construction, and then an own + * `name` follows every other own property. */ + errorNameAfter?: string | null; /** Stable module symbol identities for public instance layout fields. */ symbolFields?: { field: string; globalId: string }[]; /** RUNTIME-PROVIDED class (the builtin Error hierarchy): the struct, RC diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6403-error-own-name-order.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6403-error-own-name-order.ts.json new file mode 100644 index 000000000..da39be0a0 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6403-error-own-name-order.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6403-error-own-name-order.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6404-error-own-name-order-js.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6404-error-own-name-order-js.js.json new file mode 100644 index 000000000..b19997ac3 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6404-error-own-name-order-js.js.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6404-error-own-name-order-js.js" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_dyn.h b/packages/runtime/src/scr_dyn.h index a3d506a10..78efdd129 100644 --- a/packages/runtime/src/scr_dyn.h +++ b/packages/runtime/src/scr_dyn.h @@ -1066,6 +1066,7 @@ extern SCR_TL size_t scr_uncloneable_count; ScrDyn *scr_dyn_from_error(const ScrError *e); void scr_error_register_dyn(const ScrVt *vt, ScrDyn *(*box)(void *)); ScrDyn *scr_error_dyn_fields(const ScrError *e); +void scr_error_dyn_place_name(ScrDyn *view, const char *after, size_t after_len); void scr_error_commit_dyn(ScrError *e, const ScrDyn *view); /* The reverse extraction, riding the same identity cache: a dyn error * that came from a runtime ScrError answers THAT instance (+1; diff --git a/packages/runtime/src/scr_json.c b/packages/runtime/src/scr_json.c index e41c2c37d..ce8ba53ed 100644 --- a/packages/runtime/src/scr_json.c +++ b/packages/runtime/src/scr_json.c @@ -6822,10 +6822,6 @@ ScrDyn *scr_error_dyn_fields(const ScrError *e) { d->v.obj.entries[d->v.obj.len - 1].enumerable = false; /* Node lists `stack` first among an error's own properties. */ scr_error_dyn_add_stack(d); - if (e->name_present) { - scr_dyn_obj_set(d, "name", 4, scr_dyn_new_str(e->name)); - d->v.obj.entries[d->v.obj.len - 1].enumerable = e->name_enumerable; - } if (!e->name_present || !e->message_present) { ScrDyn *prototype = scr_dyn_new_obj(); if (!e->name_present) { @@ -6901,9 +6897,37 @@ ScrDyn *scr_error_dyn_fields(const ScrError *e) { scr_dyn_obj_set(d, "errors", 6, scr_aggregate_errors((ScrError *)e)); d->v.obj.entries[d->v.obj.len - 1].enumerable = false; } + /* An own `name` is assigned after construction defines the others. */ + if (e->name_present) { + scr_dyn_obj_set(d, "name", 4, scr_dyn_new_str(e->name)); + d->v.obj.entries[d->v.obj.len - 1].enumerable = e->name_enumerable; + } return d; } +/* Moves an Error subclass view's own `name` to follow the property + * `after`, or every other property when `after` is NULL: the position + * the class's construction creates it at. Nothing changes when the view + * has no own `name` or no `after` property. */ +void scr_error_dyn_place_name(ScrDyn *view, const char *after, size_t after_len) { + if (view->kind != SCR_DYN_OBJ) return; + ScrDynEntry *entries = view->v.obj.entries; + size_t n = view->v.obj.len, from = n, to = n; + for (size_t i = 0; i < n; i++) { + if (entries[i].key_len == 4 && memcmp(entries[i].key, "name", 4) == 0) from = i; + else if (after && entries[i].key_len == after_len && memcmp(entries[i].key, after, after_len) == 0) to = i; + } + if (from == n || (after && to == n)) return; + size_t at = after ? (to > from ? to : to + 1) : n - 1; + if (at == from) return; + ScrDynEntry name = entries[from]; + memmove(&entries[from], &entries[from + 1], (n - from - 1) * sizeof *entries); + memmove(&entries[at + 1], &entries[at], (n - 1 - at) * sizeof *entries); + entries[at] = name; + free(view->v.obj.index); + view->v.obj.index = NULL; +} + ScrDyn *scr_dyn_from_error(const ScrError *e) { size_t hit = scr_errdyn_find(e, true); if (hit != SIZE_MAX) return scr_dyn_retain(scr_errdyn_cache[hit].dyn); diff --git a/tests/corpus/6403-error-own-name-order.ts b/tests/corpus/6403-error-own-name-order.ts new file mode 100644 index 000000000..25e59bb7c --- /dev/null +++ b/tests/corpus/6403-error-own-name-order.ts @@ -0,0 +1,102 @@ +// An error's own `name` is listed where it was created, as in Node: +// after `message` and `cause`, after the fields created before it, and +// before the fields created after it. A `name` field takes its place among +// the fields, `this.name = ...` in a constructor follows that class's +// fields, and a name assigned after construction follows every other own +// property. Object.keys, JSON.stringify and Object.getOwnPropertyNames +// list the same order, for typed values and values held as `any`. + +class AssignedName extends Error { + extra = 1; + constructor(message: string) { + super(message); + this.name = "AssignedName"; + } +} + +class NameField extends Error { + override name = "NameField"; + extra = 1; +} + +class LaterNameField extends Error { + extra = 1; + override name = "LaterNameField"; + last = 2; +} + +class CodeThenName extends Error { + code: string; + constructor(message: string) { + super(message); + this.code = "E_CODE"; + this.name = "CodeThenName"; + } +} + +class NamedBase extends Error { + constructor(message: string) { + super(message); + this.name = "NamedBase"; + } +} + +class ChildOfNamed extends NamedBase { + child = true; +} + +class FieldBase extends Error { + base = "base"; +} + +class NamedChild extends FieldBase { + child = "child"; + constructor(message: string) { + super(message); + this.name = "NamedChild"; + } +} + +class Unnamed extends Error { + extra = 1; +} + +function dyn(value: unknown): any { + return value; +} + +function show(label: string, error: Error): void { + console.log( + label, + "|", + Object.keys(dyn(error)).join(","), + "|", + JSON.stringify(dyn(error)), + "|", + Object.getOwnPropertyNames(dyn(error)).join(","), + ); +} + +show("assigned", new AssignedName("a")); +show("field", new NameField("b")); +show("later field", new LaterNameField("c")); +show("code then name", new CodeThenName("d")); +show("child of named", new ChildOfNamed("e")); +show("named child", new NamedChild("f")); + +const late = new Unnamed("g"); +late.name = "Late"; +show("assigned later", late); + +const builtin = new TypeError("h", { cause: 1 }); +builtin.name = "Renamed"; +show("builtin", builtin); + +const viaAny = dyn(new RangeError("i")); +viaAny.code = "E_RANGE"; +viaAny.name = "Custom"; +viaAny.after = true; +console.log("through any", Object.keys(viaAny).join(","), JSON.stringify(viaAny)); + +const typed = new AssignedName("j"); +console.log("typed keys", Object.keys(typed).join(","), JSON.stringify({ error: typed })); diff --git a/tests/corpus/6404-error-own-name-order-js.js b/tests/corpus/6404-error-own-name-order-js.js new file mode 100644 index 000000000..0b5ad4e82 --- /dev/null +++ b/tests/corpus/6404-error-own-name-order-js.js @@ -0,0 +1,84 @@ +// In JavaScript sources, an error's own `name` is listed where it was +// created, as in Node: among the properties a constructor assigns, in +// assignment order, after the fields declared before it, and after every +// other own property when it is assigned after construction. + +class AssignedBetween extends Error { + constructor(message) { + super(message); + this.code = "E_CODE"; + this.name = "AssignedBetween"; + this.detail = { at: 1 }; + } +} + +class AssignedFirst extends Error { + constructor(message) { + super(message); + this.name = "AssignedFirst"; + this.status = 500; + } +} + +class DeclaredField extends Error { + kind = "declared"; + constructor(message) { + super(message); + this.name = "DeclaredField"; + this.after = true; + } +} + +class NameField extends Error { + name = "NameField"; + extra = 1; +} + +class Base extends Error { + constructor(message) { + super(message); + this.base = 1; + } +} + +class Derived extends Base { + constructor(message) { + super(message); + this.name = "Derived"; + this.derived = 2; + } +} + +class Unnamed extends Error { + constructor(message) { + super(message); + this.extra = 1; + } +} + +function show(label, error) { + console.log( + label, + "|", + Object.keys(error).join(","), + "|", + JSON.stringify(error), + "|", + Object.getOwnPropertyNames(error).join(","), + ); +} + +show("between", new AssignedBetween("a")); +show("first", new AssignedFirst("b")); +show("declared field", new DeclaredField("c")); +show("name field", new NameField("d")); +show("derived", new Derived("e")); + +const late = new Unnamed("f"); +late.name = "Late"; +show("assigned later", late); + +const builtin = new Error("g", { cause: "reason" }); +builtin.name = "Renamed"; +builtin.code = "E_AFTER"; +show("builtin", builtin); diff --git a/tests/harness/corpus-costs/6403-error-own-name-order.ts.json b/tests/harness/corpus-costs/6403-error-own-name-order.ts.json new file mode 100644 index 000000000..882c13186 --- /dev/null +++ b/tests/harness/corpus-costs/6403-error-own-name-order.ts.json @@ -0,0 +1 @@ +{ "plain": 3289, "san": 4302 } diff --git a/tests/harness/corpus-costs/6404-error-own-name-order-js.js.json b/tests/harness/corpus-costs/6404-error-own-name-order-js.js.json new file mode 100644 index 000000000..05ac342cb --- /dev/null +++ b/tests/harness/corpus-costs/6404-error-own-name-order-js.js.json @@ -0,0 +1 @@ +{ "plain": 3030, "san": 3963 } From 85a2567ecdcacfcbf1004af9cc248b7fc04b8ffc Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Mon, 19 Oct 2026 20:35:22 -0700 Subject: [PATCH 043/122] Treat optional members an index signature covers as keys, erase 'as never', and keep fromEntries objects asserted to records A record type whose declared members are all optional and typed as its index signature's slot (ProcessEnv's TZ?, '{ CI?: string } & Dictionary') interns as the pure index-signature record, its slot admitting undefined: spread, delete and enumeration then follow one insertion order. 'value as never' erases, so the consuming slot checks an unknown value like any other. 'Object.fromEntries(...) as T' for a declared record T goes through the checked-dynamic bridge, which keeps the entries' keys and order. Co-Authored-By: Claude Opus 5.5 --- .../lowering/expressions/narrowing.ts | 52 +++++++++++++ packages/compiler/src/frontend/type-mapper.ts | 47 ++++++++++++ ...ional-members-over-index-signature.ts.json | 6 ++ .../6466-never-assertion-argument.ts.json | 6 ++ .../6467-fromentries-record-assertion.ts.json | 6 ++ ...5-optional-members-over-index-signature.ts | 73 +++++++++++++++++++ tests/corpus/6466-never-assertion-argument.ts | 31 ++++++++ .../6467-fromentries-record-assertion.ts | 29 ++++++++ 8 files changed, 250 insertions(+) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6465-optional-members-over-index-signature.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6466-never-assertion-argument.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6467-fromentries-record-assertion.ts.json create mode 100644 tests/corpus/6465-optional-members-over-index-signature.ts create mode 100644 tests/corpus/6466-never-assertion-argument.ts create mode 100644 tests/corpus/6467-fromentries-record-assertion.ts diff --git a/packages/compiler/src/frontend/lowering/expressions/narrowing.ts b/packages/compiler/src/frontend/lowering/expressions/narrowing.ts index 207000717..09bb4b4d1 100644 --- a/packages/compiler/src/frontend/lowering/expressions/narrowing.ts +++ b/packages/compiler/src/frontend/lowering/expressions/narrowing.ts @@ -1151,6 +1151,50 @@ export function withRuntimeOptionalNarrowed( * memory); * - dyn → anything else (closures, class instances, void) is rejected: * those types cannot be found inside a JSON dyn. */ +/** `Object.fromEntries(...) as T` with T a declared record (the generic + * `transform>(t: T): T` idiom): the + * entries build a pure index-signature record, which has no layout in + * common with T's struct. Node keeps the object itself, with exactly the + * keys and the order the entries gave it. The result of fromEntries is a + * fresh object nothing else references, so the checked-dynamic bridge, + * which keeps keys and their order the same way `JSON.parse(text) as T` + * does, cannot be told apart from the original object; a value outside + * T throws the catchable TypeError of every checked cast. Other sources + * may be aliased, where a rebuilt object would sever mutation, so they + * keep the exact-shape fence. */ +function freshEntriesRecordAssertion( + lowerer: Lowerer, + expr: ts.AsExpression | ts.TypeAssertion, + inner: IrExpr, + target: IrType | null, +): IrExpr | null { + if (inner.type.kind !== "record" || target?.kind !== "record") return null; + const source = lowerer.shapes.get(inner.type.shapeId); + const dest = lowerer.shapes.get(target.shapeId); + if (!source?.indexValue || source.tuple || source.fields.length > 0) return null; + if (!dest || dest.tuple || dest.indexValue || shapeHasAccessorSlots(dest)) return null; + let operand: ts.Expression = expr.expression; + while (ts.isParenthesizedExpression(operand)) operand = operand.expression; + if ( + !ts.isCallExpression(operand) || + !ts.isPropertyAccessExpression(operand.expression) || + operand.expression.name.text !== "fromEntries" || + !lowerer.isStdlibGlobal(operand.expression.expression, "Object") + ) + return null; + if (!lowerer.dynConvertible(inner.type)) return null; + if ( + !canDynCheckTo( + target, + (id) => lowerer.shapes.get(id), + (id) => lowerer.unions.get(id), + ) + ) + return null; + const loc = locOf(expr); + return { kind: "dynCheck", value: lowerer.coerceToExpected(inner, DYN), type: target, loc }; +} + /** `e as T` and the old-style assertion `e` — one node shape (both * carry `.type` and `.expression`), one lowering. */ export function lowerAsExpression( @@ -1286,6 +1330,8 @@ export function lowerAsExpression( ) { return lowerer.coerceInto(expr, inner, target); } + const rebuilt = freshEntriesRecordAssertion(lowerer, expr, inner, target); + if (rebuilt) return rebuilt; // Static assertions normally erase, but record layouts are // monomorphic: a consumer selected from the asserted shape must see // that shape physically. Reuse the ordinary slot coercion so plain @@ -1327,6 +1373,12 @@ export function lowerAsExpression( return inner; // erasure, unchanged } + // `value as never` is the escape hatch that makes an unknown value fit + // any slot (`tagSuccess(value as never)`). No value has type never, so + // checking against its placeholder layout would reject every value. + // Erase it instead: the consuming slot applies the same validated + // extraction an unknown value gets wherever the checker admits it. + if (expr.type.kind === ts.SyntaxKind.NeverKeyword) return inner; const targetTs = lowerer.checker.getTypeFromTypeNode(expr.type); const target = lowerer.mapTypeOf(targetTs) ?? dynFallbackType(lowerer, expr.type, targetTs); if (!target) lowerer.badType(expr.type, targetTs); diff --git a/packages/compiler/src/frontend/type-mapper.ts b/packages/compiler/src/frontend/type-mapper.ts index 437aba2b8..2b47c8ff3 100644 --- a/packages/compiler/src/frontend/type-mapper.ts +++ b/packages/compiler/src/frontend/type-mapper.ts @@ -5860,6 +5860,8 @@ function mapRecordType(widened: ts.Type, ctx: TypeMapperCtx): IrType | null { (inner.indexValue === undefined || inner.indexValue.kind === "dyn") ) return DYN; + const covered = coveredOptionalFieldsShape(widened, inner, ctx); + if (covered !== null) return { kind: "record", shapeId: covered }; const refined = refinedConstituentShape(widened, inner, ctx); if (refined !== null) return { kind: "record", shapeId: refined }; return { @@ -5871,6 +5873,51 @@ function mapRecordType(widened: ts.Type, ctx: TypeMapperCtx): IrType | null { } } +/** An index signature already describes every OPTIONAL declared member + * whose type is its slot type: @types/node's `ProcessEnv extends + * Dict { TZ?: string }` and every spread of it, or a declared + * environment `{ CI?: string } & Dictionary`. At runtime such a + * member is just another key, so the shape interns as the pure + * index-signature record. Its slot admits undefined, as the optional + * members do (`env.CI = undefined` keeps the key). A hybrid layout would + * give each member a struct slot, which enumerates before the overflow + * keys (Node keeps one insertion order) and makes `delete` and spread + * answer differently for declared and undeclared keys. Required members, + * and members typed otherwise than the slot (whose reads the slot type + * could not give back), keep the hybrid shape. */ +function coveredOptionalFieldsShape( + widened: ts.Type, + parts: RecordShapeParts, + ctx: TypeMapperCtx, +): string | null { + const slot = parts.indexValue; + if (!slot || parts.fields.length === 0) return null; + const { checker, shapes, unions } = ctx; + // The members' common type must be the slot plus undefined. Read it off + // the members (already interned) rather than building it: a hybrid that + // does not qualify must not intern anything new. + const optionalSlot = parts.fields[0]!.type; + if (!parts.fields.every((field) => typeEquals(field.type, optionalSlot))) return null; + if (optionalSlot.kind !== "union" || unions.armTag(optionalSlot.unionId, UNDEFINED_T) < 0) + return null; + const memberArms = unions.get(optionalSlot.unionId)?.arms ?? []; + const slotArms = + slot.kind === "union" ? (unions.get(slot.unionId)?.arms ?? []) : isUnitType(slot) ? [] : [slot]; + const definedArms = memberArms.filter((arm) => arm.kind !== "undefinedT"); + const slotDefined = slotArms.filter((arm) => arm.kind !== "undefinedT"); + if ( + definedArms.length === 0 || + definedArms.length !== slotDefined.length || + !slotDefined.every((arm) => definedArms.some((other) => typeEquals(arm, other))) + ) + return null; + if (!isSupportedIndexValue(optionalSlot)) return null; + const props = checker.getPropertiesOfType(widened); + if (props.length !== parts.fields.length) return null; + if (!props.every((p) => (p.flags & ts.SymbolFlags.Optional) !== 0)) return null; + return shapes.intern([], false, optionalSlot, []); +} + /** A refinement intersection (`Expr & { kind: "mul" }`, which the checker * reduces to `Mul & { kind: "mul" }`) keeps the shape of the constituent it * refines when the refinement changes no field's representation. Recursive diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6465-optional-members-over-index-signature.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6465-optional-members-over-index-signature.ts.json new file mode 100644 index 000000000..91a157207 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6465-optional-members-over-index-signature.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6465-optional-members-over-index-signature.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6466-never-assertion-argument.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6466-never-assertion-argument.ts.json new file mode 100644 index 000000000..17f25aa22 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6466-never-assertion-argument.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6466-never-assertion-argument.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6467-fromentries-record-assertion.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6467-fromentries-record-assertion.ts.json new file mode 100644 index 000000000..21ca66953 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6467-fromentries-record-assertion.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6467-fromentries-record-assertion.ts" + ], + "diags": [] +} diff --git a/tests/corpus/6465-optional-members-over-index-signature.ts b/tests/corpus/6465-optional-members-over-index-signature.ts new file mode 100644 index 000000000..df957c8d9 --- /dev/null +++ b/tests/corpus/6465-optional-members-over-index-signature.ts @@ -0,0 +1,73 @@ +// An index signature whose value admits undefined already describes an +// optional member of the same type (`interface Env extends Dict +// { TZ?: string }`, @types/node's ProcessEnv): such a record is one key +// set in insertion order, so spread, delete, `in` and every enumeration +// treat the declared member like any other key. +interface Env { + TZ?: string; + [key: string]: string | undefined; +} + +const base: Env = { A: "1", TZ: "UTC", B: "2" }; +const env = { ...base }; +env.C = "3"; +for (const key of ["A", "TZ", "MISSING"]) { + delete env[key]; +} +console.log(Object.entries(env), Object.keys(base)); +const probe = (key: string): boolean => key in env; +console.log(probe("TZ"), probe("B"), env.TZ, env.B); +env.TZ = "Europe/Berlin"; +console.log(JSON.stringify(env), env); + +const extended: Env = { ...base, D: "4" }; +delete extended.TZ; +extended.TZ = "Asia/Tokyo"; +console.log(extended, Object.keys(extended).join(",")); + +// A declared member written after another key comes after it. +interface Scores { + best?: number; + [name: string]: number | undefined; +} +const scores: Scores = {}; +scores["x"] = 1; +scores.best = 2; +console.log(Object.keys(scores), JSON.stringify(scores)); + +// The process environment has the same shape; a child environment built +// from it keeps the parent's keys in order. +process.env.SCX_FIRST = "first"; +process.env.SCX_DROP = "drop"; +process.env.SCX_LAST = "last"; +const DROP = ["SCX_DROP", "SCX_NOT_SET"]; +function childEnv(): string[] { + const runEnv = { ...process.env }; + runEnv.SCX_ADDED = "added"; + for (const key of DROP) { + delete runEnv[key]; + } + runEnv.SCX_FIRST = runEnv.SCX_FIRST ? `${runEnv.SCX_FIRST}!` : "none"; + return Object.entries(runEnv) + .filter(([key]) => key.startsWith("SCX_")) + .map(([key, value]) => `${key}=${value || ""}`); +} +console.log(childEnv()); + +// A record of declared optional members over a slot without undefined is +// the same key set: merging it over the process environment keeps both. +type Dictionary = { [key: string]: T }; +type BuildEnv = { CI?: string; DEBUG?: string } & Dictionary; +function merged(buildEnv: BuildEnv): string[] { + const env = { ...process.env, ...buildEnv }; + return [ + String(env.CI === "1" || env.SCX_FIRST === "first"), + `${env.DEBUG ?? "-"}`, + Object.keys(env) + .filter((key) => key.startsWith("SCX_") || key === "CI" || key === "DEBUG") + .join(","), + ]; +} +const buildEnv: BuildEnv = { SCX_BUILD: "b", CI: "1" }; +buildEnv.DEBUG = "yes"; +console.log(merged(buildEnv), Object.keys(buildEnv), buildEnv.CI, buildEnv["SCX_BUILD"]); diff --git a/tests/corpus/6466-never-assertion-argument.ts b/tests/corpus/6466-never-assertion-argument.ts new file mode 100644 index 000000000..8d2c1f2bf --- /dev/null +++ b/tests/corpus/6466-never-assertion-argument.ts @@ -0,0 +1,31 @@ +// `value as never` makes an unknown value fit any parameter: the argument +// is checked against the parameter's type like any unknown value the +// checker admits there, and a typed value passes unchanged. +type Out = { routes: { src: string; dest?: string }[]; count: number }; + +function show(tags: Record): void { + console.log(JSON.stringify(tags)); +} + +function report(tag: (result: Out) => Record, value: unknown): void { + show(tag(value as never)); +} + +const describe = (r: Out): Record => ({ count: r.count, first: r.routes[0]?.src }); +report(describe, JSON.parse('{"routes":[{"src":"/a"},{"src":"/b","dest":"/c"}],"count":2}')); + +// Through a promise of unknown, the way a wrapper tags an async result. +function later(value: Out): Promise { + return Promise.resolve(value); +} +later({ routes: [], count: 7 }).then((value) => { + show(describe(value as never)); +}); + +// A typed value keeps its identity through the assertion. +function bump(target: Out): void { + target.count++; +} +const typed: Out = { routes: [{ src: "/x" }], count: 1 }; +bump(typed as never); +console.log(typed.count); diff --git a/tests/corpus/6467-fromentries-record-assertion.ts b/tests/corpus/6467-fromentries-record-assertion.ts new file mode 100644 index 000000000..1496292b3 --- /dev/null +++ b/tests/corpus/6467-fromentries-record-assertion.ts @@ -0,0 +1,29 @@ +// `Object.fromEntries(...) as T` for a declared record T keeps exactly +// the keys the entries created, in their order, as Node keeps the object +// itself. +type Tags = { "error.type": string; "error.msg": string; "error.cause"?: string }; + +function scrub(input: string): string { + return input.replace(/secret/g, "***"); +} + +function redact(tags: Tags): Tags { + return Object.fromEntries( + Object.entries(tags).map(([key, value]) => [key, typeof value === "string" ? scrub(value) : value]), + ) as Tags; +} + +const e = new Error("a secret here"); +const plain = redact({ "error.type": e.name, "error.msg": e.message }); +console.log(plain, Object.keys(plain), "error.cause" in plain, plain["error.msg"].length); +const caused = redact({ "error.type": "TypeError", "error.msg": "m", "error.cause": "secret cause" }); +console.log(caused, JSON.stringify(caused)); + +// Keys the record does not declare stay with the object. +type Point = { x: number; y: number }; +const p = Object.fromEntries([ + ["y", 2], + ["x", 1], + ["z", 3], +]) as Point; +console.log(p, p.x + p.y, Object.keys(p), JSON.stringify(p)); From 4b82886458e784373908d24ff361141467a4f4a2 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Mon, 19 Oct 2026 22:23:28 -0700 Subject: [PATCH 044/122] Keep builtin constructor members refused instead of reading undefined Reading Object, Date, RegExp, Map, Set, Promise, WeakMap and WeakSet as interned function values made every use of those globals a dynamic value, including the receivers of member reads. The interned function models only name, length and constructor, so `Map.prototype`, `Date.prototype.getYear`, `Object.isFrozen` as a value, `Map["prototype"]` and `"prototype" in Map` stopped reaching their SC2020/SC1090 fences and read undefined through the dynamic paths instead (769 Test262 variants went from a refusal to a wrong result). A builtin global read for its members now keeps its member lowerings and fences; only name, length and constructor read through the value. A member read through an alias typed as the constructor's static side (`const D = Date; D.prototype`) reports SC2020 before the generic field read. Passing the value to Object or Reflect statics that reflect on or redefine its members (`Object.hasOwn(Object, ...)`, `Object.defineProperty(Promise, ...)`, `hasOwnProperty.call(Map, ...)`) is refused; identity and the empty enumerable key lists still compile. The interned functions answer Node's lengths (Date 7, RegExp 2, Promise 1). Co-Authored-By: Claude Opus 5.5 --- .../src/frontend/lowering/lower-exprs.ts | 114 +++++++++++++++++- .../builtin-constructor-members.ts.json | 6 + packages/runtime/src/scr_dyn_invoke.c | 5 +- .../builtin-constructor-members.ts | 14 +++ .../builtin-constructor-members.ts.txt | 71 +++++++++++ tests/harness/errors.test.ts | 64 ++++++++++ 6 files changed, 271 insertions(+), 3 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/builtin-constructor-members.ts.json create mode 100644 tests/diagnostics/builtin-constructor-members.ts create mode 100644 tests/harness/__snapshots__/builtin-constructor-members.ts.txt diff --git a/packages/compiler/src/frontend/lowering/lower-exprs.ts b/packages/compiler/src/frontend/lowering/lower-exprs.ts index e391dac20..653f4b9ce 100644 --- a/packages/compiler/src/frontend/lowering/lower-exprs.ts +++ b/packages/compiler/src/frontend/lowering/lower-exprs.ts @@ -1554,7 +1554,9 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr { const canonical = stdlibGlobalNameOf(lowerer, expr) ?? expr.text; // Builtin constructors have real function values in both source // languages (TypeScript reads them as dynamic values). - const builtinValue = builtinGlobalValue( + const builtinValue = builtinGlobalValueAt( + lowerer, + expr, canonical, loc, isJsSourceFile(expr.getSourceFile()), @@ -2440,6 +2442,12 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr { lowerStreamObjectProperty(lowerer, expr) ?? lowerEventObjectProperty(lowerer, expr) ?? lowerer.lowerUnionProperty(expr) ?? + // A builtin constructor reached through another binding (`const D = + // Date; D.prototype`) holds its interned function value, which + // models only name, length and constructor: a static member or the + // prototype that no lowering above claimed keeps the SC2020 fence + // rather than reading undefined through the field and dynamic reads. + fenceBuiltinConstructorStaticRead(lowerer, expr) ?? lowerer.lowerFieldRead(expr); // A union-typed field read narrows like an identifier when the // checker has narrowed this use to one arm. @@ -2487,7 +2495,7 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr { loc, }; return ( - builtinGlobalValue(canonical, loc, true) ?? { + builtinGlobalValueAt(lowerer, expr, canonical, loc, true) ?? { kind: "strLit", value: `[builtin ${canonical}]`, type: STRING, @@ -17090,6 +17098,108 @@ export function builtinGlobalValue(canonical: string, loc: SrcLoc, js = false): return null; } +/** The members the interned builtin function values model (the runtime + * answers each one as Node does); any other member read through them would + * answer undefined, so it keeps its static lowering or its fence. */ +const BUILTIN_VALUE_MEMBERS: ReadonlySet = new Set(["name", "length", "constructor"]); + +/** The Object statics whose answers for an interned builtin function value + * match Node's (identity, and the empty enumerable own-key lists). */ +const BUILTIN_VALUE_REFLECTION: ReadonlySet = new Set(["is", "keys", "values", "entries"]); + +/** The value `builtinGlobalValue` gives the stdlib global `expr` read at its + * use site: null when the use reads the global's members (they keep their + * lowerings and fences), and SC2020 when it is the target of reflection or + * redefinition (`Object.hasOwn(Object, "hasOwn")`, + * `Object.defineProperty(Promise, ...)`, `hasOwnProperty.call(Map, ...)`), + * which would observe or change members the interned value does not + * model. */ +function builtinGlobalValueAt( + lowerer: Lowerer, + expr: ts.Expression, + canonical: string, + loc: SrcLoc, + js: boolean, +): IrExpr | null { + if (readsBuiltinGlobalMembers(expr)) return null; + const value = builtinGlobalValue(canonical, loc, js); + if (value === null) return null; + let node: ts.Node = expr; + while (node.parent !== undefined && ts.isParenthesizedExpression(node.parent)) node = node.parent; + const call = node.parent; + if ( + call !== undefined && + ts.isCallExpression(call) && + call.expression !== node && + ts.isPropertyAccessExpression(call.expression) + ) { + const callee = call.expression; + const owner = stdlibGlobalNameOf(lowerer, callee.expression); + const reflects = + owner === "Reflect" || + (owner === "Object" && !BUILTIN_VALUE_REFLECTION.has(callee.name.text)) || + ((callee.name.text === "call" || callee.name.text === "apply") && + call.arguments.nodes[0] === node && + lowerer.isStdlibSymbol( + lowerer.checker.getSymbolAtLocation( + ts.isPropertyAccessExpression(callee.expression) + ? callee.expression.name + : callee.expression, + ), + )); + if (reflects) { + const name = + owner !== null ? `${owner}.${callee.name.text}` : callee.getText().replace(/\s+/g, ""); + lowerer.noLowering( + `${name}(${canonical}, ...)`, + expr, + "builtin constructor and function values model only name, length and constructor, so reflecting on or redefining their members has no lowering", + ); + } + } + return value; +} + +/** Whether the stdlib global `expr` is read for its members (`Map.prototype`, + * `Date["UTC"]`, `"prototype" in Map`) rather than as a value. Those reads + * keep the global's member lowerings and fences: its interned function value + * models only BUILTIN_VALUE_MEMBERS. */ +function readsBuiltinGlobalMembers(expr: ts.Expression): boolean { + let node: ts.Node = expr; + while (node.parent !== undefined && ts.isParenthesizedExpression(node.parent)) node = node.parent; + const parent = node.parent; + if (parent === undefined) return false; + if (ts.isPropertyAccessExpression(parent) && parent.expression === node) + return !BUILTIN_VALUE_MEMBERS.has(parent.name.text); + if (ts.isElementAccessExpression(parent) && parent.expression === node) return true; + return ( + ts.isBinaryExpression(parent) && + parent.operatorToken.kind === ts.SyntaxKind.InKeyword && + parent.right === node + ); +} + +/** Reports SC2020 when `access` reads a stdlib static member or the + * prototype of a builtin constructor with an interned function value through + * a receiver the checker types as the constructor's static side + * (`MapConstructor`); null otherwise. Calls that lower statically never + * reach the member read. */ +function fenceBuiltinConstructorStaticRead( + lowerer: Lowerer, + access: ts.PropertyAccessExpression, +): null { + if (BUILTIN_VALUE_MEMBERS.has(access.name.text)) return null; + const symbol = lowerer.typeOf(access.expression).getSymbol(); + if ( + symbol !== undefined && + symbol.name.endsWith("Constructor") && + GLOBAL_CONSTRUCTOR_VALUES.has(symbol.name.slice(0, -"Constructor".length)) && + lowerer.isStdlibSymbol(symbol) + ) + lowerer.stdlibMemberFence(access); + return null; +} + /** Object.prototype members a plain record inherits, read as values. */ const OBJECT_PROTOTYPE_VALUE_READS: ReadonlySet = new Set([ "constructor", diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/builtin-constructor-members.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/builtin-constructor-members.ts.json new file mode 100644 index 000000000..ae3a963f3 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/builtin-constructor-members.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/diagnostics/builtin-constructor-members.ts" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_dyn_invoke.c b/packages/runtime/src/scr_dyn_invoke.c index 5b6491b83..d03b7381e 100644 --- a/packages/runtime/src/scr_dyn_invoke.c +++ b/packages/runtime/src/scr_dyn_invoke.c @@ -1846,8 +1846,11 @@ static ScrDyn *dyn_global_ctor(const char *name) { scr_atexit(dyn_global_ctors_cleanup); } if (!dyn_global_ctors[i]) { + /* Each constructor's own length: Object 1, Date 7, RegExp 2, Promise 1, + * the collections 0. */ + uint32_t length = i == 0 || i == 5 ? 1 : i == 1 ? 7 : i == 2 ? 2 : 0; dyn_global_ctors[i] = scr_dyn_new_func(scr_closure_new((void *)dyn_global_ctor_names[i], 0), - dyn_global_ctor_call, i == 0 ? 1 : 0, dyn_s6, + dyn_global_ctor_call, length, dyn_s6, dyn_global_ctor_names[i]); } return scr_dyn_retain(dyn_global_ctors[i]); diff --git a/tests/diagnostics/builtin-constructor-members.ts b/tests/diagnostics/builtin-constructor-members.ts new file mode 100644 index 000000000..294fe397b --- /dev/null +++ b/tests/diagnostics/builtin-constructor-members.ts @@ -0,0 +1,14 @@ +// Builtin constructors have interned function values, but those values model +// only name, length and constructor. Every other static member or prototype +// read as a value reports SC2020 at the read, whether it is spelled directly, +// through globalThis or through an alias; it never reads undefined. +const mapPrototype = Map.prototype; +const datePrototype = Date.prototype; +const regExpPrototype = RegExp.prototype; +const promisePrototype = Promise.prototype; +const weakSetPrototype = WeakSet.prototype; +const frozen = Object.isFrozen; +const setPrototype = globalThis.Set.prototype; +const D = Date; +const aliasPrototype = D.prototype; +// End with a nonblank context line so diagnostic snapshots carry no trailing whitespace. diff --git a/tests/harness/__snapshots__/builtin-constructor-members.ts.txt b/tests/harness/__snapshots__/builtin-constructor-members.ts.txt new file mode 100644 index 000000000..c0e45edea --- /dev/null +++ b/tests/harness/__snapshots__/builtin-constructor-members.ts.txt @@ -0,0 +1,71 @@ +builtin-constructor-members.ts:5:22 - error SC2020: 'Map.prototype' is part of the standard library types but has no scriptc lowering yet + + 4 | // through globalThis or through an alias; it never reads undefined. + 5 | const mapPrototype = Map.prototype; + | ^~~~~~~~~~~~~ + 6 | const datePrototype = Date.prototype; + + hint: prototype objects are not values here (method lookup is static) — call the method on an instance instead + +builtin-constructor-members.ts:6:23 - error SC2020: 'Date.prototype' is part of the standard library types but has no scriptc lowering yet + + 5 | const mapPrototype = Map.prototype; + 6 | const datePrototype = Date.prototype; + | ^~~~~~~~~~~~~~ + 7 | const regExpPrototype = RegExp.prototype; + + hint: prototype objects are not values here (method lookup is static) — call the method on an instance instead + +builtin-constructor-members.ts:7:25 - error SC2020: 'RegExp.prototype' is part of the standard library types but has no scriptc lowering yet + + 6 | const datePrototype = Date.prototype; + 7 | const regExpPrototype = RegExp.prototype; + | ^~~~~~~~~~~~~~~~ + 8 | const promisePrototype = Promise.prototype; + + hint: prototype objects are not values here (method lookup is static) — call the method on an instance instead + +builtin-constructor-members.ts:8:26 - error SC2020: 'Promise.prototype' is part of the standard library types but has no scriptc lowering yet + + 7 | const regExpPrototype = RegExp.prototype; + 8 | const promisePrototype = Promise.prototype; + | ^~~~~~~~~~~~~~~~~ + 9 | const weakSetPrototype = WeakSet.prototype; + + hint: prototype objects are not values here (method lookup is static) — call the method on an instance instead + +builtin-constructor-members.ts:9:26 - error SC2020: 'WeakSet.prototype' is part of the standard library types but has no scriptc lowering yet + + 8 | const promisePrototype = Promise.prototype; + 9 | const weakSetPrototype = WeakSet.prototype; + | ^~~~~~~~~~~~~~~~~ + 10 | const frozen = Object.isFrozen; + + hint: prototype objects are not values here (method lookup is static) — call the method on an instance instead + +builtin-constructor-members.ts:10:16 - error SC2020: 'Object.isFrozen' is part of the standard library types but has no scriptc lowering yet + + 9 | const weakSetPrototype = WeakSet.prototype; + 10 | const frozen = Object.isFrozen; + | ^~~~~~~~~~~~~~~ + 11 | const setPrototype = globalThis.Set.prototype; + + hint: the type checker sees the full standard library, but only the supported surface compiles (https://scriptc.dev/limitations) + +builtin-constructor-members.ts:11:22 - error SC2020: 'SetConstructor.prototype' is part of the standard library types but has no scriptc lowering yet + + 10 | const frozen = Object.isFrozen; + 11 | const setPrototype = globalThis.Set.prototype; + | ^~~~~~~~~~~~~~~~~~~~~~~~ + 12 | const D = Date; + + hint: prototype objects are not values here (method lookup is static) — call the method on an instance instead + +builtin-constructor-members.ts:13:24 - error SC2020: 'DateConstructor.prototype' is part of the standard library types but has no scriptc lowering yet + + 12 | const D = Date; + 13 | const aliasPrototype = D.prototype; + | ^~~~~~~~~~~ + 14 | // End with a nonblank context line so diagnostic snapshots carry no trailing whitespace. + + hint: prototype objects are not values here (method lookup is static) — call the method on an instance instead \ No newline at end of file diff --git a/tests/harness/errors.test.ts b/tests/harness/errors.test.ts index 92ac8f5ab..869ed1a80 100644 --- a/tests/harness/errors.test.ts +++ b/tests/harness/errors.test.ts @@ -1130,3 +1130,67 @@ console.log(count(require("./lib.cjs"))); ); }); }); + +describe("builtin constructor values (JS lane)", () => { + test("members without a lowering stay refused instead of reading undefined", async () => { + // Builtin constructors read as interned function values that model only + // name, length and constructor. Every other static member or prototype + // read (direct, bracketed, `in`, through an alias or globalThis), and + // reflection on or redefinition of their members, must stay a located + // SC2020/SC1090 refusal, never an undefined or otherwise wrong value. + // Test262 lost 769 refusals to undefined reads before this was pinned. + const reads = [ + ["Map.prototype", "Map.prototype"], + ["Date.prototype", "Date.prototype"], + ["RegExp.prototype", "RegExp.prototype"], + ["Set.prototype", "Set.prototype"], + ["Promise.prototype", "Promise.prototype"], + ["WeakMap.prototype", "WeakMap.prototype"], + ["WeakSet.prototype", "WeakSet.prototype"], + ["Date.prototype.getYear", "Date.prototype.getYear"], + ["Map.prototype.constructor", "Map.prototype.constructor"], + ["Object.isFrozen", "Object.isFrozen"], + [ + "getOwnPropertyDescriptor(Map.prototype)", + 'Object.getOwnPropertyDescriptor(Map.prototype, "size")', + ], + ["isExtensible(Date.prototype)", "Object.isExtensible(Date.prototype)"], + ["Map['prototype']", 'Map["prototype"]'], + ["'prototype' in Map", '"prototype" in Map'], + ["globalThis.Map.prototype", "globalThis.Map.prototype"], + ["Map alias prototype", "MapAlias.prototype"], + ["Date alias prototype", "DateAlias.prototype"], + ["Object alias isFrozen", "ObjectAlias.isFrozen"], + ["Object.hasOwn(Object)", 'Object.hasOwn(Object, "hasOwn")'], + ["getOwnPropertyNames(Date)", "Object.getOwnPropertyNames(Date)"], + ["defineProperty(Promise)", 'Object.defineProperty(Promise, "resolve", { value: 1 })'], + ["hasOwnProperty.call(Map)", 'Object.prototype.hasOwnProperty.call(Map, "groupBy")'], + ] as const; + const source = `"use strict"; +const MapAlias = Map; +const DateAlias = Date; +const { Object: ObjectAlias } = globalThis; +function report(label, e) { + const m = /\\[(SC\\d{4}) at [^\\]]*:(\\d+)\\]$/.exec(e.message); + console.log(label, m ? "refused " + m[1] + " line " + m[2] : "threw " + e.message); +} +${reads.map(([label, read]) => `try { const value = ${read}; console.log(${JSON.stringify(label)}, "read", typeof value); } catch (e) { report(${JSON.stringify(label)}, e); }`).join("\n")} +console.log(Date.length, RegExp.length, Promise.length, Object.length, Map.length, Set.length); +console.log(Date.name, Map.name, Map.constructor === Function, Object.constructor === Function); +`; + const r = await compileAndRun("builtin-constructor-members", source, "cjs"); + expect(r.stderr).toBe(""); + expect(r.exitCode).toBe(0); + const lines = r.stdout.trimEnd().split("\n"); + expect(lines.slice(0, reads.length)).toEqual( + reads.map(([label], i) => + expect.stringMatching( + new RegExp( + `^${label.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")} refused SC(2020|1090) line ${i + 9}$`, + ), + ), + ), + ); + expect(lines.slice(reads.length)).toEqual(["7 2 1 1 0 0", "Date Map true true"]); + }); +}); From 7d1d440b7b25c56bbec94a5ba57ab5342ded9088 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Mon, 19 Oct 2026 21:05:53 -0700 Subject: [PATCH 045/122] Adapt method overrides across ABI differences, write Error codes, and close wrapper-reachable gaps Overrides whose parameters widen and whose results convert into the inherited ones keep the inherited slot, which converts around the override's own body; Error values take code writes, `in` tests and Object.assign; and a few gaps behind generic wrappers close: - Method overrides: each inherited argument converts into the override's parameter (equal, wider, or a checked extraction from an `any` slot) and the override's result converts into the inherited one (narrower, a convertible union, a widening Promise payload, anything convertible over an `any` base). The slot %C.m keeps the inherited ABI and forwards to %C.m$own; TypeScript callers typed with the subclass call $own directly when nothing overrides below. Async overrides of sync and async bases, and JavaScript overrides of builtin or TypeScript bases (zod's ZodError.toString) are included; a toString slot converts its result with ToString. - `err.code = value` on Error values in TypeScript: strings set the runtime code slot (and the error's checked-dynamic node, if any); other values clear it and keep the dynamic own property. - `'key' in err` with any receiver: declared members answer true; otherwise a class test over the declaring subclasses, a runtime lookup of the error's own properties, or an Object.prototype member. - `Object.assign(err, ...sources)` onto an Error target copies onto the error, re-reads name, message, stack, cause and code into the typed slots, and returns the error. - `Error & { ... }` intersections whose extra members have checkable types are Error values; those members read and write the error's own properties, each read checked at its declared type. - Catch bindings typed `any` read as checked-dynamic values (member reads and writes, assertions); a multi-statement type guard with an `unknown` parameter takes the caught value as an ordinary argument; Error members of a binding narrowed to an Error-extending interface read through the same value. - Lambda parameters typed `any` by their default are checked-dynamic slots; forwarded-only rest tuples with optional positions forward their slots; `value as never` from an untyped value into a typed parameter takes the checked extraction to the parameter's type. - Lexical `this` stops at generic instance frames, so an instance no longer sees the receiver of the caller that triggered it. The fence for `this` captured through a plain function now points at the `this` keyword. Co-Authored-By: Claude Opus 5.5 --- .../compiler/src/backend/llvm/lib-shared.ts | 4 + .../src/frontend/lowering/builtins/errors.ts | 41 ++++ .../src/frontend/lowering/function-context.ts | 15 +- .../frontend/lowering/generic-functions.ts | 2 + .../src/frontend/lowering/lower-calls.ts | 91 ++++++++ .../src/frontend/lowering/lower-classes.ts | 202 +++++++++++++++++- .../src/frontend/lowering/lower-exprs.ts | 122 ++++++++--- .../src/frontend/lowering/lower-stmts.ts | 34 +++ .../compiler/src/frontend/lowering/lowerer.ts | 101 +++++++-- .../src/frontend/lowering/typed-arguments.ts | 4 +- packages/compiler/src/frontend/type-mapper.ts | 45 +++- packages/compiler/src/ir/builtin-effects.ts | 1 + .../compiler/src/ir/builtin-signatures.ts | 16 ++ packages/compiler/src/ir/validate.ts | 4 + .../corpus/6400-override-any-base.ts.json | 6 + .../6401-override-param-variance.ts.json | 6 + .../corpus/6402-override-js-base/main.ts.json | 7 + .../corpus/6403-error-code-write.ts.json | 6 + .../corpus/6404-error-in-and-assign.ts.json | 6 + .../6405-wrapped-default-parameters.ts.json | 6 + .../6406-npm-instance-receiver/main.ts.json | 6 + .../main.ts.json | 7 + .../6408-override-js-async-base/main.ts.json | 7 + .../corpus/6409-catch-binding-shapes.ts.json | 6 + packages/runtime/src/scr_error.h | 14 ++ packages/runtime/src/scr_json.c | 64 ++++++ tests/corpus/6400-override-any-base.ts | 35 +++ tests/corpus/6401-override-param-variance.ts | 33 +++ tests/corpus/6402-override-js-base/main.ts | 26 +++ tests/corpus/6402-override-js-base/pool.js | 17 ++ tests/corpus/6403-error-code-write.ts | 39 ++++ tests/corpus/6404-error-in-and-assign.ts | 105 +++++++++ .../corpus/6405-wrapped-default-parameters.ts | 67 ++++++ .../corpus/6406-npm-instance-receiver/main.ts | 7 + .../node_modules/corked-writer/index.d.ts | 4 + .../node_modules/corked-writer/index.js | 45 ++++ .../node_modules/corked-writer/package.json | 1 + .../6407-js-error-tostring-override/errors.js | 22 ++ .../6407-js-error-tostring-override/main.ts | 9 + .../6408-override-js-async-base/lock.js | 22 ++ .../6408-override-js-async-base/main.ts | 30 +++ tests/corpus/6409-catch-binding-shapes.ts | 69 ++++++ 42 files changed, 1296 insertions(+), 58 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6400-override-any-base.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6401-override-param-variance.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6402-override-js-base/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6403-error-code-write.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6404-error-in-and-assign.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6405-wrapped-default-parameters.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6406-npm-instance-receiver/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6407-js-error-tostring-override/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6408-override-js-async-base/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6409-catch-binding-shapes.ts.json create mode 100644 tests/corpus/6400-override-any-base.ts create mode 100644 tests/corpus/6401-override-param-variance.ts create mode 100644 tests/corpus/6402-override-js-base/main.ts create mode 100644 tests/corpus/6402-override-js-base/pool.js create mode 100644 tests/corpus/6403-error-code-write.ts create mode 100644 tests/corpus/6404-error-in-and-assign.ts create mode 100644 tests/corpus/6405-wrapped-default-parameters.ts create mode 100644 tests/corpus/6406-npm-instance-receiver/main.ts create mode 100644 tests/corpus/6406-npm-instance-receiver/node_modules/corked-writer/index.d.ts create mode 100644 tests/corpus/6406-npm-instance-receiver/node_modules/corked-writer/index.js create mode 100644 tests/corpus/6406-npm-instance-receiver/node_modules/corked-writer/package.json create mode 100644 tests/corpus/6407-js-error-tostring-override/errors.js create mode 100644 tests/corpus/6407-js-error-tostring-override/main.ts create mode 100644 tests/corpus/6408-override-js-async-base/lock.js create mode 100644 tests/corpus/6408-override-js-async-base/main.ts create mode 100644 tests/corpus/6409-catch-binding-shapes.ts diff --git a/packages/compiler/src/backend/llvm/lib-shared.ts b/packages/compiler/src/backend/llvm/lib-shared.ts index 8ab89bd22..46dff20e1 100644 --- a/packages/compiler/src/backend/llvm/lib-shared.ts +++ b/packages/compiler/src/backend/llvm/lib-shared.ts @@ -125,6 +125,10 @@ export const LIB_FN_SYMS: Record = { "str.encodeUriComponent": "scr_str_encode_uri_component", "error.cause": "scr_error_cause", "error.setCause": "scr_error_set_cause", + "error.setCode": "scr_error_set_code_value", + "error.setCodeDyn": "scr_error_set_code_dyn", + "error.assign": "scr_error_assign_dyn", + "error.hasKey": "scr_error_has_key", "error.defineCause": "scr_error_define_cause", "error.deleteCause": "scr_error_delete_cause", "error.hasCause": "scr_error_has_cause", diff --git a/packages/compiler/src/frontend/lowering/builtins/errors.ts b/packages/compiler/src/frontend/lowering/builtins/errors.ts index 1572aa6ab..ad0b044b9 100644 --- a/packages/compiler/src/frontend/lowering/builtins/errors.ts +++ b/packages/compiler/src/frontend/lowering/builtins/errors.ts @@ -43,6 +43,20 @@ export function refuseExecExceptionCode(lowerer: Lowerer, node: ts.Node): never * declaration is not stdlib). Reads only: writes keep their fence (no * compiled program constructs an errno error). Null for non-error * receivers and non-stdlib members, so the chain keeps trying. */ +/** Error members with slots of their own: never read through the dynamic + * node, even when an intersection restates them. */ +const ERROR_SLOT_MEMBERS = new Set([ + "code", + "cause", + "stack", + "message", + "name", + "errno", + "syscall", + "path", + "dest", +]); + export function lowerErrorCodeProperty( lowerer: Lowerer, expr: ts.PropertyAccessExpression, @@ -126,6 +140,33 @@ export function lowerErrorCodeProperty( declared, ); } + // A member an `Error & { ... }` intersection adds beyond Error's own and + // the system-error metadata (`Object.assign(new Error(m), { status })`): + // an own property of the error's checked-dynamic node, read at the + // declared type (the intersection mapping admits only checkable types). + if ( + recvT.className === "%Error" && + !ERROR_SLOT_MEMBERS.has(expr.name.text) && + !expr.questionDotToken && + !lowerer.isStdlibMember(expr) && + lowerer.typeOf(expr.expression).isIntersectionType() + ) { + const declared = lowerer.mapTypeOf(lowerer.typeOf(expr)); + if (declared && declared.kind !== "void") { + const receiver = lowerer.lowerExpr(expr.expression); + const loc = locOf(expr); + return lowerer.coerceToExpected( + { + kind: "dynKeyGet", + value: lowerer.coerceToExpected(receiver, DYN), + key: { kind: "strLit", value: expr.name.text, type: STRING, loc }, + type: DYN, + loc, + }, + declared, + ); + } + } const systemField = ["errno", "syscall", "path", "dest"].includes(expr.name.text); if ( !systemField && diff --git a/packages/compiler/src/frontend/lowering/function-context.ts b/packages/compiler/src/frontend/lowering/function-context.ts index 55d8ed0b0..92042b7d7 100644 --- a/packages/compiler/src/frontend/lowering/function-context.ts +++ b/packages/compiler/src/frontend/lowering/function-context.ts @@ -48,6 +48,10 @@ export interface FnCtx { argumentsLocal?: IrLocal | null; /** Arrows inherit the nearest ordinary function's arguments binding. */ inheritsArguments?: boolean; + /** A function lowered on demand on top of an unrelated caller's frames + * (a generic instance): lexical `this` resolution stops here, so the + * body sees what it sees when lowered alone. */ + thisBoundary?: true; /** Tuple bindings of this function that are only forwarded * (typed-arguments.ts onlyForwarded): binding local id → the native * slots forwarding reads. Such a binding is never materialized. Local ids @@ -161,7 +165,16 @@ export function captureContextBinding( throw new InternalCompilerError("lowerer bug: no active function context"); const direct = bindingInContext(current, symbol); if (direct !== null) return { local: direct, origin: direct, error: null }; - for (let depth = stack.length - 2; depth >= 0; depth--) { + let floor = 0; + if (symbol === undefined) { + for (let depth = stack.length - 1; depth >= 0; depth--) { + if (stack[depth]!.thisBoundary) { + floor = depth; + break; + } + } + } + for (let depth = stack.length - 2; depth >= floor; depth--) { const origin = bindingInContext(stack[depth]!, symbol); if (origin === null) continue; diff --git a/packages/compiler/src/frontend/lowering/generic-functions.ts b/packages/compiler/src/frontend/lowering/generic-functions.ts index ac611a7df..824880bd8 100644 --- a/packages/compiler/src/frontend/lowering/generic-functions.ts +++ b/packages/compiler/src/frontend/lowering/generic-functions.ts @@ -905,6 +905,8 @@ export function lowerGenericInstance( : lowerer.bodyReturnType(isAsync, inst.returnType); const fnCtx = newFnCtx(false, null, null, bodyReturn); fnCtx.isAsync = isAsync; + // Instances lower on demand, on top of whichever caller reached them. + fnCtx.thisBoundary = true; // Implicit-any instances whose declared return did not map lower in // return-INFERENCE mode: `return` statements record here bare, and the // post-pass (resolveInferredReturn) settles the type and wraps them. diff --git a/packages/compiler/src/frontend/lowering/lower-calls.ts b/packages/compiler/src/frontend/lowering/lower-calls.ts index 23629a7ba..f9abf2d19 100644 --- a/packages/compiler/src/frontend/lowering/lower-calls.ts +++ b/packages/compiler/src/frontend/lowering/lower-calls.ts @@ -4285,6 +4285,14 @@ function lowerCaughtPredicateCall( const paramSymbol = lowerer.checker.getSymbolAtLocation(param.name); if (!paramSymbol) return null; const ret = decl.body.statements.nodes.length === 1 ? decl.body.statements.nodes[0] : undefined; + // A longer guard whose parameter is `unknown` (or `any`) takes the caught + // value as an ordinary checked-dynamic argument: the call path converts + // the binding, and the narrowed binding reads through the same value. + if ( + (!ret || !ts.isReturnStatement(ret) || !ret.expression) && + lowerer.mapTypeOf(lowerer.checker.getTypeOfSymbol(paramSymbol))?.kind === "dyn" + ) + return null; if (!ret || !ts.isReturnStatement(ret) || !ret.expression) { lowerer.unsupported( "SC1090", @@ -7258,6 +7266,8 @@ function lowerObjectStaticCall( }; } } + const onError = lowerObjectAssignOntoError(lowerer, call); + if (onError) return onError; const hybrid = lowerObjectAssignHybrid(lowerer, call); if (hybrid) return hybrid; // `Object.assign({}, lit)` — an EMPTY fresh-literal target and one @@ -9388,6 +9398,30 @@ export function lowerObjectMethodCall( `calls of the abstract method '${method}' with no concrete implementation below the receiver's static class`, ); } + // The static receiver's nearest declaration is an override adapted to + // the inherited ABI, and nothing below overrides it: call its body + // directly under its declared signature (exact parameters and result). + // The slot function stays reachable; it lifts the body. + const adapted = found.declarer.returnAdapters?.get(method); + // A TypeScript override's own entry answers its declared result. A + // JavaScript body's result is inferred, so its callers keep the slot. + const declarerDecl = found.declarer.decl; + if ( + adapted && + !(declarerDecl && isJsSourceFile(declarerDecl.getSourceFile())) && + !lowerer.overrideBelow(info, method) + ) { + lowerer.noteEdge(`%${found.declarer.def.name}.${method}`); + const receiver = lowerReceiver(); + const args = lowerer.completeArgs(call.arguments.nodes, adapted.params, locOf(call), call); + return { + kind: "call", + callee: `%${found.declarer.def.name}.${method}$own`, + args: [lowerer.upcastTo(receiver, found.declarer.def.name), ...args], + type: adapted.ret, + loc: locOf(call), + }; + } if (lowerer.overrideBelow(info, method)) lowerer.noteVirtualEdge(info, method); else lowerer.noteEdge(`%${found.declarer.def.name}.${method}`); const receiver = lowerReceiver(); @@ -9945,3 +9979,60 @@ function maybePromiseHandlerResult( } return stmt; } + +/** `Object.assign(err, ...sources)` in TypeScript onto an error-hierarchy + * target (`Object.assign(new Error(message), details)`): the sources' + * own members land on the error — the runtime copy over its + * checked-dynamic node, whose name, message, stack, cause and code feed + * back into the typed slots — and the call answers the error itself. + * Null for every other target. */ +function lowerObjectAssignOntoError(lowerer: Lowerer, call: ts.CallExpression): IrExpr | null { + const argNodes = call.arguments.nodes; + if ( + isJsSourceFile(call.getSourceFile()) || + argNodes.length < 2 || + argNodes.some((a) => ts.isSpreadElement(a)) + ) + return null; + const probe = tryLowerExpression(lowerer, argNodes[0]!); + if (probe?.type.kind !== "object" || !lowerer.isErrorRootedClass(probe.type.className)) + return null; + const loc = locOf(call); + const target = lowerer.lowerExpr(argNodes[0]!); + const slot = lowerer.declareHiddenLocal("%assignErr", target.type); + const sources: IrExpr[] = []; + for (const node of argNodes.slice(1)) { + const raw = lowerer.lowerExpr(node); + fenceSymbolFieldCopy(lowerer, node, raw.type); + const source = lowerer.coerceToExpected(raw, DYN); + if (source.type.kind !== "dyn") { + lowerer.unsupported( + "SC1090", + node, + `Object.assign onto an Error from a '${lowerer.fmt(raw.type)}' source (sources enter the error's dynamic properties)`, + ); + } + sources.push(source); + } + const ref = (): IrExpr => ({ kind: "varRef", localId: slot.id, type: target.type, loc }); + return { + kind: "seqExpr", + stmts: [ + { kind: "varDecl", localId: slot.id, init: target, loc }, + { + kind: "exprStmt", + expr: { + kind: "libCall", + fn: "error.assign", + args: [ref(), { kind: "dynArrLit", elems: sources, type: DYN, loc }], + type: VOID, + loc, + }, + loc, + }, + ], + result: ref(), + type: target.type, + loc, + }; +} diff --git a/packages/compiler/src/frontend/lowering/lower-classes.ts b/packages/compiler/src/frontend/lowering/lower-classes.ts index 770c74840..14c8d1806 100644 --- a/packages/compiler/src/frontend/lowering/lower-classes.ts +++ b/packages/compiler/src/frontend/lowering/lower-classes.ts @@ -23,6 +23,7 @@ import type { FnCtx, Lowerer } from "./lowerer.js"; import { inlineNamespaceRequireOf, lowerWithInlineRequireInit } from "./cjs-inline-require.js"; import { BOOL, + canDynCheckTo, DATE_T, DYN, F64, @@ -462,6 +463,12 @@ export interface ClassInfo { /** Own JS overrides whose signature cannot use the inherited ABI. The slot * retains that ABI, but its implementation throws before executing. */ methodEntryFences?: Map; + /** Own TypeScript overrides whose declared result differs from the + * inherited ABI's but converts into it (`Promise` over `any`, a + * union narrowed to its inherited arm): the slot function `%C.m` keeps the + * inherited ABI and converts the result of the body, which lowers as + * `%C.m$own` with the declared result (async when declared so). */ + returnAdapters?: Map; /** OWN GENERIC instance methods (own type parameters — `m(x: T)`), * monomorphized per call site like top-level generic functions: instance * `n` is the module function `%C.m%n` taking `this` as param 0. They @@ -2225,6 +2232,7 @@ export function collectClassShapeInner( } >(); const methodEntryFences = new Map(); + const returnAdapters = new Map(); // Own accessor declarations ("get:x"/"set:x" → node), for the // partial-override analysis below (diagnostics need the node). const accessorNodes = new Map(); @@ -3360,7 +3368,67 @@ export function collectClassShapeInner( overridden.declarer.decl !== null && isJsSourceFile(overridden.declarer.decl.getSourceFile()) && overridden.sig.gen === undefined; - if (fencedJsOverride) { + // A TypeScript override that only changes the result, into a + // type that converts into the inherited one (`Promise` over + // an `any` base, `string | undefined` over `string`): the slot + // keeps the inherited ABI and converts the body's result at the + // boundary (lowerClassMethodMemberInner). + const ownAsync = + member.modifiers?.nodes.some((m) => m.kind === ts.SyntaxKind.AsyncKeyword) === true; + if ( + !fencedJsOverride && + !functionLocalClass(decl) && + overridden.sig.gen === undefined && + // An async base's slot answers its promise: a Promise type, or + // the checked-dynamic value of an untyped async method. + (overridden.sig.async !== true || + overridden.sig.ret.kind === "promise" || + overridden.sig.ret.kind === "dyn") && + !overridden.sig.abstract && + overridden.sig.params.length === shapes.length && + overridden.sig.params.every( + (p, i) => + p.mode === shapes[i]!.mode && + (p.mode === "required" || p.mode === "omittable") && + // Each inherited argument converts into the override's + // parameter: equal, a checked extraction from an `any` + // slot, or a widening (a contravariant parameter). + (typeEquals(p.type, shapes[i]!.type) || + (p.type.kind === "dyn" && + canDynCheckTo( + shapes[i]!.type, + (id) => lowerer.shapes.get(id), + (id) => lowerer.unions.get(id), + )) || + lowerer.coercibleValue(p.type, shapes[i]!.type)), + ) && + (ownAsync ? ft.ret.kind === "promise" : true) && + (typeEquals(ft.ret, overridden.sig.ret) || + (overridden.sig.ret.kind === "dyn" + ? ft.ret.kind !== "void" && lowerer.dynConvertible(ft.ret) + : lowerer.coercibleValue(ft.ret, overridden.sig.ret) || + // A promise whose payload widens into the inherited one + // (`Promise` over an untyped async `Promise`). + (ft.ret.kind === "promise" && + overridden.sig.ret.kind === "promise" && + lowerer.promisePayloadWidens(ft.ret, overridden.sig.ret)) || + // A JavaScript override's inferred `any` result checks + // into the inherited type (`toString() { return + // this.message }` over Error's). + (ft.ret.kind === "dyn" && + isJsSourceFile(member.getSourceFile()) && + overridden.sig.ret.kind !== "void" && + canDynCheckTo( + overridden.sig.ret, + (id) => lowerer.shapes.get(id), + (id) => lowerer.unions.get(id), + )))) + ) { + returnAdapters.set(mName, { params: [...shapes], ret: ft.ret, async: ownAsync }); + shapes.splice(0, shapes.length, ...overridden.sig.params); + ft.ret = overridden.sig.ret; + if (overridden.sig.async === true) thunkAsyncSlot(overridden, mName); + } else if (fencedJsOverride) { // A dormant JS method must not poison the whole class. Keep // the inherited call/dispatch ABI and refuse at method entry; // lowering this body with the base signature would change JS. @@ -3415,7 +3483,34 @@ export function collectClassShapeInner( // fiber spawn wrapper; vtable slots hold raw implementations) — // an override chain touching an async method on either end would // put a spawn wrapper behind a virtual slot, so it fences. - if (overridden && (asyncMember || overridden.sig.async === true)) { + // An override chain touching async methods dispatches through + // plain slot functions that return the body's promise: the async + // bodies lower as `%C.m$own` behind them (thunkAsyncSlot). + if ( + overridden && + !returnAdapters.has(mName) && + (asyncMember || overridden.sig.async === true) && + !isJsSourceFile(member.getSourceFile()) && + !functionLocalClass(decl) && + member.asteriskToken === undefined && + overridden.sig.gen === undefined && + !overridden.sig.abstract && + ft.ret.kind === "promise" && + typeEquals(ft.ret, overridden.sig.ret) && + overridden.sig.params.length === shapes.length && + overridden.sig.params.every( + (p, i) => typeEquals(p.type, shapes[i]!.type) && p.mode === shapes[i]!.mode, + ) + ) { + if (overridden.sig.async === true) thunkAsyncSlot(overridden, mName); + if (asyncMember) + returnAdapters.set(mName, { params: [...shapes], ret: ft.ret, async: true }); + } + if ( + overridden && + !returnAdapters.has(mName) && + (asyncMember || overridden.sig.async === true) + ) { const message = `overriding ${overridden.sig.async === true ? "the async method" : "a method with an async method"} '${mName}' (async methods dispatch statically — the vtable slot machinery has no fiber-spawn story)`; if (isJsSourceFile(member.getSourceFile())) methodEntryFences.set(mName, unsupportedDiag("SC1090", locOf(member.name), message)); @@ -3446,7 +3541,7 @@ export function collectClassShapeInner( } else { methods.set( mName, - asyncMember + asyncMember && !returnAdapters.has(mName) ? { params: shapes, ret: ft.ret, async: true as const } : { params: shapes, ret: ft.ret }, ); @@ -4166,6 +4261,7 @@ export function collectClassShapeInner( fieldOrder, methods, ...(methodEntryFences.size > 0 ? { methodEntryFences } : {}), + ...(returnAdapters.size > 0 ? { returnAdapters } : {}), decl, ...(emitOverride !== undefined ? { emitOverride } : {}), ctor, @@ -6474,6 +6570,28 @@ function abstractMemberSignature( /** A nominal return subtype uses the same owned pointer as the inherited * return slot. The current class is still being collected, so consult its * already collected base chain before the complete class graph is available. */ +/** An inherited ASYNC method that an override now dispatches against: its + * slot becomes a plain function returning the body's promise, and the + * async body lowers behind it as `%C.m$own` (the adapter path with an + * identity conversion), so a virtual call never enters an async body + * without its spawn wrapper. */ +function thunkAsyncSlot( + overridden: { declarer: ClassInfo; sig: ClassMethodSignature }, + mName: string, +): void { + if (overridden.sig.async !== true) return; + const declarer = overridden.declarer; + declarer.returnAdapters ??= new Map(); + if (!declarer.returnAdapters.has(mName)) { + declarer.returnAdapters.set(mName, { + params: [...overridden.sig.params], + ret: overridden.sig.ret, + async: true, + }); + } + delete overridden.sig.async; +} + function overrideReturnAbi( lowerer: Lowerer, declared: IrType, @@ -7672,6 +7790,82 @@ function lowerClassMethodMemberInner( allowDiagSink: true, }); } + // An override converting its declared result into the inherited ABI: + // the body lowers as `%C.m$own` under its declared result, and the slot + // function forwards to it and converts what it returns. + const adapter = info.returnAdapters?.get(mName); + if (adapter) { + const ownSig: ClassMethodSignature = { ...sig, params: adapter.params, ret: adapter.ret }; + if (adapter.async) ownSig.async = true; + const own = lowerClassMethodBody(lowerer, info, fnLike, `%${className}.${mName}$own`, ownSig); + if (!own) return null; + lowerer.liftedFns.push(own); + const loc = locOf(fnLike); + const params: IrParam[] = [ + { localId: "this.0", name: "this", type: thisType }, + ...sig.params.map((p, i) => ({ localId: `p.${i}`, name: `p${i}`, type: p.type })), + ]; + const fnCtx = newFnCtx(false, null, null, sig.ret); + lowerer.fnStack.push(fnCtx); + try { + const call: IrExpr = { + kind: "call", + callee: own.name, + args: params.map((p, i): IrExpr => { + const ref: IrExpr = { kind: "varRef", localId: p.localId, type: p.type, loc }; + // `this` passes through; each inherited argument converts into + // the override's declared parameter (checked where it narrows). + return i === 0 ? ref : lowerer.coerceInto(fnLike.name, ref, adapter.params[i - 1]!.type); + }), + type: adapter.ret, + loc, + }; + // A void slot discards the override's result after running it. A + // toString slot hands on what every ToString consumer of the result + // makes of it (`String(err)`, templates): undefined reads + // "undefined", as in Node. Other slots check the result into the + // inherited type. + const result = (): IrExpr => + mName === "toString" && sig.ret.kind === "string" + ? call.type.kind === "dyn" + ? { kind: "libCall", fn: "dyn.stringConstructor", args: [call], type: STRING, loc } + : lowerer.ensureString(call, fnLike.name) + : lowerer.coerceInto(fnLike.name, call, sig.ret); + const body: IrStmt[] = + sig.ret.kind === "void" + ? [ + { kind: "exprStmt", expr: call, loc }, + { kind: "return", value: null, loc }, + ] + : [{ kind: "return", value: result(), loc }]; + return { + name: `%${className}.${mName}`, + params, + returnType: sig.ret, + locals: [ + ...params.map((p) => ({ id: p.localId, name: p.name, type: p.type, mutable: false })), + ...lowerer.ctx.locals, + ], + body, + loc, + }; + } finally { + lowerer.fnStack.pop(); + } + } + return lowerClassMethodBody(lowerer, info, fnLike, `%${className}.${mName}`, sig); +} + +function lowerClassMethodBody( + lowerer: Lowerer, + info: ClassInfo, + fnLike: ts.MethodDeclaration | ts.AccessorDeclaration, + fnName: string, + sig: ClassMethodSignature, +): IrFunction | null { + const className = info.def.name; + const thisType: IrType = { kind: "object", className }; + if (!fnLike.body) return null; const prevClass = lowerer.currentClass; lowerer.currentClass = info; // ASYNC methods: the module function is an async IrFunction — its @@ -7713,7 +7907,7 @@ function lowerClassMethodMemberInner( const body = [...declared.prologue, ...lowerer.lowerStmts(fnLike.body.statements.nodes)]; appendImplicitUndefinedReturn(lowerer, body, bodyReturn, locOf(fnLike)); const fn: IrFunction = { - name: `%${className}.${mName}`, + name: fnName, params, returnType: bodyReturn, locals: lowerer.ctx.locals, diff --git a/packages/compiler/src/frontend/lowering/lower-exprs.ts b/packages/compiler/src/frontend/lowering/lower-exprs.ts index aa597d8a2..8b02597ab 100644 --- a/packages/compiler/src/frontend/lowering/lower-exprs.ts +++ b/packages/compiler/src/frontend/lowering/lower-exprs.ts @@ -1724,7 +1724,7 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr { // Only arrows can see an enclosing method's `this` (JS lexical-this); // `this` in plain nested functions is already a tsc error under // noImplicitThis, so the generic scope walk here is preflight-safe. - const local = lowerer.resolveThis(); + const local = lowerer.resolveThis(expr); if (local) { const value: IrExpr = { kind: "varRef", localId: local.id, type: local.type, loc }; const initialized = lowerer.ctx.superInitialized; @@ -2768,6 +2768,25 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr { ); } } + // A stdlib-declared member (`message` of an interface extending Error) + // on a catch binding narrowed to a structural type: the binding reads + // as its checked-dynamic value, so the member does too. + if (!expr.questionDotToken && lowerer.caughtLocalOf(expr.expression)) { + const recv = lowerer.lowerExpr(expr.expression); + if (recv.kind === "caughtToDyn") { + const read: IrExpr = { + kind: "dynKeyGet", + key: { kind: "strLit", value: expr.name.text, type: STRING, loc: locOf(expr.name) }, + value: recv, + type: DYN, + loc, + }; + const declared = lowerer.mapTypeOf(lowerer.typeOf(expr)); + return declared && declared.kind !== "void" + ? lowerer.coerceToExpected(read, declared) + : read; + } + } // The lib fence's PROPERTY chokepoint: a stdlib-declared member that // no lowering above claimed ([1,2].entries, Math.SQRT2, Promise.all, // re.exec as a value, ...) reports SC2020 here. @@ -9433,12 +9452,14 @@ export function lowerAsExpression( // the requested shape; an assertion does not bypass runtime checks. const narrowed = lowerer.mapTypeOf(lowerer.typeOf(expr.expression)); const bridged = - narrowed && - (narrowed.kind === "f64" || - narrowed.kind === "bool" || - narrowed.kind === "string" || - narrowed.kind === "object" || - narrowed.kind === "dyn"); + (narrowed && + (narrowed.kind === "f64" || + narrowed.kind === "bool" || + narrowed.kind === "string" || + narrowed.kind === "object" || + narrowed.kind === "dyn")) || + // `catch (e: any)`: the binding reads as its checked-dynamic value. + (narrowed === null && (lowerer.typeOf(expr.expression).flags & ts.TypeFlags.Any) !== 0); if (!bridged) lowerer.unsupported("SC1063", expr); } const inner = lowerer.lowerExpr(expr.expression); @@ -12708,7 +12729,11 @@ export function caughtRead( // Unnarrowed reads cross into unknown. The caught-value dispatch pass // retains known native class payloads through live identity capsules; // other payload kinds use the runtime's ordinary conversion. - if (narrowed?.kind === "dyn") { + // `catch (e: any)` (or useUnknownInCatchVariables off) reads the same way. + if ( + narrowed?.kind === "dyn" || + (narrowed === null && (lowerer.typeOf(node).flags & ts.TypeFlags.Any) !== 0) + ) { return { kind: "caughtToDyn", value: ref, type: DYN, loc }; } // A binding a user type predicate (or an assertion) narrowed to a @@ -14030,41 +14055,68 @@ function lowerInExpression(lowerer: Lowerer, expr: ts.BinaryExpression, loc: Src loc, }; } - if ( - (key === "message" || key === "name") && - (recv.kind === "varRef" || recv.kind === "caughtNarrow") - ) { - return { kind: "boolLit", value: true, type: BOOL, loc }; - } - // Any other key: a member a program class declares. The receiver's - // runtime class is its static class or a descendant; the key is - // present exactly when that class (or an ancestor) declares it. - if (recv.kind === "varRef" || recv.kind === "caughtNarrow") { - const declares = (c: ClassInfo): boolean => - c.fields.has(key) || - lowerer.findMethodOn(c, key) !== null || - lowerer.findMethodOn(c, `get:${key}`) !== null || - lowerer.findMethodOn(c, `set:${key}`) !== null; - const staticName = recv.type.className; - const staticInfo = lowerer.classes.get(staticName); - if (staticInfo && declares(staticInfo)) - return { kind: "boolLit", value: true, type: BOOL, loc }; + // The receiver evaluates for its effects when the answer is static. + const always = (): IrExpr => + recv.kind === "varRef" || recv.kind === "caughtNarrow" + ? { kind: "boolLit", value: true, type: BOOL, loc } + : { + kind: "seqExpr", + stmts: [{ kind: "exprStmt", expr: recv, loc }], + result: { kind: "boolLit", value: true, type: BOOL, loc }, + type: BOOL, + loc, + }; + if (key === "message" || key === "name") return always(); + // A member the static class declares is always present. + const declares = (c: ClassInfo): boolean => + c.fields.has(key) || + lowerer.findMethodOn(c, key) !== null || + lowerer.findMethodOn(c, `get:${key}`) !== null || + lowerer.findMethodOn(c, `set:${key}`) !== null; + const staticInfo = lowerer.classes.get(recv.type.className); + if (staticInfo && declares(staticInfo)) return always(); + // Any other key: a descendant class that declares it (instanceof), + // an own property set at runtime (a throw site's extra fields, + // Object.assign, writes through a dynamic view), or an + // Object.prototype member. `stack` stays fenced: Node has one, a + // compiled error has none. + if (key !== "stack") { + const stable = recv.kind === "varRef" || recv.kind === "caughtNarrow"; + const slot = stable ? null : lowerer.declareHiddenLocal("%inRecv", recv.type); + const ref = (): IrExpr => (slot ? varRef(slot.id, recv.type, loc) : recv); const tests: IrExpr[] = []; + const staticName = recv.type.className; for (const c of lowerer.classes.values()) { const name = c.def.name; if (name === staticName || !lowerer.isSubclassOf(name, staticName) || !declares(c)) continue; // A declaring ancestor below the static class already covers it. if (c.base && c.base.def.name !== staticName && declares(c.base)) continue; - tests.push({ kind: "instanceOf", value: recv, className: name, type: BOOL, loc }); + tests.push({ kind: "instanceOf", value: ref(), className: name, type: BOOL, loc }); } - return tests.reduce( - (acc, test) => - acc.kind === "boolLit" - ? test - : { kind: "logical", op: "||", left: acc, right: test, type: BOOL, loc }, - { kind: "boolLit", value: false, type: BOOL, loc }, - ); + tests.push({ + kind: "libCall", + fn: "error.hasKey", + args: [ref(), { kind: "strLit", value: key, type: STRING, loc }], + type: BOOL, + loc, + }); + const answer = tests.reduce((acc, test) => ({ + kind: "logical", + op: "||", + left: acc, + right: test, + type: BOOL, + loc, + })); + if (!slot) return answer; + return { + kind: "seqExpr", + stmts: [{ kind: "varDecl", localId: slot.id, init: recv, loc }], + result: answer, + type: BOOL, + loc, + }; } lowerer.unsupported( "SC1090", diff --git a/packages/compiler/src/frontend/lowering/lower-stmts.ts b/packages/compiler/src/frontend/lowering/lower-stmts.ts index 3af47f614..eb0d31cbd 100644 --- a/packages/compiler/src/frontend/lowering/lower-stmts.ts +++ b/packages/compiler/src/frontend/lowering/lower-stmts.ts @@ -7124,6 +7124,34 @@ export function lowerExprStatement(lowerer: Lowerer, expr: ts.Expression): IrStm loc, }; } + // `err.code = v` on an Error value (`err as Error & { code?: + // string }`): typed reads come from the runtime error's code + // slot, so the write lands there. A value that is not statically + // a string checks at runtime: strings take the slot, anything + // else clears it and stays a dynamic own property. The + // system-error members (errno, syscall, path, dest) read + // through the checked-dynamic node and keep the dynamic write, as + // do JavaScript files, whose reads use that node too. + if ( + !isJsSourceFile(expr.getSourceFile()) && + recv?.type.kind === "object" && + lowerer.isErrorRootedClass(recv.type.className) && + expr.left.name.text === "code" + ) { + const loc = locOf(expr); + const stringValue = isStringTyped(lowerer.typeOf(expr.right)); + return { + kind: "exprStmt", + expr: { + kind: "libCall", + fn: stringValue ? "error.setCode" : "error.setCodeDyn", + args: [recv, lowerer.lowerExprExpecting(expr.right, stringValue ? STRING : DYN)], + type: VOID, + loc, + }, + loc, + }; + } if (recv?.type.kind === "object" && recv.type.className === "%Error") { const loc = locOf(expr); return { @@ -10815,3 +10843,9 @@ export function lowerForStatement(lowerer: Lowerer, stmt: ts.ForStatement): IrSt lowerer.scopes.pop(); } } + +/** A checker type that is a string or string literal (or a union of them). */ +function isStringTyped(t: ts.Type): boolean { + if (t.isUnionType()) return ts.constituentTypes(t).every(isStringTyped); + return (t.flags & (ts.TypeFlags.String | ts.TypeFlags.StringLiteral)) !== 0; +} diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index 8913b9eeb..6bc661c4c 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -9512,6 +9512,8 @@ export class Lowerer { node = node.expression; if (expected) { if (ts.isConditionalExpression(node)) return lowerTernary(this, node, expected); + const escaped = this.neverAssertionInto(node, expected); + if (escaped) return escaped; const fresh = this.emptyCollectionFor(node, expected); if (fresh) return this.coerceInto(node, fresh, expected); } @@ -10408,6 +10410,14 @@ export class Lowerer { return findMethodOn(this, info, name); } + /** A class whose root is the runtime's %Error (builtin errors and their + * program subclasses). */ + isErrorRootedClass(className: string): boolean { + let info = this.classes.get(className) ?? null; + while (info && info.base) info = info.base; + return info?.def.name === "%Error"; + } + isSubclassOf(sub: string, sup: string): boolean { return isSubclassOf(this, sub, sup); } @@ -10706,24 +10716,30 @@ export class Lowerer { ): void { const loc = locOf(decl); const name = nameNode.text; - const tupleType = this.irTypeOf(nameNode); + const fn = decl.parent; + const symbol = this.checker.getSymbolAtLocation(nameNode); + const isRead = (id: ts.Identifier): boolean => + id !== nameNode && this.checker.getSymbolAtLocation(id) === symbol; + const pure = fn !== undefined && symbol !== undefined && onlyForwarded(this, fn, isRead); + // A forwarded-only tuple is never built: a tuple with optional + // positions (`Parameters<(n?: T) => R>`, whose length varies per call) + // forwards its slots, an omitted position as undefined — which a + // callee's default treats exactly as an omitted argument. Its length + // is the one read that would observe the difference. + const tupleType = + pure && + elemShapes.length > 0 && + this.mapTypeOf(this.typeOf(nameNode)) === null && + !readsLength(fn!, isRead) + ? this.forwardedSlotsTuple(elemShapes) + : this.irTypeOf(nameNode); const slots = elemShapes.map((shape, i) => { const slot = this.declareHiddenLocal(`%${name}.${i}`, shape.type); params.push({ localId: slot.id, name: `%${name}.${i}`, type: shape.type }); return slot; }); const body = this.declareLocal(nameNode, name, tupleType, true); - const fn = decl.parent; - const symbol = this.checker.getSymbolAtLocation(nameNode); - if ( - fn !== undefined && - symbol !== undefined && - onlyForwarded( - this, - fn, - (id) => id !== nameNode && this.checker.getSymbolAtLocation(id) === symbol, - ) - ) { + if (pure) { this.ctx.pureTuples.set(body.id, slots); return; } @@ -10731,6 +10747,38 @@ export class Lowerer { prologue.push({ kind: "varDecl", localId: body.id, init, loc }); } + /** `value as never` flowing into a typed slot: `never` is assignable to + * every type, so the assertion promises the slot's type. A dynamic value + * takes the checked extraction to it, as `value as T` would. Null for + * every other form, and for numeric, dynamic and union slots, which + * keep the existing lowering. */ + private neverAssertionInto(node: ts.Expression, expected: IrType): IrExpr | null { + if (!(ts.isAsExpression(node) || ts.isTypeAssertion(node))) return null; + if (expected.kind === "f64" || expected.kind === "dyn" || expected.kind === "union") + return null; + if (!(this.checker.getTypeFromTypeNode(node.type).flags & ts.TypeFlags.Never)) return null; + const innerTs = this.typeOf(node.expression); + if (!(innerTs.flags & ts.TypeFlags.Any) && this.mapTypeOf(innerTs)?.kind !== "dyn") return null; + if ( + !canDynCheckTo( + expected, + (id) => this.shapes.get(id), + (id) => this.unions.get(id), + ) + ) + return null; + const inner = this.lowerExpr(node.expression); + if (inner.type.kind !== "dyn") return null; + return { kind: "dynCheck", value: inner, type: expected, loc: locOf(node) }; + } + + /** The tuple record of a forwarded-only rest tuple's native slots. */ + private forwardedSlotsTuple(elemShapes: readonly ParamShape[]): IrType { + const fields = elemShapes.map((shape, i) => ({ name: String(i), type: shape.type })); + fields.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0)); + return { kind: "record", shapeId: this.shapes.intern(fields, true) }; + } + /** A fresh tuple holding the given positional slots, typed `tupleType` * (a tuple record, or the empty-array form of `[]`). */ tupleOfSlots(slots: readonly IrLocal[], tupleType: IrType, blame: ts.Node, loc: SrcLoc): IrExpr { @@ -11157,8 +11205,8 @@ export class Lowerer { /** Lexical `this` — the enclosing method's this-param, possibly captured * through arrows (function expressions/declarations reset `this` in JS; * their bodies never see an enclosing method's binding). */ - resolveThis(): IrLocal | null { - return this.resolveKey(undefined); + resolveThis(blame?: ts.Node): IrLocal | null { + return this.resolveKey(undefined, blame); } peekLocal(ident: ts.Identifier): IrLocal | null { @@ -12980,3 +13028,28 @@ export class Lowerer { return lowerStdlibMethodCall(this, call, access); } } + +/** Whether some read of a binding (identified by `isRead`) in `fn` takes + * its `.length`. */ +function readsLength(fn: ts.Node, isRead: (id: ts.Identifier) => boolean): boolean { + let found = false; + ts.walkPreorder(fn, (n) => { + if (found) return "stop"; + if (!ts.isIdentifier(n) || !isRead(n)) return undefined; + let top: ts.Node = n; + while ( + top.parent && + (ts.isParenthesizedExpression(top.parent) || + ts.isAsExpression(top.parent) || + ts.isTypeAssertion(top.parent) || + ts.isSatisfiesExpression(top.parent) || + ts.isNonNullExpression(top.parent)) + ) + top = top.parent; + const p = top.parent; + if (p && ts.isPropertyAccessExpression(p) && p.expression === top && p.name.text === "length") + found = true; + return undefined; + }); + return found; +} diff --git a/packages/compiler/src/frontend/lowering/typed-arguments.ts b/packages/compiler/src/frontend/lowering/typed-arguments.ts index 82b7f23c3..0170e87b3 100644 --- a/packages/compiler/src/frontend/lowering/typed-arguments.ts +++ b/packages/compiler/src/frontend/lowering/typed-arguments.ts @@ -321,7 +321,9 @@ export function forwardedTupleType(lowerer: Lowerer, node: ts.Expression): IrTyp // Checker-only probe first: an ordinary array rest never maps here. restTupleElementTypes(lowerer.typeOf(operand), lowerer.typeCtx) !== null ) { - const t = lowerer.mapTypeOf(lowerer.typeOf(operand)); + // A tuple with optional positions has no value type of its own; its + // forwarded-only binding carries the slots' tuple (declareRestTupleParam). + const t = lowerer.mapTypeOf(lowerer.typeOf(operand)) ?? lowerer.peekLocal(operand)?.type; if (t && isTupleValueType(lowerer, t)) return t; } return null; diff --git a/packages/compiler/src/frontend/type-mapper.ts b/packages/compiler/src/frontend/type-mapper.ts index 1e3954586..2d083d0cb 100644 --- a/packages/compiler/src/frontend/type-mapper.ts +++ b/packages/compiler/src/frontend/type-mapper.ts @@ -4373,7 +4373,9 @@ function mapCallSignature(sig: ts.Signature, ctx: TypeMapperCtx): IrType | null // A parameter a TypeScript program types `any` takes whatever the // caller passes: the checked-dynamic slot, as an `any`-typed binding // already is (dynFallbackType). Declaration files keep their paths. - // Only function declarations and spelled function types: lambdas and + // Only function declarations, spelled function types and lambda + // parameters whose default is the only source of their type (`(n = + // lookup() ?? 5) => ...` with an `any` lookup): other lambdas and // methods in object literals keep their existing checked-dynamic story. if ( !pt && @@ -4381,7 +4383,12 @@ function mapCallSignature(sig: ts.Signature, ctx: TypeMapperCtx): IrType | null decl !== undefined && ts.isParameter(decl) && decl.parent !== undefined && - (ts.isFunctionDeclaration(decl.parent) || ts.isFunctionTypeNode(decl.parent)) && + (ts.isFunctionDeclaration(decl.parent) || + ts.isFunctionTypeNode(decl.parent) || + ((ts.isArrowFunction(decl.parent) || ts.isFunctionExpression(decl.parent)) && + decl.initializer !== undefined && + decl.type === undefined && + !isJsSourceFile(decl.getSourceFile()))) && !decl.getSourceFile().isDeclarationFile && (checker.getTypeOfSymbol(p).flags & ts.TypeFlags.Any) !== 0 ) @@ -5748,6 +5755,7 @@ function mapErrorMetadataIntersection(type: ts.Type, ctx: TypeMapperCtx): IrType }); if (!hasError) return null; let error: IrType | null = null; + const extra: ts.Symbol[] = []; for (const part of parts) { const mapped = mapType(part, ctx); if (mapped?.kind === "object" && RUNTIME_ERROR_CLASSES.has(mapped.className)) { @@ -5768,14 +5776,45 @@ function mapErrorMetadataIntersection(type: ts.Type, ctx: TypeMapperCtx): IrType : ["code", "syscall", "path", "dest"].includes(property.name) ? "string" : null; - if (!expected || !(property.flags & ts.SymbolFlags.Optional)) return null; + if (!expected) { + extra.push(property); + continue; + } const field = mapType(ctx.checker.getTypeOfSymbol(property), ctx); const arms = field?.kind === "union" ? ctx.unions.get(field.unionId)?.arms : field ? [field] : []; if (!arms?.length || !arms.every((arm) => arm.kind === expected || arm.kind === "undefinedT")) return null; + // A required member must still admit the absent value the slot reads. + if ( + !(property.flags & ts.SymbolFlags.Optional) && + !arms.some((arm) => arm.kind === "undefinedT") + ) + return null; } } + if (!error) return null; + // Members beyond Error's own and the system-error metadata (`Error & { + // status: number }`, Object.assign's result) are own properties of the + // error's checked-dynamic node: each must map to a type a checked read + // can produce. Error's own string members keep their slots. + for (const property of extra) { + const field = mapType(ctx.checker.getTypeOfSymbol(property), ctx); + if (!field || field.kind === "void") return null; + if (property.name === "message" || property.name === "name") { + if (field.kind !== "string") return null; + continue; + } + if (property.name === "stack" || property.name === "cause") return null; + if ( + !canDynCheckTo( + field, + (id) => ctx.shapes.get(id), + (id) => ctx.unions.get(id), + ) + ) + return null; + } return error; } diff --git a/packages/compiler/src/ir/builtin-effects.ts b/packages/compiler/src/ir/builtin-effects.ts index f20d0945e..f2041a6aa 100644 --- a/packages/compiler/src/ir/builtin-effects.ts +++ b/packages/compiler/src/ir/builtin-effects.ts @@ -389,6 +389,7 @@ export const MAY_THROW_LIB_FNS: ReadonlySet = new Set([ "error.stack", "error.cause", "error.setCause", + "error.assign", "error.deleteCause", "dyn.objectTag", // Numeric coercion runs user valueOf/toString — throws propagate. diff --git a/packages/compiler/src/ir/builtin-signatures.ts b/packages/compiler/src/ir/builtin-signatures.ts index 02943ae1b..34f9da161 100644 --- a/packages/compiler/src/ir/builtin-signatures.ts +++ b/packages/compiler/src/ir/builtin-signatures.ts @@ -3101,6 +3101,22 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ "error.hasCause": { argTypes: [null], result: BOOL }, /** Assignment borrows both operands and retains the new cause. */ "error.setCause": { argTypes: [null, DYN], result: VOID }, + /** `err.code = s` on an error-hierarchy receiver: the code slot that + * error.code reads and coded throws stamp. Borrows both; never throws. */ + "error.setCode": { argTypes: [null, STRING], result: VOID }, + /** `err.code = v` with a value that is not statically a string: strings + * take the code slot, anything else clears it and sets the dynamic own + * property. Borrows both; never throws. */ + "error.setCodeDyn": { argTypes: [null, DYN], result: VOID }, + /** `Object.assign(err, ...sources)` onto an error-hierarchy receiver + * (args: the error, a dyn array of the sources): the own-member copy + * onto its checked-dynamic node, then the typed slots (name, message, + * stack, cause, code) re-read from it. Throws what the copy throws. */ + "error.assign": { argTypes: [null, DYN], result: VOID }, + /** `key in err` for a key no compiled class of the receiver declares + * (args: the error, the key): a runtime-set own property (Object.assign, + * throw-site fields, dynamic writes) or an Object.prototype member. */ + "error.hasKey": { argTypes: [null, STRING], result: BOOL }, "error.defineCause": { argTypes: [null, DYN], result: VOID }, "error.deleteCause": { argTypes: [null], result: VOID }, /** The compiler-resolved Node-parity throw for always-throwing lowered diff --git a/packages/compiler/src/ir/validate.ts b/packages/compiler/src/ir/validate.ts index 5cf262e1f..0bbc948a0 100644 --- a/packages/compiler/src/ir/validate.ts +++ b/packages/compiler/src/ir/validate.ts @@ -4112,6 +4112,10 @@ function validateFunction( e.fn === "error.cause" || e.fn === "error.hasCause" || e.fn === "error.setCause" || + e.fn === "error.setCode" || + e.fn === "error.setCodeDyn" || + e.fn === "error.assign" || + e.fn === "error.hasKey" || e.fn === "error.defineCause" || e.fn === "error.deleteCause" ) { diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6400-override-any-base.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6400-override-any-base.ts.json new file mode 100644 index 000000000..a885a5175 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6400-override-any-base.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6400-override-any-base.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6401-override-param-variance.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6401-override-param-variance.ts.json new file mode 100644 index 000000000..ee14b5af3 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6401-override-param-variance.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6401-override-param-variance.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6402-override-js-base/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6402-override-js-base/main.ts.json new file mode 100644 index 000000000..3ac1bf1fe --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6402-override-js-base/main.ts.json @@ -0,0 +1,7 @@ +{ + "order": [ + "/tests/corpus/6402-override-js-base/pool.js", + "/tests/corpus/6402-override-js-base/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6403-error-code-write.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6403-error-code-write.ts.json new file mode 100644 index 000000000..b673f7920 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6403-error-code-write.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6403-error-code-write.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6404-error-in-and-assign.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6404-error-in-and-assign.ts.json new file mode 100644 index 000000000..c880bc57b --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6404-error-in-and-assign.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6404-error-in-and-assign.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6405-wrapped-default-parameters.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6405-wrapped-default-parameters.ts.json new file mode 100644 index 000000000..8be83f858 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6405-wrapped-default-parameters.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6405-wrapped-default-parameters.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6406-npm-instance-receiver/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6406-npm-instance-receiver/main.ts.json new file mode 100644 index 000000000..e651808f7 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6406-npm-instance-receiver/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6406-npm-instance-receiver/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6407-js-error-tostring-override/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6407-js-error-tostring-override/main.ts.json new file mode 100644 index 000000000..057a8a163 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6407-js-error-tostring-override/main.ts.json @@ -0,0 +1,7 @@ +{ + "order": [ + "/tests/corpus/6407-js-error-tostring-override/errors.js", + "/tests/corpus/6407-js-error-tostring-override/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6408-override-js-async-base/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6408-override-js-async-base/main.ts.json new file mode 100644 index 000000000..6acb919ed --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6408-override-js-async-base/main.ts.json @@ -0,0 +1,7 @@ +{ + "order": [ + "/tests/corpus/6408-override-js-async-base/lock.js", + "/tests/corpus/6408-override-js-async-base/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6409-catch-binding-shapes.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6409-catch-binding-shapes.ts.json new file mode 100644 index 000000000..57e3aeaf1 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6409-catch-binding-shapes.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6409-catch-binding-shapes.ts" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_error.h b/packages/runtime/src/scr_error.h index ce60dac23..597361d59 100644 --- a/packages/runtime/src/scr_error.h +++ b/packages/runtime/src/scr_error.h @@ -279,6 +279,20 @@ SCR_COLD void scr_undef_global_read(ScrStr *name); * +1 or NULL when absent (the compiler's undefined arm). The _msg_code * thrower is scr_throw_error_msg with the code stamped on the payload. */ void scr_error_set_code(ScrError *e, const char *code); +/* The program's `err.code = s` write into the same slot (and the error's + * checked-dynamic node, when it has one). Borrows both (scr_json.c). */ +void scr_error_set_code_value(ScrError *e, ScrStr *code); +/* `err.code = v` with a value that is not statically a string: a string + * takes the slot as above; anything else clears the slot and lands on the + * error's checked-dynamic node, as a dynamic property write would. + * Borrows both (scr_json.c). */ +void scr_error_set_code_dyn(ScrError *e, struct ScrDyn *value); +/* Object.assign(err, ...sources): the copy onto the error's checked-dynamic + * node, then the typed slots re-read from it. Borrows both (scr_json.c). */ +void scr_error_assign_dyn(ScrError *e, const struct ScrDyn *sources); +/* `key in err` for a key no compiled class declares: a runtime-set own + * property or an Object.prototype member. Borrows both (scr_json.c). */ +bool scr_error_has_key(ScrError *e, const ScrStr *key); const char *scr_error_message_code(const ScrStr *msg); ScrStr *scr_error_code(ScrError *e); SCR_COLD void scr_throw_error_msg_code(int kind, const char *message, size_t len, const char *code); diff --git a/packages/runtime/src/scr_json.c b/packages/runtime/src/scr_json.c index 6174913c0..56edcf049 100644 --- a/packages/runtime/src/scr_json.c +++ b/packages/runtime/src/scr_json.c @@ -14092,3 +14092,67 @@ ScrDyn *scr_dyn_iter_take(const ScrDyn *src, size_t n, const char *spell) { bool scr_dyn_iterates_by_protocol(const ScrDyn *value) { return scr_dyn_sync_generator(value); } + +/* The program's writes onto compiled errors (scr_error.h). Kept at the end + * of the file, after every function they call, so the rest of the unit + * emits in its existing order. */ + +/* `err.code = s` from the program (an Error asserted to carry `code`): + * the same slot every coded throw stamps, so reads and `'code' in err` + * observe the write. Borrows both. */ +void scr_error_set_code_value(ScrError *e, ScrStr *code) { + scr_str_release(e->code); + e->code = scr_str_retain(code); + /* An error that already crossed into the checked-dynamic tree renders + * and reads through that node, so the write lands there too. */ + ScrDyn *view = scr_errdyn_view_of(e); + if (view != NULL && view->kind == SCR_DYN_OBJ) scr_dyn_obj_set(view, "code", 4, scr_dyn_new_str(code)); +} + +void scr_error_set_code_dyn(ScrError *e, ScrDyn *value) { + if (value != NULL && value->kind == SCR_DYN_STR) { + scr_error_set_code_value(e, value->v.str); + return; + } + scr_str_release(e->code); + e->code = NULL; + ScrDyn *view = scr_dyn_from_error(e); + ScrStr *key = scr_str_new("code", 4); + scr_dyn_key_set(view, key, value); + scr_str_release(key); + scr_dyn_release(view); +} + +/* Object.assign(err, ...sources) onto a compiled error: each source's own + * members land on the error's checked-dynamic node, then the slots that + * typed reads use (name, message, stack, cause, code) take the result. + * Borrows both; throws what the copy throws (a nullish source is skipped, + * as in JS). */ +void scr_error_assign_dyn(ScrError *e, const ScrDyn *sources) { + ScrDyn *view = scr_dyn_from_error(e); + ScrDyn *r = scr_dyn_assign_all(view, sources); + if (r != NULL) { + scr_dyn_release(r); + if (view->kind == SCR_DYN_OBJ) { + scr_error_commit_dyn(e, view); + if (!scr_exc_pending()) { + ScrDyn *code = scr_dyn_obj_get(view, "code", 4); + ScrStr *next = code != NULL && code->kind == SCR_DYN_STR ? scr_str_retain(code->v.str) : NULL; + scr_str_release(e->code); + e->code = next; + } + } + } + scr_dyn_release(view); +} + +/* `key in err` for a key no compiled class of the receiver declares: an own + * property set at runtime, which lives on the error's checked-dynamic node + * (that node exists once anything reflected or wrote through it), or a + * member of Object.prototype. Borrows both; never throws. */ +bool scr_error_has_key(ScrError *e, const ScrStr *key) { + const ScrDyn *view = scr_errdyn_view_of(e); + if (view != NULL && view->kind == SCR_DYN_OBJ && scr_dyn_find_entry(view, key->data, key->len) != NULL) + return true; + return scr_dyn_object_proto_name(key); +} diff --git a/tests/corpus/6400-override-any-base.ts b/tests/corpus/6400-override-any-base.ts new file mode 100644 index 000000000..72b050425 --- /dev/null +++ b/tests/corpus/6400-override-any-base.ts @@ -0,0 +1,35 @@ +// Overrides that narrow an `any`-typed base result: a typed async override +// over an untyped base method, and a sync override returning a union. +class Pool { + name = "pool"; + acquire(): any { + return Promise.resolve("base"); + } + describe(): any { + return this.name; + } +} +class MeteredPool extends Pool { + waits = 0; + override async acquire(): Promise { + this.waits++; + await super.acquire(); + } + override describe(): string | undefined { + return this.waits > 1 ? `metered ${this.waits}` : undefined; + } +} +async function use(pool: Pool, label: string): Promise { + const got = await pool.acquire(); + console.log(label, got === undefined ? "undefined" : got, String(pool.describe())); +} +async function main(): Promise { + const metered = new MeteredPool(); + await use(new Pool(), "plain"); + await use(metered, "metered"); + await metered.acquire(); + console.log(metered.waits, metered.describe()); + const pools: Pool[] = [new Pool(), metered]; + for (const p of pools) console.log(typeof p.describe()); +} +main(); diff --git a/tests/corpus/6401-override-param-variance.ts b/tests/corpus/6401-override-param-variance.ts new file mode 100644 index 000000000..997207af8 --- /dev/null +++ b/tests/corpus/6401-override-param-variance.ts @@ -0,0 +1,33 @@ +// Overrides whose parameters differ from the base: a typed parameter over +// an `any` slot, a wider (contravariant) parameter, and a narrowed result. +class Handler { + handle(input: any): any { + return `base:${input}`; + } + score(value: string): number | undefined { + return value.length; + } +} +class TypedHandler extends Handler { + override handle(input: string): string { + return `typed:${input.toUpperCase()}`; + } + override score(value: string | number): number { + return typeof value === "string" ? value.length * 10 : value; + } +} +function run(h: Handler): void { + console.log(h.handle("abc"), h.score("xy")); +} +run(new Handler()); +run(new TypedHandler()); +const typed = new TypedHandler(); +console.log(typed.handle("q"), typed.score(7)); +try { + run2(new TypedHandler()); +} catch (e) { + console.log("caught", (e as Error).name); +} +function run2(h: Handler): void { + console.log(h.handle(42)); +} diff --git a/tests/corpus/6402-override-js-base/main.ts b/tests/corpus/6402-override-js-base/main.ts new file mode 100644 index 000000000..6821f09dd --- /dev/null +++ b/tests/corpus/6402-override-js-base/main.ts @@ -0,0 +1,26 @@ +// TypeScript overrides of an untyped JavaScript base: a typed async +// override over an untyped method, and a narrowed toString. +import { Pool } from "./pool.js"; + +class MeteredPool extends Pool { + waits = 0; + override async acquire(): Promise { + this.waits++; + const n: number = await super.acquire(); + return n * 10; + } + override toString(): string { + return `Metered(${this.waits})`; + } +} +async function main(): Promise { + const pools: Pool[] = [new Pool(2), new MeteredPool(3)]; + for (const pool of pools) { + const got = await pool.acquire(); + console.log(String(got), pool.toString(), `${pool}`); + pool.release(); + } + const metered = new MeteredPool(1); + console.log(await metered.acquire(), metered.waits); +} +main(); diff --git a/tests/corpus/6402-override-js-base/pool.js b/tests/corpus/6402-override-js-base/pool.js new file mode 100644 index 000000000..d1a337882 --- /dev/null +++ b/tests/corpus/6402-override-js-base/pool.js @@ -0,0 +1,17 @@ +// An untyped JavaScript base class, the shape of a small npm semaphore. +export class Pool { + constructor(size) { + this.size = size; + this.taken = 0; + } + async acquire() { + this.taken++; + return this.taken; + } + release() { + this.taken--; + } + toString() { + return `Pool(${this.taken}/${this.size})`; + } +} diff --git a/tests/corpus/6403-error-code-write.ts b/tests/corpus/6403-error-code-write.ts new file mode 100644 index 000000000..3d6efc2a0 --- /dev/null +++ b/tests/corpus/6403-error-code-write.ts @@ -0,0 +1,39 @@ +// Own properties written on Error values typed with an intersection. +const coded = new Error("c") as Error & { code?: string }; +coded.code = "E1"; +console.log(coded.code, "code" in coded); +function has(err: Error): boolean { + return "code" in err; +} +console.log(has(coded), has(new Error("plain"))); +try { + throw coded; +} catch (e) { + console.log((e as Error & { code?: string }).code); +} + +// System-error members first, then the code: reads stay consistent. +type SysError = Error & { code?: string; errno?: number; syscall?: string; path?: string }; +function sysError(message: string, code: string | undefined): SysError { + const err = new Error(message) as SysError; + err.errno = -2; + err.syscall = "open"; + err.path = "/missing"; + err.code = code; + return err; +} +const enoent = sysError("no such file", "ENOENT"); +console.log(enoent.code, enoent.errno, enoent.syscall, enoent.path, "code" in enoent); +console.log(enoent.message, enoent instanceof Error); +const uncoded = sysError("no code", undefined); +console.log(uncoded.code, uncoded.syscall); + +// A code that is only known at runtime. +function withCode(err: Error & { code?: string }, code: unknown): void { + err.code = code as string; +} +const late = new Error("late") as Error & { code?: string }; +withCode(late, "ELATE"); +console.log(late.code); +late.code = "EAGAIN"; +console.log(late.code, late.message); diff --git a/tests/corpus/6404-error-in-and-assign.ts b/tests/corpus/6404-error-in-and-assign.ts new file mode 100644 index 000000000..7003a8ae8 --- /dev/null +++ b/tests/corpus/6404-error-in-and-assign.ts @@ -0,0 +1,105 @@ +// `'key' in err` on Error values of every receiver form, and +// Object.assign onto a fresh Error. +class HttpError extends Error { + status = 404; + get retryable(): boolean { + return this.status >= 500; + } + describe(): string { + return `${this.status}`; + } +} + +// A receiver narrowed from unknown by instanceof. +function statusOf(value: unknown): unknown { + if (value instanceof Error) { + return "status" in value ? value.status : "none"; + } + return "not an error"; +} +console.log(statusOf(new HttpError("not found")), statusOf(new Error("plain")), statusOf("text")); + +// Parameters, call results and caught values; members a subclass declares +// as fields, accessors and methods; Error.prototype's own members. +function keys(err: Error): string { + return [ + "status" in err, + "retryable" in err, + "describe" in err, + "toString" in err, + "missing" in err, + "message" in err, + ].join(" "); +} +console.log(keys(new HttpError("h"))); +console.log(keys(new Error("e"))); +console.log(keys(new TypeError("t"))); +function latest(): Error { + return new HttpError("latest"); +} +console.log("status" in latest(), "message" in latest()); +function caught(fn: () => void): string { + try { + fn(); + return "ok"; + } catch (e) { + if (e instanceof Error && "status" in e) return `status ${String(e.status)}`; + return "plain"; + } +} +console.log( + caught(() => { + throw new HttpError("t"); + }), + caught(() => { + throw new Error("u"); + }), +); + +// Properties set at runtime are own properties too. +const expando = new Error("x"); +(expando as unknown as { status: number }).status = 1; +console.log(keys(expando)); + +// Object.assign copies the source's own properties onto the error and +// answers the error itself. +interface ErrorLike { + message: string; + status?: number; + code?: string; +} +function toError(error: ErrorLike | string): Error { + return typeof error === "string" + ? new Error(error) + : Object.assign(new Error(error.message), error); +} +const assigned = toError({ message: "boom", status: 500, code: "EBOOM" }); +console.log(assigned.message, (assigned as Error & { code?: string }).code, "status" in assigned); +console.log(statusOf(assigned), assigned instanceof Error, keys(assigned)); +const renamed = toError({ message: "renamed" }); +console.log(renamed.message, "status" in renamed, toError("text").message); +const fresh = new Error("before"); +console.log(Object.assign(fresh, { message: "after" }) === fresh, fresh.message); + +// An Error intersection carrying extra fields: Object.assign's result, +// read and written through its declared members. +interface ApiErrorFields { + status: number; + action: string | undefined; + code: string | undefined; + link: string | undefined; +} +function apiError(message: string, fields: ApiErrorFields) { + const error = Object.assign(new Error(message), fields); + return error; +} +const gateway = apiError("bad gateway", { status: 502, action: undefined, code: "E502", link: "https://example.com" }); +console.log(gateway.message, gateway.status, gateway.code, gateway.link, "action" in gateway, gateway.action); +gateway.status = 503; +console.log(gateway.status, gateway instanceof Error); +Object.assign(gateway, { status: 504, code: "E504" }); +console.log(gateway.status, gateway.code); +function describeApiError(error: Error & { status?: number }): string { + return `${error.message}:${error.status ?? "none"}`; +} +console.log(describeApiError(gateway), describeApiError(new Error("plain"))); diff --git a/tests/corpus/6405-wrapped-default-parameters.ts b/tests/corpus/6405-wrapped-default-parameters.ts new file mode 100644 index 000000000..f607eb885 --- /dev/null +++ b/tests/corpus/6405-wrapped-default-parameters.ts @@ -0,0 +1,67 @@ +// Lambdas whose parameters take their type from an `any` default, passed +// through generic wrappers that forward their arguments. +function trace any>(name: string, fn: F): F { + return ((...args: Parameters) => { + console.log("span", name); + return fn(...args); + }) as F; +} +function traceArgs(name: string, fn: (...args: A) => R): (...args: A) => R { + return (...args: A) => { + console.log("span", name); + return fn(...args); + }; +} +function lookup(key: string): any { + return key === "set" ? 7 : undefined; +} + +const doubled = trace("doubled", (n = lookup("unset") ?? 5) => n * 2); +console.log(doubled(), doubled(4)); +const sum = trace("sum", (n = lookup("set") ?? 5, m = 1) => n * 4 + m); +console.log(sum(), sum(2), sum(2, 3)); +const label = trace("label", (prefix = lookup("unset") ?? "item", count: number = 0) => `${prefix}#${count}`); +console.log(label(), label("row"), label(undefined, 3)); +const tripled = traceArgs("tripled", (n: number = lookup("unset") ?? 5) => n * 3); +console.log(tripled(), tripled(2)); + +// A wrapper that also reports tags computed from the wrapped function's +// result, handing its untyped result on through `as never`. +function traceTagged any>( + name: string, + fn: F, + resultTags?: ( + result: ReturnType extends PromiseLike ? U : ReturnType, + ) => Record, +): F { + return ((...args: Parameters) => { + const result: unknown = fn(...args); + if (resultTags) console.log(name, JSON.stringify(resultTags(result as never))); + return result; + }) as F; +} +const summarize = traceTagged( + "summarize", + (limit = lookup("unset") ?? 2) => ({ kept: Number(limit), dropped: 1 }), + (r) => ({ total: r.kept + r.dropped }), +); +console.log(summarize().kept, summarize(5).kept); + +// The same through a function expression whose result is untyped. +function traceTaggedAny any>( + name: string, + fn: F, + resultTags: (result: ReturnType) => Record, +): F { + return function (this: unknown, ...args: Parameters) { + const result = fn.apply(this, args); + console.log(name, JSON.stringify(resultTags(result as never))); + return result; + } as F; +} +const measure = traceTaggedAny( + "measure", + (width = lookup("set") ?? 1, height = 2) => ({ area: Number(width) * height }), + (r) => ({ area: r.area }), +); +console.log(measure().area, measure(3, 4).area); diff --git a/tests/corpus/6406-npm-instance-receiver/main.ts b/tests/corpus/6406-npm-instance-receiver/main.ts new file mode 100644 index 000000000..3dc893548 --- /dev/null +++ b/tests/corpus/6406-npm-instance-receiver/main.ts @@ -0,0 +1,7 @@ +// A package's constructor function, compiled per call site, reads its own +// receiver while the constructor that reached it has a receiver too. +import { create } from "corked-writer"; +const w = create({ highWaterMark: 4 }); +console.log(w.uncork()); +console.log(w.uncork()); +console.log(create().uncork()); diff --git a/tests/corpus/6406-npm-instance-receiver/node_modules/corked-writer/index.d.ts b/tests/corpus/6406-npm-instance-receiver/node_modules/corked-writer/index.d.ts new file mode 100644 index 000000000..1de006a3d --- /dev/null +++ b/tests/corpus/6406-npm-instance-receiver/node_modules/corked-writer/index.d.ts @@ -0,0 +1,4 @@ +export interface Writable { + uncork(): string; +} +export function create(options?: { highWaterMark?: number }): Writable; diff --git a/tests/corpus/6406-npm-instance-receiver/node_modules/corked-writer/index.js b/tests/corpus/6406-npm-instance-receiver/node_modules/corked-writer/index.js new file mode 100644 index 000000000..6a7e75541 --- /dev/null +++ b/tests/corpus/6406-npm-instance-receiver/node_modules/corked-writer/index.js @@ -0,0 +1,45 @@ +'use strict'; + +// Constructor functions in the shape of a stream implementation: a request +// object built inside another constructor keeps its receiver in a var for +// a callback, and the writer is also exported as a value. + +function CorkedRequest(state) { + var _this = this; + this.next = null; + this.entry = null; + this.finish = function () { + onCorkedFinish(_this, state); + }; +} +module.exports = { + create: function (options) { + return new Writable(options); + }, + Writable: Writable, +}; +Writable.WritableState = WritableState; +function WritableState(options, stream) { + this.highWaterMark = options.highWaterMark || 16; + this.pending = 2; + this.stream = stream; + this.corkedRequestsFree = new CorkedRequest(this); +} +WritableState.prototype.getBuffer = function getBuffer() { + return this.pending; +}; +function Writable(options) { + this._writableState = new WritableState(options || {}, this); + this.label = "w"; +} +Writable.prototype.uncork = function () { + var state = this._writableState; + var req = state.corkedRequestsFree; + req.finish(); + state.corkedRequestsFree = new CorkedRequest(state); + return this.label + " " + req.next + " " + state.highWaterMark; +}; +function onCorkedFinish(corkReq, state) { + state.pending--; + corkReq.next = "finished:" + state.pending; +} diff --git a/tests/corpus/6406-npm-instance-receiver/node_modules/corked-writer/package.json b/tests/corpus/6406-npm-instance-receiver/node_modules/corked-writer/package.json new file mode 100644 index 000000000..94a00403d --- /dev/null +++ b/tests/corpus/6406-npm-instance-receiver/node_modules/corked-writer/package.json @@ -0,0 +1 @@ +{ "name": "corked-writer", "version": "1.0.0", "main": "index.js", "types": "index.d.ts" } diff --git a/tests/corpus/6407-js-error-tostring-override/errors.js b/tests/corpus/6407-js-error-tostring-override/errors.js new file mode 100644 index 000000000..839b57a67 --- /dev/null +++ b/tests/corpus/6407-js-error-tostring-override/errors.js @@ -0,0 +1,22 @@ +// JavaScript Error subclasses whose toString overrides infer results that +// differ from Error.prototype.toString's string. +export class IssueError extends Error { + constructor(issues) { + super("invalid"); + this.issues = issues; + this.name = "IssueError"; + } + /** @returns {string | undefined} */ + toString() { + return this.issues.length > 0 ? this.issues.join("; ") : undefined; + } +} +export class LabelError extends Error { + constructor(label, detail) { + super(detail); + this.label = label; + } + toString() { + return this.label; + } +} diff --git a/tests/corpus/6407-js-error-tostring-override/main.ts b/tests/corpus/6407-js-error-tostring-override/main.ts new file mode 100644 index 000000000..15a857efd --- /dev/null +++ b/tests/corpus/6407-js-error-tostring-override/main.ts @@ -0,0 +1,9 @@ +// TypeScript callers of JavaScript toString overrides whose inferred +// results differ from the inherited string. +import { IssueError, LabelError } from "./errors.js"; +const issues = new IssueError(["a is required", "b must be a number"]); +console.log(issues.toString()); +console.log(String(issues), `${new IssueError([])}`); +const errors: Error[] = [issues, new LabelError("db", "down"), new Error("plain")]; +for (const e of errors) console.log(e.toString(), e instanceof IssueError); +console.log(errors.map((e) => String(e)).join(" | ")); diff --git a/tests/corpus/6408-override-js-async-base/lock.js b/tests/corpus/6408-override-js-async-base/lock.js new file mode 100644 index 000000000..1ea00239d --- /dev/null +++ b/tests/corpus/6408-override-js-async-base/lock.js @@ -0,0 +1,22 @@ +// An untyped JavaScript lock whose async acquire resolves to an untyped +// ticket. +export class Lock { + constructor() { + this.held = false; + this.log = []; + this.queue = makeQueue(this.log); + } + async acquire() { + const ticket = await this.queue.wait(); + this.held = true; + this.log.push("base acquire"); + return ticket; + } + release() { + this.held = false; + } +} + +function makeQueue(log) { + return { wait: () => new Promise((resolve) => setTimeout(() => resolve(log.length), 1)) }; +} diff --git a/tests/corpus/6408-override-js-async-base/main.ts b/tests/corpus/6408-override-js-async-base/main.ts new file mode 100644 index 000000000..da0c0b183 --- /dev/null +++ b/tests/corpus/6408-override-js-async-base/main.ts @@ -0,0 +1,30 @@ +// TypeScript overrides returning Promise over an untyped JavaScript +// async method: an async override and a promise-returning one. +import { Lock } from "./lock.js"; +class TimedLock extends Lock { + waits = 0; + async acquire(): Promise { + this.waits++; + await super.acquire(); + this.log.push("timed acquire"); + } +} +class EagerLock extends Lock { + acquire(): Promise { + this.log.push("eager"); + return Promise.resolve(); + } +} +async function main(): Promise { + const locks: Lock[] = [new TimedLock(), new EagerLock(), new Lock()]; + for (const lock of locks) { + await lock.acquire(); + console.log(lock.held, lock.log.join(",")); + } + const timed = new TimedLock(); + await timed.acquire(); + console.log(timed.waits, timed.held); + const viaBase: Lock = new TimedLock(); + console.log(await new Lock().acquire(), await viaBase.acquire()); +} +main(); diff --git a/tests/corpus/6409-catch-binding-shapes.ts b/tests/corpus/6409-catch-binding-shapes.ts new file mode 100644 index 000000000..930c41f38 --- /dev/null +++ b/tests/corpus/6409-catch-binding-shapes.ts @@ -0,0 +1,69 @@ +// Catch bindings typed any, multi-statement guards over catch bindings, +// and writes into a caught error. +interface SafeFetchError extends Error { + kind: "safe-fetch"; + host: string; +} +function isSafeFetchError(e: unknown): e is SafeFetchError { + if (!(e instanceof Error)) return false; + return "kind" in e && e.kind === "safe-fetch"; +} +function blocked(host: string): Error { + return Object.assign(new Error(`blocked ${host}`), { kind: "safe-fetch", host }); +} +function run(fn: () => void): string { + try { + fn(); + return "ok"; + } catch (err) { + if (isSafeFetchError(err)) return `guarded ${err.host} ${err.message}`; + return `other ${err instanceof Error ? err.message : String(err)}`; + } +} +console.log(run(() => {})); +console.log(run(() => { + throw blocked("example.com"); +})); +console.log(run(() => { + throw new Error("plain"); +})); +console.log(run(() => { + throw "text"; +})); + +function annotate(fn: () => void): void { + try { + fn(); + } catch (e: any) { + e.message = `config: ${e.message}`; + if (e.code === "ENOPE") e.hint = "check the file"; + throw e; + } +} +for (const code of ["ENOPE", "EOTHER"]) { + try { + annotate(() => { + throw Object.assign(new Error("missing"), { code }); + }); + } catch (e) { + const err = e as Error & { code?: string; hint?: string }; + console.log(err.message, err.code, err.hint, err instanceof Error); + } +} + +// An assertion on a catch binding typed any. +function codeOf(fn: () => void): string { + try { + fn(); + return "none"; + } catch (e: any) { + const err = e as Error & { code?: string }; + return `${err.message}:${err.code ?? "-"}`; + } +} +console.log( + codeOf(() => { + throw Object.assign(new Error("coded"), { code: "E1" }); + }), + codeOf(() => {}), +); From ba23a111105c1d3dbd99caaab7d79fd0c7935ee4 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Mon, 19 Oct 2026 22:44:14 -0700 Subject: [PATCH 046/122] Format the npm case filter Co-Authored-By: Claude Opus 5.5 --- tests/harness/npm-cases.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/harness/npm-cases.ts b/tests/harness/npm-cases.ts index f77807d83..01718abf9 100644 --- a/tests/harness/npm-cases.ts +++ b/tests/harness/npm-cases.ts @@ -24,7 +24,9 @@ export function npmCases(fixturesRoot: string): NpmCase[] { // consumers exercise the static rewrite's surface (lexer-visible // names the shipped .d.ts never declares, the __toESM interop // family), so they stay out of the flagless island lane by design. - .filter((entry) => !/\/(246[5-9]|255[6-9]|256[0-7]|2571|258[0-4])-[^/]+\/main\.ts$/.test(entry)) + .filter( + (entry) => !/\/(246[5-9]|255[6-9]|256[0-7]|2571|258[0-4])-[^/]+\/main\.ts$/.test(entry), + ) .map((entry) => ({ name: entry.split("/").at(-2)!, entry, From 4b4f80581e3267a5c759a7c477d730d79311e52d Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Mon, 19 Oct 2026 22:44:14 -0700 Subject: [PATCH 047/122] Format the npm case filter Co-Authored-By: Claude Opus 5.5 --- tests/harness/npm-cases.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/harness/npm-cases.ts b/tests/harness/npm-cases.ts index 1d15ddc6b..ef46a59de 100644 --- a/tests/harness/npm-cases.ts +++ b/tests/harness/npm-cases.ts @@ -24,7 +24,9 @@ export function npmCases(fixturesRoot: string): NpmCase[] { // consumers exercise the static rewrite's surface (lexer-visible // names the shipped .d.ts never declares, the __toESM interop // family), so they stay out of the flagless island lane by design. - .filter((entry) => !/\/(246[5-9]|255[6-9]|256[0-7]|2571|258[0-2])-[^/]+\/main\.ts$/.test(entry)) + .filter( + (entry) => !/\/(246[5-9]|255[6-9]|256[0-7]|2571|258[0-2])-[^/]+\/main\.ts$/.test(entry), + ) .map((entry) => ({ name: entry.split("/").at(-2)!, entry, From 5e65642fcf2802a4377be8c6efaab6aaed3c9b41 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Mon, 19 Oct 2026 22:17:17 -0700 Subject: [PATCH 048/122] Compile mixed-tuple Promise.all, Boolean callbacks, enum key lookups and nullable regex receivers - `Promise.all([p, q, r])` over different promise types (the tuple overload) lowers in TypeScript through one interned async helper that awaits the runtime combinator over the same promises (widened to Promise, a type-only view), so settlement order and rejection handling are Node's, then reads each fulfilled entry into the tuple. Entries keep their identity; plain scalar entries pass through, and a checked-dynamic entry joins through the promise it holds. - The ambient `Boolean` as an `every`/`some`/`find`/`findIndex`/`map` callback in TypeScript is the interned one-parameter truthiness closure `filter(Boolean)` already used, and `filter(Boolean)` accepts checked-dynamic elements (runtime truthiness). - `E[key]` with a runtime string key answers the named member's constant, or undefined for a name no member carries; a numeric member's reverse key and inherited Object.prototype names throw a named runtime refusal. - Regex-argument `replace`/`replaceAll`/`split`/`search`/`matchAll` on a `string | undefined` receiver take the string lowering and throw Node's "Cannot read properties of undefined (reading 'replace')". Co-Authored-By: Claude Opus 5.5 --- .../frontend/lowering/builtins/promises.ts | 173 +++++++++++++++++- .../lowering/containers/array-callbacks.ts | 32 +--- .../lowering/containers/callback-arguments.ts | 60 ++++++ .../lowering/containers/string-and-regexp.ts | 81 +++++++- .../src/frontend/lowering/lower-enums.ts | 115 +++++++++++- .../corpus/6480-boolean-callbacks.ts.json | 6 + .../corpus/6481-enum-runtime-keys.ts.json | 6 + .../corpus/6482-promise-all-tuples.ts.json | 6 + .../6483-nullable-regex-receivers.ts.json | 6 + tests/corpus/6480-boolean-callbacks.ts | 34 ++++ tests/corpus/6481-enum-runtime-keys.ts | 29 +++ tests/corpus/6482-promise-all-tuples.ts | 76 ++++++++ tests/corpus/6483-nullable-regex-receivers.ts | 23 +++ tests/diagnostics/promise-all-fences.ts | 17 +- .../__snapshots__/promise-all-fences.ts.txt | 27 +-- 15 files changed, 628 insertions(+), 63 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6480-boolean-callbacks.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6481-enum-runtime-keys.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6482-promise-all-tuples.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6483-nullable-regex-receivers.ts.json create mode 100644 tests/corpus/6480-boolean-callbacks.ts create mode 100644 tests/corpus/6481-enum-runtime-keys.ts create mode 100644 tests/corpus/6482-promise-all-tuples.ts create mode 100644 tests/corpus/6483-nullable-regex-receivers.ts diff --git a/packages/compiler/src/frontend/lowering/builtins/promises.ts b/packages/compiler/src/frontend/lowering/builtins/promises.ts index 2656da18b..5d4a2e3f2 100644 --- a/packages/compiler/src/frontend/lowering/builtins/promises.ts +++ b/packages/compiler/src/frontend/lowering/builtins/promises.ts @@ -1,15 +1,18 @@ import { checkedPromiseAll } from "../checked-promise-all.js"; import { InternalCompilerError } from "../../../errors.js"; import * as ts from "../../ts7/adapter.js"; -import { type Lowerer } from "../lowerer.js"; +import { newFnCtx, type Lowerer } from "../lowerer.js"; +import { varRef } from "../../../ir/build.js"; import { maybePromiseResolve, maybePromiseUnionOf } from "../maybe-promise.js"; import { isJsSourceFile, locOf } from "../../program.js"; import { + BOOL, DYN, type IrExpr, type IrFunction, type IrStmt, type IrType, + UNDEFINED_T, VOID, arrayOf, isUnitType, @@ -17,6 +20,170 @@ import { typeKey, } from "../../../ir/ir.js"; +/** `Promise.all([p, q, r])` over DIFFERENT promise types — the checker's + * tuple overload, `Promise<[A, B, C]>`. One interned async helper per + * entry/result signature takes the entries as arguments, awaits the + * runtime combinator over the same promises (each widened to + * Promise, a type-only view), so settlement is Node's exactly: the + * first rejection in settlement order wins and every later one counts as + * handled; then it reads each (now fulfilled) entry and returns the + * tuple. Entries keep their identity — no value converts through the + * checked-dynamic tree. Non-promise entries (numbers, strings, booleans, + * unit values — never thenable) pass straight into their slot; a + * checked-dynamic entry (a JavaScript function's result) joins through + * the promise it holds, or a promise fulfilled with its plain value. The tuple + * fulfills one microtask turn after the combinator does. Null when the + * result type or an entry has no typed home here. */ +function lowerTuplePromiseAll( + lowerer: Lowerer, + call: ts.CallExpression, + elems: IrExpr[], +): IrExpr | null { + const resultT = lowerer.mapTypeOf(lowerer.typeOf(call)); + if (resultT?.kind !== "promise" || resultT.inner.kind !== "record") return null; + const shape = lowerer.shapes.get(resultT.inner.shapeId); + if (!shape?.tuple || shape.fields.length !== elems.length) return null; + const plainValue = (t: IrType): boolean => + t.kind === "f64" || t.kind === "string" || t.kind === "bool" || isUnitType(t); + // A checked-dynamic entry (a JavaScript function's result) may hold a + // promise or a plain value at run time; it joins through its promise view. + const settles = (t: IrType): boolean => t.kind === "promise" || t.kind === "dyn"; + if (!elems.some((e) => settles(e.type))) return null; + if (!elems.every((e) => settles(e.type) || plainValue(e.type))) return null; + const loc = locOf(call); + const key = `${elems.map((e) => typeKey(e.type)).join(",")}->${typeKey(resultT.inner)}`; + let name = lowerer.arrHofHelpers.get(`promise.allTuple:${key}`); + if (name === undefined) { + name = `%promise.allTuple.${lowerer.arrHofHelpers.size}`; + lowerer.arrHofHelpers.set(`promise.allTuple:${key}`, name); + const ctx = newFnCtx(false, null, null, resultT.inner); + ctx.isAsync = true; + lowerer.fnStack.push(ctx); + try { + const params = elems.map((e, i) => ({ + localId: `entry.${i}`, + name: `entry${i}`, + type: e.type, + })); + const ref = (i: number): IrExpr => varRef(`entry.${i}`, elems[i]!.type, loc); + const dynPromise: IrType = { kind: "promise", inner: DYN }; + const body: IrStmt[] = []; + // Each dyn entry's promise view: the promise it holds, or a promise + // fulfilled with the plain value (Promise.resolve's rule). + const views = new Map(); + elems.forEach((e, i) => { + if (e.type.kind !== "dyn") return; + const view = lowerer.declareHiddenLocal("%allEntry", dynPromise); + body.push({ + kind: "varDecl", + localId: view.id, + init: { + kind: "ternary", + cond: { kind: "dynTest", test: "promise", value: ref(i), type: BOOL, loc }, + then: { kind: "dynCheck", value: ref(i), type: dynPromise, loc }, + else_: { + kind: "intrinsic", + name: "promise.resolve", + args: [ref(i)], + type: dynPromise, + loc, + }, + type: dynPromise, + loc, + }, + loc, + }); + views.set(i, varRef(view.id, dynPromise, loc)); + }); + const widened: IrExpr[] = []; + elems.forEach((e, i) => { + const view = views.get(i); + if (view !== undefined) { + widened.push({ + kind: "promiseVoidWiden", + value: view, + type: { kind: "promise", inner: VOID }, + loc, + }); + return; + } + if (e.type.kind !== "promise") return; + widened.push( + e.type.inner.kind === "void" + ? ref(i) + : { + kind: "promiseVoidWiden", + value: ref(i), + type: { kind: "promise", inner: VOID }, + loc, + }, + ); + }); + const settled: IrExpr = { + kind: "awaitExpr", + value: { + kind: "intrinsic", + name: "promise.all", + args: [ + { + kind: "arrayLit", + elems: widened, + type: arrayOf({ kind: "promise", inner: VOID }), + loc, + }, + ], + type: { kind: "promise", inner: VOID }, + loc, + }, + type: VOID, + loc, + }; + const fields = shape.fields.map((field, i) => { + const entry = elems[i]!; + let value: IrExpr; + const view = views.get(i); + if (view !== undefined) { + value = { kind: "awaitExpr", value: view, type: DYN, loc }; + } else if (entry.type.kind === "promise") { + value = + entry.type.inner.kind === "void" + ? { kind: "unitLit", unit: "undefined", type: UNDEFINED_T, loc } + : { kind: "awaitExpr", value: ref(i), type: entry.type.inner, loc }; + } else { + value = ref(i); + } + return { name: field.name, value: lowerer.coerceToExpected(value, field.type) }; + }); + body.push( + { kind: "exprStmt", expr: settled, loc }, + { + kind: "return", + value: { kind: "recordLit", fields, type: resultT.inner, loc }, + loc, + }, + ); + lowerer.liftedFns.push({ + name, + params, + returnType: resultT.inner, + locals: [ + ...params.map((p) => ({ id: p.localId, name: p.name, type: p.type, mutable: false })), + ...lowerer.ctx.locals, + ], + body, + async: true, + loc, + }); + } catch (e) { + lowerer.arrHofHelpers.delete(`promise.allTuple:${key}`); + throw e; + } finally { + lowerer.fnStack.pop(); + } + } + return { kind: "call", callee: name, args: elems, type: resultT, loc }; +} + /** `Promise.race([...])` on THE Promise global: the entries lower * individually (the array never materializes — promise-element arrays * have no representation) into a promise.race intrinsic; the result @@ -140,6 +307,10 @@ export function lowerPromiseStaticCall( return { kind: "intrinsic", name: "promise.all", args: [entriesArr], type: resultT, loc }; } + if (!isJsSourceFile(call.getSourceFile())) { + const tuple = lowerTuplePromiseAll(lowerer, call, elems); + if (tuple) return tuple; + } if ( isJsSourceFile(call.getSourceFile()) && elems.every((entry) => lowerer.dynConvertible(entry.type)) diff --git a/packages/compiler/src/frontend/lowering/containers/array-callbacks.ts b/packages/compiler/src/frontend/lowering/containers/array-callbacks.ts index 2e93e2fd5..735361f54 100644 --- a/packages/compiler/src/frontend/lowering/containers/array-callbacks.ts +++ b/packages/compiler/src/frontend/lowering/containers/array-callbacks.ts @@ -44,6 +44,7 @@ import { lowerArrayCallback, requireProducedArrayElement, callbackArrayElement, + truthyFilterCallback, } from "./callback-arguments.js"; import { reverseArrayLoop, @@ -85,7 +86,9 @@ export function lowerArrayHofCall( const valueT = callbackValueType(lowerer, elem, present); if (booleanFilter) { if (valueT.kind === "union") lowerer.requireTruthyUnion(valueT.unionId, argNode); - if (valueT.kind === "dyn" || valueT.kind === "void" || isUnitType(valueT)) { + // Checked-dynamic elements (`anyText.split("\n").map(...).filter(Boolean)`) + // keep their runtime kind, so their truthiness is the runtime's. + if (valueT.kind === "void" || isUnitType(valueT)) { lowerer.badType(argNode, lowerer.typeOf(argNode)); } } @@ -231,33 +234,6 @@ function filterResultElem( return outElem; } -/** A native closure for the ambient Boolean callback. Reifying the - * JavaScript constructor itself is unnecessary; Array#filter only observes - * its one-argument ToBoolean result. */ -function truthyFilterCallback( - lowerer: Lowerer, - valueT: IrType, - loc: SrcLoc, -): IrExpr & { type: IrType & { kind: "func" } } { - const fnT: IrType & { kind: "func" } = { kind: "func", params: [valueT], ret: BOOL }; - const key = `filterBoolean:${typeKey(valueT)}`; - let name = lowerer.arrHofHelpers.get(key); - if (!name) { - name = `%arr.filterBoolean.${lowerer.arrHofHelpers.size}`; - lowerer.arrHofHelpers.set(key, name); - const value = varRef("v.0", valueT, loc); - lowerer.liftedFns.push({ - name, - params: [{ localId: "v.0", name: "v", type: valueT }], - returnType: BOOL, - locals: [{ id: "v.0", name: "v", type: valueT, mutable: false }], - body: [{ kind: "return", value: { kind: "toBool", operand: value, type: BOOL, loc }, loc }], - loc, - }); - } - return { kind: "closure", fnName: name, captures: [], type: fnT, loc }; -} - function filterPredicateOk(lowerer: Lowerer, ret: IrType): boolean { if (ret.kind === "bool") return true; // VOID is a TYPE erasure, not a runtime value: TS lets a value-returning diff --git a/packages/compiler/src/frontend/lowering/containers/callback-arguments.ts b/packages/compiler/src/frontend/lowering/containers/callback-arguments.ts index e87a5e1bc..dcd9934bd 100644 --- a/packages/compiler/src/frontend/lowering/containers/callback-arguments.ts +++ b/packages/compiler/src/frontend/lowering/containers/callback-arguments.ts @@ -1,12 +1,18 @@ import * as ts from "../../ts7/adapter.js"; import type { Lowerer } from "../lowerer.js"; +import { isJsSourceFile, locOf } from "../../program.js"; +import { varRef } from "../../../ir/build.js"; +import { typeKey } from "../../type-mapper.js"; import { + BOOL, F64, type IrExpr, type IrType, + type SrcLoc, UNDEFINED_T, funcOf, isSupportedArrayElem, + isUnitType, typeEquals, } from "../../../ir/ir.js"; @@ -44,6 +50,24 @@ export function lowerArrayCallback( bindUntyped = false, expectedReturn?: IrType, ): { fnArg: IrExpr & { type: IrType & { kind: "func" } }; arity: number } { + // The ambient `Boolean` as the callback (`flags.every(Boolean)`, + // `xs.some(Boolean)`, `xs.map(Boolean)`): Boolean(value) answers the + // ToBoolean of its FIRST argument and ignores the rest, so the callback + // is the interned one-parameter truthiness closure over the lead slot. + // The constructor's own generic signature has no compiled value form. + // JavaScript files keep their primitive-constructor closure. + if ( + ts.isIdentifier(argNode) && + argNode.text === "Boolean" && + !isJsSourceFile(argNode.getSourceFile()) && + lowerer.isStdlibSymbol(lowerer.resolveValueSymbol(argNode) ?? undefined) + ) { + const valueT = lead[0]; + if (valueT === undefined || valueT.kind === "void" || isUnitType(valueT)) + lowerer.badType(argNode, lowerer.typeOf(argNode)); + if (valueT.kind === "union") lowerer.requireTruthyUnion(valueT.unionId, argNode); + return { fnArg: truthyFilterCallback(lowerer, valueT, locOf(argNode)), arity: 1 }; + } const full = [...lead, F64, arrT]; // A DYN-receiver HOF's callback (`parsed.flatMap((value) => ...)`): // the contextual signature types the unannotated param `any` (the @@ -248,3 +272,39 @@ function runtimeOptionalPairVariant(lowerer: Lowerer, expected: IrType, mapped: : t; return lowerer.isRuntimeOptionalRecordVariant(strip(expected), strip(mapped)); } + +/** A native closure for the ambient Boolean callback. Reifying the + * JavaScript constructor itself is unnecessary; Array#filter only observes + * its one-argument ToBoolean result. */ +export function truthyFilterCallback( + lowerer: Lowerer, + valueT: IrType, + loc: SrcLoc, +): IrExpr & { type: IrType & { kind: "func" } } { + const fnT: IrType & { kind: "func" } = { kind: "func", params: [valueT], ret: BOOL }; + const key = `filterBoolean:${typeKey(valueT)}`; + let name = lowerer.arrHofHelpers.get(key); + if (!name) { + name = `%arr.filterBoolean.${lowerer.arrHofHelpers.size}`; + lowerer.arrHofHelpers.set(key, name); + const value = varRef("v.0", valueT, loc); + lowerer.liftedFns.push({ + name, + params: [{ localId: "v.0", name: "v", type: valueT }], + returnType: BOOL, + locals: [{ id: "v.0", name: "v", type: valueT, mutable: false }], + body: [ + { + kind: "return", + value: + valueT.kind === "dyn" + ? { kind: "dynTest", test: "truthy", value, type: BOOL, loc } + : { kind: "toBool", operand: value, type: BOOL, loc }, + loc, + }, + ], + loc, + }); + } + return { kind: "closure", fnName: name, captures: [], type: fnT, loc }; +} diff --git a/packages/compiler/src/frontend/lowering/containers/string-and-regexp.ts b/packages/compiler/src/frontend/lowering/containers/string-and-regexp.ts index 39154a820..50693e5e4 100644 --- a/packages/compiler/src/frontend/lowering/containers/string-and-regexp.ts +++ b/packages/compiler/src/frontend/lowering/containers/string-and-regexp.ts @@ -302,6 +302,53 @@ function lowerMethodReceiver( return lowerer.coerceInto(node, lowered, expected); } +/** A `string | undefined | null` receiver of a string method: evaluated + * once, each unit arm throws the TypeError Node's member read throws + * ("Cannot read properties of undefined (reading 'replace')") before the + * arguments evaluate, and the string arm is the receiver. */ +function nullableStringReceiver(lowerer: Lowerer, node: ts.Expression, member: string): IrExpr { + const lowered = lowerer.lowerExpr(node); + if (lowered.type.kind !== "union") return lowerer.coerceInto(node, lowered, STRING); + const loc = locOf(node); + const unionId = lowered.type.unionId; + const local = lowerer.declareHiddenLocal("%nullableReceiver", lowered.type); + const ref: IrExpr = { kind: "varRef", localId: local.id, type: lowered.type, loc }; + const arms = lowerer.unions.get(unionId)?.arms ?? []; + let result: IrExpr = { + kind: "unionNarrow", + unionId, + tag: arms.findIndex((arm) => arm.kind === "string"), + value: ref, + type: STRING, + loc, + }; + for (const [tag, arm] of arms.entries()) { + if (!isUnitType(arm)) continue; + const unit = arm.kind === "nullT" ? "null" : "undefined"; + result = { + kind: "ternary", + cond: { kind: "unionIsTag", unionId, tag, value: ref, negated: false, type: BOOL, loc }, + then: nodeThrowExpr( + 1, + "", + `Cannot read properties of ${unit} (reading '${member}')`, + STRING, + loc, + ), + else_: result, + type: STRING, + loc, + }; + } + return { + kind: "seqExpr", + stmts: [{ kind: "varDecl", localId: local.id, init: lowered, loc }], + result, + type: STRING, + loc, + }; +} + /** Relative string indexing and code-point decoding over the UTF-16 intrinsics. */ export function lowerStringIndexCall( lowerer: Lowerer, @@ -733,6 +780,15 @@ export function lowerStringPaddingCall( return { kind: "call", callee: helper, args: values, type: STRING, loc }; } +/** String methods whose regex-argument forms lower in lowerRegexMethodCall. */ +const REGEX_ARGUMENT_STRING_METHODS = new Set([ + "replace", + "replaceAll", + "split", + "search", + "matchAll", +]); + export function lowerRegexMethodCall( lowerer: Lowerer, call: ts.CallExpression, @@ -747,11 +803,30 @@ export function lowerRegexMethodCall( // by construction — the symbol gate doesn't apply to `any` receivers. if (dynReceiver === undefined && !lowerer.isStdlibMember(access)) return null; const name = access.name.text; - const receiverKind = dynReceiver - ? "string" - : lowerer.mapTypeOf(lowerer.typeOf(access.expression))?.kind; + const mappedReceiver = dynReceiver ? null : lowerer.mapTypeOf(lowerer.typeOf(access.expression)); + // A nullable string receiver of a REGEX-argument call + // (`part.replace(re, "")` where an unchecked index read typed `part` + // `string | undefined`) takes the string path, as + // lowerStringMethodCall's gate does for the string-argument forms: + // lowerMethodReceiver throws Node's "Cannot read properties of + // undefined" on the unit arms. (`match` keeps its own gate below.) + const regexArgument = call.arguments.nodes[0]; + const nullableString = + REGEX_ARGUMENT_STRING_METHODS.has(name) && + regexArgument !== undefined && + !ts.isSpreadElement(regexArgument) && + lowerer.mapTypeOf(lowerer.typeOf(regexArgument))?.kind === "regex" && + mappedReceiver?.kind === "union" && + (lowerer.unions.get(mappedReceiver.unionId)?.arms.some((arm) => arm.kind === "string") ?? + false) && + (lowerer.unions + .get(mappedReceiver.unionId) + ?.arms.every((arm) => arm.kind === "string" || isUnitType(arm)) ?? + false); + const receiverKind = dynReceiver || nullableString ? "string" : mappedReceiver?.kind; const lowerReceiver = (): IrExpr => { if (dynReceiver) return dynReceiver(); + if (nullableString) return nullableStringReceiver(lowerer, access.expression, name); if (receiverKind === "string") return lowerMethodReceiver(lowerer, access.expression, STRING, access.name.text); if (receiverKind === "regex") diff --git a/packages/compiler/src/frontend/lowering/lower-enums.ts b/packages/compiler/src/frontend/lowering/lower-enums.ts index 159d72f32..cc2797fb9 100644 --- a/packages/compiler/src/frontend/lowering/lower-enums.ts +++ b/packages/compiler/src/frontend/lowering/lower-enums.ts @@ -29,7 +29,8 @@ import * as ts from "../ts7/adapter.js"; import type { Lowerer } from "./lowerer.js"; import { locOf } from "../program.js"; -import { F64, type IrExpr, STRING } from "../../ir/ir.js"; +import { BOOL, F64, type IrExpr, STRING, UNDEFINED_T } from "../../ir/ir.js"; +import { nodeThrowExpr } from "../../ir/build.js"; /** The member's source-name text (identifiers and string-literal names — * the only name forms tsc allows on enum members). */ @@ -182,6 +183,15 @@ function lowerEnumReverseRead( // existence tracking; nothing needs it — fence through the generic path. if (decls.some((d) => isAmbientEnumDecl(d))) return null; const idx = foldConstIndex(lowerer, key); + const keyType = lowerer.typeOf(key); + if ( + idx === null && + ((keyType.flags & ts.TypeFlags.StringLike) !== 0 || + (keyType.isUnionType() && + ts.constituentTypes(keyType).every((t) => (t.flags & ts.TypeFlags.StringLike) !== 0))) + ) { + return lowerEnumKeyRead(lowerer, expr, decls, key); + } if (idx === null) { lowerer.unsupported( "SC1090", @@ -206,6 +216,109 @@ function lowerEnumReverseRead( return { kind: "strLit", value: name, type: STRING, loc }; } +/** Object.prototype members every enum object inherits: a runtime key + * naming one reads a function, which the member value types cannot hold. */ +const OBJECT_PROTOTYPE_KEYS = new Set([ + "__defineGetter__", + "__defineSetter__", + "__lookupGetter__", + "__lookupSetter__", + "__proto__", + "constructor", + "hasOwnProperty", + "isPrototypeOf", + "propertyIsEnumerable", + "toLocaleString", + "toString", + "valueOf", +]); + +/** `E[key]` with a RUNTIME string key (`Color[name as keyof typeof + * Color]` — the name-to-value lookup): the key evaluates once and is + * compared against every member NAME, each hit answering that member's + * constant, exactly the forward entries tsc's emit stores. A key naming no + * member is Node's undefined, so the read carries an undefined arm. Keys + * Node would answer from elsewhere on the object throw a named runtime + * refusal instead of a wrong value: a numeric member's reverse entry (its + * value spelled as a string — Node answers the member NAME) and the + * inherited Object.prototype members. */ +function lowerEnumKeyRead( + lowerer: Lowerer, + expr: ts.ElementAccessExpression, + decls: readonly ts.EnumDeclaration[], + key: ts.Expression, +): IrExpr { + const loc = locOf(expr); + const enumName = decls[0]!.name.text; + const valueType = lowerer.mapTypeOf(lowerer.typeOf(expr)); + if (valueType === null) lowerer.badType(expr, lowerer.typeOf(expr)); + const resultType = lowerer.withUndefinedArm(valueType); + const keyValue = lowerer.coerceToExpected(lowerer.lowerExpr(key), STRING); + const keyLocal = lowerer.declareHiddenLocal("%enumKey", STRING); + const keyRef: IrExpr = { kind: "varRef", localId: keyLocal.id, type: STRING, loc }; + const matches = (text: string): IrExpr => ({ + kind: "strEq", + negated: false, + left: keyRef, + right: { kind: "strLit", value: text, type: STRING, loc }, + type: BOOL, + loc, + }); + const refusal = (what: string): IrExpr => + nodeThrowExpr( + 0, + "SC1090", + `${what} through a runtime key ('${enumName}[key]') is not supported yet`, + resultType, + loc, + ); + const arms: { cond: IrExpr; value: IrExpr }[] = []; + const names = new Set(); + const reverse = new Set(); + for (const d of decls) { + for (const member of d.members.nodes) { + const name = memberNameText(member); + const constant = lowerEnumMemberRead(lowerer, expr, member); + if (constant === null) + lowerer.unsupported("SC1090", expr, `'${enumName}[key]' over this enum`); + if (!names.has(name)) { + names.add(name); + arms.push({ cond: matches(name), value: lowerer.coerceToExpected(constant, resultType) }); + } + const value = lowerer.checker.getConstantValue(member); + if (typeof value === "number") reverse.add(String(value)); + } + } + for (const text of reverse) { + if (names.has(text)) continue; + arms.push({ cond: matches(text), value: refusal("enum reverse lookups") }); + } + for (const text of OBJECT_PROTOTYPE_KEYS) { + if (names.has(text) || reverse.has(text)) continue; + arms.push({ cond: matches(text), value: refusal("inherited enum object members") }); + } + let result: IrExpr = { kind: "unitLit", unit: "undefined", type: UNDEFINED_T, loc }; + result = lowerer.coerceToExpected(result, resultType); + for (let i = arms.length - 1; i >= 0; i--) { + const arm = arms[i]!; + result = { + kind: "ternary", + cond: arm.cond, + then: arm.value, + else_: result, + type: resultType, + loc, + }; + } + return { + kind: "seqExpr", + stmts: [{ kind: "varDecl", localId: keyLocal.id, init: keyValue, loc }], + result, + type: resultType, + loc, + }; +} + /** A compile-time-constant NUMERIC index: a numeric literal, its negation, * or an enum member read whose constant is a number. */ function foldConstIndex(lowerer: Lowerer, e: ts.Expression): number | null { diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6480-boolean-callbacks.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6480-boolean-callbacks.ts.json new file mode 100644 index 000000000..3279178b8 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6480-boolean-callbacks.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6480-boolean-callbacks.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6481-enum-runtime-keys.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6481-enum-runtime-keys.ts.json new file mode 100644 index 000000000..7fc1885aa --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6481-enum-runtime-keys.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6481-enum-runtime-keys.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6482-promise-all-tuples.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6482-promise-all-tuples.ts.json new file mode 100644 index 000000000..5ceea99a5 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6482-promise-all-tuples.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6482-promise-all-tuples.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6483-nullable-regex-receivers.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6483-nullable-regex-receivers.ts.json new file mode 100644 index 000000000..6a6263bbc --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6483-nullable-regex-receivers.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6483-nullable-regex-receivers.ts" + ], + "diags": [] +} diff --git a/tests/corpus/6480-boolean-callbacks.ts b/tests/corpus/6480-boolean-callbacks.ts new file mode 100644 index 000000000..b9ac4b4ab --- /dev/null +++ b/tests/corpus/6480-boolean-callbacks.ts @@ -0,0 +1,34 @@ +// The ambient Boolean as an array callback beyond filter: every, some, +// find, findIndex and map call it with the element first, so each answers +// ToBoolean of the element (later arguments are ignored). filter(Boolean) +// also covers elements the checker types `any`. +const flags = [true, true, false]; +console.log(flags.every(Boolean), flags.some(Boolean), flags.slice(0, 2).every(Boolean)); +const counts = [0, 3, 0, 7]; +console.log(counts.find(Boolean), counts.findIndex(Boolean), counts.map(Boolean).join(",")); +const names = ["", "a", "", "b"]; +console.log(names.some(Boolean), names.every(Boolean), names.findIndex(Boolean)); +const maybe: (string | undefined)[] = [undefined, "x", undefined]; +console.log(maybe.some(Boolean), maybe.find(Boolean), maybe.map(Boolean).join(",")); + +// Elements the checker types `any` keep their runtime kind; filter(Boolean) +// answers their runtime truthiness. +function untyped(value: unknown): any { + return value; +} +const configFiles = [untyped("app.json"), untyped(""), untyped(undefined), untyped("site.json")].filter( + Boolean, +) as string[]; +console.log(configFiles.length, configFiles.join(",")); + +async function check(n: number): Promise { + return n % 2 === 1; +} + +async function main(): Promise { + const all = (await Promise.all([1, 3, 5].map(async (n) => check(n)))).every(Boolean); + const any = (await Promise.all([2, 4].map(async (n) => check(n)))).some(Boolean); + console.log(all, any); +} + +main(); diff --git a/tests/corpus/6481-enum-runtime-keys.ts b/tests/corpus/6481-enum-runtime-keys.ts new file mode 100644 index 000000000..183d509b2 --- /dev/null +++ b/tests/corpus/6481-enum-runtime-keys.ts @@ -0,0 +1,29 @@ +// @transform-types +// Enum member lookup through a runtime string key (`E[name as keyof +// typeof E]`): each member name answers its constant — string, numeric and +// renamed members alike — and a name no member carries reads undefined. +enum Color { + red = "red", + "dark-blue" = "dark-blue", + green = "light-green", +} + +enum Level { + low, + mid = 5, + high, +} + +function color(name: string | undefined): Color | undefined { + return name ? Color[name as keyof typeof Color] : undefined; +} + +for (const name of ["red", "dark-blue", "green", "light-green", "", undefined]) { + console.log(JSON.stringify(name), JSON.stringify(color(name))); +} + +const levelName = process.argv.length > 99 ? "unused" : "high"; +const level: Level | undefined = Level[levelName as keyof typeof Level]; +console.log(level, level === Level.high); +const missing: Level | undefined = Level[("med" + "ium") as keyof typeof Level]; +console.log(missing === undefined); diff --git a/tests/corpus/6482-promise-all-tuples.ts b/tests/corpus/6482-promise-all-tuples.ts new file mode 100644 index 000000000..ff1615604 --- /dev/null +++ b/tests/corpus/6482-promise-all-tuples.ts @@ -0,0 +1,76 @@ +// Promise.all over an array literal of DIFFERENT promise types (the +// checker's tuple overload): destructuring with holes and nested +// patterns, void entries, plain values, `any` entries that may or may not +// hold a promise, entry identity, and rejection — +// the first rejection in settlement order wins, later ones count as +// handled. +interface Version { + major: number; + range: string; +} + +class Scan { + tool: string; + schema: number | undefined; + constructor(tool: string, schema?: number) { + this.tool = tool; + this.schema = schema; + } +} + +const shared: Version = { major: 22, range: "22.x" }; +const scanned = new Scan("make", 9); + +async function version(): Promise { + return shared; +} +async function scan(): Promise { + return scanned; +} +async function patch(): Promise { + console.log("patched"); +} +async function creds(ok: boolean): Promise<{ key: string } | undefined> { + return ok ? { key: "k" } : undefined; +} +function delay(ms: number, value: T, fail?: string): Promise { + return new Promise((resolve, reject) => + setTimeout(() => (fail ? reject(new Error(fail)) : resolve(value)), ms), + ); +} + +// JavaScript-style results the checker types `any`: one holds a promise, +// one a plain value. +function untypedCount(): any { + return 41; +} +function untypedRecord(): any { + return Promise.resolve({ label: "from a promise" }); +} + +async function main(): Promise { + const [runtime, { tool: kind, schema: level }] = await Promise.all([version(), scan()]); + console.log(runtime.major, kind, level, runtime === shared); + + const first = scan(); + const second = patch(); + const third = creds(true); + const [found, , c] = await Promise.all([first, second, third]); + console.log(found === scanned, c?.key); + + const both = await Promise.all([version(), 5, "x", creds(false)]); + console.log(both[0].range, both[1] + 1, both[2], both[3]); + + const [count, { label }, plain] = await Promise.all([untypedCount(), untypedRecord(), "p"]); + console.log(count + 1, label, plain); + + try { + await Promise.all([delay(20, 1, "slow failure"), delay(5, "s", "fast failure"), delay(1, true)]); + } catch (e) { + console.log("rejected:", (e as Error).message); + } + await delay(40, 0); + console.log("done"); +} + +main(); diff --git a/tests/corpus/6483-nullable-regex-receivers.ts b/tests/corpus/6483-nullable-regex-receivers.ts new file mode 100644 index 000000000..d6b190a30 --- /dev/null +++ b/tests/corpus/6483-nullable-regex-receivers.ts @@ -0,0 +1,23 @@ +// Regex-argument string methods on a receiver the checker types +// `string | undefined` (an unchecked index read, ts-ignored): a string +// takes the regex lowering, undefined throws Node's TypeError. +const lines = "100 /srv/node1/a.txt\n2048 /srv/node22/b/c.bin"; +const pairs = lines.split("\n").map((line) => { + const [count, file] = line.trim().split(/\s+/) as [string | undefined, string | undefined]; + // @ts-ignore possibly undefined + return `${count.replace(/0+$/, "")}:${file.replace(/^\/srv\/node\d+\//, "")}`; +}); +console.log(pairs); + +function parts(text: string): (string | undefined)[] { + return text.split(",").map((s) => (s === "" ? undefined : s)); +} + +for (const value of parts("a-b-c,")) { + try { + // @ts-ignore possibly undefined + console.log(value.replaceAll(/-/g, "+"), value.split(/-/).length, value.search(/c/)); + } catch (e) { + console.log(e instanceof TypeError, (e as Error).message); + } +} diff --git a/tests/diagnostics/promise-all-fences.ts b/tests/diagnostics/promise-all-fences.ts index 89c29c308..c7ddc74bd 100644 --- a/tests/diagnostics/promise-all-fences.ts +++ b/tests/diagnostics/promise-all-fences.ts @@ -1,14 +1,8 @@ -// Promise.all, Promise.allSettled and Promise.any lower over ONE promise -// type (any Promise[] expression); heterogeneous array literals land -// on the checker's tuple overload and fence with the shared-type hint, -// and non-array arguments fence on their shape. -async function hetero(): Promise { - const pair = await Promise.all([ - new Promise((resolve) => resolve("s")), - new Promise((resolve) => resolve(1)), - ]); - console.log(pair.length); -} +// Promise.allSettled and Promise.any lower over ONE promise type (any +// Promise[] expression); their heterogeneous array literals land on +// the checker's tuple overload and fence with the shared-type hint, and +// non-array arguments fence on their shape. (Promise.all's tuple overload +// lowers: corpus 6482.) async function settled(): Promise { const results = await Promise.allSettled([ new Promise((resolve) => resolve("s")), @@ -22,6 +16,5 @@ function* pending(): Generator> { async function anyOfGenerator(): Promise { console.log(await Promise.any(pending())); } -hetero(); settled(); anyOfGenerator(); diff --git a/tests/harness/__snapshots__/promise-all-fences.ts.txt b/tests/harness/__snapshots__/promise-all-fences.ts.txt index 8d17f3f57..18018a8b5 100644 --- a/tests/harness/__snapshots__/promise-all-fences.ts.txt +++ b/tests/harness/__snapshots__/promise-all-fences.ts.txt @@ -1,26 +1,17 @@ -promise-all-fences.ts:6:34 - error SC2020: 'Promise.all over this argument shape' is part of the standard library types but has no scriptc lowering yet +promise-all-fences.ts:7:44 - error SC2020: 'Promise.allSettled over this argument shape' is part of the standard library types but has no scriptc lowering yet - 5 | async function hetero(): Promise { - 6 | const pair = await Promise.all([ - | ^ - 7 | new Promise((resolve) => resolve("s")), - - hint: the entries must share ONE promise type — Promise.all([p, q]) lowers when p and q are the same Promise - -promise-all-fences.ts:13:44 - error SC2020: 'Promise.allSettled over this argument shape' is part of the standard library types but has no scriptc lowering yet - - 12 | async function settled(): Promise { - 13 | const results = await Promise.allSettled([ - | ^ - 14 | new Promise((resolve) => resolve("s")), + 6 | async function settled(): Promise { + 7 | const results = await Promise.allSettled([ + | ^ + 8 | new Promise((resolve) => resolve("s")), hint: the entries must share ONE promise type — Promise.allSettled([p, q]) lowers when p and q are the same Promise -promise-all-fences.ts:23:33 - error SC2020: 'Promise.any over this argument shape' is part of the standard library types but has no scriptc lowering yet +promise-all-fences.ts:17:33 - error SC2020: 'Promise.any over this argument shape' is part of the standard library types but has no scriptc lowering yet - 22 | async function anyOfGenerator(): Promise { - 23 | console.log(await Promise.any(pending())); + 16 | async function anyOfGenerator(): Promise { + 17 | console.log(await Promise.any(pending())); | ^~~~~~~~~ - 24 | } + 18 | } hint: an array of promises (Promise[]) is the supported form \ No newline at end of file From 0e8b4cf9909dbad22e7a4f350191b4576c2ec68e Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Mon, 19 Oct 2026 23:36:12 -0700 Subject: [PATCH 049/122] Hand back untyped wrapper results at the slot's type, and no-op transpiled prototype resets - A function adapter whose source returns `any`/`unknown` (a generic wrapper's result handed back `as F`) converts the result through the checked-dynamic bridge when the slot's result is checkable: a record checks its shape, and a Promise keeps its identity and checks its payload when it settles. Before, these adapters threw "the call has no exact lowering" when called. - In a class constructor, `Object.setPrototypeOf(this, new.target.prototype)` (directly, through a const, or the `if (Object.setPrototypeOf) ... else this.__proto__ = ...` form), and `Object.setPrototypeOf(this, C.prototype)` in C's own constructor when no class extends C, are no-ops: a compiled instance already has that prototype. - Object.assign between records copies a source field into a wider target field (`{ bail: true }` into `bail?: boolean`). Co-Authored-By: Claude Opus 5.5 --- .../lowering/coercions/function-adapters.ts | 16 +++ .../src/frontend/lowering/lower-calls.ts | 11 +- .../src/frontend/lowering/lower-exprs.ts | 9 ++ .../src/frontend/lowering/lower-stmts.ts | 123 ++++++++++++++++++ tests/corpus/6404-error-in-and-assign.ts | 14 ++ tests/corpus/6410-wrapper-untyped-results.ts | 50 +++++++ .../issues.js | 27 ++++ .../6411-prototype-reset-constructors/main.ts | 33 +++++ 8 files changed, 281 insertions(+), 2 deletions(-) create mode 100644 tests/corpus/6410-wrapper-untyped-results.ts create mode 100644 tests/corpus/6411-prototype-reset-constructors/issues.js create mode 100644 tests/corpus/6411-prototype-reset-constructors/main.ts diff --git a/packages/compiler/src/frontend/lowering/coercions/function-adapters.ts b/packages/compiler/src/frontend/lowering/coercions/function-adapters.ts index b99c24a15..14cc8ba86 100644 --- a/packages/compiler/src/frontend/lowering/coercions/function-adapters.ts +++ b/packages/compiler/src/frontend/lowering/coercions/function-adapters.ts @@ -4,6 +4,7 @@ import { InternalCompilerError } from "../../../errors.js"; import type { IrExpr, IrParam, IrStmt, IrType, SrcLoc } from "../../../ir/ir.js"; import { canAdaptDynFuncTo, + canDynCheckTo, DYN, F64, isUnitType, @@ -208,6 +209,19 @@ export function funcCoerceAdapter( voidRet = toT.ret.kind === "dyn" ? "dyn" : "strand"; } } + // An untyped result (a generic wrapper's `any` handed back `as F`) takes + // the checked-dynamic extraction to the slot's result: a record checks + // its shape, a Promise keeps its identity and checks its payload when it + // settles. + const checkedRet = + strandRet && + fromT.ret.kind === "dyn" && + canDynCheckTo( + toT.ret, + (id) => lowerer.shapes.get(id), + (id) => lowerer.unions.get(id), + ); + if (checkedRet) strandRet = false; const key = `fnadapt:${typeKey(fromT)}:${typeKey(toT)}`; const existing = lowerer.coercions.retags.get(key); if (existing) return existing; @@ -307,6 +321,8 @@ export function funcCoerceAdapter( `a '${lowerer.fmt(fromT)}' function invoked through a '${lowerer.fmt(toT)}' slot (the result cannot convert to '${lowerer.fmt(toT.ret)}' — the checker's loose function compatibility admitted the assignment, but the call has no exact lowering)`, ), ]; + } else if (checkedRet) { + body = [{ kind: "return", value: { kind: "dynCheck", value: call, type: toT.ret, loc }, loc }]; } else { const result = lowerer.coerceToExpected(call, toT.ret); if (!typeEquals(result.type, toT.ret)) diff --git a/packages/compiler/src/frontend/lowering/lower-calls.ts b/packages/compiler/src/frontend/lowering/lower-calls.ts index f9abf2d19..14d5d71d3 100644 --- a/packages/compiler/src/frontend/lowering/lower-calls.ts +++ b/packages/compiler/src/frontend/lowering/lower-calls.ts @@ -6699,6 +6699,7 @@ function recordAssignHelper( const helper = `%obj.assign.${lowerer.arrHofHelpers.size}`; lowerer.arrHofHelpers.set(key, helper); const sShape = lowerer.shapes.get(srcShapeId)!; + const tShape = lowerer.shapes.get(targetShapeId)!; const tT: IrType = { kind: "record", shapeId: targetShapeId }; const sT: IrType = { kind: "record", shapeId: srcShapeId }; const tRef: IrExpr = { kind: "varRef", localId: "t.0", type: tT, loc }; @@ -6713,12 +6714,13 @@ function recordAssignHelper( type: f.type, loc, }; + const targetType = tShape.fields.find((x) => x.name === f.name)!.type; const set: IrStmt = { kind: "recordSet", obj: tRef, shapeId: targetShapeId, field: f.name, - value: get, + value: typeEquals(targetType, f.type) ? get : lowerer.coerceToExpected(get, targetType), loc, }; const present = lowerer.recordFieldPresent(sRef, srcShapeId, f.name, loc); @@ -7338,7 +7340,12 @@ function lowerObjectStaticCall( !shapeHasAccessorSlots(sShape) && sShape.fields.every((sf) => { const tf = tShape.fields.find((x) => x.name === sf.name); - return tf !== undefined && typeEquals(tf.type, sf.type); + // A source value converts into a wider target field (`{ bail: + // true }` into `bail?: boolean`). + return ( + tf !== undefined && + (typeEquals(tf.type, sf.type) || lowerer.coercibleValue(sf.type, tf.type)) + ); }); if (ok) { const loc = locOf(call); diff --git a/packages/compiler/src/frontend/lowering/lower-exprs.ts b/packages/compiler/src/frontend/lowering/lower-exprs.ts index 8b02597ab..61fc0f8d0 100644 --- a/packages/compiler/src/frontend/lowering/lower-exprs.ts +++ b/packages/compiler/src/frontend/lowering/lower-exprs.ts @@ -4824,6 +4824,15 @@ function nullishBranches( export function lowerCondition(lowerer: Lowerer, expr: ts.Expression): IrExpr { let e: ts.Expression = expr; while (ts.isParenthesizedExpression(e)) e = e.expression; + // `if (Object.setPrototypeOf)`: the feature test of transpiled Error + // subclasses' prototype reset. Node always has it. + if ( + ts.isPropertyAccessExpression(e) && + !e.questionDotToken && + e.name.text === "setPrototypeOf" && + lowerer.isStdlibGlobal(e.expression, "Object") + ) + return { kind: "boolLit", value: true, type: BOOL, loc: locOf(e) }; if (ts.isPropertyAccessExpression(e)) { const presence = lowerPromiseThenPresence(lowerer, e, { kind: "boolLit", diff --git a/packages/compiler/src/frontend/lowering/lower-stmts.ts b/packages/compiler/src/frontend/lowering/lower-stmts.ts index eb0d31cbd..e6c6720e0 100644 --- a/packages/compiler/src/frontend/lowering/lower-stmts.ts +++ b/packages/compiler/src/frontend/lowering/lower-stmts.ts @@ -1350,6 +1350,7 @@ function lowerLabeled(lowerer: Lowerer, stmt: ts.LabeledStatement): IrStmt | IrS } export function lowerStmt(lowerer: Lowerer, stmt: ts.Statement): IrStmt | IrStmt[] | null { + if (ownPrototypeResetStatement(lowerer, stmt)) return { kind: "block", body: [], loc: locOf(stmt) }; if (ts.isClassDeclaration(stmt)) { if (!stmt.name) lowerer.unsupported("SC1090", stmt, "anonymous local class declarations"); const info = lowerer.lowerClassExpressionInfo(stmt); @@ -10849,3 +10850,125 @@ function isStringTyped(t: ts.Type): boolean { if (t.isUnionType()) return ts.constituentTypes(t).every(isStringTyped); return (t.flags & (ts.TypeFlags.String | ts.TypeFlags.StringLiteral)) !== 0; } + +/** The Error-subclass prototype reset of transpiled code, inside a class + * constructor: + * + * const proto = new.target.prototype; + * if (Object.setPrototypeOf) Object.setPrototypeOf(this, proto); + * else this.__proto__ = proto; + * + * (also `Object.setPrototypeOf(this, new.target.prototype)` and + * `Object.setPrototypeOf(this, C.prototype)` in C's own constructor when no + * class extends C). A compiled instance already has exactly that prototype, + * so each statement is a no-op. True for those statements. */ +function ownPrototypeResetStatement(lowerer: Lowerer, stmt: ts.Statement): boolean { + if (ts.isVariableStatement(stmt)) { + const decls = stmt.declarationList.declarations.nodes; + if (decls.length !== 1 || (stmt.declarationList.flags & ts.NodeFlags.Const) === 0) return false; + const decl = decls[0]!; + if (!ts.isIdentifier(decl.name) || !decl.initializer) return false; + const ctor = enclosingConstructor(decl); + if (!ctor || !ownPrototypeRef(lowerer, decl.initializer, ctor)) return false; + const symbol = lowerer.checker.getSymbolAtLocation(decl.name); + if (!symbol) return false; + // Every other use of the binding must be one of the reset statements. + let onlyResets = true; + ts.walkPreorder(ctor, (n) => { + if (!onlyResets) return "stop"; + if (!ts.isIdentifier(n) || n === decl.name) return undefined; + if (lowerer.checker.getSymbolAtLocation(n) !== symbol) return undefined; + const statement = n.parent?.parent; + if ( + !statement || + !ts.isExpressionStatement(statement) || + resetTarget(lowerer, statement.expression) !== n + ) + onlyResets = false; + return undefined; + }); + return onlyResets; + } + if (!ts.isExpressionStatement(stmt)) return false; + const ctor = enclosingConstructor(stmt); + if (!ctor) return false; + const target = resetTarget(lowerer, stmt.expression); + return target !== null && ownPrototypeRef(lowerer, target, ctor); +} + +/** The prototype operand of `Object.setPrototypeOf(this, P)` or + * `this.__proto__ = P`, or null. */ +function resetTarget(lowerer: Lowerer, expr: ts.Expression): ts.Expression | null { + if ( + ts.isCallExpression(expr) && + expr.arguments.nodes.length === 2 && + expr.arguments.nodes[0]!.kind === ts.SyntaxKind.ThisKeyword && + ts.isPropertyAccessExpression(expr.expression) && + expr.expression.name.text === "setPrototypeOf" && + lowerer.isStdlibGlobal(expr.expression.expression, "Object") + ) + return expr.arguments.nodes[1]!; + if ( + ts.isBinaryExpression(expr) && + expr.operatorToken.kind === ts.SyntaxKind.EqualsToken && + ts.isPropertyAccessExpression(expr.left) && + expr.left.expression.kind === ts.SyntaxKind.ThisKeyword && + expr.left.name.text === "__proto__" + ) + return expr.right; + return null; +} + +/** The class constructor whose body holds `node`, crossing only statements + * and blocks (any nested function rebinds `this`). */ +function enclosingConstructor(node: ts.Node): ts.ConstructorDeclaration | null { + for (let cur = node.parent; cur !== undefined; cur = cur.parent) { + if (ts.isConstructorDeclaration(cur)) return cur; + if (ts.isFunctionLike(cur) || (ts.isClassDeclaration(cur) || ts.isClassExpression(cur)) || ts.isSourceFile(cur)) return null; + } + return null; +} + +/** Whether `node` denotes the prototype of the object `ctor` constructs: + * `new.target.prototype`, a const holding it, or the class's own + * `C.prototype` when no class extends C. */ +function ownPrototypeRef( + lowerer: Lowerer, + node: ts.Expression, + ctor: ts.ConstructorDeclaration, +): boolean { + while (ts.isParenthesizedExpression(node)) node = node.expression; + if (ts.isPropertyAccessExpression(node) && node.name.text === "prototype") { + const base = node.expression; + if ( + ts.isMetaProperty(base) && + base.keywordToken === ts.SyntaxKind.NewKeyword && + base.name.text === "target" + ) + return true; + const cls = ctor.parent; + if (!ts.isIdentifier(base) || !(ts.isClassDeclaration(cls) || ts.isClassExpression(cls))) return false; + const symbol = lowerer.resolveValueSymbol(base); + const info = symbol ? lowerer.classBySymbol.get(symbol) : undefined; + if (!info || lowerer.checker.valueDeclarationOf(symbol!) !== cls) return false; + for (const other of lowerer.classes.values()) { + if (other !== info && lowerer.isSubclassOf(other.def.name, info.def.name)) return false; + } + return true; + } + if (ts.isIdentifier(node)) { + const symbol = lowerer.resolveValueSymbol(node); + const decl = symbol ? lowerer.checker.valueDeclarationOf(symbol) : undefined; + return ( + decl !== undefined && + ts.isVariableDeclaration(decl) && + decl.initializer !== undefined && + ts.isVariableDeclarationList(decl.parent) && + (decl.parent.flags & ts.NodeFlags.Const) !== 0 && + enclosingConstructor(decl) === ctor && + !ts.isIdentifier(decl.initializer) && + ownPrototypeRef(lowerer, decl.initializer, ctor) + ); + } + return false; +} diff --git a/tests/corpus/6404-error-in-and-assign.ts b/tests/corpus/6404-error-in-and-assign.ts index 7003a8ae8..fc6f470e6 100644 --- a/tests/corpus/6404-error-in-and-assign.ts +++ b/tests/corpus/6404-error-in-and-assign.ts @@ -103,3 +103,17 @@ function describeApiError(error: Error & { status?: number }): string { return `${error.message}:${error.status ?? "none"}`; } console.log(describeApiError(gateway), describeApiError(new Error("plain"))); + +// Object.assign into a record whose fields are wider than the source's. +interface BuildFailure { + message: string; + bail?: boolean; + code?: string; +} +function failure(message: string, bail: boolean): BuildFailure { + const err: BuildFailure = { message }; + if (bail) Object.assign(err, { bail: true }); + return Object.assign(err, { code: "E_BUILD" }); +} +for (const f of [failure("stop", true), failure("go", false)]) + console.log(f.message, f.bail, f.code); diff --git a/tests/corpus/6410-wrapper-untyped-results.ts b/tests/corpus/6410-wrapper-untyped-results.ts new file mode 100644 index 000000000..f8df7683a --- /dev/null +++ b/tests/corpus/6410-wrapper-untyped-results.ts @@ -0,0 +1,50 @@ +// Generic wrappers returning their untyped result `as F`: async functions, +// records with unknown fields, and rejections passing through. +function trace any>(name: string, fn: F): F { + return ((...args: Parameters) => { + console.log("span", name); + return fn(...args); + }) as F; +} +function traceThis any>(name: string, fn: F): F { + return function (this: unknown, ...args: Parameters) { + const result: unknown = fn.apply(this, args); + console.log("span", name, typeof result); + return result; + } as F; +} +function lookup(key: string): any { + return key === "set" ? 7 : undefined; +} +const fetchCount = trace("fetchCount", async (base: number): Promise => { + await null; + return base + 1; +}); +const fetchUser = traceThis("fetchUser", async (id: string): Promise<{ id: string; admin: boolean }> => ({ + id, + admin: id === "root", +})); +const fails = trace("fails", async (why: string): Promise => { + throw new Error(why); +}); +const summarize = trace("summarize", (limit = lookup("unset") ?? 2) => ({ kept: limit, dropped: 1 })); +const measure = traceThis("measure", (n: number) => ({ size: n, label: lookup("set") })); +async function main(): Promise { + console.log(await fetchCount(1)); + const user = await fetchUser("root"); + console.log(user.id, user.admin); + try { + await fails("nope"); + } catch (e) { + console.log("caught", e instanceof Error ? e.message : String(e)); + } + const s = summarize(); + console.log(s.kept, s.dropped, summarize(5).kept); + const m = measure(3); + console.log(m.size, m.label); + const pending = fetchCount(10); + console.log("before"); + console.log(await pending); +} +main(); +export {}; diff --git a/tests/corpus/6411-prototype-reset-constructors/issues.js b/tests/corpus/6411-prototype-reset-constructors/issues.js new file mode 100644 index 000000000..1058d7188 --- /dev/null +++ b/tests/corpus/6411-prototype-reset-constructors/issues.js @@ -0,0 +1,27 @@ +// An Error subclass in the shape of a validation library's: the constructor +// resets the prototype to new.target's, as transpiled code does. +export class IssueError extends Error { + constructor(issues) { + super(); + this.issues = []; + const actualProto = new.target.prototype; + if (Object.setPrototypeOf) { + Object.setPrototypeOf(this, actualProto); + } else { + this.__proto__ = actualProto; + } + this.name = "IssueError"; + this.issues = issues; + } + get message() { + return JSON.stringify(this.issues); + } + get isEmpty() { + return this.issues.length === 0; + } +} +export class StrictIssueError extends IssueError { + get strict() { + return true; + } +} diff --git a/tests/corpus/6411-prototype-reset-constructors/main.ts b/tests/corpus/6411-prototype-reset-constructors/main.ts new file mode 100644 index 000000000..65f155e8d --- /dev/null +++ b/tests/corpus/6411-prototype-reset-constructors/main.ts @@ -0,0 +1,33 @@ +// Constructors that reset the instance prototype, the transpiled idiom for +// Error subclasses. +import { IssueError, StrictIssueError } from "./issues.js"; +class AppError extends Error { + constructor(message: string) { + super(message); + Object.setPrototypeOf(this, new.target.prototype); + this.name = "AppError"; + } + describe(): string { + return `${this.name}: ${this.message}`; + } +} +class NotFound extends AppError { + constructor(what: string) { + super(`${what} not found`); + Object.setPrototypeOf(this, NotFound.prototype); + this.name = "NotFound"; + } +} +const app = new AppError("boom"); +const missing = new NotFound("user"); +console.log(app.describe(), app instanceof AppError, app instanceof Error); +console.log(missing.describe(), missing instanceof NotFound, missing instanceof AppError); +const issues = new IssueError([{ path: "a" }]); +console.log(issues.message, issues.isEmpty, issues instanceof IssueError, issues.name); +const strict = new StrictIssueError([]); +console.log(strict.isEmpty, strict.strict, strict instanceof IssueError, String(strict instanceof StrictIssueError)); +try { + throw new NotFound("page"); +} catch (e) { + console.log(e instanceof NotFound ? e.describe() : "other"); +} From b3bcbaaf7cea09e9a17582ad9249f887db081f2a Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Tue, 20 Oct 2026 01:46:36 -0500 Subject: [PATCH 050/122] Decode memargs the same way in validation and translation Validation, translation and instruction skipping now read memarg flags through one function, readMemargFlags in module.ts. Flags with bit 6 set are followed by an explicit memory index, which may be 0; flags of 128 or more are malformed. The translator refuses a module (WasmTranslateError) when a memarg, memory.size, memory.grow, memory.init, memory.copy or memory.fill names a memory other than 0, and when the operand of local.set, local.tee, global.set, table.set, a load or a store does not have the type of its destination. Locals are stored with their declared type. --- packages/compiler/src/wasm/binary.ts | 23 +++---- packages/compiler/src/wasm/module.ts | 18 +++++- packages/compiler/src/wasm/translate.ts | 83 ++++++++++++++++++------- 3 files changed, 85 insertions(+), 39 deletions(-) diff --git a/packages/compiler/src/wasm/binary.ts b/packages/compiler/src/wasm/binary.ts index 5ddc28cc9..ad5297739 100644 --- a/packages/compiler/src/wasm/binary.ts +++ b/packages/compiler/src/wasm/binary.ts @@ -20,6 +20,7 @@ * The decoder never evaluates anything and keeps no module contents beyond * the counts and the feature set it reports. It is plain TypeScript that * the native compiler also compiles. */ +import { readMemargFlags } from "./module.js"; /** Proposals the summary can name, in report order. The first group is the * translatable set; the rest are reported as unsupported. */ @@ -1244,19 +1245,9 @@ class FunctionValidator { /** A memarg. A memory other than 0 stops typing (multi-memory). */ memarg(naturalAlign: number): void { const r = this.r; - let align = r.u32(); - let multi = false; - if (align >= 64 && align < 128) { - align -= 64; - multi = true; - } else if (align >= 64) { - r.malformed("malformed memop flags"); - } - let memory = 0; - if (multi) { - memory = r.u32(); - if (memory !== 0) this.otherMemory(memory); - } + const { align, memory } = readMemargFlags(r); + if (align < 0) r.malformed("malformed memop flags"); + if (memory !== 0) this.otherMemory(memory); if (this.m.memory64) r.u64(); else r.u32(); if (!this.typed) return; @@ -1840,9 +1831,9 @@ class FunctionValidator { scanMemarg(): void { const r = this.r; - const align = r.u32(); - if (align >= 128) r.malformed("malformed memop flags"); - if (align >= 64 && r.u32() !== 0) this.m.feature("multi-memory"); + const { align, memory } = readMemargFlags(r); + if (align < 0) r.malformed("malformed memop flags"); + if (memory !== 0) this.m.feature("multi-memory"); if (this.m.memory64) r.u64(); else r.u32(); } diff --git a/packages/compiler/src/wasm/module.ts b/packages/compiler/src/wasm/module.ts index 3b0dd54c0..d7ec38701 100644 --- a/packages/compiler/src/wasm/module.ts +++ b/packages/compiler/src/wasm/module.ts @@ -283,6 +283,20 @@ function decodeUtf8(bytes: Uint8Array, start: number, end: number): string { return out; } +/** The flags of a memarg (the immediates of loads, stores and atomics): + * the alignment exponent and the memory index. Flags with bit 6 set + * (multi-memory) are followed by an explicit memory index, which may be + * 0; flags of 128 or more are malformed and answer align -1 without + * reading further. The offset follows. Validation (binary.ts), translation + * and skipImmediates all read memargs through this function, so they agree + * on where each instruction ends. */ +export function readMemargFlags(r: { u32(): number }): { align: number; memory: number } { + const flags = r.u32(); + if (flags >= 128) return { align: -1, memory: 0 }; + if (flags >= 64) return { align: flags - 64, memory: r.u32() }; + return { align: flags, memory: 0 }; +} + /** Reads the immediates of opcode `op` (already consumed). Covers the * translatable feature level; anything else was refused before * translation. */ @@ -372,7 +386,9 @@ export function skipImmediates(c: WasmCursor, op: number): void { } default: if (op >= 0x28 && op <= 0x3e) { - c.u32(); + const flags = readMemargFlags(c); + if (flags.align < 0 || flags.memory !== 0) + throw new Error(`wasm: memarg of memory ${flags.memory} is not supported`); c.u32(); } return; diff --git a/packages/compiler/src/wasm/translate.ts b/packages/compiler/src/wasm/translate.ts index e0ce27be5..813dab84a 100644 --- a/packages/compiler/src/wasm/translate.ts +++ b/packages/compiler/src/wasm/translate.ts @@ -48,6 +48,7 @@ import { VAL_I64, WasmCursor, parseWasmModule, + readMemargFlags, skipImmediates, type WasmFuncType, type WasmModule, @@ -830,6 +831,39 @@ class FunctionTranslator { return v; } + /** Pops a value that must have type `t`: the type a local, global, + * table or memory store writes. Validation guarantees it; a mismatch + * means the translator and the validator disagree about the code, so + * the module is not translated. */ + popType(t: number, what: string): StackValue { + const v = this.pop(); + if (v.t !== t) throw new WasmTranslateError(`${what} operand type does not match`); + return v; + } + + /** The declared type of local `i` (parameters first). */ + localType(i: number): number { + const t = this.localTypes[i]; + if (t === undefined) throw new WasmTranslateError(`unknown local ${i}`); + return t; + } + + /** The offset of a memarg over memory 0 (the only memory a translatable + * module has). */ + memargOffset(c: WasmCursor): number { + const flags = readMemargFlags(c); + if (flags.align < 0 || flags.memory !== 0) + throw new WasmTranslateError(`memarg of memory ${flags.memory} is not supported`); + return c.u32(); + } + + /** A memory index immediate (memory.size, memory.grow and the bulk + * memory instructions), which must be 0. */ + memoryIndex(c: WasmCursor): void { + const index = c.u32(); + if (index !== 0) throw new WasmTranslateError(`memory ${index} is not supported`); + } + popN(n: number): StackValue[] { const out = this.stack.splice(this.stack.length - n, n); if (out.length !== n) throw new Error("wasm translator: operand stack underflow"); @@ -1540,20 +1574,23 @@ class FunctionTranslator { } case 0x20: { const i = c.u32(); - const t = this.localTypes[i]!; + const t = this.localType(i); this.push(this.emit(`load ${llType(t)}, ptr %l${i}`), t); return; } case 0x21: { const i = c.u32(); - const v = this.pop(); - this.add(` store ${llType(v.t)} ${v.v}, ptr %l${i}`); + const t = this.localType(i); + const v = this.popType(t, "local.set"); + this.add(` store ${llType(t)} ${v.v}, ptr %l${i}`); return; } case 0x22: { const i = c.u32(); - const v = this.stack[this.stack.length - 1]!; - this.add(` store ${llType(v.t)} ${v.v}, ptr %l${i}`); + const t = this.localType(i); + const v = this.popType(t, "local.tee"); + this.stack.push(v); + this.add(` store ${llType(t)} ${v.v}, ptr %l${i}`); return; } case 0x23: { @@ -1565,7 +1602,9 @@ class FunctionTranslator { } case 0x24: { const g = c.u32(); - const v = this.pop(); + const global = this.m.globals[g]; + if (global === undefined) throw new WasmTranslateError(`unknown global ${g}`); + const v = this.popType(global.type, "global.set"); const cell = this.loadPtrSlot(this.mt.globalSlot(g)); if (v.t === VAL_EXTERNREF) this.add(` call void @scr_wasm_global_set_ref(ptr ${cell}, ptr ${v.v})`); @@ -1583,13 +1622,15 @@ class FunctionTranslator { } case 0x26: { const t = c.u32(); - const v = this.pop(); - const i = this.pop(); + const table = this.m.tables[t]; + if (table === undefined) throw new WasmTranslateError(`unknown table ${t}`); + const v = this.popType(table.elemType, "table.set"); + const i = this.popType(VAL_I32, "table.set"); this.add(` call void @scr_wasm_table_set(ptr %inst, i32 ${t}, i32 ${i.v}, ptr ${v.v})`); return; } case 0x3f: { - c.u32(); + this.memoryIndex(c); const mem = this.loadPtrSlot(OFF_MEMORY); const sizePtr = this.emit(`getelementptr inbounds i8, ptr ${mem}, i64 8`); const size = this.emit(`load i64, ptr ${sizePtr}, align 8`); @@ -1598,7 +1639,7 @@ class FunctionTranslator { return; } case 0x40: { - c.u32(); + this.memoryIndex(c); const delta = this.pop(); this.push(this.emit(`call i32 @scr_wasm_memory_grow(ptr %inst, i32 ${delta.v})`), VAL_I32); this.refreshMemory(); @@ -1641,10 +1682,9 @@ class FunctionTranslator { break; } if (op >= 0x28 && op <= 0x35) { - c.u32(); - const offset = c.u32(); + const offset = this.memargOffset(c); const [vt, mt, bytes, ext] = LOAD_INFO[op]!; - const addr = this.pop(); + const addr = this.popType(VAL_I32, "load address"); const p = this.address(addr.v, offset, bytes); const line = this.lines.lines.length; const loaded = this.emit(`load ${mt}, ptr ${p}, align 1`); @@ -1656,11 +1696,10 @@ class FunctionTranslator { return; } if (op >= 0x36 && op <= 0x3e) { - c.u32(); - const offset = c.u32(); - const [, mt, bytes] = STORE_INFO[op]!; - const v = this.pop(); - const addr = this.pop(); + const offset = this.memargOffset(c); + const [vt, mt, bytes] = STORE_INFO[op]!; + const v = this.popType(vt, "store"); + const addr = this.popType(VAL_I32, "store address"); const p = this.address(addr.v, offset, bytes); let value = v.v; if (mt === "i8" || mt === "i16" || (mt === "i32" && v.t === VAL_I64)) { @@ -1960,7 +1999,7 @@ class FunctionTranslator { case 8: { // memory.init const seg = c.u32(); - c.u32(); + this.memoryIndex(c); const n = this.pop(); const s = this.pop(); const d = this.pop(); @@ -1973,8 +2012,8 @@ class FunctionTranslator { this.add(` call void @scr_wasm_data_drop(ptr %inst, i32 ${c.u32()})`); return; case 10: { - c.u32(); - c.u32(); + this.memoryIndex(c); + this.memoryIndex(c); const n = this.pop(); const s = this.pop(); const d = this.pop(); @@ -1984,7 +2023,7 @@ class FunctionTranslator { return; } case 11: { - c.u32(); + this.memoryIndex(c); const n = this.pop(); const v = this.pop(); const d = this.pop(); From 6197a45ab0796615bca979264168ce376be4a547 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Tue, 20 Oct 2026 01:48:22 -0500 Subject: [PATCH 051/122] Refuse arguments through class values of ctor-less stream subclasses A class without a constructor that extends a stream class or EventEmitter inherits the runtime constructor, which takes no arguments here. Direct construction with arguments already reported that, but construction through the class's value (`new registry.upper({ ... })`) completed the arguments against the empty inherited signature and dropped them. Both paths now share refuseInheritedBuiltinCtorArgs, so the class-value path reports at the first argument with the same message. --- .../src/frontend/lowering/lower-classes.ts | 48 ++++++++++++------- ...builtin-base-class-value-arguments.ts.json | 6 +++ .../builtin-base-class-value-arguments.ts | 14 ++++++ .../builtin-base-class-value-arguments.ts.txt | 17 +++++++ 4 files changed, 67 insertions(+), 18 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/builtin-base-class-value-arguments.ts.json create mode 100644 tests/diagnostics/builtin-base-class-value-arguments.ts create mode 100644 tests/harness/__snapshots__/builtin-base-class-value-arguments.ts.txt diff --git a/packages/compiler/src/frontend/lowering/lower-classes.ts b/packages/compiler/src/frontend/lowering/lower-classes.ts index 81cf09e1f..7f86de06c 100644 --- a/packages/compiler/src/frontend/lowering/lower-classes.ts +++ b/packages/compiler/src/frontend/lowering/lower-classes.ts @@ -8605,6 +8605,32 @@ function inheritsBuiltinStreamCtor(lowerer: Lowerer, info: ClassInfo): boolean { return false; } +/** `new C(args)` where C's constructor is the inherited runtime stream or + * EventEmitter constructor (a ctor-less chain into the base): that + * constructor takes zero arguments here (the options bag fences, like the + * super() form), so arguments report at the first one. Applies to direct + * construction and to construction through a class value of C. Stream + * subclasses come first: their chain roots at the emitter too, but the + * message should name the stream story. */ +function refuseInheritedBuiltinCtorArgs( + lowerer: Lowerer, + info: ClassInfo, + expr: ts.NewExpression, +): void { + const first = expr.arguments?.nodes[0]; + if (first === undefined) return; + if (inheritsBuiltinStreamCtor(lowerer, info)) { + lowerer.noLowering( + `new ${info.def.name.replace(/^%/, "")} with arguments through an inherited stream constructor`, + first, + "declare a constructor that passes an inline options object to super(...)", + ); + } + if (lowerer.inheritsBuiltinEmitterCtor(info)) { + lowerer.unsupported("SC1090", first, "EventEmitter constructor options ('captureRejections')"); + } +} + /** Preserve argument evaluation order and option presence through builtin * and inherited Error constructors. Checked-dynamic conversion retains * Error identity and distinguishes a missing cause from undefined. */ @@ -8729,24 +8755,7 @@ function lowerProgramClassNew( "construction through a function-local class's internal name", ); lowerer.noteEdge(`%${info.def.name}.constructor`); - // A ctor-less chain into an EventEmitter base inherits `new C()` — - // zero arguments (the options bag fences, like the super() form). - // Stream subclasses come first: their chain roots at the emitter - // too, but the message should name the stream story. - if (inheritsBuiltinStreamCtor(lowerer, info) && (expr.arguments?.nodes ?? []).length > 0) { - lowerer.noLowering( - `new ${info.def.name.replace(/^%/, "")} with arguments through an inherited stream constructor`, - expr.arguments!.nodes[0]!, - "declare a constructor that passes an inline options object to super(...)", - ); - } - if (lowerer.inheritsBuiltinEmitterCtor(info) && (expr.arguments?.nodes ?? []).length > 0) { - lowerer.unsupported( - "SC1090", - expr.arguments!.nodes[0]!, - "EventEmitter constructor options ('captureRejections')", - ); - } + refuseInheritedBuiltinCtorArgs(lowerer, info, expr); // A ctor-less chain into a builtin error base inherits both arguments; // each synthesized constructor forwards the same pair to its base. const args = lowerer.inheritsBuiltinErrorCtor(info) @@ -9994,6 +10003,9 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { `EventEmitter and stream class values constructed with options (construct with 'new ${info.def.name.slice(1)}({ ... })' directly)`, ); } + // A program class whose constructor is the inherited runtime one + // takes no arguments through its class value either. + refuseInheritedBuiltinCtorArgs(lowerer, info, expr); defineClassValueConstruct(lowerer, info); const args = lowerer.completeArgs( expr.arguments?.nodes ?? [], diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/builtin-base-class-value-arguments.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/builtin-base-class-value-arguments.ts.json new file mode 100644 index 000000000..622882c24 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/builtin-base-class-value-arguments.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/diagnostics/builtin-base-class-value-arguments.ts" + ], + "diags": [] +} diff --git a/tests/diagnostics/builtin-base-class-value-arguments.ts b/tests/diagnostics/builtin-base-class-value-arguments.ts new file mode 100644 index 000000000..db5be873b --- /dev/null +++ b/tests/diagnostics/builtin-base-class-value-arguments.ts @@ -0,0 +1,14 @@ +// A class without a constructor that extends a stream class inherits the +// runtime constructor, which takes no arguments here. Passing arguments +// through the class's value reports at the first argument, as direct +// construction does. +import { Transform } from "node:stream"; + +class Upper extends Transform {} +const registry = { upper: Upper }; +const upper = new registry.upper({ objectMode: true }); +upper.destroy(); + +const Kind = Upper; +const other = new Kind({ highWaterMark: 4 }); +other.destroy(); diff --git a/tests/harness/__snapshots__/builtin-base-class-value-arguments.ts.txt b/tests/harness/__snapshots__/builtin-base-class-value-arguments.ts.txt new file mode 100644 index 000000000..5e3af4f45 --- /dev/null +++ b/tests/harness/__snapshots__/builtin-base-class-value-arguments.ts.txt @@ -0,0 +1,17 @@ +builtin-base-class-value-arguments.ts:9:34 - error SC2020: 'new Upper with arguments through an inherited stream constructor' is part of the standard library types but has no scriptc lowering yet + + 8 | const registry = { upper: Upper }; + 9 | const upper = new registry.upper({ objectMode: true }); + | ^~~~~~~~~~~~~~~~~~~~ + 10 | upper.destroy(); + + hint: declare a constructor that passes an inline options object to super(...) + +builtin-base-class-value-arguments.ts:13:24 - error SC2020: 'new Upper with arguments through an inherited stream constructor' is part of the standard library types but has no scriptc lowering yet + + 12 | const Kind = Upper; + 13 | const other = new Kind({ highWaterMark: 4 }); + | ^~~~~~~~~~~~~~~~~~~~ + 14 | other.destroy(); + + hint: declare a constructor that passes an inline options object to super(...) \ No newline at end of file From 44f4e2e74c39f1ad1ad3fed7021a7547ed283183 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Mon, 19 Oct 2026 22:35:23 -0700 Subject: [PATCH 052/122] Refuse inline stream completion callbacks that name the error Node passes write and end callbacks an error argument (null on success), but the native callbacks receive none, so an inline callback that names the parameter read undefined. It now refuses where it is written. The stream-shims npm case compiles now that stream subclasses are values; its remaining scenarios are deferred refusals, so it is recorded as one. Co-Authored-By: Claude Opus 5.5 --- .../src/frontend/lowering/lower-stream.ts | 26 ++++++++++++++++--- .../stream-completion-callback-error.ts.json | 6 +++++ .../6451-builtin-base-class-expressions.cjs | 13 ++++++++++ .../stream-completion-callback-error.ts | 11 ++++++++ .../stream-completion-callback-error.ts.txt | 17 ++++++++++++ tests/harness/inheritance.test.ts | 20 ++++++-------- tests/harness/npm.test.ts | 2 +- 7 files changed, 78 insertions(+), 17 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/stream-completion-callback-error.ts.json create mode 100644 tests/diagnostics/stream-completion-callback-error.ts create mode 100644 tests/harness/__snapshots__/stream-completion-callback-error.ts.txt diff --git a/packages/compiler/src/frontend/lowering/lower-stream.ts b/packages/compiler/src/frontend/lowering/lower-stream.ts index 5769e1e0a..64d2e60e8 100644 --- a/packages/compiler/src/frontend/lowering/lower-stream.ts +++ b/packages/compiler/src/frontend/lowering/lower-stream.ts @@ -3654,8 +3654,10 @@ export function lowerStreamMethodCall( : lowerer.lowerExprExpecting(args[0]!, STRING); const fn = kind === "bytes" ? "writable.write" : "writable.writeStr"; if (cbArg !== undefined) { - const cb = lowerer.lowerExprExpecting(cbArg, funcOf([], VOID)); - if (cb.type.kind !== "func" || cb.type.params.length > 0) { + const cb = callbackDeclaresParameters(cbArg) + ? null + : lowerer.lowerExprExpecting(cbArg, funcOf([], VOID)); + if (cb === null || cb.type.kind !== "func" || cb.type.params.length > 0) { lowerer.noLowering( "write completion callbacks taking the error argument", cbArg, @@ -3712,8 +3714,10 @@ export function lowerStreamMethodCall( ); } if (cbArg !== undefined) { - const cb = lowerer.lowerExprExpecting(cbArg, funcOf([], VOID)); - if (cb.type.kind !== "func" || cb.type.params.length > 0) { + const cb = callbackDeclaresParameters(cbArg) + ? null + : lowerer.lowerExprExpecting(cbArg, funcOf([], VOID)); + if (cb === null || cb.type.kind !== "func" || cb.type.params.length > 0) { lowerer.noLowering( "end callbacks with parameters", cbArg, @@ -3978,3 +3982,17 @@ export function lowerStreamProperty( } return null; } + +/** Whether an inline completion callback declares a parameter. Node calls + * write and end callbacks with an error argument (null on success), but the + * native callback receives none, so the parameter would read undefined + * where Node passes null or the error. A function literal with parameters + * would otherwise adapt to the zero-parameter slot silently. */ +function callbackDeclaresParameters(node: ts.Expression): boolean { + let callback = node; + while (ts.isParenthesizedExpression(callback)) callback = callback.expression; + return ( + (ts.isArrowFunction(callback) || ts.isFunctionExpression(callback)) && + callback.parameters.nodes.length > 0 + ); +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/stream-completion-callback-error.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/stream-completion-callback-error.ts.json new file mode 100644 index 000000000..af31bb8cf --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/stream-completion-callback-error.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/diagnostics/stream-completion-callback-error.ts" + ], + "diags": [] +} diff --git a/tests/corpus/6451-builtin-base-class-expressions.cjs b/tests/corpus/6451-builtin-base-class-expressions.cjs index 64230a679..6d9241b97 100644 --- a/tests/corpus/6451-builtin-base-class-expressions.cjs +++ b/tests/corpus/6451-builtin-base-class-expressions.cjs @@ -31,3 +31,16 @@ const l = new Lower(); l.on("data", (d) => console.log("lower", d.toString())); l.end("HeLLo"); console.log(Wrap.name, Bus.name, typeof Lower); +{ + // A class declared in a block is collected like a class expression. + class Once extends Readable { + _read() { + this.push(null); + } + } + function onReadable() { + console.log("block", stream.read() === null); + } + const stream = new Once(); + stream.once("readable", onReadable); +} diff --git a/tests/diagnostics/stream-completion-callback-error.ts b/tests/diagnostics/stream-completion-callback-error.ts new file mode 100644 index 000000000..eb73b485d --- /dev/null +++ b/tests/diagnostics/stream-completion-callback-error.ts @@ -0,0 +1,11 @@ +import { Writable } from "node:stream"; + +// Node passes write and end callbacks an error argument (null on success); +// the native callbacks receive none, so a callback that names it refuses. +const sink = new Writable({ + write(_chunk, _encoding, done) { + done(); + }, +}); +sink.write("a", (err) => console.log("written", err)); +sink.end((err?: Error | null) => console.log("ended", err)); diff --git a/tests/harness/__snapshots__/stream-completion-callback-error.ts.txt b/tests/harness/__snapshots__/stream-completion-callback-error.ts.txt new file mode 100644 index 000000000..f3b876cc7 --- /dev/null +++ b/tests/harness/__snapshots__/stream-completion-callback-error.ts.txt @@ -0,0 +1,17 @@ +stream-completion-callback-error.ts:10:17 - error SC2020: 'write completion callbacks taking the error argument' is part of the standard library types but has no scriptc lowering yet + + 9 | }); + 10 | sink.write("a", (err) => console.log("written", err)); + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 11 | sink.end((err?: Error | null) => console.log("ended", err)); + + hint: () => void callbacks are supported (listen for 'error' to observe failures) + +stream-completion-callback-error.ts:11:10 - error SC2020: 'end callbacks with parameters' is part of the standard library types but has no scriptc lowering yet + + 10 | sink.write("a", (err) => console.log("written", err)); + 11 | sink.end((err?: Error | null) => console.log("ended", err)); + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 12 | + + hint: () => void callbacks are supported \ No newline at end of file diff --git a/tests/harness/inheritance.test.ts b/tests/harness/inheritance.test.ts index c0ce3d7d4..27a176764 100644 --- a/tests/harness/inheritance.test.ts +++ b/tests/harness/inheritance.test.ts @@ -204,14 +204,11 @@ r.on("data", (b: Buffer) => console.log(b.toString())); expect(llvm).toContain("call void @scr_stream_init_readable(ptr "); }); - test("a runtime-fenced nested stream class emits the refusal without class helpers", async () => { - // The phase-1 reachable bug: a class declared inside a block (a - // runtime fence in JS) whose instances are captured emitted capture- - // box RC adapters for the uncollected class — a C compile error. - // run()'s unregistered-class sweep now rewrites such type slots to - // the inert f64 placeholder before emission (no instance can exist; - // every use traps), so the capture box is a PLAIN-kind box and the - // class never reaches the emitter at all. + test("a block-scoped stream class lowers natively with its class helpers", async () => { + // A class declared inside a block is collected like a class expression. + // Its captured instances once needed the unregistered-class sweep (the + // class was fenced); a builtin stream base no longer fences, so the + // class reaches the emitter with its object type and stream init. const llvm = await emittedLlvm( "stream-nested-fence", `const { Readable } = require("stream"); @@ -226,11 +223,10 @@ r.on("data", (b: Buffer) => console.log(b.toString())); `, "js", ); - expect(llvm).not.toContain("sc_retain_R_v"); expect(llvm).not.toContain("uncollected class"); - expect(llvm).not.toContain("%sc_o_R"); - expect(llvm).toMatch(/%sc_l_stream_0 = alloca ptr/); - expect(llvm).toContain("call void @scr_throw_error_msg_code"); + expect(llvm).toContain("%sc_o_R"); + expect(llvm).toContain("call void @scr_stream_init_readable(ptr "); + expect(llvm).not.toContain("call void @scr_throw_error_msg_code"); }); test("a getter-only override of a pair fills the set slot with a thrower", async () => { diff --git a/tests/harness/npm.test.ts b/tests/harness/npm.test.ts index 54207f246..ac3210dee 100644 --- a/tests/harness/npm.test.ts +++ b/tests/harness/npm.test.ts @@ -104,7 +104,7 @@ const KNOWN: Record = { "package-imports": { build: "SC2013" }, "promise-bridge": { build: "SC1090" }, "self-name": { build: "SC2013" }, - "stream-shims": { build: "SC1090" }, + "stream-shims": { run: "SC1090" }, "util-shims": { run: "SC2020" }, "web-streams": { build: "SC2013" }, "workspace-copied": { build: "SC2013" }, From 45431aec320c53e12e9b664e80d8d48ffbb4f547 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Tue, 20 Oct 2026 02:01:48 -0500 Subject: [PATCH 053/122] Choose comptime union arms by discriminant and refuse recursive records A comptime record value whose union has a discriminant takes the arm its discriminant field names, so a value that fits two arms with overlapping shapes gets that arm's tag and layout instead of the first arm it fits. comptimeBakeable reports a record type reachable from its own fields as not bakeable instead of recursing without end, so such a comptime result reports SC1090, and the message names unions and that limit. --- .../src/frontend/lowering/lower-comptime.ts | 54 ++++++++++++++----- tests/corpus/1052-comptime-records.ts | 17 ++++++ tests/diagnostics/comptime.ts | 7 +++ tests/harness/__snapshots__/comptime.ts.txt | 9 +++- 4 files changed, 74 insertions(+), 13 deletions(-) diff --git a/packages/compiler/src/frontend/lowering/lower-comptime.ts b/packages/compiler/src/frontend/lowering/lower-comptime.ts index 31fa5afe0..bdd020127 100644 --- a/packages/compiler/src/frontend/lowering/lower-comptime.ts +++ b/packages/compiler/src/frontend/lowering/lower-comptime.ts @@ -76,7 +76,7 @@ export function lowerComptime(lowerer: Lowerer, expr: ts.CallExpression): IrExpr "SC1090", expr, `baking comptime results of type '${lowerer.fmt(target)}' into the binary ` + - `(only number, string, boolean, arrays, and records lower to literals)`, + `(number, string, boolean, and arrays, records and unions of those lower to literals; a record type reachable from its own fields does not)`, ); } // 5. The host executes the extracted callback under a finite budget. @@ -112,25 +112,36 @@ function comptimeFailureDetail(error: unknown): string { * expression: number/string/boolean, arrays and record shapes of those, * and unions of those with null or undefined (the mixed rows of a JSON * table: `(string | number)[][]`), nested. Closures, class instances, - * promises, dyn, and void cannot be baked. */ + * promises, dyn, void, and recursive record types (a record reachable + * from its own fields) cannot be baked. */ export function comptimeBakeable(lowerer: Lowerer, t: IrType): boolean { + return bakeableWithin(lowerer, t, new Set()); +} + +/** comptimeBakeable below the records in `open`, whose fields are being + * checked: reaching one of them again means the type is recursive. */ +function bakeableWithin(lowerer: Lowerer, t: IrType, open: Set): boolean { switch (t.kind) { case "f64": case "string": case "bool": return true; case "array": - return lowerer.comptimeBakeable(t.elem); + return bakeableWithin(lowerer, t.elem, open); case "record": { const shape = lowerer.shapes.get(t.shapeId); - return !!shape && shape.fields.every((f) => lowerer.comptimeBakeable(f.type)); + if (!shape || open.has(t.shapeId)) return false; + open.add(t.shapeId); + const bakeable = shape.fields.every((f) => bakeableWithin(lowerer, f.type, open)); + open.delete(t.shapeId); + return bakeable; } case "union": { const arms = lowerer.unions.get(t.unionId)?.arms; return ( arms !== undefined && arms.every( - (arm) => isUnitType(arm) || (arm.kind !== "union" && comptimeBakeable(lowerer, arm)), + (arm) => isUnitType(arm) || (arm.kind !== "union" && bakeableWithin(lowerer, arm, open)), ) ); } @@ -139,6 +150,30 @@ export function comptimeBakeable(lowerer: Lowerer, t: IrType): boolean { } } +/** The tag of the arm of union `unionId` that `value` bakes into, or -1. + * A record value whose discriminant field holds one of the union's + * discriminant values takes that case's arm, since arms with overlapping + * shapes can both fit it. Any other value takes the first arm it fits, in + * the union's arm order: a JSON value fits exactly one scalar arm, and + * array or record arms are told apart by their contents. */ +function comptimeUnionArm(lowerer: Lowerer, unionId: string, value: unknown): number { + const def = lowerer.unions.get(unionId); + if (!def) return -1; + const discriminant = def.discriminant; + if ( + discriminant !== undefined && + typeof value === "object" && + value !== null && + !Array.isArray(value) + ) { + const key = (value as Record)[discriminant.field]; + const named = discriminant.cases.find((c) => c.values.some((v) => v === key)); + if (named !== undefined) + return comptimeFits(lowerer, value, def.arms[named.tag]!) ? named.tag : -1; + } + return def.arms.findIndex((arm) => comptimeFits(lowerer, value, arm)); +} + /** Whether `value` bakes into the bakeable type `t` without a mismatch: * the arm choice of a union literal. Records are exact, as in * comptimeValueToIr; an absent field reads as undefined. */ @@ -168,9 +203,7 @@ function comptimeFits(lowerer: Lowerer, value: unknown, t: IrType): boolean { ); } case "union": - return (lowerer.unions.get(t.unionId)?.arms ?? []).some((arm) => - comptimeFits(lowerer, value, arm), - ); + return comptimeUnionArm(lowerer, t.unionId, value) >= 0; default: return false; } @@ -313,11 +346,8 @@ export function comptimeValueToIr( return { kind: "recordLit", fields, type: expected, loc }; } case "union": { - // The first arm the value fits, in the union's arm order: a JSON - // value fits exactly one scalar arm, and array or record arms are - // told apart by their contents. const arms = lowerer.unions.get(expected.unionId)!.arms; // bakeable-checked - const tag = arms.findIndex((arm) => comptimeFits(lowerer, value, arm)); + const tag = comptimeUnionArm(lowerer, expected.unionId, value); if (tag < 0) fail(describeComptimeValue(value)); const arm = arms[tag]!; const payload: IrExpr = isUnitType(arm) diff --git a/tests/corpus/1052-comptime-records.ts b/tests/corpus/1052-comptime-records.ts index 47cccf7b3..658aaed32 100644 --- a/tests/corpus/1052-comptime-records.ts +++ b/tests/corpus/1052-comptime-records.ts @@ -43,3 +43,20 @@ const meta = comptime(() => { return { kind: "meta", tags, count: tags.length }; }); console.log(meta.kind, meta.tags.join("|"), meta.count); + +// A discriminated union whose arms both fit a "labeled" value without a +// label: the discriminant picks the arm. +type Item = { kind: "plain"; v: number } | { kind: "labeled"; v: number; label?: string }; +const items: Item[] = comptime( + () => + [ + { kind: "plain", v: 1 }, + { kind: "labeled", v: 2 }, + { kind: "labeled", v: 3, label: "x" }, + ] as Item[], +); +for (const item of items) { + if (item.kind === "labeled") console.log("labeled", item.v, item.label); + else console.log("plain", item.v); +} +console.log(JSON.stringify(items)); diff --git a/tests/diagnostics/comptime.ts b/tests/diagnostics/comptime.ts index 9ba882aaf..9c8239464 100644 --- a/tests/diagnostics/comptime.ts +++ b/tests/diagnostics/comptime.ts @@ -20,3 +20,10 @@ const nested = comptime(() => comptime(() => 1)); const eventually = comptime(async () => 1); const lied = comptime(() => 1 as unknown as string); + +// A record type reachable from its own fields has no literal form. +interface Chain { + value: number; + next: Chain | null; +} +const chain: Chain = comptime(() => ({ value: 1, next: null }) as Chain); diff --git a/tests/harness/__snapshots__/comptime.ts.txt b/tests/harness/__snapshots__/comptime.ts.txt index 2b1411ae5..603c297a8 100644 --- a/tests/harness/__snapshots__/comptime.ts.txt +++ b/tests/harness/__snapshots__/comptime.ts.txt @@ -45,4 +45,11 @@ comptime.ts:22:23 - error SC1110: comptime evaluation failed: expected 'string' 21 | 22 | const lied = comptime(() => 1 as unknown as string); | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~ - 23 | \ No newline at end of file + 23 | + +comptime.ts:29:22 - error SC1090: baking comptime results of type '{ next: null | ...; value: number }' into the binary (number, string, boolean, and arrays, records and unions of those lower to literals; a record type reachable from its own fields does not) is not supported yet + + 28 | } + 29 | const chain: Chain = comptime(() => ({ value: 1, next: null }) as Chain); + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 30 | \ No newline at end of file From 9febfe8ca3939e79923a8e3a35d47cbcb89e46ce Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Tue, 20 Oct 2026 02:07:18 -0500 Subject: [PATCH 054/122] Place a quoted name field among an error's fields The position of an Error subclass's own `name` among its fields was found from identifier keys only, so a quoted `"name"` field, other quoted fields, and `this["name"] = ...` in a constructor were not seen, and `name` was listed last. Field keys and constructor assignments now read any statically spelled key: an identifier, a quoted string, a number in its canonical spelling, or a computed string literal. --- .../src/frontend/lowering/error-name-order.ts | 32 +++++++++++++------ tests/corpus/6403-error-own-name-order.ts | 6 ++++ tests/corpus/6404-error-own-name-order-js.js | 10 ++++++ 3 files changed, 38 insertions(+), 10 deletions(-) diff --git a/packages/compiler/src/frontend/lowering/error-name-order.ts b/packages/compiler/src/frontend/lowering/error-name-order.ts index 8523b47cb..7453d123e 100644 --- a/packages/compiler/src/frontend/lowering/error-name-order.ts +++ b/packages/compiler/src/frontend/lowering/error-name-order.ts @@ -6,17 +6,27 @@ type ErrorClassLink = Pick & { base: ErrorClassLink | null; }; -/** Whether `node` is an assignment to `this.`, and the name. */ +/** The property key a member or element-access name spells statically: + * an identifier, a quoted string, a number in its canonical spelling, or a + * computed string literal. Null for other keys. */ +function staticKeyText(name: ts.Node): string | null { + if (ts.isIdentifier(name) || ts.isStringLiteral(name)) return name.text; + if (ts.isNumericLiteral(name)) return String(Number(name.text)); + if (ts.isComputedPropertyName(name)) return staticKeyText(name.expression); + return null; +} + +/** Whether `node` is an assignment to `this.` or `this[""]`, + * and the name. */ function thisAssignmentName(node: ts.Node): string | null { if (!ts.isBinaryExpression(node) || node.operatorToken.kind !== ts.SyntaxKind.EqualsToken) return null; const target = node.left; - if ( - !ts.isPropertyAccessExpression(target) || - target.expression.kind !== ts.SyntaxKind.ThisKeyword - ) - return null; - return ts.isIdentifier(target.name) ? target.name.text : null; + if (ts.isPropertyAccessExpression(target) && target.expression.kind === ts.SyntaxKind.ThisKeyword) + return ts.isIdentifier(target.name) ? target.name.text : null; + if (ts.isElementAccessExpression(target) && target.expression.kind === ts.SyntaxKind.ThisKeyword) + return staticKeyText(target.argumentExpression); + return null; } /** Where an Error subclass instance's own `name` sits among its fields when @@ -45,15 +55,17 @@ export function errorNameAfterField( for (const link of chain) { for (const property of link.paramProps ?? []) create(property.name); for (const member of link.decl!.members.nodes) { - if (!ts.isPropertyDeclaration(member) || !ts.isIdentifier(member.name)) continue; + if (!ts.isPropertyDeclaration(member)) continue; + const key = staticKeyText(member.name); + if (key === null) continue; if ( member.modifiers?.nodes.some( (m) => m.kind === ts.SyntaxKind.StaticKeyword || m.kind === ts.SyntaxKind.DeclareKeyword, ) ) continue; - if (member.name.text === "name") return last; - create(member.name.text); + if (key === "name") return last; + create(key); } const body = link.ctor?.body; if (!body) continue; diff --git a/tests/corpus/6403-error-own-name-order.ts b/tests/corpus/6403-error-own-name-order.ts index 25e59bb7c..d1a5200c4 100644 --- a/tests/corpus/6403-error-own-name-order.ts +++ b/tests/corpus/6403-error-own-name-order.ts @@ -57,6 +57,11 @@ class NamedChild extends FieldBase { } } +class QuotedName extends Error { + override "name" = "QuotedName"; + "quoted-extra" = 1; +} + class Unnamed extends Error { extra = 1; } @@ -83,6 +88,7 @@ show("later field", new LaterNameField("c")); show("code then name", new CodeThenName("d")); show("child of named", new ChildOfNamed("e")); show("named child", new NamedChild("f")); +show("quoted name field", new QuotedName("q")); const late = new Unnamed("g"); late.name = "Late"; diff --git a/tests/corpus/6404-error-own-name-order-js.js b/tests/corpus/6404-error-own-name-order-js.js index 0b5ad4e82..b5f9b4d32 100644 --- a/tests/corpus/6404-error-own-name-order-js.js +++ b/tests/corpus/6404-error-own-name-order-js.js @@ -49,6 +49,15 @@ class Derived extends Base { } } +class ElementName extends Error { + constructor(message) { + super(message); + this.first = 1; + this["name"] = "ElementName"; + this.second = 2; + } +} + class Unnamed extends Error { constructor(message) { super(message); @@ -73,6 +82,7 @@ show("first", new AssignedFirst("b")); show("declared field", new DeclaredField("c")); show("name field", new NameField("d")); show("derived", new Derived("e")); +show("element name", new ElementName("g")); const late = new Unnamed("f"); late.name = "Late"; From 069545a126a0d48e1a7c0bd39130906fd4ab4f86 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Tue, 20 Oct 2026 02:07:46 -0500 Subject: [PATCH 055/122] Address review: monitorEventLoopDelay spellings and validation, Error 'in' keys, event-loop metrics in libraries - monitorEventLoopDelay is lowered from the builtin call dispatch, by module and member, so aliased named imports, namespace members and destructured requires all reach it. Shorthand and possibly undefined resolutions take Node's default. - The runtime validates the resolution as Node does (an integer of at least 1) and throws a catchable RangeError with ERR_OUT_OF_RANGE. - `'stack' in err` and Object.prototype's keys are true for every Error. The declared-member fold applies only to keys some program class declares and that built-in errors don't carry in runtime slots; other keys stay fenced. - Library mode's async_free gate refuses the event-loop metrics, which library links leave out. They join the perf_hooks fence family. --- .../src/frontend/lowering/builtin-calls.ts | 4 ++ .../frontend/lowering/builtins/performance.ts | 32 ++++++------- .../src/frontend/lowering/lower-calls.ts | 11 ----- .../src/frontend/lowering/lower-exprs.ts | 46 ++++++++++++++++--- packages/compiler/src/ir/builtin-effects.ts | 2 + .../compiler/src/ir/builtin-signatures.ts | 2 +- packages/compiler/src/ir/runtime-features.ts | 4 +- packages/compiler/src/library/ir-policy.ts | 6 +++ packages/runtime/src/scr_perf_loop.c | 24 +++++++++- tests/corpus/6394-in-key-error-subclasses.ts | 6 +++ tests/corpus/6397-event-loop-metrics.ts | 21 +++++++++ tests/harness/library-asyncfree.test.ts | 15 ++++++ tests/harness/library-profile.test.ts | 5 +- 13 files changed, 138 insertions(+), 40 deletions(-) diff --git a/packages/compiler/src/frontend/lowering/builtin-calls.ts b/packages/compiler/src/frontend/lowering/builtin-calls.ts index 7e1852600..7d3a5d41a 100644 --- a/packages/compiler/src/frontend/lowering/builtin-calls.ts +++ b/packages/compiler/src/frontend/lowering/builtin-calls.ts @@ -1,4 +1,5 @@ import { lowerReadlineCursorCall } from "./builtins/readline.js"; +import { lowerMonitorEventLoopDelayCall } from "./builtins/performance.js"; import { lowerEnvironmentData, lowerReceiveMessageOnPort, @@ -69,6 +70,9 @@ export function lowerBuiltinCall( return lowerAddAbortListener(lowerer, expr, loc); } if (bi.module === "events" && bi.member === "once") return lowerEventsOnce(lowerer, expr, loc); + if (bi.module === "perf_hooks" && bi.member === "monitorEventLoopDelay") { + return lowerMonitorEventLoopDelayCall(lowerer, expr); + } if (bi.module === "events") { const helper = lowerEventsEmitterHelper(lowerer, expr, bi.member, loc); if (helper) return helper; diff --git a/packages/compiler/src/frontend/lowering/builtins/performance.ts b/packages/compiler/src/frontend/lowering/builtins/performance.ts index 19b585e37..7aac3b80c 100644 --- a/packages/compiler/src/frontend/lowering/builtins/performance.ts +++ b/packages/compiler/src/frontend/lowering/builtins/performance.ts @@ -18,6 +18,7 @@ import { } from "../../../ir/ir.js"; import { PERF_ENTRY_FIELDS, PERF_ENTRY_MARKER, perfEntryJsonShape } from "../../type-mapper.js"; import { builtinImportOf } from "./module-bindings.js"; +import { lowerOptionalArgument } from "../optional-arguments.js"; import { checkedIterableSpread } from "../checked-iterable-spread.js"; /** True for any spelling of node:perf_hooks' `performance` object — the @@ -663,21 +664,11 @@ export function lowerHistogramMethodCall( ); } -/** `monitorEventLoopDelay(options?)` from node:perf_hooks (a named import - * or a namespace member): a new sampling histogram handle. The resolution - * must be a literal-free number expression or an object literal option. */ -export function lowerMonitorEventLoopDelayCall( - lowerer: Lowerer, - expr: ts.CallExpression, -): IrExpr | null { - const callee = expr.expression; - const bi = ts.isIdentifier(callee) - ? builtinImportOf(lowerer, callee) - : ts.isPropertyAccessExpression(callee) && !callee.questionDotToken - ? lowerer.builtinMemberOf(callee) - : null; - if (bi === null || bi.module !== "perf_hooks" || bi.member !== "monitorEventLoopDelay") - return null; +/** `monitorEventLoopDelay(options?)` from node:perf_hooks, in any import + * spelling (`lowerBuiltinCall`): a new sampling histogram handle. The + * options are an inline object literal; the runtime validates the + * resolution as Node does (an integer of at least 1). */ +export function lowerMonitorEventLoopDelayCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { const loc = locOf(expr); const args = expr.arguments.nodes; let resolution: IrExpr = numLit(10, loc); @@ -698,8 +689,9 @@ export function lowerMonitorEventLoopDelayCall( ); } for (const property of options.properties.nodes) { + const shorthand = ts.isShorthandPropertyAssignment(property); if ( - !ts.isPropertyAssignment(property) || + (!shorthand && !ts.isPropertyAssignment(property)) || !ts.isIdentifier(property.name) || property.name.text !== "resolution" ) { @@ -709,7 +701,13 @@ export function lowerMonitorEventLoopDelayCall( "resolution is the one option", ); } - resolution = lowerer.lowerExprExpecting(property.initializer, F64); + // An undefined resolution takes the default, as Node's destructuring. + resolution = lowerOptionalArgument( + lowerer, + shorthand ? property.name : property.initializer, + F64, + numLit(10, loc), + ); } } return { kind: "libCall", fn: "perf.eldNew", args: [resolution], type: F64, loc }; diff --git a/packages/compiler/src/frontend/lowering/lower-calls.ts b/packages/compiler/src/frontend/lowering/lower-calls.ts index 23629a7ba..e60ee2367 100644 --- a/packages/compiler/src/frontend/lowering/lower-calls.ts +++ b/packages/compiler/src/frontend/lowering/lower-calls.ts @@ -144,7 +144,6 @@ import { fencePerfEntryKeyWalk, isPerfEntryType, lowerHistogramMethodCall, - lowerMonitorEventLoopDelayCall, lowerPerfHooksCall, perfEntryTag, } from "./builtins/performance.js"; @@ -243,16 +242,6 @@ const ZLIB_STREAM_METHODS: ReadonlySet = new Set(["flush", "params", "cl export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { const loc = locOf(expr); - // node:perf_hooks monitorEventLoopDelay (named import or namespace member). - if ( - (ts.isIdentifier(expr.expression) && expr.expression.text === "monitorEventLoopDelay") || - (ts.isPropertyAccessExpression(expr.expression) && - expr.expression.name.text === "monitorEventLoopDelay") - ) { - const histogram = lowerMonitorEventLoopDelayCall(lowerer, expr); - if (histogram) return histogram; - } - // Object's checker return type is `any`. Claim known object inputs // before generic checked-dynamic dispatch tries to load the constructor. if ( diff --git a/packages/compiler/src/frontend/lowering/lower-exprs.ts b/packages/compiler/src/frontend/lowering/lower-exprs.ts index aa597d8a2..684ffde58 100644 --- a/packages/compiler/src/frontend/lowering/lower-exprs.ts +++ b/packages/compiler/src/frontend/lowering/lower-exprs.ts @@ -13801,6 +13801,24 @@ const OBJECT_PROTOTYPE_KEYS: ReadonlySet = new Set([ * idiom over multiple shapes), index-signature keys, class instances, * and dyn/unknown stay fenced. Keys are literal strings — a computed key * over a shape would need the runtime key table. */ +/** Keys built-in errors carry outside their class fields: Node's system + * error properties, and AggregateError's, SuppressedError's and + * AssertionError's own members. */ +const BUILTIN_ERROR_SLOT_KEYS: ReadonlySet = new Set([ + "errno", + "syscall", + "path", + "dest", + "info", + "errors", + "error", + "suppressed", + "actual", + "expected", + "operator", + "generatedMessage", +]); + function lowerInExpression(lowerer: Lowerer, expr: ts.BinaryExpression, loc: SrcLoc): IrExpr { if ( lowerer.isStdlibGlobal(expr.right, "process") && @@ -14036,15 +14054,29 @@ function lowerInExpression(lowerer: Lowerer, expr: ts.BinaryExpression, loc: Src ) { return { kind: "boolLit", value: true, type: BOOL, loc }; } + // Every Error has its own `stack`, and Error.prototype's `toString` + // and `constructor` are among Object.prototype's keys. + if ( + (key === "stack" || OBJECT_PROTOTYPE_KEYS.has(key)) && + (recv.kind === "varRef" || recv.kind === "caughtNarrow") + ) { + return { kind: "boolLit", value: true, type: BOOL, loc }; + } // Any other key: a member a program class declares. The receiver's // runtime class is its static class or a descendant; the key is - // present exactly when that class (or an ancestor) declares it. - if (recv.kind === "varRef" || recv.kind === "caughtNarrow") { - const declares = (c: ClassInfo): boolean => - c.fields.has(key) || - lowerer.findMethodOn(c, key) !== null || - lowerer.findMethodOn(c, `get:${key}`) !== null || - lowerer.findMethodOn(c, `set:${key}`) !== null; + // present exactly when that class (or an ancestor) declares it. Keys + // built-in errors keep outside their class fields depend on the + // runtime error and stay fenced, as do keys no program class declares. + const declares = (c: ClassInfo): boolean => + c.fields.has(key) || + lowerer.findMethodOn(c, key) !== null || + lowerer.findMethodOn(c, `get:${key}`) !== null || + lowerer.findMethodOn(c, `set:${key}`) !== null; + if ( + (recv.kind === "varRef" || recv.kind === "caughtNarrow") && + !BUILTIN_ERROR_SLOT_KEYS.has(key) && + [...lowerer.classes.values()].some((c) => c.builtinError !== true && declares(c)) + ) { const staticName = recv.type.className; const staticInfo = lowerer.classes.get(staticName); if (staticInfo && declares(staticInfo)) diff --git a/packages/compiler/src/ir/builtin-effects.ts b/packages/compiler/src/ir/builtin-effects.ts index f20d0945e..9a7ca178b 100644 --- a/packages/compiler/src/ir/builtin-effects.ts +++ b/packages/compiler/src/ir/builtin-effects.ts @@ -363,6 +363,8 @@ export const MAY_THROW_LIB_FNS: ReadonlySet = new Set([ "perf.entries", "perf.clearMarks", "perf.clearMeasures", + // monitorEventLoopDelay's resolution: Node's ERR_OUT_OF_RANGE. + "perf.eldNew", // Non-TTY stdin: Node's stdin has no setRawMode member, so the call is // the exact catchable TypeError. "process.stdinSetRawMode", diff --git a/packages/compiler/src/ir/builtin-signatures.ts b/packages/compiler/src/ir/builtin-signatures.ts index 02943ae1b..de50aab1c 100644 --- a/packages/compiler/src/ir/builtin-signatures.ts +++ b/packages/compiler/src/ir/builtin-signatures.ts @@ -364,7 +364,7 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ /** perf_hooks event-loop metrics (scr_perf_loop.c): loopStart is the * loop's start in performance.now() ms (0 before it), idleTime the idle * ms since. eldNew(resolution ms) creates a monitorEventLoopDelay - * histogram handle; enable/disable answer whether the state changed; + * histogram handle (MAY THROW Node's resolution RangeError); enable/disable answer whether the state changed; * eldPercentile expects Node's validated range; eldStat(which) reads * 0 min, 1 max, 2 mean, 3 stddev, 4 count, 5 exceeds. */ "perf.loopStart": { argTypes: [], result: F64 }, diff --git a/packages/compiler/src/ir/runtime-features.ts b/packages/compiler/src/ir/runtime-features.ts index b2628fa10..1946faeb3 100644 --- a/packages/compiler/src/ir/runtime-features.ts +++ b/packages/compiler/src/ir/runtime-features.ts @@ -506,11 +506,11 @@ function scanRuntimeFeatures(mod: IrModule, stopAt?: keyof RuntimeFeatures): Run } if (fn.startsWith("dgram.") || fn.startsWith("dns.")) features.dgram = true; if (fn.startsWith("tlsca.")) features.tlsCa = true; + if (fn.startsWith("perf.loop") || fn === "perf.idleTime" || fn.startsWith("perf.eld")) + features.perfLoop = true; // scr_cipher.c over the mbedTLS archive; DEP0182 reports through the // process-warning unit, and unknown output encodings render through // the inspect unit. - if (fn.startsWith("perf.loop") || fn === "perf.idleTime" || fn.startsWith("perf.eld")) - features.perfLoop = true; if (fn === "crypto.cipherNew") { features.cipher = true; features.dynAsync = true; diff --git a/packages/compiler/src/library/ir-policy.ts b/packages/compiler/src/library/ir-policy.ts index 3c3855e5c..a9c9609f5 100644 --- a/packages/compiler/src/library/ir-policy.ts +++ b/packages/compiler/src/library/ir-policy.ts @@ -53,6 +53,11 @@ const LIB_MODE_REFUSED_PREFIXES: readonly [string, string][] = [ ["crypto.cipherNew", "crypto.createCipheriv and createDecipheriv"], ["fs.watch", "fs.watch"], ["watcher.", "fs.watch"], + // The event-loop metrics sample the loop and its timers (scr_perf_loop.c), + // which library links exclude. + ["perf.loopStart", "performance.eventLoopUtilization"], + ["perf.idleTime", "performance.eventLoopUtilization"], + ["perf.eld", "perf_hooks.monitorEventLoopDelay"], ["test.", "the node:test surface"], ["readable.", "the node:stream surface"], ["writable.", "the node:stream surface"], @@ -157,6 +162,7 @@ export function moduleLibAsyncSurface(mod: IrModule): { surface: string; loc: Sr [features.nodeTest, "the node:test surface"], [features.dynAsync, "the checked-dynamic async surface"], [features.dc, "the diagnostics_channel surface"], + [features.perfLoop, "the perf_hooks event-loop metrics"], ]; for (const [on, surface] of coarse) { if (on) return { surface, loc: entryLoc }; diff --git a/packages/runtime/src/scr_perf_loop.c b/packages/runtime/src/scr_perf_loop.c index 29d98ea29..68d3bf10f 100644 --- a/packages/runtime/src/scr_perf_loop.c +++ b/packages/runtime/src/scr_perf_loop.c @@ -21,7 +21,9 @@ #include #include +#include #include +#include #include #ifdef _WIN32 #include @@ -212,7 +214,27 @@ static void scr_eld_cleanup(void) { scr_elds_len = 0; } +/* Node's validateInteger(resolution, 'options.resolution', 1): a catchable + * RangeError (ERR_OUT_OF_RANGE) for a non-integer or a value out of range, + * with the value as util.inspect renders it ("-0" included). */ +static bool scr_eld_resolution_ok(double resolution) { + bool integer = isfinite(resolution) && trunc(resolution) == resolution; + if (integer && resolution >= 1 && resolution <= 9007199254740991.0) return true; + char received[48]; + if (resolution == 0 && signbit(resolution)) + memcpy(received, "-0", 3); + else + scr_num_received(resolution, received); + char msg[192]; + int len = snprintf(msg, sizeof msg, + "The value of \"options.resolution\" is out of range. It must be %s. Received %s", + integer ? ">= 1 && <= 9007199254740991" : "an integer", received); + scr_throw_error_msg_code(SCR_ERR_RANGE, msg, (size_t)len, "ERR_OUT_OF_RANGE"); + return false; +} + double scr_eld_new(double resolution) { + if (!scr_eld_resolution_ok(resolution)) return 0; if (scr_elds_len == 0 && scr_elds == NULL) scr_atexit(scr_eld_cleanup); ScrEld *h = calloc(1, sizeof(ScrEld)); ScrEld **grown = realloc(scr_elds, (scr_elds_len + 1) * sizeof(ScrEld *)); @@ -223,7 +245,7 @@ double scr_eld_new(double resolution) { } scr_elds = grown; h->min_value = INT64_MAX; - h->resolution = resolution >= 1 ? resolution : 10; + h->resolution = resolution; h->tick = scr_closure_new((void *)scr_eld_tick, 0); scr_elds[scr_elds_len++] = h; return (double)scr_elds_len; diff --git a/tests/corpus/6394-in-key-error-subclasses.ts b/tests/corpus/6394-in-key-error-subclasses.ts index cdb28a5a3..5ade6bcd1 100644 --- a/tests/corpus/6394-in-key-error-subclasses.ts +++ b/tests/corpus/6394-in-key-error-subclasses.ts @@ -23,3 +23,9 @@ function retryable(err: Error): boolean { } console.log(retryable(new Error("x")), retryable(new HttpError("a", 500)), retryable(new Timeout("t", 504))); console.log(describe(new Timeout("t", 504))); +// Keys every Error has: its own stack, and Error.prototype's and +// Object.prototype's members. +function inherent(err: Error): boolean[] { + return ["stack" in err, "toString" in err, "constructor" in err, "hasOwnProperty" in err]; +} +console.log(inherent(new Error("s")), inherent(new HttpError("h", 400)), inherent(new TypeError("t"))); diff --git a/tests/corpus/6397-event-loop-metrics.ts b/tests/corpus/6397-event-loop-metrics.ts index e1c3829ac..8477dcc48 100644 --- a/tests/corpus/6397-event-loop-metrics.ts +++ b/tests/corpus/6397-event-loop-metrics.ts @@ -1,6 +1,8 @@ // Event-loop monitoring under @types/node: monitorEventLoopDelay's histogram, // eventLoopUtilization deltas, and an unref'd interval typed NodeJS.Timeout. import { monitorEventLoopDelay, performance } from "node:perf_hooks"; +import * as perfHooks from "node:perf_hooks"; +import { monitorEventLoopDelay as eld } from "node:perf_hooks"; const histogram = monitorEventLoopDelay({ resolution: 10 }); console.log("enable", histogram.enable(), histogram.enable()); @@ -47,3 +49,22 @@ const interval = setInterval(() => { }, 50); interval.unref(); console.log("hasRef", interval.hasRef()); + +// Other import spellings, and Node's resolution validation. +const aliased = eld(); +const namespaced = perfHooks.monitorEventLoopDelay({ resolution: 20 }); +console.log("spellings", aliased.enable(), namespaced.enable(), aliased.disable(), namespaced.disable()); +for (const resolution of [0, -0, -1, 1.5, NaN, Infinity, 1e16, 1]) { + try { + eld({ resolution }); + console.log("created", resolution); + } catch (e) { + console.log((e as Error).name, (e as { code?: string }).code, (e as Error).message); + } +} +// An undefined resolution takes Node's default. +function withResolution(resolution?: number): boolean { + const h = monitorEventLoopDelay({ resolution }); + return h.enable() && h.disable(); +} +console.log("defaults", withResolution(), withResolution(20), eld({ resolution: undefined }).enable()); diff --git a/tests/harness/library-asyncfree.test.ts b/tests/harness/library-asyncfree.test.ts index 0adbef7bc..7c4d029cc 100644 --- a/tests/harness/library-asyncfree.test.ts +++ b/tests/harness/library-asyncfree.test.ts @@ -73,6 +73,21 @@ describe("async_free detection over the IR", () => { ); }); + test("the event-loop metrics refuse", async () => { + expect( + await surfaceOf( + "eld", + `import { monitorEventLoopDelay } from "node:perf_hooks";\nconst h = monitorEventLoopDelay();\nconsole.log(h.enable());\n`, + ), + ).toContain("monitorEventLoopDelay"); + expect( + await surfaceOf( + "elu", + `import { performance } from "node:perf_hooks";\nconsole.log(performance.eventLoopUtilization().idle >= 0);\n`, + ), + ).toContain("eventLoopUtilization"); + }); + test("child_process refuses even in its synchronous spelling", async () => { expect( await surfaceOf( diff --git a/tests/harness/library-profile.test.ts b/tests/harness/library-profile.test.ts index c7cb13751..c83b31ada 100644 --- a/tests/harness/library-profile.test.ts +++ b/tests/harness/library-profile.test.ts @@ -569,10 +569,13 @@ describe("library profile fences", () => { expect(processIds).toContain("node-builtin.process.isTTY"); expect(r.profile.fences[1]!.surfaces.every((s) => s.detector !== undefined)).toBe(true); // The user-timing timeline reads the same clock (mark() and measure() - // default to performance.now()), so it joins the perf_hooks family. + // default to performance.now()), and so do the event-loop metrics, so + // they join the perf_hooks family. expect(r.profile.fences[2]!.surfaces.map((s) => s.id)).toEqual([ + "node-builtin.perf_hooks.monitorEventLoopDelay", "node-builtin.perf_hooks.performance.clearMarks", "node-builtin.perf_hooks.performance.clearMeasures", + "node-builtin.perf_hooks.performance.eventLoopUtilization", "node-builtin.perf_hooks.performance.getEntries", "node-builtin.perf_hooks.performance.mark", "node-builtin.perf_hooks.performance.measure", From 2553818b0c037ee59d48972694e5d7a69385e898 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Mon, 19 Oct 2026 23:44:30 -0700 Subject: [PATCH 056/122] Compile the round-2 index-signature and union-of-records shapes - An object literal that spreads an index-signature record (the metric-tags idiom '{ ...tags, bucket, status }') maps to the pure index-signature record joining the spread slots and member types: the checker drops the signature, but the object holds every spread key. Declared members read and write as keys of it. - A conditional spread into an index-signature record adds no key when it does not apply. - '{ ...unknownValue }' into an index-signature record copies the value's own keys through the checked extraction; null, undefined, numbers and booleans spread nothing. - 'return Promise.reject(e)' in an async function is typed by the function's own result. - A union of record layouts flowing into one record slot converts each arm; fields the checker types 'undefined' alone lift into undefined-armed slots. - '(x as { m: T }).m' on a union of records reads the arms that declare m and answers undefined for the others. Co-Authored-By: Claude Opus 5.5 --- .../lowering/coercions/structural-plans.ts | 14 +- .../frontend/lowering/coercions/structural.ts | 13 ++ .../lowering/expressions/object-literals.ts | 71 ++++++- .../lowering/expressions/property-access.ts | 125 +++++++++++++ .../src/frontend/lowering/lower-exprs.ts | 22 ++- .../src/frontend/lowering/lower-stmts.ts | 26 +++ .../compiler/src/frontend/lowering/lowerer.ts | 83 +++++++++ .../src/frontend/lowering/width-lift.ts | 1 + packages/compiler/src/frontend/type-mapper.ts | 174 ++++++++++++++++++ .../6470-promise-reject-async-return.ts.json | 6 + ...-union-literal-results-into-record.ts.json | 6 + .../corpus/6472-asserted-member-read.ts.json | 6 + ...3-unknown-spread-into-index-record.ts.json | 6 + .../6474-index-record-spread-literals.ts.json | 6 + .../6470-promise-reject-async-return.ts | 17 ++ .../6471-union-literal-results-into-record.ts | 37 ++++ tests/corpus/6472-asserted-member-read.ts | 16 ++ .../6473-unknown-spread-into-index-record.ts | 21 +++ .../6474-index-record-spread-literals.ts | 59 ++++++ 19 files changed, 706 insertions(+), 3 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6470-promise-reject-async-return.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6471-union-literal-results-into-record.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6472-asserted-member-read.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6473-unknown-spread-into-index-record.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6474-index-record-spread-literals.ts.json create mode 100644 tests/corpus/6470-promise-reject-async-return.ts create mode 100644 tests/corpus/6471-union-literal-results-into-record.ts create mode 100644 tests/corpus/6472-asserted-member-read.ts create mode 100644 tests/corpus/6473-unknown-spread-into-index-record.ts create mode 100644 tests/corpus/6474-index-record-spread-literals.ts diff --git a/packages/compiler/src/frontend/lowering/coercions/structural-plans.ts b/packages/compiler/src/frontend/lowering/coercions/structural-plans.ts index 2bc991c14..53b0748b4 100644 --- a/packages/compiler/src/frontend/lowering/coercions/structural-plans.ts +++ b/packages/compiler/src/frontend/lowering/coercions/structural-plans.ts @@ -23,7 +23,19 @@ export function widthLiftPlan(lowerer: Lowerer, src: IrType, dst: IrType): Width } if (dst.kind === "union") { if (src.kind === "union") { - return lowerer.unionRetagMappable(src.unionId, dst.unionId) ? { how: "retag" } : null; + if (lowerer.unionRetagMappable(src.unionId, dst.unionId)) return { how: "retag" }; + // A field the checker types `undefined` alone (an object literal's + // omitted member, normalized to `?: undefined` across a union of + // literal types) carries the unit-only union. Into a slot with an + // undefined arm it lifts as that arm; its null arm cannot occur. + const srcArms = lowerer.unions.get(src.unionId)?.arms ?? []; + if ( + srcArms.length > 0 && + srcArms.every(isUnitType) && + lowerer.armTag(dst.unionId, UNDEFINED_T) >= 0 + ) + return { how: "unitRetag" }; + return null; } // A unit-typed source can't wrap here (unionWrap requires the // LITERAL unit — and no lowered shape carries a bare unit field). diff --git a/packages/compiler/src/frontend/lowering/coercions/structural.ts b/packages/compiler/src/frontend/lowering/coercions/structural.ts index c85b9868d..496a29607 100644 --- a/packages/compiler/src/frontend/lowering/coercions/structural.ts +++ b/packages/compiler/src/frontend/lowering/coercions/structural.ts @@ -125,6 +125,19 @@ export function applyWidthLift( throw new InternalCompilerError("lowerer bug: planned retag lift failed to intern"); return { kind: "call", callee: retag, args: [value], type: dst, loc }; } + case "unitRetag": { + if (dst.kind !== "union" || value.type.kind !== "union") + throw new InternalCompilerError("lowerer bug: unit retag lift shape"); + const fromId = value.type.unionId; + const trappable = new Set(); + (lowerer.unions.get(fromId)?.arms ?? []).forEach((arm, tag) => { + if (lowerer.armTag(dst.unionId, arm) < 0) trappable.add(tag); + }); + const retag = lowerer.unionRetagHelper(fromId, dst.unionId, loc, trappable); + if (!retag) + throw new InternalCompilerError("lowerer bug: planned unit retag lift failed to intern"); + return { kind: "call", callee: retag, args: [value], type: dst, loc }; + } case "liftWrap": { if (dst.kind !== "union") throw new InternalCompilerError("lowerer bug: liftWrap lift against a non-union"); diff --git a/packages/compiler/src/frontend/lowering/expressions/object-literals.ts b/packages/compiler/src/frontend/lowering/expressions/object-literals.ts index 6238479e0..5a9d0ea10 100644 --- a/packages/compiler/src/frontend/lowering/expressions/object-literals.ts +++ b/packages/compiler/src/frontend/lowering/expressions/object-literals.ts @@ -640,6 +640,74 @@ function literalUnionArmOf( /** Object spread treats nullish sources as empty. Preserve evaluation once, * then turn `Record | undefined` into either that record or a fresh * empty record of the same represented shape. */ +/** `{ ...unknownValue }` into a pure index-signature record (headers + * forwarded from an untyped parameter): Node copies the source's own + * enumerable keys in order, and spreads nothing for null, undefined, + * numbers and booleans. An object source converts through the checked + * extraction into the target's own shape, which keeps its keys and order + * and checks each value against the slot type (a value outside it throws + * the catchable TypeError of every unknown-to-typed conversion). A string + * source takes the same extraction, which rejects it, rather than + * silently dropping the characters Node would spread. Null for other + * sources and targets. */ +function dynIndexSpreadSource( + lowerer: Lowerer, + source: IrExpr, + target: IrType, + loc: SrcLoc, +): IrExpr | null { + if (source.type.kind !== "dyn" || target.kind !== "record") return null; + const shape = lowerer.shapes.get(target.shapeId); + if (!shape?.indexValue || shape.fields.length > 0 || shape.tuple) return null; + if ( + !canDynCheckTo( + target, + (id) => lowerer.shapes.get(id), + (id) => lowerer.unions.get(id), + ) + ) + return null; + const local = lowerer.declareHiddenLocal("%dynSpread", DYN); + const ref = varRef(local.id, DYN, loc); + const test = (kind: "object" | "string" | "nullish"): IrExpr => ({ + kind: "dynTest", + test: kind, + value: ref, + type: BOOL, + loc, + }); + const extract: IrExpr = { kind: "dynCheck", value: ref, type: target, loc }; + const empty: IrExpr = { kind: "recordLit", fields: [], type: target, loc }; + return { + kind: "seqExpr", + stmts: [{ kind: "varDecl", localId: local.id, init: source, loc }], + result: { + kind: "ternary", + cond: test("nullish"), + then: empty, + else_: { + kind: "ternary", + cond: { + kind: "logical", + op: "||", + left: test("object"), + right: test("string"), + type: BOOL, + loc, + }, + then: extract, + else_: empty, + type: target, + loc, + }, + type: target, + loc, + }, + type: target, + loc, + }; +} + function lowerOptionalIndexSpreadSource( lowerer: Lowerer, source: IrExpr, @@ -1372,7 +1440,7 @@ export function lowerObjectLiteral( vNode, ); contributors.push({ - kind: "field", + kind: "condField", name: csProp.name.text, value: { kind: "ternary", @@ -1387,6 +1455,7 @@ export function lowerObjectLiteral( } let src = lowerer.lowerExpr(prop.expression); src = lowerOptionalIndexSpreadSource(lowerer, src, locOf(prop)) ?? src; + src = dynIndexSpreadSource(lowerer, src, type, locOf(prop)) ?? src; if (src.type.kind !== "record") { lowerer.unsupported( "SC1090", diff --git a/packages/compiler/src/frontend/lowering/expressions/property-access.ts b/packages/compiler/src/frontend/lowering/expressions/property-access.ts index 7f0b2ddbb..b7414f986 100644 --- a/packages/compiler/src/frontend/lowering/expressions/property-access.ts +++ b/packages/compiler/src/frontend/lowering/expressions/property-access.ts @@ -1239,6 +1239,8 @@ export function lowerPropertyAccessExpr( const mapped = lowerer.mapTypeOf(lowerer.typeOf(expr)); return mapped && mapped.kind !== "void" ? lowerer.coerceInto(expr, read, mapped) : read; } + const asserted = assertedMemberRead(lowerer, expr, recvLowered, loc); + if (asserted) return asserted; lowerer.unsupported( "SC1090", expr, @@ -1246,6 +1248,101 @@ export function lowerPropertyAccessExpr( ); } +/** `(source as { repoName: string }).repoName` where the assertion names a + * member some arms of the stored union of records do not declare: Node + * reads the property off whichever object it is, so arms that declare it + * answer their slot, and the others answer undefined (native records are + * closed, so they cannot hold it). The result keeps that undefined arm + * whatever the assertion claims; a consumer that needs the asserted type + * checks it like any other narrowing. The arms that declare the member + * must agree on its type. Null when the receiver is not such an + * assertion over records, or no arm declares the member. */ +function assertedMemberRead( + lowerer: Lowerer, + expr: ts.PropertyAccessExpression, + recv: IrExpr, + loc: SrcLoc, +): IrExpr | null { + let receiverNode: ts.Expression = expr.expression; + while (ts.isParenthesizedExpression(receiverNode)) receiverNode = receiverNode.expression; + if (!ts.isAsExpression(receiverNode) && !ts.isTypeAssertion(receiverNode)) return null; + if (recv.type.kind !== "union") return null; + const unionId = recv.type.unionId; + const arms = lowerer.unions.get(unionId)?.arms ?? []; + const name = expr.name.text; + let resultType: IrType | null = null; + let records = 0; + const declaring: { tag: number; shapeId: string; type: IrType }[] = []; + const units: number[] = []; + for (let tag = 0; tag < arms.length; tag++) { + const arm = arms[tag]!; + if (isUnitType(arm)) { + units.push(tag); + continue; + } + if (arm.kind !== "record") return null; + records++; + const shape = lowerer.shapes.get(arm.shapeId); + if (!shape || shape.tuple || shape.indexValue) return null; + const field = shape.fields.find((f) => f.name === name); + if (!field) continue; + // The declaring arms agree up to an undefined arm (`string` beside + // `string | undefined`). + const armResult = lowerer.withUndefinedArmOf(field.type); + if (!armResult || (resultType && !typeEquals(resultType, armResult))) return null; + resultType = armResult; + declaring.push({ tag, shapeId: arm.shapeId, type: field.type }); + } + if (!resultType || declaring.length === records) return null; + const stable = lowerer.declareHiddenLocal("%assertedRecv", recv.type); + const ref = (): IrExpr => ({ kind: "varRef", localId: stable.id, type: recv.type, loc }); + let result: IrExpr | null = lowerer.wrappedUndefined(resultType, loc); + if (!result) return null; + // A unit arm the checker narrowed away reads like Node would read it. + for (const tag of units) { + const unit = arms[tag]!.kind === "nullT" ? "null" : "undefined"; + result = { + kind: "ternary", + cond: { kind: "unionIsTag", unionId, tag, negated: false, value: ref(), type: BOOL, loc }, + then: nodeThrowExpr( + 1, + "", + `Cannot read properties of ${unit} (reading '${name}')`, + resultType, + loc, + ), + else_: result, + type: resultType, + loc, + }; + } + for (const { tag, shapeId, type: fieldType } of declaring) { + const read: IrExpr = { + kind: "recordGet", + obj: { kind: "unionNarrow", unionId, tag, value: ref(), type: arms[tag]!, loc }, + shapeId, + field: name, + type: fieldType, + loc, + }; + result = { + kind: "ternary", + cond: { kind: "unionIsTag", unionId, tag, negated: false, value: ref(), type: BOOL, loc }, + then: lowerer.coerceToExpected(read, resultType), + else_: result, + type: resultType, + loc, + }; + } + return { + kind: "seqExpr", + stmts: [{ kind: "varDecl", localId: stable.id, init: recv, loc }], + result, + type: resultType, + loc, + }; +} + function importMetaProperty(lowerer: Lowerer, expr: ts.PropertyAccessExpression): IrExpr | null { return isImportMeta(expr.expression) ? importMetaField(lowerer, expr, expr.name.text) : null; } @@ -2568,6 +2665,27 @@ export function lowerFieldRead(lowerer: Lowerer, expr: ts.PropertyAccessExpressi loc: locOf(expr), }; } + // A member the checker declares on the receiver, stored in a pure + // index-signature record (an object literal that spread one, see + // dictSpreadLiteralShape): the key is always present, so the keyed + // read answers the member through the slot type. + if ( + actual?.type.kind === "record" && + shape.fields.length === 0 && + declaredMemberOf(lowerer, expr.expression, expr.name.text) + ) { + return lowerer.maybeNarrow( + { + kind: "recordKeyGet", + obj: actual, + shapeId: actual.type.shapeId, + key: { kind: "strLit", value: expr.name.text, type: STRING, loc: locOf(expr.name) }, + type: shape.indexValue, + loc: locOf(expr), + }, + expr, + ); + } lowerer.unsupported( "SC1090", expr, @@ -3549,3 +3667,10 @@ function jsFunctionExpandoMember(lowerer: Lowerer, access: ts.PropertyAccessExpr }) ); } + +/** Whether the checker declares `name` as a property of `receiver`'s type + * (as opposed to reading it through an index signature). */ +export function declaredMemberOf(lowerer: Lowerer, receiver: ts.Expression, name: string): boolean { + const symbol = lowerer.checker.getPropertyOfType(lowerer.typeOf(receiver), name); + return !!symbol && symbol.declarations.length > 0; +} diff --git a/packages/compiler/src/frontend/lowering/lower-exprs.ts b/packages/compiler/src/frontend/lowering/lower-exprs.ts index ac51cfbe6..fb8db2657 100644 --- a/packages/compiler/src/frontend/lowering/lower-exprs.ts +++ b/packages/compiler/src/frontend/lowering/lower-exprs.ts @@ -2573,6 +2573,24 @@ export function symbolFieldTarget( }; } +/** `return Promise.reject(reason)` in an async function: the checker's + * context is the awaited return type (or its union with PromiseLike), not + * a promise, but the async function adopts the returned promise, so the + * promise of its own result type is the honest type. Null elsewhere. */ +function asyncReturnPromise(lowerer: Lowerer, call: ts.CallExpression): IrType | null { + if (lowerer.ctx.isAsync !== true || lowerer.ctx.generator) return null; + let node: ts.Node = call; + while (ts.isParenthesizedExpression(node.parent)) node = node.parent; + const parent = node.parent; + const returned = + (ts.isReturnStatement(parent) && parent.expression === node) || + (ts.isArrowFunction(parent) && parent.body === node); + if (!returned) return null; + const ret = lowerer.ctx.returnType; + if (ret.kind === "void" || ret.kind === "promise") return null; + return { kind: "promise", inner: ret }; +} + /** `Promise.reject(reason)` on THE Promise global: a pre-rejected * promise. The reason is pinned to the Error hierarchy (the executor * reject parameter's contract — rejection payloads share the @@ -2604,7 +2622,9 @@ export function lowerPromiseRejectCall( // A maybe-promise context (`return Promise.reject(e)` from a function // returning `T | Promise`) names its one promise arm. const maybePromise = contextual ? maybePromiseUnionOf(lowerer, contextual) : null; - const type = maybePromise ? maybePromise.promise : contextual; + const type = maybePromise + ? maybePromise.promise + : (asyncReturnPromise(lowerer, call) ?? contextual); const reasonNode = call.arguments.nodes[0]!; // A CHECKED-DYNAMIC reason (`Promise.reject(value)` where value rode // an untyped binding — the tracingChannel suite's shape): the checked-dynamic tree diff --git a/packages/compiler/src/frontend/lowering/lower-stmts.ts b/packages/compiler/src/frontend/lowering/lower-stmts.ts index 41c048ae9..9866fba8a 100644 --- a/packages/compiler/src/frontend/lowering/lower-stmts.ts +++ b/packages/compiler/src/frontend/lowering/lower-stmts.ts @@ -1,3 +1,4 @@ +import { declaredMemberOf } from "./expressions/property-access.js"; import { matchAllRowsReadByElements, storedMatchAllRowsReadByElements, @@ -7469,6 +7470,31 @@ export function lowerExprStatement(lowerer: Lowerer, expr: ts.Expression): IrStm const recvIr = lowerer.mapTypeOf(lowerer.typeOf(expr.left.expression)); if (recvIr?.kind === "record") { const shape = lowerer.shapes.get(recvIr.shapeId); + if ( + shape?.indexValue && + shape.fields.length === 0 && + declaredMemberOf(lowerer, expr.left.expression, expr.left.name.text) + ) { + // A declared member of a literal stored as a pure index-signature + // record (dictSpreadLiteralShape): a keyed write of that key. + const loc = locOf(expr); + const obj = lowerer.lowerExpr(expr.left.expression); + if (obj.type.kind === "record") { + const value = lowerer.intoIndexValueSlot( + lowerer.lowerExprExpecting(expr.right, shape.indexValue), + shape.indexValue, + expr.right, + ); + return { + kind: "recordKeySet", + obj, + shapeId: obj.type.shapeId, + key: { kind: "strLit", value: expr.left.name.text, type: STRING, loc }, + value, + loc, + }; + } + } if ( shape?.indexValue && !shape.fields.some( diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index 27011df24..6428b7c45 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -8927,6 +8927,10 @@ export class Lowerer { return this.applyWidthLift(lift, narrowed, expected, expr.loc); } } + if (expr.type.kind === "union" && expected.kind === "record") { + const lifted = unionArmsWidthLift(this, expr, expected); + if (lifted) return lifted; + } if (!typeEquals(expr.type, expected)) { const w = this.widthCoerce(expr, expected); if (w) return w; @@ -12984,3 +12988,82 @@ export class Lowerer { return lowerStdlibMethodCall(this, call, access); } } + +/** A union of record layouts flowing into one record slot: a function + * returning differently-shaped object literals (`{ ok: false }` and + * `{ ok: true, size }`, whose union the checker normalizes to arms with + * `size?: undefined`) into its declared result type. Each record arm + * width-lifts into the slot on its own tag; unit arms take the stranded + * TypeError (the checker excluded them). Null when some record arm does + * not lift, or when there is a non-record, non-unit arm. */ +function unionArmsWidthLift( + lowerer: Lowerer, + expr: IrExpr, + expected: IrType & { kind: "record" }, +): IrExpr | null { + if (expr.type.kind !== "union") return null; + const unionId = expr.type.unionId; + const arms = lowerer.unions.get(unionId)?.arms ?? []; + const lifts: (WidthLift | null)[] = []; + let records = 0; + for (const arm of arms) { + if (isUnitType(arm)) { + lifts.push(null); + continue; + } + if (arm.kind !== "record") return null; + const lift = lowerer.widthLiftPlan(arm, expected); + if (!lift) return null; + lifts.push(lift); + records++; + } + if (records < 2) return null; + const loc = expr.loc; + const local = lowerer.declareHiddenLocal("%unionLift", expr.type); + const ref = (): IrExpr => ({ kind: "varRef", localId: local.id, type: expr.type, loc }); + let result: IrExpr | null = null; + for (let tag = arms.length - 1; tag >= 0; tag--) { + const lift = lifts[tag]; + const arm = arms[tag]!; + const branch: IrExpr = lift + ? lowerer.applyWidthLift( + lift, + { kind: "unionNarrow", unionId, tag, value: ref(), type: arm, loc }, + expected, + loc, + ) + : nodeThrowExpr( + 1, + "", + `${arm.kind === "nullT" ? "null" : "undefined"} is not representable in a '${lowerer.fmt(expected)}' slot (a value narrowed or asserted past the type still held it)`, + expected, + loc, + ); + result = + result === null + ? branch + : { + kind: "ternary", + cond: { + kind: "unionIsTag", + unionId, + tag, + negated: false, + value: ref(), + type: BOOL, + loc, + }, + then: branch, + else_: result, + type: expected, + loc, + }; + } + return { + kind: "seqExpr", + stmts: [{ kind: "varDecl", localId: local.id, init: expr, loc }], + result: result!, + type: expected, + loc, + }; +} diff --git a/packages/compiler/src/frontend/lowering/width-lift.ts b/packages/compiler/src/frontend/lowering/width-lift.ts index d90f9ed35..e3722bc32 100644 --- a/packages/compiler/src/frontend/lowering/width-lift.ts +++ b/packages/compiler/src/frontend/lowering/width-lift.ts @@ -9,6 +9,7 @@ export type WidthLift = | { how: "discriminantWrap" } | { how: "discriminantRoute" } | { how: "retag" } + | { how: "unitRetag" } | { how: "liftWrap"; tag: number; arm: IrType } | { how: "width" } | { how: "unionWidth" } diff --git a/packages/compiler/src/frontend/type-mapper.ts b/packages/compiler/src/frontend/type-mapper.ts index 437aba2b8..de4e13de0 100644 --- a/packages/compiler/src/frontend/type-mapper.ts +++ b/packages/compiler/src/frontend/type-mapper.ts @@ -5860,6 +5860,8 @@ function mapRecordType(widened: ts.Type, ctx: TypeMapperCtx): IrType | null { (inner.indexValue === undefined || inner.indexValue.kind === "dyn") ) return DYN; + const merged = dictSpreadLiteralShape(widened, inner, ctx); + if (merged !== null) return { kind: "record", shapeId: merged }; const refined = refinedConstituentShape(widened, inner, ctx); if (refined !== null) return { kind: "record", shapeId: refined }; return { @@ -5871,6 +5873,178 @@ function mapRecordType(widened: ts.Type, ctx: TypeMapperCtx): IrType | null { } } +/** The type of an object literal that spreads an index-signature record + * (`{ ...tags, bucket: b, status }`, the metric-tags idiom). The checker + * drops the signature and types the literal by its explicit members only, + * but at run time the object holds every key the spread copied, in one + * insertion order. Such a literal maps to the pure index-signature record + * whose slot joins the spread slots and the member types, so the merge, + * every enumeration and keyed writes see the same keys Node does. A + * member read still answers its member type: members written after the + * spreads always win, and a member written before a spread must have that + * spread's slot type, so an override cannot change its type. The join + * covers scalar and unit types only; other literals keep their shape and + * the spread fence. */ +function dictSpreadLiteralShape( + widened: ts.Type, + parts: RecordShapeParts, + ctx: TypeMapperCtx, +): string | null { + if (parts.indexValue) return null; + const symbol = widened.getSymbol(); + if (!symbol || (symbol.flags & ts.SymbolFlags.ObjectLiteral) === 0) return null; + const literal = symbol.declarations.find((d): d is ts.ObjectLiteralExpression => + ts.isObjectLiteralExpression(d), + ); + if (!literal) return null; + const { checker, shapes, unions } = ctx; + const props = literal.properties.nodes; + if (!props.some((p) => ts.isSpreadAssignment(p))) return null; + // Decide on checker types alone before mapping anything: object literals + // with ordinary spreads are everywhere, and mapping their sources here + // would intern types out of their usual order. + // A literal written into a typed index-signature slot already lowers into + // it (the merge into the contextual record); its own type keeps its + // mapping. + const contextualRaw = checker.getContextualType(literal); + const contextual = contextualRaw ? checker.getNonNullableType(contextualRaw) : undefined; + // An unknown-valued slot (`Record`) is the checked-dynamic + // value, which has no typed merge of its own: the literal still needs its + // own record. + if ( + contextual && + checker + .getIndexInfosOfType(contextual) + .some((info) => (info.valueType.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) === 0) + ) + return null; + const kinds = props.map((p) => + ts.isSpreadAssignment(p) ? spreadSourceKindTs(checker, p.expression) : "member", + ); + if (kinds.some((k) => k === null) || !kinds.includes("dict")) return null; + const dictSlotOf = (e: ts.Expression): IrType | null => { + const t = checker.getNonNullableType(checker.getTypeAtLocation(e)); + if (checker.getPropertiesOfType(t).length === 0) { + const infos = checker.getIndexInfosOfType(t); + if (infos.length !== 1 || !(infos[0]!.keyType.flags & ts.TypeFlags.String)) return null; + return mapType(infos[0]!.valueType, ctx); + } + // Another such literal's type (`{ ...tags, repo }` over a binding + // initialized by `{ ...base, plan }`) is stored as the same kind of + // pure index-signature record. + if (t === widened) return null; + const mapped = mapType(t, ctx); + const shape = mapped?.kind === "record" ? shapes.get(mapped.shapeId) : undefined; + return shape?.indexValue && shape.fields.length === 0 && !shape.tuple ? shape.indexValue : null; + }; + const arms = new Map(); + const addArms = (t: IrType): boolean => { + const list = t.kind === "union" ? (unions.get(t.unionId)?.arms ?? []) : [t]; + for (const arm of list) { + if (!OVERLOAD_JOIN_SCALARS.has(arm.kind)) return false; + arms.set(typeKey(arm), arm); + } + return true; + }; + const before: string[] = []; + for (const [i, p] of props.entries()) { + // A fixed-shape or conditional spread contributes its own members, + // which the checker already types exactly (they are in `parts`). + if (kinds[i] === "record") { + // Its members may be overwritten by a later dict spread, like members + // written before one. + before.push(...spreadMemberNamesTs(checker, (p as ts.SpreadAssignment).expression)); + continue; + } + if (ts.isSpreadAssignment(p)) { + const slot = dictSlotOf(p.expression); + if (!slot || !addArms(slot)) return null; + // A member written before this spread may be overwritten by it. + for (const name of before) { + const field = parts.fields.find((f) => f.name === name); + if (!field || !typeEquals(field.type, slot)) return null; + } + continue; + } + if (!(ts.isPropertyAssignment(p) || ts.isShorthandPropertyAssignment(p))) return null; + const name = p.name; + if (!(ts.isIdentifier(name) || ts.isStringLiteral(name))) return null; + before.push(name.text); + } + if (parts.fields.some((f) => f.name.startsWith("%"))) return null; + for (const field of parts.fields) if (!addArms(field.type)) return null; + if (![...arms.values()].some((arm) => !isUnitType(arm))) return null; + const sorted = [...arms.values()].sort((a, b) => (typeKey(a) < typeKey(b) ? -1 : 1)); + const slot: IrType = + sorted.length === 1 ? sorted[0]! : { kind: "union", unionId: unions.intern(sorted) }; + return shapes.intern([], false, slot, []); +} + +/** How a spread source contributes to dictSpreadLiteralShape, judged on + * checker types only: "dict" for a pure string index signature or another + * literal that spreads one, "record" for a fixed object shape or a + * conditional spread (`...(c && { k: v })`), null otherwise. */ +function spreadSourceKindTs(checker: ts.TypeChecker, e: ts.Expression): "dict" | "record" | null { + if (dictSpreadSourceTs(checker, e, 0)) return "dict"; + let inner = e; + while (ts.isParenthesizedExpression(inner)) inner = inner.expression; + if ( + (ts.isBinaryExpression(inner) && + inner.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken) || + ts.isConditionalExpression(inner) + ) + return "record"; + const t = checker.getNonNullableType(checker.getTypeAtLocation(e)); + if ((t.flags & ts.TypeFlags.Object) === 0 || t.isUnionType()) return null; + if (checker.getIndexInfosOfType(t).length > 0) return null; + if (checker.getCallSignatures(t).length > 0 || checker.getConstructSignatures(t).length > 0) + return null; + const symbol = t.getSymbol(); + if (symbol && (symbol.flags & ts.SymbolFlags.Class) !== 0) return null; + return "record"; +} + +/** The member names a fixed-shape or conditional spread contributes. */ +function spreadMemberNamesTs(checker: ts.TypeChecker, e: ts.Expression): string[] { + let inner = e; + while (ts.isParenthesizedExpression(inner)) inner = inner.expression; + const literals: ts.Expression[] = []; + if (ts.isBinaryExpression(inner)) literals.push(inner.right); + else if (ts.isConditionalExpression(inner)) literals.push(inner.whenTrue, inner.whenFalse); + else literals.push(inner); + const names: string[] = []; + for (const l of literals) { + const t = checker.getNonNullableType(checker.getTypeAtLocation(l)); + for (const property of checker.getPropertiesOfType(t)) names.push(property.name); + } + return names; +} + +/** A spread source the checker types as a pure string index signature, + * or as another object literal that spreads one (dictSpreadLiteralShape), + * judged on checker types only. */ +function dictSpreadSourceTs(checker: ts.TypeChecker, e: ts.Expression, depth: number): boolean { + const t = checker.getNonNullableType(checker.getTypeAtLocation(e)); + if (checker.getPropertiesOfType(t).length === 0) { + const infos = checker.getIndexInfosOfType(t); + return infos.length === 1 && (infos[0]!.keyType.flags & ts.TypeFlags.String) !== 0; + } + if (depth > 4) return false; + const symbol = t.getSymbol(); + if (!symbol || (symbol.flags & ts.SymbolFlags.ObjectLiteral) === 0) return false; + const literal = symbol.declarations.find((d): d is ts.ObjectLiteralExpression => + ts.isObjectLiteralExpression(d), + ); + if (!literal) return false; + const spreads = literal.properties.nodes.filter((p) => ts.isSpreadAssignment(p)); + return ( + spreads.length > 0 && + spreads.every((p) => + dictSpreadSourceTs(checker, (p as ts.SpreadAssignment).expression, depth + 1), + ) + ); +} + /** A refinement intersection (`Expr & { kind: "mul" }`, which the checker * reduces to `Mul & { kind: "mul" }`) keeps the shape of the constituent it * refines when the refinement changes no field's representation. Recursive diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6470-promise-reject-async-return.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6470-promise-reject-async-return.ts.json new file mode 100644 index 000000000..dda9bd1e8 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6470-promise-reject-async-return.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6470-promise-reject-async-return.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6471-union-literal-results-into-record.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6471-union-literal-results-into-record.ts.json new file mode 100644 index 000000000..1a02071a1 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6471-union-literal-results-into-record.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6471-union-literal-results-into-record.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6472-asserted-member-read.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6472-asserted-member-read.ts.json new file mode 100644 index 000000000..dfb88018b --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6472-asserted-member-read.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6472-asserted-member-read.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6473-unknown-spread-into-index-record.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6473-unknown-spread-into-index-record.ts.json new file mode 100644 index 000000000..abf47b7db --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6473-unknown-spread-into-index-record.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6473-unknown-spread-into-index-record.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6474-index-record-spread-literals.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6474-index-record-spread-literals.ts.json new file mode 100644 index 000000000..8991f227d --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6474-index-record-spread-literals.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6474-index-record-spread-literals.ts" + ], + "diags": [] +} diff --git a/tests/corpus/6470-promise-reject-async-return.ts b/tests/corpus/6470-promise-reject-async-return.ts new file mode 100644 index 000000000..4d9a62eab --- /dev/null +++ b/tests/corpus/6470-promise-reject-async-return.ts @@ -0,0 +1,17 @@ +// `return Promise.reject(err)` in an async function returns a promise of the +// function's own result type, which the async function adopts. +type Ctx = { id: string; endpoint: string; meta?: { [key: string]: string } }; +async function context(endpoint: string | undefined): Promise { + if (!endpoint || endpoint.length === 0) { + return Promise.reject(new Error("invalid endpoint")); + } + return Promise.resolve({ id: "c1", endpoint }); +} +function sync(flag: boolean): Promise { + if (flag) return Promise.reject(new TypeError("nope")); + return Promise.resolve(3); +} +context("http://x").then((c) => console.log(c.id, c.endpoint)); +context(undefined).catch((e) => console.log("rejected", String(e))); +sync(false).then((n) => console.log(n)); +sync(true).catch((e) => console.log(e instanceof TypeError, String(e))); diff --git a/tests/corpus/6471-union-literal-results-into-record.ts b/tests/corpus/6471-union-literal-results-into-record.ts new file mode 100644 index 000000000..0f6c158d1 --- /dev/null +++ b/tests/corpus/6471-union-literal-results-into-record.ts @@ -0,0 +1,37 @@ +// A function returning differently shaped object literals (the checker +// normalizes omitted members to `?: undefined`) flows into a declared record +// result: each literal shape converts on its own. +class Tracer { + trace(_name: string, fn: () => T): T { + return fn(); + } +} +const tracer = new Tracer(); +type CacheResult = { downloaded: boolean; size?: number; cacheType?: string; dir?: string }; +async function download(n: number, dir: string | undefined): Promise { + const pending = tracer.trace("download", async () => { + if (n === 0) { + return { downloaded: false, dir }; + } + if (n < 0) { + return { downloaded: false }; + } + return { downloaded: true, size: n, cacheType: "full", dir }; + }); + return pending.finally(() => { + console.log("done", n); + }); +} +type Details = { obtain: string; url?: string; kind?: string }; +async function details(flag: number): Promise
{ + return tracer.trace("details", async () => { + if (flag === 0) return { obtain: "skipped" }; + return { obtain: "found", url: "u", kind: "k" }; + }); +} +async function main() { + console.log(await download(0, "/d"), await download(5, undefined), await download(-1, "z")); + const d0 = await details(0); + console.log(d0, "url" in d0, JSON.stringify(await details(1))); +} +main(); diff --git a/tests/corpus/6472-asserted-member-read.ts b/tests/corpus/6472-asserted-member-read.ts new file mode 100644 index 000000000..498a9bb57 --- /dev/null +++ b/tests/corpus/6472-asserted-member-read.ts @@ -0,0 +1,16 @@ +// `(source as { repoName: string }).repoName` on a union of records: arms +// that declare the member answer it, the others answer undefined, as Node +// reads a missing property. +type GithubSource = { type: string; repoId: number; repoName: string; ref: string }; +type GitlabSource = { type: string; projectId: number; ref: string }; +type BitbucketSource = { type: string; repoName: string; owner: string; ref: string }; +type Source = GithubSource | GitlabSource | BitbucketSource; +function label(source: Source): string { + return (source as { repoName: string }).repoName || "???"; +} +const sources: Source[] = [ + { type: "github", repoId: 1, repoName: "r1", ref: "main" }, + { type: "gitlab", projectId: 2, ref: "dev" }, + { type: "bitbucket", repoName: "", owner: "o", ref: "x" }, +]; +console.log(sources.map(label)); diff --git a/tests/corpus/6473-unknown-spread-into-index-record.ts b/tests/corpus/6473-unknown-spread-into-index-record.ts new file mode 100644 index 000000000..c5cd4f6ad --- /dev/null +++ b/tests/corpus/6473-unknown-spread-into-index-record.ts @@ -0,0 +1,21 @@ +// Spreading an unknown value into an index-signature record copies its own +// keys in order and spreads nothing for null and undefined. +function build(extra: unknown, attempt: number): Record { + return { + "content-type": "application/json", + ...(extra as Record), + "x-attempt": String(attempt), + }; +} +function headersOf(params: { headers?: T }, attempt: number): Record { + return { + ...params.headers, + "x-attempt": String(attempt), + }; +} +const parsed: unknown = JSON.parse('{"authorization":"t","content-type":"text/plain"}'); +console.log(build(parsed, 1)); +console.log(headersOf({ headers: parsed }, 3), headersOf({}, 4)); +const nothing: unknown = JSON.parse("null"); +const five: unknown = JSON.parse("5"); +console.log(headersOf({ headers: nothing }, 5), headersOf({ headers: five }, 6)); diff --git a/tests/corpus/6474-index-record-spread-literals.ts b/tests/corpus/6474-index-record-spread-literals.ts new file mode 100644 index 000000000..59e1603a9 --- /dev/null +++ b/tests/corpus/6474-index-record-spread-literals.ts @@ -0,0 +1,59 @@ +// An object literal that spreads an index-signature record holds every key +// the spread copied, in insertion order, though the checker types it by its +// explicit members only. A conditional spread that does not apply adds no +// key. +function bucket(n: number | undefined): string { + return n === undefined ? "unknown" : n < 100 ? "small" : "large"; +} +function emit(name: string, tags: Record): void { + console.log(name, JSON.stringify(tags), Object.keys(tags).length); +} +function measure(base: Record, count: number | undefined, ok: boolean) { + const metricTags = { + ...base, + fileCountBucket: bucket(count), + status: ok ? "succeeded" : "failed", + }; + emit("files", metricTags); + console.log(metricTags.status.toUpperCase(), metricTags.fileCountBucket); + return metricTags; +} +const base = { plan: "pro", region: "iad1", status: "pending" }; +const m = measure(base, 42, true); +console.log(Object.entries(m)); +function walker(enable: boolean, metricTags: Record = {}) { + const tags = { + plan: "unknown", + ...metricTags, + largeCliDplPerformance: String(enable), + walkerFallback: "false", + }; + tags.walkerFallback = "true"; + console.log(tags, tags.plan); +} +walker(true, { plan: "hobby", extra: "x" }); +walker(false); +function counts(tags: Record, n: number) { + const t = { ...tags, attempts: n, retried: n > 1 }; + console.log(t, t.attempts + 1, t.retried); +} +counts({ a: "1" }, 2); +function depthTags(deep: boolean): { depth: number | undefined; kind: "shallow" | "deep"; useDeep: boolean } { + return { depth: deep ? undefined : 1, kind: deep ? "deep" : "shallow", useDeep: deep }; +} +function attempt(base: Record, n: number, deep: boolean, bytes: number | undefined) { + const tags = { + ...base, + git_attempt: n, + repo: "r", + ...(bytes !== undefined && { packBytes: bytes }), + ...depthTags(deep), + }; + console.log(JSON.stringify(tags), Object.keys(tags).join(","), tags.git_attempt, tags.useDeep); +} +attempt({ plan: "pro" }, 1, false, 10); +attempt({ plan: "pro", repo: "x" }, 2, true, undefined); +function addTags(tags: Record): void { + console.log(Object.entries(tags).map(([k, v]) => `${k}=${String(v)}`).join(" ")); +} +addTags({ ...{ plan: "pro", team: "t" } as Record, count: 3, ok: true }); From 37551e41b485d475ad4bf8696932db6a17b98025 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Mon, 19 Oct 2026 22:17:12 -0700 Subject: [PATCH 057/122] Compile `any` crossings: destructuring, namespace patterns, `any` parameters and async callbacks - Destructuring an `any` value in TypeScript (a function typed `any`, an awaited `any`, `JSON.parse`, an `any` table read) now goes through the checked-dynamic value, as JavaScript sources and #1407's JS-declared results already did. Object sources get V8's RequireObjectCoercible TypeError for the spellings the assignment form reproduces. - A method call or member read on a checker-`any` receiver whose value is a fresh typed call result (`out.split("\n").map(...)`, `.pop()`, `.length`) boxes that temporary and dispatches on the runtime kind, only after every other lowering declined. - `const { a, b: c } = ns` and `= await import("./m")` over a program module bind each export the way `ns.a` reads it. - A defaulted parameter of a program-source function, arrow or function expression that the checker types `any` (a default under an `(...args: any[]) => any` wrapper slot, where the contextual `any` wins) is a dyn ABI slot in the callable's type. - An async lambda whose signature answered a bare dyn result (a library slot `A | Promise` with A = any) produces Promise. - `761-any-objects` compiles and moves back to the corpus; the retired-engine 2083/2104 snapshots shrink to their remaining fences. - JavaScript `Promise.resolve(value)` (in a JavaScript file) over a dyn value holding a promise answers that promise instead of wrapping it, so a TypeScript async callback's result is adopted by JavaScript retry helpers. Co-Authored-By: Claude Opus 5.5 --- .../frontend/lowering/builtins/promises.ts | 27 ++++ .../lowering/expressions/property-access.ts | 28 ++++ .../src/frontend/lowering/lower-calls.ts | 28 ++++ .../src/frontend/lowering/lower-functions.ts | 13 ++ .../src/frontend/lowering/lower-stmts.ts | 139 ++++++++++++------ packages/compiler/src/frontend/type-mapper.ts | 21 +++ .../corpus/6470-any-destructuring.ts.json | 6 + .../corpus/6471-any-method-chains.ts.json | 6 + .../6472-namespace-destructuring/main.ts.json | 7 + .../corpus/6473-any-parameter-values.ts.json | 6 + .../6474-async-any-callbacks/main.ts.json | 6 + .../main.ts.json | 6 + .../tests/corpus/761-any-objects.ts.json | 6 + .../retired-engine-761-any-objects.ts.json | 6 - tests/corpus/6470-any-destructuring.ts | 55 +++++++ tests/corpus/6471-any-method-chains.ts | 24 +++ .../6472-namespace-destructuring/lib.ts | 21 +++ .../6472-namespace-destructuring/main.ts | 19 +++ tests/corpus/6473-any-parameter-values.ts | 23 +++ tests/corpus/6474-async-any-callbacks/main.ts | 41 ++++++ .../node_modules/retry-lite/index.d.ts | 15 ++ .../node_modules/retry-lite/index.js | 36 +++++ .../node_modules/retry-lite/package.json | 6 + .../6474-async-any-callbacks/tsconfig.json | 3 + .../6475-js-promise-resolve-adoption/main.ts | 23 +++ .../node_modules/calls/index.d.ts | 4 + .../node_modules/calls/index.js | 21 +++ .../node_modules/calls/package.json | 6 + .../761-any-objects.ts} | 6 +- ...gine-2083-destructuring-island-decl.ts.txt | 63 -------- ...ine-2104-computed-key-destructuring.ts.txt | 21 +-- .../retired-engine-761-any-objects.ts.txt | 6 - 32 files changed, 559 insertions(+), 139 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6470-any-destructuring.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6471-any-method-chains.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6472-namespace-destructuring/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6473-any-parameter-values.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6474-async-any-callbacks/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6475-js-promise-resolve-adoption/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/761-any-objects.ts.json delete mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/retired-engine-761-any-objects.ts.json create mode 100644 tests/corpus/6470-any-destructuring.ts create mode 100644 tests/corpus/6471-any-method-chains.ts create mode 100644 tests/corpus/6472-namespace-destructuring/lib.ts create mode 100644 tests/corpus/6472-namespace-destructuring/main.ts create mode 100644 tests/corpus/6473-any-parameter-values.ts create mode 100644 tests/corpus/6474-async-any-callbacks/main.ts create mode 100644 tests/corpus/6474-async-any-callbacks/node_modules/retry-lite/index.d.ts create mode 100644 tests/corpus/6474-async-any-callbacks/node_modules/retry-lite/index.js create mode 100644 tests/corpus/6474-async-any-callbacks/node_modules/retry-lite/package.json create mode 100644 tests/corpus/6474-async-any-callbacks/tsconfig.json create mode 100644 tests/corpus/6475-js-promise-resolve-adoption/main.ts create mode 100644 tests/corpus/6475-js-promise-resolve-adoption/node_modules/calls/index.d.ts create mode 100644 tests/corpus/6475-js-promise-resolve-adoption/node_modules/calls/index.js create mode 100644 tests/corpus/6475-js-promise-resolve-adoption/node_modules/calls/package.json rename tests/{diagnostics/retired-engine-761-any-objects.ts => corpus/761-any-objects.ts} (67%) delete mode 100644 tests/harness/__snapshots__/retired-engine-761-any-objects.ts.txt diff --git a/packages/compiler/src/frontend/lowering/builtins/promises.ts b/packages/compiler/src/frontend/lowering/builtins/promises.ts index 2656da18b..70e2ee954 100644 --- a/packages/compiler/src/frontend/lowering/builtins/promises.ts +++ b/packages/compiler/src/frontend/lowering/builtins/promises.ts @@ -5,6 +5,7 @@ import { type Lowerer } from "../lowerer.js"; import { maybePromiseResolve, maybePromiseUnionOf } from "../maybe-promise.js"; import { isJsSourceFile, locOf } from "../../program.js"; import { + BOOL, DYN, type IrExpr, type IrFunction, @@ -488,6 +489,32 @@ export function lowerPromiseStaticCall( lowerer.noLowering("Promise.resolve at a unit-typed promise", call); } const value = lowerer.lowerExprExpecting(argNode, resultT.inner); + // In JavaScript, a checked-dynamic argument may itself hold a promise + // (a TypeScript async callback's result: `Promise.resolve(fn())`): + // Promise.resolve answers THAT promise — the same object, adopted — + // and wraps every other value. + if ( + isJsSourceFile(call.getSourceFile()) && + value.type.kind === "dyn" && + resultT.inner.kind === "dyn" + ) { + const local = lowerer.declareHiddenLocal("%resolveValue", DYN); + const ref: IrExpr = { kind: "varRef", localId: local.id, type: DYN, loc }; + return { + kind: "seqExpr", + stmts: [{ kind: "varDecl", localId: local.id, init: value, loc }], + result: { + kind: "ternary", + cond: { kind: "dynTest", test: "promise", value: ref, type: BOOL, loc }, + then: { kind: "dynCheck", value: ref, type: resultT, loc }, + else_: { kind: "intrinsic", name: "promise.resolve", args: [ref], type: resultT, loc }, + type: resultT, + loc, + }, + type: resultT, + loc, + }; + } return { kind: "intrinsic", name: "promise.resolve", args: [value], type: resultT, loc }; } return null; // reject lands on lowerPromiseRejectCall / the member fence diff --git a/packages/compiler/src/frontend/lowering/expressions/property-access.ts b/packages/compiler/src/frontend/lowering/expressions/property-access.ts index a3154c45a..f564cd333 100644 --- a/packages/compiler/src/frontend/lowering/expressions/property-access.ts +++ b/packages/compiler/src/frontend/lowering/expressions/property-access.ts @@ -1255,6 +1255,27 @@ export function lowerPropertyAccessExpr( const mapped = lowerer.mapTypeOf(lowerer.typeOf(expr)); return mapped && mapped.kind !== "void" ? lowerer.coerceInto(expr, read, mapped) : read; } + // A checker-`any` receiver whose value is a FRESH typed result of an + // earlier step in the chain (`out.split(" ").length`, where split + // extracted the string and answered a native string[]): the member read + // goes through the boxed checked-dynamic value, as the method-call twin + // does. The box copies; a call result has no other holder to observe it. + if ( + (lowerer.typeOf(expr.expression).flags & ts.TypeFlags.Any) !== 0 && + isFreshCallResult(expr.expression) && + lowerer.dynConvertible(recvLowered.type) + ) { + return lowerer.maybeNarrow( + { + kind: "dynKeyGet", + value: lowerer.coerceToExpected(recvLowered, DYN), + key: { kind: "strLit", value: expr.name.text, type: STRING, loc }, + type: DYN, + loc, + }, + expr, + ); + } lowerer.unsupported( "SC1090", expr, @@ -1262,6 +1283,13 @@ export function lowerPropertyAccessExpr( ); } +/** A call expression, through parentheses: its value has no other holder. */ +function isFreshCallResult(node: ts.Expression): boolean { + let e = node; + while (ts.isParenthesizedExpression(e)) e = e.expression; + return ts.isCallExpression(e); +} + function importMetaProperty(lowerer: Lowerer, expr: ts.PropertyAccessExpression): IrExpr | null { return isImportMeta(expr.expression) ? importMetaField(lowerer, expr, expr.name.text) : null; } diff --git a/packages/compiler/src/frontend/lowering/lower-calls.ts b/packages/compiler/src/frontend/lowering/lower-calls.ts index ed549e4ff..01788c6d5 100644 --- a/packages/compiler/src/frontend/lowering/lower-calls.ts +++ b/packages/compiler/src/frontend/lowering/lower-calls.ts @@ -1570,6 +1570,10 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { // monomorphized against the defining literal's declaration. lowerObjLitGenericMethodCall(lowerer, expr, expr.expression); if (intrinsic) return intrinsic; + { + const boxed = lowerDynReceiverMethodCall(lowerer, expr, expr.expression, true); + if (boxed) return boxed; + } if (isJsSourceFile(expr.getSourceFile())) { const staticField = lowerStaticFieldRead(lowerer, expr.expression); if (staticField?.type.kind === "dyn" && !expr.arguments.nodes.some(ts.isSpreadElement)) { @@ -3277,6 +3281,13 @@ const ITERATOR_HELPER_METHODS = new Set([ "find", ]); +/** A call expression, through parentheses: its value has no other holder. */ +function isFreshCallResult(node: ts.Expression): boolean { + let e = node; + while (ts.isParenthesizedExpression(e)) e = e.expression; + return ts.isCallExpression(e); +} + /** METHOD calls on dyn receivers (`pkg.name.replace(...)`, `rawName.split`, * `ws.packages.filter(...)` — JSON.parse-derived values): validate the * receiver's dyn kind, extract, and ride the STATIC method machinery — the @@ -3294,6 +3305,7 @@ function lowerDynReceiverMethodCall( lowerer: Lowerer, call: ts.CallExpression, access: ts.PropertyAccessExpression, + boxTypedTemporary = false, ): IrExpr | null { if (lowerer.chainBlocked(call, access)) return null; if (stdlibGlobalNameOf(lowerer, access.expression) !== null) { @@ -3476,6 +3488,22 @@ function lowerDynReceiverMethodCall( // 'unknown' slot does. if (checkerUntyped && DYN_HANDLE_KINDS.has(recv.type.kind)) recv = lowerer.coerceToExpected(recv, DYN); + // The last-resort retry (boxTypedTemporary — every other lowering + // already declined): a checker-`any` receiver whose value is a FRESH + // typed result of an earlier step in the chain (`out.split("\n")` + // extracted the string and answered a native string[]) has no typed + // home under its `any` spelling, so it boxes into the checked-dynamic + // tree and the call dispatches on the runtime kind — `.map`, `.pop`, + // `.filter` on that array run JS-exact. Only call results qualify: + // the box copies, and a temporary has no other holder to observe it. + if ( + boxTypedTemporary && + checkerUntyped && + recv.type.kind !== "dyn" && + isFreshCallResult(access.expression) && + lowerer.dynConvertible(recv.type) + ) + recv = lowerer.coerceToExpected(recv, DYN); if (recv.type.kind !== "dyn") return null; if ( access.name.text === "normalize" && diff --git a/packages/compiler/src/frontend/lowering/lower-functions.ts b/packages/compiler/src/frontend/lowering/lower-functions.ts index 0ef601672..6394daaa1 100644 --- a/packages/compiler/src/frontend/lowering/lower-functions.ts +++ b/packages/compiler/src/frontend/lowering/lower-functions.ts @@ -214,6 +214,19 @@ export function lowerLambda( ret: funcType.ret.kind === "promise" ? { ...funcType.ret, inner: DYN } : DYN, }; } + // An ASYNC lambda whose signature answered a bare checked-dynamic result + // (`retry(async () => loose())` against a library slot typed + // `(...) => A | Promise` with A = any — the union collapses to `any`): + // calling it still produces a promise, so the value's result is + // Promise, the payload the dyn the body returns. + if ( + funcType.ret.kind === "dyn" && + !ts.isAccessor(node) && + node.asteriskToken === undefined && + node.modifiers?.nodes.some((m) => m.kind === ts.SyntaxKind.AsyncKeyword) === true + ) { + funcType = { ...funcType, ret: { kind: "promise", inner: DYN } }; + } // A lambda IS a value: the completed-ABI rule applies at birth. The // contextual (target) type decides — `(x?: number) => void` may flow // into a slot annotated `(x: number | undefined) => void` (same ABI diff --git a/packages/compiler/src/frontend/lowering/lower-stmts.ts b/packages/compiler/src/frontend/lowering/lower-stmts.ts index e530a57f7..c1aaceded 100644 --- a/packages/compiler/src/frontend/lowering/lower-stmts.ts +++ b/packages/compiler/src/frontend/lowering/lower-stmts.ts @@ -2087,43 +2087,6 @@ export function isParseArgsDynCheckerType(lowerer: Lowerer, type: ts.Type): bool .some((p) => p.declarations.some(declarationBelongs)); } -/** True when an `any` value comes from a JavaScript function's result: - * a call (possibly awaited) whose callee is declared in a JavaScript file, - * or a const bound to one. A TypeScript program importing a JavaScript - * package sees the package's inferred types (`jwtVerify` from jose - * answers `any`), and its object destructuring reads the members of that - * checked-dynamic value as JavaScript does — member reads on the same - * value already compile. Other `any` sources keep the fence. */ -function javaScriptAnyOrigin(lowerer: Lowerer, node: ts.Expression, depth: number): boolean { - let expr = node; - while (ts.isParenthesizedExpression(expr) || ts.isAwaitExpression(expr)) expr = expr.expression; - if (ts.isCallExpression(expr)) { - const callee = ts.isPropertyAccessExpression(expr.expression) - ? expr.expression.name - : expr.expression; - if (!ts.isIdentifier(callee)) return false; - const symbol = lowerer.resolveValueSymbol(callee); - const declarations = symbol ? symbol.declarations : []; - return ( - declarations.length > 0 && - declarations.every((d) => isJsSourceFile(d.getSourceFile()) && ts.isFunctionLike(d)) - ); - } - if (ts.isIdentifier(expr) && depth === 0) { - const symbol = lowerer.resolveValueSymbol(expr); - const declaration = symbol ? symbol.valueDeclaration : undefined; - return ( - declaration !== undefined && - ts.isVariableDeclaration(declaration) && - ts.isIdentifier(declaration.name) && - declaration.initializer !== undefined && - (ts.getCombinedNodeFlags(declaration) & ts.NodeFlags.Const) !== 0 && - javaScriptAnyOrigin(lowerer, declaration.initializer, depth + 1) - ); - } - return false; -} - /** Destructuring declarations, desugared as sugar over indexed/field * reads: the initializer evaluates ONCE into a hidden temp, then each * bound name declares (or assigns its pre-registered module global) from @@ -2162,18 +2125,18 @@ export function lowerDestructuringDecl( const parseArgsDynObject = init.type.kind === "dyn" && isParseArgsDynCheckerType(lowerer, lowerer.typeOf(decl.initializer)); - // An `any`-origin source needs a represented record or array shape for - // destructuring. JS files retain their per-site runtime fences. + // An `any`-origin source destructures through a represented record or + // array shape, or through the checked-dynamic value it lowered to: the + // pattern reads members (or iterates) the dyn value exactly as a + // JavaScript file's pattern does, with the same per-site runtime fences + // (null/undefined sources throw Node's TypeError). Primitive and unit + // sources keep their own fences below; other carriers keep this one. if ( lowerer.anyOrigin(decl.initializer) && !isJsSourceFile(decl.getSourceFile()) && init.type.kind !== "record" && init.type.kind !== "array" && - !( - init.type.kind === "dyn" && - ts.isObjectBindingPattern(decl.name) && - javaScriptAnyOrigin(lowerer, decl.initializer, 0) - ) + init.type.kind !== "dyn" ) { lowerer.anyOpFence("destructuring", decl); } @@ -2278,6 +2241,38 @@ export function lowerDestructuringDecl( if (groupsSource !== null) init = groupsSource; const tmp = lowerer.declareHiddenLocal("%destr", init.type); out.push({ kind: "varDecl", localId: tmp.id, init, loc }); + // A TypeScript `any` source destructured through its checked-dynamic + // value: RequireObjectCoercible with V8's own TypeError ("Cannot + // destructure property 'a' of 'f(...)' as it is null.") for the source + // spellings the assignment form already reproduces. JavaScript files + // keep their existing per-read errors. + if ( + init.type.kind === "dyn" && + ts.isObjectBindingPattern(decl.name) && + !isJsSourceFile(decl.getSourceFile()) && + lowerer.anyOrigin(decl.initializer) + ) { + const spelling = destrSpellingOf(decl.initializer); + const first = decl.name.elements.nodes[0]; + const firstKey = + first === undefined || first.dotDotDotToken !== undefined + ? undefined + : (first.propertyName ?? first.name); + if (spelling !== null && (firstKey === undefined || ts.isIdentifier(firstKey))) { + out.push({ + kind: "exprStmt", + expr: { + kind: "dynDestrCheck", + value: { kind: "varRef", localId: tmp.id, type: init.type, loc }, + spelling, + ...(firstKey !== undefined ? { firstProp: firstKey.text } : {}), + type: init.type, + loc, + }, + loc, + }); + } + } // V8's destructuring TypeError spells STATIC sources: an identifier // source reads " is not iterable", an identifier-callee call // " is not a function or its return value is not iterable"; @@ -3099,6 +3094,10 @@ export function lowerBindingPattern( lowerArrayPositionObjectPattern(lowerer, pattern, srcRef, srcType, isLet, out); return; } + if (srcType.kind === "moduleNs" && lowerer.sourceFileOfModuleNamespace(srcType) !== null) { + lowerModuleNamespacePattern(lowerer, pattern, isLet, out); + return; + } if (srcType.kind !== "record") { lowerer.unsupported( "SC1031", @@ -3356,6 +3355,58 @@ function patternBindingType(lowerer: Lowerer, name: ts.BindingName): IrType | nu ); } +/** `const { a, b: c } = ns` / `const { f } = await import("./m")` over + * a program module's namespace: each element reads the export exactly + * like `ns.a` does — the member resolves to the exporter's own binding + * (nsMemberIdentOf's rule), read once at the pattern's position, so the + * bound name holds the export's value at that moment (destructuring + * copies; it does not alias the live binding). The namespace value + * itself was already evaluated into the pattern's temp, so its side + * effects (the dynamic import) keep their order. Rest elements would + * materialize the namespace object and keep their fence. */ +function lowerModuleNamespacePattern( + lowerer: Lowerer, + pattern: ts.ObjectBindingPattern, + isLet: boolean, + out: IrStmt[], +): void { + const nsType = lowerer.typeOf(pattern); + for (const el of pattern.elements.nodes) { + if (el.name === undefined) continue; + if (el.dotDotDotToken) { + lowerer.unsupported( + "SC1031", + el, + "rest bindings over module namespaces (the namespace object is not materialized — bind the exports you need by name)", + ); + } + const prop = el.propertyName ?? el.name; + const propName = + ts.isIdentifier(prop) || + ts.isComputedPropertyName(prop) || + ts.isStringLiteralLike(prop) || + ts.isNumericLiteral(prop) + ? patternKeyNameOf(lowerer, prop as ts.PropertyName) + : null; + let exported = + propName === null ? undefined : lowerer.checker.getPropertyOfType(nsType, propName); + if (exported !== undefined && exported.flags & ts.SymbolFlags.Alias) { + exported = lowerer.checker.getAliasedSymbol(exported); + } + const declared = exported?.valueDeclaration?.name as ts.Node | undefined; + if (declared === undefined || !ts.isIdentifier(declared)) { + lowerer.unsupported( + "SC1031", + el, + "destructuring module namespace members that are not named value exports", + ); + } + let value = lowerer.lowerExpr(declared); + if (el.initializer) value = applyBindingDefault(lowerer, el, value); + lowerer.bindPatternTarget(el.name, value, isLet, out); + } +} + function patternKeyNameOf(lowerer: Lowerer, prop: ts.PropertyName): string | null { if (ts.isIdentifier(prop) || ts.isPrivateIdentifier(prop)) return prop.text; if (ts.isComputedPropertyName(prop)) return lowerer.foldedStringKeyOf(prop.expression); diff --git a/packages/compiler/src/frontend/type-mapper.ts b/packages/compiler/src/frontend/type-mapper.ts index ea706992a..ab29f08ff 100644 --- a/packages/compiler/src/frontend/type-mapper.ts +++ b/packages/compiler/src/frontend/type-mapper.ts @@ -4309,6 +4309,27 @@ function mapCallSignature(sig: ts.Signature, ctx: TypeMapperCtx): IrType | null ) ) pt = DYN; + // A DEFAULTED program-source parameter of a function, arrow or function + // expression that the checker types `any` — a default under an + // `(...args: any[]) => any` wrapper slot (`wrap((limit = 5) => ...)`), + // where the contextual `any` wins over the default's type: its binding + // is already the checked-dynamic kind inside the body + // (dynFallbackType), so the callable's ABI slot is the same dyn value. + // Other `any` parameters keep their existing paths (an object literal + // holding such a callback stays a checked-dynamic object). + if ( + pt === null && + !rest && + decl !== undefined && + ts.isParameter(decl) && + decl.initializer !== undefined && + (ts.isArrowFunction(decl.parent) || + ts.isFunctionExpression(decl.parent) || + ts.isFunctionDeclaration(decl.parent)) && + !decl.getSourceFile().isDeclarationFile && + (checker.getTypeOfSymbol(p).flags & ts.TypeFlags.Any) !== 0 + ) + pt = DYN; if (pt) pt = jsOpenObjectType(decl, pt, ctx.shapes, ctx.unions); // Belt and braces for non-strict type worlds: an optional param's // ABI slot is always the undefined-armed union (strictNullChecks diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6470-any-destructuring.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6470-any-destructuring.ts.json new file mode 100644 index 000000000..eb1120cae --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6470-any-destructuring.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6470-any-destructuring.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6471-any-method-chains.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6471-any-method-chains.ts.json new file mode 100644 index 000000000..463bc3a67 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6471-any-method-chains.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6471-any-method-chains.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6472-namespace-destructuring/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6472-namespace-destructuring/main.ts.json new file mode 100644 index 000000000..ec90234c8 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6472-namespace-destructuring/main.ts.json @@ -0,0 +1,7 @@ +{ + "order": [ + "/tests/corpus/6472-namespace-destructuring/lib.ts", + "/tests/corpus/6472-namespace-destructuring/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6473-any-parameter-values.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6473-any-parameter-values.ts.json new file mode 100644 index 000000000..1747655f0 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6473-any-parameter-values.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6473-any-parameter-values.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6474-async-any-callbacks/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6474-async-any-callbacks/main.ts.json new file mode 100644 index 000000000..6a7e7a966 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6474-async-any-callbacks/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6474-async-any-callbacks/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6475-js-promise-resolve-adoption/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6475-js-promise-resolve-adoption/main.ts.json new file mode 100644 index 000000000..d27bf5fd6 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6475-js-promise-resolve-adoption/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6475-js-promise-resolve-adoption/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/761-any-objects.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/761-any-objects.ts.json new file mode 100644 index 000000000..aa907b2ae --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/761-any-objects.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/761-any-objects.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/retired-engine-761-any-objects.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/retired-engine-761-any-objects.ts.json deleted file mode 100644 index ed04bcd16..000000000 --- a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/retired-engine-761-any-objects.ts.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "order": [ - "/tests/diagnostics/retired-engine-761-any-objects.ts" - ], - "diags": [] -} diff --git a/tests/corpus/6470-any-destructuring.ts b/tests/corpus/6470-any-destructuring.ts new file mode 100644 index 000000000..3d615472d --- /dev/null +++ b/tests/corpus/6470-any-destructuring.ts @@ -0,0 +1,55 @@ +// Destructuring `any` values in TypeScript: a function typed to return +// `any` (an untyped command runner), an awaited `any` promise, JSON.parse, +// and an `any` table read by key all destructure through the +// checked-dynamic value, with renames, defaults, nesting, rest, holes and +// Node's TypeError for a null source. +function run(command: string): any { + return { stdout: ` ${command} output \n`, stderr: "", exitCode: 0, meta: { pid: 42 } }; +} + +const { stdout, stderr } = run("echo"); +console.log(JSON.stringify(stdout.toString().trim()), stderr.length); + +const { exitCode: code, missing, meta: { pid }, ...rest } = run("ls"); +console.log(code, missing, pid, Object.keys(rest).join(",")); + +const { signal = "none", exitCode = 9 } = run("true"); +console.log(signal, exitCode); + +const [first, , third = "default", ...others] = JSON.parse('["a", "b", null, "d", "e"]'); +console.log(first, third, others.length); + +const { list: [head, ...tail] } = JSON.parse('{"list":[1,2,3]}'); +console.log(head, tail); + +async function fetchAny(): Promise { + return { body: "payload", status: 200 }; +} + +async function main(): Promise { + const { body, status } = await fetchAny(); + console.log(body, status); + const pending = fetchAny(); + const { status: again } = await pending; + console.log(again + 1); +} + +const table: any = { a: { mode: 0o755, path: "/bin" }, b: { mode: 0o644, path: "/etc" } }; +for (const key in table) { + const { mode, path } = table[key]; + console.log(key, mode.toString(8), path); +} + +function lookup(found: boolean): any { + return found ? { anything: 1 } : null; +} +for (const found of [true, false]) { + try { + const { anything } = lookup(found); + console.log(anything); + } catch (e) { + console.log(e instanceof TypeError, (e as Error).message); + } +} + +main(); diff --git a/tests/corpus/6471-any-method-chains.ts b/tests/corpus/6471-any-method-chains.ts new file mode 100644 index 000000000..3bfce5fba --- /dev/null +++ b/tests/corpus/6471-any-method-chains.ts @@ -0,0 +1,24 @@ +// Method chains over `any`-derived strings: each step that answers a fresh +// native value (split's string[]) keeps chaining through the +// checked-dynamic value — map, filter(Boolean), pop, join — and the result +// checks back into typed slots. +function exec(): any { + return { stdout: " 3f2a\n 9c1b \n\n77de\n", stderr: "warn: shallow\n" }; +} + +const { stdout, stderr } = exec(); +let ids: string[] = []; +ids = stdout + .split("\n") + .map((s: string) => s.trim()) + .filter(Boolean); +console.log(ids.length, ids.join(", ")); + +const last: string = stdout.trim().split("\n").pop() ?? ""; +console.log(JSON.stringify(last)); + +const firstWord: string = stderr.trim().split(":")[0]; +console.log(firstWord, stderr.trim().split(" ").length); + +const words: string[] = stdout.trim().split(/\s+/); +console.log(words.length, stdout.split("\n").length, stdout.trim().split("\n").join("|")); diff --git a/tests/corpus/6472-namespace-destructuring/lib.ts b/tests/corpus/6472-namespace-destructuring/lib.ts new file mode 100644 index 000000000..d25d53d74 --- /dev/null +++ b/tests/corpus/6472-namespace-destructuring/lib.ts @@ -0,0 +1,21 @@ +export function add(a: number, b: number): number { + return a + b; +} + +export const label = "lib"; + +export let counter = 1; + +export function bump(): void { + counter++; +} + +export class Shape { + side: number; + constructor(side: number) { + this.side = side; + } + area(): number { + return this.side * this.side; + } +} diff --git a/tests/corpus/6472-namespace-destructuring/main.ts b/tests/corpus/6472-namespace-destructuring/main.ts new file mode 100644 index 000000000..dee614272 --- /dev/null +++ b/tests/corpus/6472-namespace-destructuring/main.ts @@ -0,0 +1,19 @@ +// Destructuring a program module's namespace, statically +// (`import * as ns`) and from a dynamic import: each element reads the +// export at the pattern's position — renames and defaults included — and +// binds a copy of its value (a later reassignment of an exported `let` is +// visible through the namespace, not through the binding). +import * as ns from "./lib.ts"; + +const { add, label, counter, bump, Shape: Ctor } = ns; +console.log(add(2, 3), label, counter); +bump(); +console.log(counter, ns.counter); +console.log(new Ctor(4).area()); + +async function main(): Promise { + const { add: plus, label: name = "unused" } = await import("./lib.ts"); + console.log(plus(10, 20), name); +} + +main(); diff --git a/tests/corpus/6473-any-parameter-values.ts b/tests/corpus/6473-any-parameter-values.ts new file mode 100644 index 000000000..399332551 --- /dev/null +++ b/tests/corpus/6473-any-parameter-values.ts @@ -0,0 +1,23 @@ +// Function values whose defaulted parameter the checker types `any`: under +// an `(...args: any[]) => any` wrapper slot the contextual `any` wins over +// the default's type. The callable's slot is the checked-dynamic value the +// body already reads. +function traced any>(name: string, fn: F): F { + console.log("wrap", name); + return fn; +} + +function configured(raw = process.env["SCRIPTC_TEST_UNSET_WIDTH"]): number | undefined { + const width = Number.parseInt(raw ?? "", 10); + return Number.isSafeInteger(width) && width > 0 ? width : undefined; +} + +const layout = traced("layout", (width = configured() ?? 80) => { + return { width, gutter: 2 }; +}); +console.log(JSON.stringify(layout()), JSON.stringify(layout(120))); + +const scaled = traced("scaled", function (factor = 2, base = 10) { + return base * factor; +}); +console.log(scaled(), scaled(3), scaled(undefined, 1)); diff --git a/tests/corpus/6474-async-any-callbacks/main.ts b/tests/corpus/6474-async-any-callbacks/main.ts new file mode 100644 index 000000000..c84ce37ba --- /dev/null +++ b/tests/corpus/6474-async-any-callbacks/main.ts @@ -0,0 +1,41 @@ +// Async callbacks whose result the checker types `Promise` handed to +// a JavaScript library typed `(...) => A | Promise` (A = any collapses +// the slot to `any`): the callback value still returns a promise, and its +// payload is the checked-dynamic value the body produced. +import retry from "retry-lite"; + +function loose(attempt: number): any { + if (attempt < 3) throw new Error(`attempt ${attempt} failed`); + return { text: `ok after ${attempt}`, size: attempt * 10 }; +} + +async function main(): Promise { + const got = await retry(async (_bail, attempt) => loose(attempt), { + retries: 5, + onRetry: (e: Error) => console.log("retry:", e.message), + }); + console.log(got.text, got.size); + + const parsed = await retry( + async () => { + const value = JSON.parse('{"items":[1,2,3]}'); + return value; + }, + { retries: 1 }, + ); + console.log(parsed.items.length); + + try { + await retry( + async (bail) => { + bail(new Error("gave up")); + throw new Error("ignored"); + }, + { retries: 2 }, + ); + } catch (e) { + console.log("caught:", (e as Error).message); + } +} + +main(); diff --git a/tests/corpus/6474-async-any-callbacks/node_modules/retry-lite/index.d.ts b/tests/corpus/6474-async-any-callbacks/node_modules/retry-lite/index.d.ts new file mode 100644 index 000000000..896f81f9d --- /dev/null +++ b/tests/corpus/6474-async-any-callbacks/node_modules/retry-lite/index.d.ts @@ -0,0 +1,15 @@ +declare function retryLite( + fn: retryLite.RetryFunction, + opts: retryLite.Options +): Promise; + +declare namespace retryLite { + interface Options { + retries?: number; + onRetry?: (e: Error) => any; + } + + type RetryFunction = (bail: (e: Error) => void, attempt: number) => A | Promise; +} + +export = retryLite; diff --git a/tests/corpus/6474-async-any-callbacks/node_modules/retry-lite/index.js b/tests/corpus/6474-async-any-callbacks/node_modules/retry-lite/index.js new file mode 100644 index 000000000..a1ae718ab --- /dev/null +++ b/tests/corpus/6474-async-any-callbacks/node_modules/retry-lite/index.js @@ -0,0 +1,36 @@ +"use strict"; + +function retryLite(fn, opts) { + const retries = opts.retries === undefined ? 3 : opts.retries; + return new Promise((resolve, reject) => { + let attempt = 0; + let bailed = false; + const bail = (err) => { + bailed = true; + reject(err); + }; + const onError = (err) => { + if (bailed) return; + if (attempt > retries) { + reject(err); + return; + } + if (opts.onRetry) opts.onRetry(err); + runAttempt(); + }; + const runAttempt = () => { + attempt++; + let val; + try { + val = fn(bail, attempt); + } catch (err) { + onError(err); + return; + } + Promise.resolve(val).then(resolve).catch(onError); + }; + runAttempt(); + }); +} + +module.exports = retryLite; diff --git a/tests/corpus/6474-async-any-callbacks/node_modules/retry-lite/package.json b/tests/corpus/6474-async-any-callbacks/node_modules/retry-lite/package.json new file mode 100644 index 000000000..251988634 --- /dev/null +++ b/tests/corpus/6474-async-any-callbacks/node_modules/retry-lite/package.json @@ -0,0 +1,6 @@ +{ + "name": "retry-lite", + "version": "1.0.0", + "main": "./index.js", + "types": "./index.d.ts" +} diff --git a/tests/corpus/6474-async-any-callbacks/tsconfig.json b/tests/corpus/6474-async-any-callbacks/tsconfig.json new file mode 100644 index 000000000..9297bdb5b --- /dev/null +++ b/tests/corpus/6474-async-any-callbacks/tsconfig.json @@ -0,0 +1,3 @@ +{ + "compilerOptions": { "strict": true, "types": ["node"], "esModuleInterop": true } +} diff --git a/tests/corpus/6475-js-promise-resolve-adoption/main.ts b/tests/corpus/6475-js-promise-resolve-adoption/main.ts new file mode 100644 index 000000000..61a58dbe2 --- /dev/null +++ b/tests/corpus/6475-js-promise-resolve-adoption/main.ts @@ -0,0 +1,23 @@ +// JavaScript's Promise.resolve over a checked-dynamic value that holds a +// promise (a TypeScript async callback's result handed back into a +// JavaScript library) answers that same promise — adopted, not wrapped — +// and wraps plain values. +import { sameAfterResolve, viaExecutor, viaResolve, viaThen } from "calls"; + +async function main(): Promise { + console.log(await viaResolve(async (): Promise => "resolved")); + console.log(await viaThen(async (): Promise => 42)); + console.log(await viaExecutor(async (): Promise => "executor")); + console.log(await viaExecutor((): string => "plain")); + try { + await viaExecutor(async (): Promise => { + throw new Error("callback failed"); + }); + } catch (e) { + console.log("caught:", (e as Error).message); + } + const pending = Promise.resolve("p"); + console.log(sameAfterResolve(pending), sameAfterResolve("p")); +} + +main(); diff --git a/tests/corpus/6475-js-promise-resolve-adoption/node_modules/calls/index.d.ts b/tests/corpus/6475-js-promise-resolve-adoption/node_modules/calls/index.d.ts new file mode 100644 index 000000000..d1040a227 --- /dev/null +++ b/tests/corpus/6475-js-promise-resolve-adoption/node_modules/calls/index.d.ts @@ -0,0 +1,4 @@ +export declare function viaResolve(fn: () => A | Promise): Promise; +export declare function viaThen(fn: () => A | Promise): Promise; +export declare function viaExecutor(fn: () => A | Promise): Promise; +export declare function sameAfterResolve(value: unknown): boolean; diff --git a/tests/corpus/6475-js-promise-resolve-adoption/node_modules/calls/index.js b/tests/corpus/6475-js-promise-resolve-adoption/node_modules/calls/index.js new file mode 100644 index 000000000..fb55d867c --- /dev/null +++ b/tests/corpus/6475-js-promise-resolve-adoption/node_modules/calls/index.js @@ -0,0 +1,21 @@ +"use strict"; + +function viaResolve(fn) { + return Promise.resolve(fn()); +} + +function viaThen(fn) { + return Promise.resolve(fn()).then((value) => value); +} + +function viaExecutor(fn) { + return new Promise((resolve, reject) => { + Promise.resolve(fn()).then(resolve).catch(reject); + }); +} + +function sameAfterResolve(value) { + return Promise.resolve(value) === value; +} + +module.exports = { viaResolve, viaThen, viaExecutor, sameAfterResolve }; diff --git a/tests/corpus/6475-js-promise-resolve-adoption/node_modules/calls/package.json b/tests/corpus/6475-js-promise-resolve-adoption/node_modules/calls/package.json new file mode 100644 index 000000000..c85cd1f4b --- /dev/null +++ b/tests/corpus/6475-js-promise-resolve-adoption/node_modules/calls/package.json @@ -0,0 +1,6 @@ +{ + "name": "calls", + "version": "1.0.0", + "main": "./index.js", + "types": "./index.d.ts" +} diff --git a/tests/diagnostics/retired-engine-761-any-objects.ts b/tests/corpus/761-any-objects.ts similarity index 67% rename from tests/diagnostics/retired-engine-761-any-objects.ts rename to tests/corpus/761-any-objects.ts index 59796020b..45e6e6f82 100644 --- a/tests/diagnostics/retired-engine-761-any-objects.ts +++ b/tests/corpus/761-any-objects.ts @@ -1,6 +1,6 @@ -// Object/array literals in `any` slots build natively in the island; -// property reads/writes, computed keys, and method calls go through the -// engine (this-binding and coercions JS-exact). +// Object/array literals in `any` slots: property reads/writes, computed +// keys, and method calls go through the checked-dynamic value, including a +// member read on a fresh typed call result (`s.split(" ").length`). const o: any = { a: 1, b: "x", nested: { deep: true } }; o.a = o.a + 10; o["c"] = 2.5; diff --git a/tests/harness/__snapshots__/retired-engine-2083-destructuring-island-decl.ts.txt b/tests/harness/__snapshots__/retired-engine-2083-destructuring-island-decl.ts.txt index 531002a4b..4bbff5a33 100644 --- a/tests/harness/__snapshots__/retired-engine-2083-destructuring-island-decl.ts.txt +++ b/tests/harness/__snapshots__/retired-engine-2083-destructuring-island-decl.ts.txt @@ -1,66 +1,3 @@ -retired-engine-2083-destructuring-island-decl.ts:9:5 - error SC2011: destructuring on 'any'-typed values cannot compile statically - - 8 | const a: any = [1, "two", true, 4, 5]; - 9 | var [x, , y, ...rest] = a; - | ^~~~~~~~~~~~~~~~~~~~~ - 10 | console.log(`${x} ${y} ${rest.length} ${rest[0]} ${rest[1]}`); - - hint: use a supported typed operation, or 'unknown' with a checked cast ('x as T') - -retired-engine-2083-destructuring-island-decl.ts:12:7 - error SC2011: destructuring on 'any'-typed values cannot compile statically - - 11 | const o: any = { p: 1, q: { r: "deep" } }; - 12 | const { p, q: { r } } = o; - | ^~~~~~~~~~~~~~~~~~~ - 13 | console.log(`${p} ${r}`); - - hint: use a supported typed operation, or 'unknown' with a checked cast ('x as T') - -retired-engine-2083-destructuring-island-decl.ts:14:7 - error SC2011: destructuring on 'any'-typed values cannot compile statically - - 13 | console.log(`${p} ${r}`); - 14 | const { missing = 42 } = o; - | ^~~~~~~~~~~~~~~~~~~~ - 15 | console.log(`${missing}`); - - hint: use a supported typed operation, or 'unknown' with a checked cast ('x as T') - -retired-engine-2083-destructuring-island-decl.ts:16:7 - error SC2011: destructuring on 'any'-typed values cannot compile statically - - 15 | console.log(`${missing}`); - 16 | const { p: p2 = 9, ...others } = o; - | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~ - 17 | console.log(`${p2} ${others.q.r}`); - - hint: use a supported typed operation, or 'unknown' with a checked cast ('x as T') - -retired-engine-2083-destructuring-island-decl.ts:17:22 - error SC2004: uses of 'others' inherit the blocker on its declaration - - 16 | const { p: p2 = 9, ...others } = o; - 17 | console.log(`${p2} ${others.q.r}`); - | ^~~~~~~~~~ - 18 | var [] = [1, 2] as any; - - hint: the declaration of 'others' did not compile — fix the diagnostic reported there and these sites clear with it - -retired-engine-2083-destructuring-island-decl.ts:19:5 - error SC2011: destructuring on 'any'-typed values cannot compile statically - - 18 | var [] = [1, 2] as any; - 19 | var {} = o; - | ^~~~~~ - 20 | console.log("empties ok"); - - hint: use a supported typed operation, or 'unknown' with a checked cast ('x as T') - -retired-engine-2083-destructuring-island-decl.ts:22:11 - error SC2011: destructuring on 'any'-typed values cannot compile statically - - 21 | const u: any = undefined; - 22 | try { var {} = u; } catch (e) { console.log("obj-undef throws"); } - | ^~~~~~ - 23 | try { var [] = 0 as any; } catch (e) { console.log("arr-num throws"); } - - hint: use a supported typed operation, or 'unknown' with a checked cast ('x as T') - retired-engine-2083-destructuring-island-decl.ts:23:11 - error SC2011: destructuring on 'any'-typed values cannot compile statically 22 | try { var {} = u; } catch (e) { console.log("obj-undef throws"); } diff --git a/tests/harness/__snapshots__/retired-engine-2104-computed-key-destructuring.ts.txt b/tests/harness/__snapshots__/retired-engine-2104-computed-key-destructuring.ts.txt index b41cd4d10..415eb5895 100644 --- a/tests/harness/__snapshots__/retired-engine-2104-computed-key-destructuring.ts.txt +++ b/tests/harness/__snapshots__/retired-engine-2104-computed-key-destructuring.ts.txt @@ -1,12 +1,10 @@ -retired-engine-2104-computed-key-destructuring.ts:12:9 - error SC2011: destructuring on 'any'-typed values cannot compile statically +retired-engine-2104-computed-key-destructuring.ts:12:11 - error SC1031: destructuring with computed keys that do not fold to one property name is not supported yet 11 | function pick(key: string) { 12 | const { [key]: got } = src; - | ^~~~~~~~~~~~~~~~~~~~ + | ^~~~~~~~~~ 13 | return got; - hint: use a supported typed operation, or 'unknown' with a checked cast ('x as T') - retired-engine-2104-computed-key-destructuring.ts:13:10 - error SC2004: uses of 'got' inherit the blocker on its declaration 12 | const { [key]: got } = src; @@ -16,15 +14,13 @@ retired-engine-2104-computed-key-destructuring.ts:13:10 - error SC2004: uses of hint: the declaration of 'got' did not compile — fix the diagnostic reported there and these sites clear with it -retired-engine-2104-computed-key-destructuring.ts:17:9 - error SC2011: destructuring on 'any'-typed values cannot compile statically +retired-engine-2104-computed-key-destructuring.ts:17:11 - error SC1031: destructuring with computed keys that do not fold to one property name is not supported yet 16 | function pick2(key: string) { 17 | const { [key]: got = "dflt" } = src; - | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + | ^~~~~~~~~~~~~~~~~~~ 18 | return got; - hint: use a supported typed operation, or 'unknown' with a checked cast ('x as T') - retired-engine-2104-computed-key-destructuring.ts:18:10 - error SC2004: uses of 'got' inherit the blocker on its declaration 17 | const { [key]: got = "dflt" } = src; @@ -34,15 +30,6 @@ retired-engine-2104-computed-key-destructuring.ts:18:10 - error SC2004: uses of hint: the declaration of 'got' did not compile — fix the diagnostic reported there and these sites clear with it -retired-engine-2104-computed-key-destructuring.ts:23:7 - error SC2011: destructuring on 'any'-typed values cannot compile statically - - 22 | const key2 = "y"; - 23 | const { x: sx, [key2]: sy } = src; - | ^~~~~~~~~~~~~~~~~~~~~~~~~~~ - 24 | console.log(`${sx} ${sy}`); - - hint: use a supported typed operation, or 'unknown' with a checked cast ('x as T') - retired-engine-2104-computed-key-destructuring.ts:30:38 - error SC2009: values of type '[any?]' cannot be compiled: the tuple shape is supported, but its element type 'any' does not compile 29 | let order = (n: any): any => { traceS += `${n},`; return n; }; diff --git a/tests/harness/__snapshots__/retired-engine-761-any-objects.ts.txt b/tests/harness/__snapshots__/retired-engine-761-any-objects.ts.txt deleted file mode 100644 index 008901f2d..000000000 --- a/tests/harness/__snapshots__/retired-engine-761-any-objects.ts.txt +++ /dev/null @@ -1,6 +0,0 @@ -retired-engine-761-any-objects.ts:13:38 - error SC1090: reading 'length' from a value of type 'any' is not supported yet - - 12 | const s: any = "hello world"; - 13 | console.log(`${s.toUpperCase()}`, `${s.split(" ").length}`, `${s.charAt(4)}`); - | ^~~~~~~~~~~~~~~~~~~ - 14 | const num: any = 3.14159; \ No newline at end of file From 1fb1d9c829002f78b5449b1937897ad32647a490 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Mon, 19 Oct 2026 23:16:01 -0700 Subject: [PATCH 058/122] Let constructor functions inherit from EventEmitter and the stream classes Bundled npm code (glob, yazl, from2, multistream) subclasses Node's classes with constructor functions: util.inherits(Ctor, EventEmitter) and EventEmitter.call(this). util.inherits received an identity token and threw ERR_INVALID_ARG_TYPE, EventEmitter.call(this) and EventEmitter.prototype were refused, and `this instanceof Ctor` against a function was refused. A new runtime unit (scr_dyn_builtin_class.c, linked by the new dynBuiltinClass feature) provides EventEmitter, the legacy Stream and the stream classes as untyped constructor functions with Node's prototype chain. EventEmitter's prototype ports lib/events.js over the object's own _events, _eventsCount and _maxListeners properties; Stream adds the legacy pipe. util.inherits, Base.call/apply(this, ...), Base.prototype, instanceof on untyped values and JavaScript value reads of these classes use them. Constructing over a stream class throws SC2020 where it runs. instanceof against a compiled constructor function compares its prototype with the value's prototype chain. Co-Authored-By: Claude Opus 5.5 --- .../limitations/builtin-function-values.mdx | 6 +- .../src/backend/executable-features.ts | 1 + packages/compiler/src/backend/library-pack.ts | 1 + .../compiler/src/backend/llvm/lib-shared.ts | 3 + .../src/backend/native-link-info-core.ts | 1 + .../compiler/src/backend/native/contracts.ts | 5 + .../compiler/src/backend/native/executable.ts | 4 + .../src/backend/runtime-pack-targets.test.ts | 1 + .../compiler/src/backend/runtime-pack.test.ts | 1 + packages/compiler/src/backend/warm-cache.ts | 1 + .../src/executable/executable-cache.test.ts | 1 + .../src/executable/executable-cache.ts | 2 + .../lowering/builtins/builtin-class-dyn.ts | 149 ++ .../src/frontend/lowering/class-prototypes.ts | 14 + .../lowering/expressions/identifiers.ts | 5 + .../lowering/expressions/relational.ts | 25 + .../src/frontend/lowering/lower-calls.ts | 6 + .../src/frontend/lowering/lower-inspect.ts | 19 +- .../compiler/src/frontend/lowering/lowerer.ts | 7 + packages/compiler/src/index.ts | 1 + packages/compiler/src/ir/builtin-effects.ts | 3 + .../compiler/src/ir/builtin-signatures.ts | 10 + packages/compiler/src/ir/runtime-features.ts | 20 +- packages/compiler/src/native/cli.ts | 1 + packages/compiler/src/native/prepare.ts | 1 + .../compiler/src/native/sanitizer.test.ts | 1 + ...6453-constructor-function-emitter.cjs.json | 6 + ...constructor-function-stream-bases.cjs.json | 6 + .../runtime-pack-matrix.mjs | 4 + packages/runtime/src/scr_dyn.h | 6 + packages/runtime/src/scr_dyn_builtin_class.c | 1469 +++++++++++++++++ .../6453-constructor-function-emitter.cjs | 114 ++ ...6454-constructor-function-stream-bases.cjs | 65 + .../self-hosting-native-toolchain.test.ts | 1 + .../self-hosting-runtime-features.test.ts | 1 + .../harness/self-hosting-runtime-pack.test.ts | 1 + 36 files changed, 1955 insertions(+), 7 deletions(-) create mode 100644 packages/compiler/src/frontend/lowering/builtins/builtin-class-dyn.ts create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6453-constructor-function-emitter.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6454-constructor-function-stream-bases.cjs.json create mode 100644 packages/runtime/src/scr_dyn_builtin_class.c create mode 100644 tests/corpus/6453-constructor-function-emitter.cjs create mode 100644 tests/corpus/6454-constructor-function-stream-bases.cjs diff --git a/docs/content/limitations/builtin-function-values.mdx b/docs/content/limitations/builtin-function-values.mdx index c08540c1a..f3902e75b 100644 --- a/docs/content/limitations/builtin-function-values.mdx +++ b/docs/content/limitations/builtin-function-values.mdx @@ -12,7 +12,11 @@ Other immutable aliases of table-backed Node builtins support direct calls, but Native calls accept standard process streams; custom native streams and mutations to `util.inspect.colors` remain unsupported. `util.inherits` links the prototypes of constructor functions, and `util.deprecate`, `util.callbackify`, and `util.debuglog` return native function values that forward receivers and arguments. -Class constructors passed to `util.inherits` or `util.deprecate` are refused. +In JavaScript, a constructor function can inherit from `EventEmitter`, the legacy `Stream` constructor, or a stream class through `util.inherits` or `Object.setPrototypeOf`. +`EventEmitter.call(this)` and `Stream.call(this)` initialize the object as Node does, and its listener methods keep their state in the object's own `_events`, `_eventsCount`, and `_maxListeners` properties. +The `captureRejections` option and `events.errorMonitor` listeners are not supported on such objects. +Calling `Readable`, `Writable`, `Duplex`, `Transform`, or `PassThrough` with a constructor function's receiver throws `SC2020` where the call runs. +Other class constructors passed to `util.inherits` or `util.deprecate` are refused. Deprecation warnings are emitted on the next tick and follow assignments to `process.noDeprecation` and `process.throwDeprecation`; compiled binaries take no Node command-line flags such as `--no-deprecation` or `--trace-deprecation`, and `process.traceDeprecation` is unsupported. A stored `util.deprecate` reports a `length` of 4, where Node reports 3. In TypeScript, a `util.callbackify` result must reach a callback whose error parameter accepts `null`; the @types/node overloads that type it as non-null are refused. diff --git a/packages/compiler/src/backend/executable-features.ts b/packages/compiler/src/backend/executable-features.ts index eeb6c6900..a6e228b2e 100644 --- a/packages/compiler/src/backend/executable-features.ts +++ b/packages/compiler/src/backend/executable-features.ts @@ -31,6 +31,7 @@ export function executableLinkFeatures(mod: IrModule): NativeLinkFeatures { bigint: features.bigint, searchParams: features.searchParams, qs: features.qs, + dynBuiltinClass: features.dynBuiltinClass, parseArgs: features.parseArgs, stream: features.stream, net: features.net, diff --git a/packages/compiler/src/backend/library-pack.ts b/packages/compiler/src/backend/library-pack.ts index 5070a47a8..efabdc9d4 100644 --- a/packages/compiler/src/backend/library-pack.ts +++ b/packages/compiler/src/backend/library-pack.ts @@ -42,6 +42,7 @@ export async function compilePackedLibrary( bigint: options.bigint ?? false, searchParams: options.searchParams ?? false, qs: false, + dynBuiltinClass: false, parseArgs: false, stream: false, net: false, diff --git a/packages/compiler/src/backend/llvm/lib-shared.ts b/packages/compiler/src/backend/llvm/lib-shared.ts index 44741edc8..2b25cc92e 100644 --- a/packages/compiler/src/backend/llvm/lib-shared.ts +++ b/packages/compiler/src/backend/llvm/lib-shared.ts @@ -1404,6 +1404,9 @@ export const LIB_FN_SYMS: Record = { "dyn.classPrototype": "scr_dyn_class_prototype", "dyn.classBasePrototype": "scr_dyn_class_base_prototype", "dyn.classInherit": "scr_dyn_class_inherit", + "dyn.builtinClass": "scr_dyn_builtin_class", + "dyn.builtinInstanceOf": "scr_dyn_builtin_instanceof", + "dyn.functionInstanceOf": "scr_dyn_function_instanceof", "dyn.classSuper": "scr_dyn_class_super", "dyn.assignPrototype": "scr_dyn_assign_prototype", "dyn.definePrototypeProps": "scr_dyn_define_prototype_props", diff --git a/packages/compiler/src/backend/native-link-info-core.ts b/packages/compiler/src/backend/native-link-info-core.ts index 86146259d..d7cf78327 100644 --- a/packages/compiler/src/backend/native-link-info-core.ts +++ b/packages/compiler/src/backend/native-link-info-core.ts @@ -46,6 +46,7 @@ export interface NativeLinkFeatures { bigint: boolean; searchParams: boolean; qs: boolean; + dynBuiltinClass: boolean; parseArgs: boolean; stream: boolean; net: boolean; diff --git a/packages/compiler/src/backend/native/contracts.ts b/packages/compiler/src/backend/native/contracts.ts index 33b13960e..d978d4387 100644 --- a/packages/compiler/src/backend/native/contracts.ts +++ b/packages/compiler/src/backend/native/contracts.ts @@ -150,6 +150,11 @@ export interface CcOptions { * line (escape-only programs ride the always-linked component encoder * and never flip this). */ qs?: boolean; + /** The program uses EventEmitter or a stream class as an untyped + * constructor value (a constructor function's base): compiles + * scr_dyn_builtin_class.c, which also references the inspection, + * dyn-invoke and symbol units. */ + dynBuiltinClass?: boolean; /** The program uses native util parsing, comparison, or styling. Optional * utility units also require their symbol, inspection, and warning features. */ parseArgs?: boolean; diff --git a/packages/compiler/src/backend/native/executable.ts b/packages/compiler/src/backend/native/executable.ts index 5ebb4796e..bbb720cc7 100644 --- a/packages/compiler/src/backend/native/executable.ts +++ b/packages/compiler/src/backend/native/executable.ts @@ -235,6 +235,10 @@ const RUNTIME_UNITS: readonly RuntimeUnit[] = [ (u) => u.opts.parseArgs && u.opts.inspect && u.opts.dynAsync && u.opts.dynInvoke && u.regex, "scr_util_callable.c", ), + unit( + (u) => u.opts.dynBuiltinClass && u.opts.inspect && u.opts.dynInvoke && u.opts.symbol, + "scr_dyn_builtin_class.c", + ), unit((u) => u.opts.stream, "scr_stream.c", "scr_fs_stream.c"), // The zlib stream classes reference both the zlib engine and the // stream unit. diff --git a/packages/compiler/src/backend/runtime-pack-targets.test.ts b/packages/compiler/src/backend/runtime-pack-targets.test.ts index fd57cb49d..8b0689c51 100644 --- a/packages/compiler/src/backend/runtime-pack-targets.test.ts +++ b/packages/compiler/src/backend/runtime-pack-targets.test.ts @@ -107,6 +107,7 @@ const features = { bigint: false, searchParams: false, qs: false, + dynBuiltinClass: false, parseArgs: false, stream: false, net: false, diff --git a/packages/compiler/src/backend/runtime-pack.test.ts b/packages/compiler/src/backend/runtime-pack.test.ts index 61d1dba5e..5af267d96 100644 --- a/packages/compiler/src/backend/runtime-pack.test.ts +++ b/packages/compiler/src/backend/runtime-pack.test.ts @@ -44,6 +44,7 @@ const BASE: NativeLinkFeatures = { bigint: false, searchParams: false, qs: false, + dynBuiltinClass: false, parseArgs: false, stream: false, net: false, diff --git a/packages/compiler/src/backend/warm-cache.ts b/packages/compiler/src/backend/warm-cache.ts index 2cd111060..b0736f102 100644 --- a/packages/compiler/src/backend/warm-cache.ts +++ b/packages/compiler/src/backend/warm-cache.ts @@ -50,6 +50,7 @@ export async function warmNativeCaches( bigint: false, searchParams: false, qs: false, + dynBuiltinClass: false, parseArgs: false, stream: false, net: false, diff --git a/packages/compiler/src/executable/executable-cache.test.ts b/packages/compiler/src/executable/executable-cache.test.ts index 1b42c3eca..cdd24ff7b 100644 --- a/packages/compiler/src/executable/executable-cache.test.ts +++ b/packages/compiler/src/executable/executable-cache.test.ts @@ -45,6 +45,7 @@ const native: EarlyExecutableNativeFeatures = { bigint: false, searchParams: false, qs: false, + dynBuiltinClass: false, parseArgs: false, stream: false, net: false, diff --git a/packages/compiler/src/executable/executable-cache.ts b/packages/compiler/src/executable/executable-cache.ts index 8c67c1006..349c07d42 100644 --- a/packages/compiler/src/executable/executable-cache.ts +++ b/packages/compiler/src/executable/executable-cache.ts @@ -71,6 +71,7 @@ export interface EarlyExecutableNativeFeatures { bigint: boolean; searchParams: boolean; qs: boolean; + dynBuiltinClass: boolean; parseArgs: boolean; stream: boolean; net: boolean; @@ -185,6 +186,7 @@ const BOOLEAN_NATIVE_KEYS = [ "bigint", "searchParams", "qs", + "dynBuiltinClass", "parseArgs", "stream", "net", diff --git a/packages/compiler/src/frontend/lowering/builtins/builtin-class-dyn.ts b/packages/compiler/src/frontend/lowering/builtins/builtin-class-dyn.ts new file mode 100644 index 000000000..ac73924ad --- /dev/null +++ b/packages/compiler/src/frontend/lowering/builtins/builtin-class-dyn.ts @@ -0,0 +1,149 @@ +import { numLit } from "../../../ir/build.js"; +import { BOOL, DYN, RUNTIME_EMITTER_CLASS, type IrExpr, type SrcLoc } from "../../../ir/ir.js"; +import * as ts from "../../ts7/adapter.js"; +import { locOf } from "../../program.js"; +import type { Lowerer } from "../lowerer.js"; + +/* EventEmitter and the stream classes as untyped constructor values, the + * bases of JavaScript constructor functions: + * + * function Bus() { EventEmitter.call(this); } + * util.inherits(Bus, EventEmitter); + * + * A compiled `class Bus extends EventEmitter` gets a native emitter prefix, + * but `new Bus()` on a constructor function builds an untyped object, so + * the base is the runtime's dyn constructor (scr_dyn_builtin_class.c): a + * function whose prototype carries Node's EventEmitter methods over the + * object's own `_events` table. The stream classes have prototype chains + * for util.inherits and instanceof; constructing over them refuses where + * it runs. These values appear only where a constructor function uses its + * base: util.inherits, `Base.call(this, …)` and `Base.apply(this, …)`, + * `Base.prototype` and `instanceof Base` on an untyped value. */ + +/** scr_dyn_builtin_class's kind for a runtime class, or null. */ +export function builtinClassKind(className: string): number | null { + switch (className) { + case RUNTIME_EMITTER_CLASS: + return 0; + case "%Readable": + return 2; + case "%Writable": + return 3; + case "%Duplex": + return 4; + case "%Transform": + return 5; + case "%PassThrough": + return 6; + default: + return null; + } +} + +/** The classes by builtin module member. JavaScript sources bind + * `require("events").EventEmitter` as a module member rather than a class + * value, and `require("stream")` is the legacy Stream constructor. */ +const MODULE_CLASS_KINDS: Readonly>>> = { + events: { EventEmitter: 0, default: 0 }, + stream: { Stream: 1, Readable: 2, Writable: 3, Duplex: 4, Transform: 5, PassThrough: 6 }, +}; + +/** The kind of a builtin module member that is one of those classes. */ +export function builtinMemberClassKind(module: string, member: string): number | null { + return MODULE_CLASS_KINDS[module]?.[member] ?? null; +} + +function unparenthesized(node: ts.Expression): ts.Expression { + let current = node; + while (ts.isParenthesizedExpression(current)) current = current.expression; + return current; +} + +/** The kind of a node that names one of those classes: a class value, a + * builtin module member, or the events and stream modules themselves. */ +export function builtinClassKindOf(lowerer: Lowerer, node: ts.Expression): number | null { + const expr = unparenthesized(node); + const mapped = lowerer.mapTypeOf(lowerer.typeOf(expr)); + if (mapped?.kind === "classval") return builtinClassKind(mapped.className); + const member = ts.isIdentifier(expr) + ? lowerer.builtinImportOf(expr) + : ts.isPropertyAccessExpression(expr) + ? lowerer.builtinMemberOf(expr) + : null; + if (member) return MODULE_CLASS_KINDS[member.module]?.[member.member] ?? null; + const module = lowerer.builtinNamespaceModuleOf(expr); + return module === "events" ? 0 : module === "stream" ? 1 : null; +} + +/** Whether evaluating the node can be skipped: a binding or a member of + * one, or of a builtin require. */ +function referenceOnly(node: ts.Expression): boolean { + const expr = unparenthesized(node); + if (ts.isIdentifier(expr)) return true; + if (ts.isPropertyAccessExpression(expr) && !expr.questionDotToken) { + const base = unparenthesized(expr.expression); + return ( + ts.isIdentifier(base) || + (ts.isCallExpression(base) && + ts.isIdentifier(base.expression) && + base.expression.text === "require" && + base.arguments.nodes.length === 1 && + ts.isStringLiteral(base.arguments.nodes[0]!)) + ); + } + return false; +} + +/** The untyped constructor named by a node, or null when it names none of + * those classes. */ +export function builtinClassDynValueOf(lowerer: Lowerer, node: ts.Expression): IrExpr | null { + const kind = builtinClassKindOf(lowerer, node); + if (kind === null) return null; + const loc = locOf(node); + const value = builtinClassValue(kind, loc); + if (referenceOnly(node)) return value; + return { + kind: "seqExpr", + stmts: [{ kind: "exprStmt", expr: lowerer.lowerExpr(node), loc }], + result: value, + type: DYN, + loc, + }; +} + +export function builtinClassValue(kind: number, loc: SrcLoc): IrExpr { + return { kind: "libCall", fn: "dyn.builtinClass", args: [numLit(kind, loc)], type: DYN, loc }; +} + +/** `Base.call(this, …)` / `Base.apply(this, args)` on one of those class + * values: Function.prototype.call/apply over the untyped constructor. */ +export function lowerBuiltinClassCall(lowerer: Lowerer, call: ts.CallExpression): IrExpr | null { + const callee = call.expression; + if (!ts.isPropertyAccessExpression(callee) || callee.questionDotToken) return null; + const method = callee.name.text; + if (method !== "call" && method !== "apply") return null; + if (call.arguments.nodes.some(ts.isSpreadElement)) return null; + const receiver = builtinClassDynValueOf(lowerer, callee.expression); + if (receiver === null) return null; + const loc = locOf(call); + return { + kind: "dynInvoke", + recv: receiver, + method, + calleeName: callee.getText(), + args: call.arguments.nodes.map((arg) => lowerer.lowerExprExpecting(arg, DYN)), + type: DYN, + loc, + }; +} + +/** `value instanceof Base` for an untyped value. */ +export function builtinClassInstanceOf(value: IrExpr, kind: number, loc: SrcLoc): IrExpr { + return { + kind: "libCall", + fn: "dyn.builtinInstanceOf", + args: [value, numLit(kind, loc)], + type: BOOL, + loc, + }; +} diff --git a/packages/compiler/src/frontend/lowering/class-prototypes.ts b/packages/compiler/src/frontend/lowering/class-prototypes.ts index d337dc802..6c6c7811d 100644 --- a/packages/compiler/src/frontend/lowering/class-prototypes.ts +++ b/packages/compiler/src/frontend/lowering/class-prototypes.ts @@ -1,4 +1,5 @@ import { dynUndefinedExpr, varRef } from "../../ir/build.js"; +import { builtinClassKind, builtinClassValue } from "./builtins/builtin-class-dyn.js"; import * as ts from "../ts7/adapter.js"; import { BOOL, @@ -450,6 +451,19 @@ export function lowerClassPrototypeData( exactClassOfReceiver(lowerer, expr.expression) ?? (stored?.kind === "classval" ? lowerer.classes.get(stored.className) : undefined); if (!info) return null; + // EventEmitter.prototype and the stream prototypes: the untyped + // constructor's prototype object (builtin-class-dyn.ts). + const builtinKind = builtinClassKind(info.def.name); + if (builtinKind !== null) { + const loc = locOf(expr); + return { + kind: "dynKeyGet", + value: builtinClassValue(builtinKind, loc), + key: { kind: "strLit", value: "prototype", type: STRING, loc }, + type: DYN, + loc, + }; + } const parent = expr.parent; const descriptorKey = ts.isCallExpression(parent) && diff --git a/packages/compiler/src/frontend/lowering/expressions/identifiers.ts b/packages/compiler/src/frontend/lowering/expressions/identifiers.ts index 2ee56573c..83761d131 100644 --- a/packages/compiler/src/frontend/lowering/expressions/identifiers.ts +++ b/packages/compiler/src/frontend/lowering/expressions/identifiers.ts @@ -1,5 +1,6 @@ /* Identifier references in expression position: lowerExprInner's identifier branch and its helpers. */ import { cjsMemberAliasValueOf } from "../cjs-exports-object.js"; +import { builtinClassValue, builtinMemberClassKind } from "../builtins/builtin-class-dyn.js"; import * as ts from "../../ts7/adapter.js"; import * as posix from "node:path/posix"; import { type Lowerer, PoisonError } from "../lowerer.js"; @@ -524,6 +525,10 @@ export function lowerIdentifierExpr(lowerer: Lowerer, expr: ts.Identifier, loc: // the same identity-token story as stdlib globals above (the // harness adds worker_threads.Worker to its identity Set). if (isJsSourceFile(expr.getSourceFile())) { + // EventEmitter and the stream classes are the runtime's untyped + // constructors, so constructor functions can inherit from them. + const classKind = builtinMemberClassKind(bi.module, bi.member); + if (classKind !== null) return builtinClassValue(classKind, loc); // A function member is a function value that fences when called. if (builtinModuleFnOf(lowerer, bi.module, bi.member)) return builtinFunctionFenceValue(lowerer, bi, expr, loc); diff --git a/packages/compiler/src/frontend/lowering/expressions/relational.ts b/packages/compiler/src/frontend/lowering/expressions/relational.ts index 619c0fe37..8accf2994 100644 --- a/packages/compiler/src/frontend/lowering/expressions/relational.ts +++ b/packages/compiler/src/frontend/lowering/expressions/relational.ts @@ -1,5 +1,6 @@ /* instanceof and in, including private-name and runtime-key in. */ import * as ts from "../../ts7/adapter.js"; +import { builtinClassInstanceOf, builtinClassKind } from "../builtins/builtin-class-dyn.js"; import { checkedPrivateIn, localPrivateBrandClass, @@ -471,6 +472,22 @@ export function lowerInstanceOf(lowerer: Lowerer, expr: ts.BinaryExpression, loc const value = lowerer.coerceInto(expr.left, lowerer.lowerExpr(expr.left), DYN); return checkedClassInstanceOf(lowerer, value, constructor, loc); } + // `this instanceof Glob` in a JavaScript constructor function: the + // function's prototype against the value's prototype chain. + if ( + constructor?.type.kind === "func" && + isJsSourceFile(expr.getSourceFile()) && + lowerer.dynConvertible(constructor.type) + ) { + const value = lowerer.coerceInto(expr.left, lowerer.lowerExpr(expr.left), DYN); + return { + kind: "libCall", + fn: "dyn.functionInstanceOf", + args: [value, { kind: "dynFrom", value: constructor, type: DYN, loc }], + type: BOOL, + loc, + }; + } lowerer.unsupported( "SC1090", expr.right, @@ -579,6 +596,14 @@ export function lowerInstanceOf(lowerer: Lowerer, expr: ts.BinaryExpression, loc loc, }; } + // `u instanceof EventEmitter` (or a stream class): an untyped object + // built by a constructor function inherits the untyped constructor's + // prototype (builtin-class-dyn.ts). Compiled emitters and streams have + // no untyped form, so the prototype chain answers for every value. + const builtinKind = builtinClassKind(target.def.name); + if (left.type.kind === "dyn" && builtinKind !== null) { + return builtinClassInstanceOf(left, builtinKind, loc); + } if (left.type.kind === "dyn") { lowerer.unsupported( "SC1090", diff --git a/packages/compiler/src/frontend/lowering/lower-calls.ts b/packages/compiler/src/frontend/lowering/lower-calls.ts index ed549e4ff..cc14c74ce 100644 --- a/packages/compiler/src/frontend/lowering/lower-calls.ts +++ b/packages/compiler/src/frontend/lowering/lower-calls.ts @@ -1,4 +1,5 @@ import { guardNullProto, nullProtoRecordShape } from "./null-prototype.js"; +import { lowerBuiltinClassCall } from "./builtins/builtin-class-dyn.js"; import { refuseModuleMemberThis } from "./module-member-this.js"; import { lowerUnionEquality, tagEqualityMayMissAlias } from "./strict-equality.js"; import { lowerHashStreamCall } from "./builtins/hash-stream.js"; @@ -263,6 +264,11 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { if (object) return object; } + // `EventEmitter.call(this)` in a constructor function: the untyped + // EventEmitter constructor initializes the receiver. + const builtinClassCall = lowerBuiltinClassCall(lowerer, expr); + if (builtinClassCall) return builtinClassCall; + // require() of a JSON document: the shared parsed object. const json = lowerJsonRequireCall(lowerer, expr); if (json) return json; diff --git a/packages/compiler/src/frontend/lowering/lower-inspect.ts b/packages/compiler/src/frontend/lowering/lower-inspect.ts index 245d1b676..ff5fb495d 100644 --- a/packages/compiler/src/frontend/lowering/lower-inspect.ts +++ b/packages/compiler/src/frontend/lowering/lower-inspect.ts @@ -1,4 +1,5 @@ import { dynUndefinedExpr, boolLit, helperLoc, numLit, strLit, varRef } from "../../ir/build.js"; +import { builtinClassDynValueOf, builtinClassKindOf } from "./builtins/builtin-class-dyn.js"; import { InternalCompilerError } from "../../errors.js"; import * as ts from "../ts7/adapter.js"; @@ -2544,8 +2545,15 @@ function lowerUtilCallableCall( const arity = member === "deprecate" ? 4 : member === "callbackify" ? 1 : 2; // Program classes keep static prototypes: there is no function object // whose prototype chain the runtime could relink or wrap. + // EventEmitter and the stream classes are the exception as superCtor: + // the runtime has untyped constructors for them (builtin-class-dyn.ts). const classArgs = member === "inherits" ? 2 : member === "deprecate" ? 1 : 0; - for (const arg of expr.arguments.nodes.slice(0, classArgs)) { + const builtinBase = + member === "inherits" && expr.arguments.nodes[1] !== undefined + ? builtinClassKindOf(lowerer, expr.arguments.nodes[1]) + : null; + for (const [index, arg] of expr.arguments.nodes.slice(0, classArgs).entries()) { + if (index === 1 && builtinBase !== null) continue; if (lowerer.mapTypeOf(lowerer.typeOf(arg))?.kind === "classval") lowerer.noLowering( `util.${member} over a class constructor`, @@ -2564,9 +2572,12 @@ function lowerUtilCallableCall( const stmts: IrStmt[] = []; const args: IrExpr[] = []; for (let i = 0; i < arity; i++) { - const input: IrExpr = expr.arguments.nodes[i] - ? lowerer.lowerExprExpecting(expr.arguments.nodes[i]!, DYN) - : dynUndefinedExpr(loc); + const node = expr.arguments.nodes[i]; + const input: IrExpr = !node + ? dynUndefinedExpr(loc) + : i === 1 && builtinBase !== null + ? (builtinClassDynValueOf(lowerer, node) ?? lowerer.lowerExprExpecting(node, DYN)) + : lowerer.lowerExprExpecting(node, DYN); const saved = lowerer.declareHiddenLocal("%utilInput", DYN); stmts.push({ kind: "varDecl", localId: saved.id, init: input, loc }); args.push(varRef(saved.id, DYN, loc)); diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index 7144e574e..1a050f6c4 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -1,4 +1,5 @@ import { jsDynamicDeclarations } from "./js-dynamic-objects.js"; +import { builtinClassValue, builtinMemberClassKind } from "./builtins/builtin-class-dyn.js"; import { lifetimeRegionType, lowerLifetimeRegionCall, @@ -12535,6 +12536,12 @@ export class Lowerer { } // A bare `fs.constants` read (not one of the baked bits above). if (bi.member === "constants" && bi.module === "fs") return fsConstantsObject(loc); + // `stream.Readable` as a JavaScript value: the runtime's untyped + // constructor, the base a constructor function inherits. + if (isJsSourceFile(expr.getSourceFile())) { + const classKind = builtinMemberClassKind(bi.module, bi.member); + if (classKind !== null) return builtinClassValue(classKind, loc); + } if (builtinModuleFnOf(this, bi.module, bi.member)) { this.unsupported( "SC1090", diff --git a/packages/compiler/src/index.ts b/packages/compiler/src/index.ts index 7d8d88889..05a6f7423 100644 --- a/packages/compiler/src/index.ts +++ b/packages/compiler/src/index.ts @@ -543,6 +543,7 @@ async function compileExecutableNative( bigint: features.bigint, searchParams: features.searchParams, qs: features.qs, + dynBuiltinClass: features.dynBuiltinClass, parseArgs: features.parseArgs, stream: features.stream, net: features.net, diff --git a/packages/compiler/src/ir/builtin-effects.ts b/packages/compiler/src/ir/builtin-effects.ts index 826151009..1b84c5888 100644 --- a/packages/compiler/src/ir/builtin-effects.ts +++ b/packages/compiler/src/ir/builtin-effects.ts @@ -556,6 +556,9 @@ export const MAY_THROW_LIB_FNS: ReadonlySet = new Set([ "dyn.objCreateWithProperties", "dyn.getPrototype", "dyn.setPrototype", + // a proxy in the prototype chain can throw + "dyn.builtinInstanceOf", + "dyn.functionInstanceOf", "dyn.getOwnPropertyNames", "dyn.ownKeys", "dyn.getOwnPropertySymbols", diff --git a/packages/compiler/src/ir/builtin-signatures.ts b/packages/compiler/src/ir/builtin-signatures.ts index c55444e60..a3480eb2c 100644 --- a/packages/compiler/src/ir/builtin-signatures.ts +++ b/packages/compiler/src/ir/builtin-signatures.ts @@ -3409,6 +3409,16 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ "dyn.classPrototype": { argTypes: [DYN, DYN], result: DYN }, "dyn.classBasePrototype": { argTypes: [DYN], result: DYN }, "dyn.classInherit": { argTypes: [DYN, DYN], result: DYN }, + /** EventEmitter (0), Stream (1), Readable (2), Writable (3), Duplex (4), + * Transform (5) and PassThrough (6) as untyped constructor values, one + * per thread (scr_dyn_builtin_class.c). */ + "dyn.builtinClass": { argTypes: [F64], result: DYN }, + /** `value instanceof C` for those constructors, over the value's + * prototype chain. */ + "dyn.builtinInstanceOf": { argTypes: [DYN, F64], result: BOOL }, + /** `value instanceof f` for an untyped constructor function: the + * prototype chain against f.prototype. */ + "dyn.functionInstanceOf": { argTypes: [DYN, DYN], result: BOOL }, "dyn.classSuper": { argTypes: [DYN, DYN, DYN], result: VOID }, "dyn.assignPrototype": { argTypes: [DYN, DYN, DYN], result: DYN }, "dyn.definePrototypeProps": { argTypes: [DYN, DYN, DYN], result: DYN }, diff --git a/packages/compiler/src/ir/runtime-features.ts b/packages/compiler/src/ir/runtime-features.ts index 9abe15345..de91b4b78 100644 --- a/packages/compiler/src/ir/runtime-features.ts +++ b/packages/compiler/src/ir/runtime-features.ts @@ -222,6 +222,8 @@ const CHILD_PROCESS_LIB_PREFIXES: readonly string[] = ["child.", "cp.", "spawnRe /** Entry points that link the symbol unit, besides the sym. and disposable. families. */ const SYMBOL_LIB_FNS: ReadonlySet = new Set([ + // EventEmitter's kShapeMode and kCapture properties + "dyn.builtinClass", "dyn.usingDispose", "dyn.usingMethod", "util.isDeepStrictEqual", @@ -490,9 +492,16 @@ const RUNTIME_FEATURE_RULES = { (node.name === "promise.allSettled" || node.name === "promise.any")) || (node.kind === "dynFrom" && boxesPromise(node.value.type, tables(), new Set())), }), + /** EventEmitter and the stream classes as untyped constructor values + * (scr_dyn_builtin_class.c), which call listeners through the dyn-invoke + * unit, render unhandled 'error' payloads through the inspect unit and + * create EventEmitter's private symbols through the symbol unit. */ + dynBuiltinClass: rule({ + fns: ["dyn.builtinClass", "dyn.builtinInstanceOf", "dyn.functionInstanceOf"], + }), dynInvoke: rule({ initial: (mod) => mod.workers === true, - fns: [...DYN_INVOKE_LIB_FNS, ...UTIL_CALLABLE_LIB_FNS], + fns: [...DYN_INVOKE_LIB_FNS, ...UTIL_CALLABLE_LIB_FNS, "dyn.builtinClass"], expr: (node, _tables) => node.kind === "dynInvoke", }), /** The program has dynamic-tier jsval operations (js.* builtins, the @@ -534,7 +543,13 @@ const RUNTIME_FEATURE_RULES = { * terminal unit prints completion errors through it when there is a * completer. */ inspect: rule({ - fns: ["global.native", "util.styleText", "crypto.cipherNew", ...UTIL_CALLABLE_LIB_FNS], + fns: [ + "global.native", + "util.styleText", + "crypto.cipherNew", + "dyn.builtinClass", + ...UTIL_CALLABLE_LIB_FNS, + ], prefixes: ["insp.", "console.", "test."], libCall: (node) => node.fn === "rl.terminal" && !isDynUndefined(node.args[3]!), }), @@ -637,6 +652,7 @@ function noRuntimeFeatures(): RuntimeFeatures { dc: false, dgram: false, dynAsync: false, + dynBuiltinClass: false, dynInvoke: false, dynTier: false, emitter: false, diff --git a/packages/compiler/src/native/cli.ts b/packages/compiler/src/native/cli.ts index 7d00acbc8..ce11aadf6 100644 --- a/packages/compiler/src/native/cli.ts +++ b/packages/compiler/src/native/cli.ts @@ -89,6 +89,7 @@ async function main(): Promise { bigint: false, searchParams: false, qs: false, + dynBuiltinClass: false, parseArgs: false, stream: false, net: false, diff --git a/packages/compiler/src/native/prepare.ts b/packages/compiler/src/native/prepare.ts index 54100a413..b95b978ff 100644 --- a/packages/compiler/src/native/prepare.ts +++ b/packages/compiler/src/native/prepare.ts @@ -143,6 +143,7 @@ function validFeatures(value: unknown): value is NativeLinkFeatures { "bigint", "searchParams", "qs", + "dynBuiltinClass", "parseArgs", "stream", "net", diff --git a/packages/compiler/src/native/sanitizer.test.ts b/packages/compiler/src/native/sanitizer.test.ts index 6ca84af6f..bbd07a6cd 100644 --- a/packages/compiler/src/native/sanitizer.test.ts +++ b/packages/compiler/src/native/sanitizer.test.ts @@ -95,6 +95,7 @@ test.skipIf(process.platform !== "linux" && process.platform !== "darwin")( bigint: false, searchParams: false, qs: false, + dynBuiltinClass: false, parseArgs: false, stream: false, net: false, diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6453-constructor-function-emitter.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6453-constructor-function-emitter.cjs.json new file mode 100644 index 000000000..ae5c0050e --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6453-constructor-function-emitter.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6453-constructor-function-emitter.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6454-constructor-function-stream-bases.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6454-constructor-function-stream-bases.cjs.json new file mode 100644 index 000000000..bc48a7f35 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6454-constructor-function-stream-bases.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6454-constructor-function-stream-bases.cjs" + ], + "diags": [] +} diff --git a/packages/runtime-pack-common/runtime-pack-matrix.mjs b/packages/runtime-pack-common/runtime-pack-matrix.mjs index 1e959eccc..a6b11f53e 100644 --- a/packages/runtime-pack-common/runtime-pack-matrix.mjs +++ b/packages/runtime-pack-common/runtime-pack-matrix.mjs @@ -88,6 +88,10 @@ export const OPTIONAL_RUNTIME_SOURCES = [ ["scr_util_compare.c", all("parseArgs", "symbol")], ["scr_util_style.c", all("parseArgs", "inspect", "dynAsync")], ["scr_util_callable.c", all("parseArgs", "inspect", "dynAsync", "dynInvoke", "regex")], + // Untyped EventEmitter and stream constructor values call listeners + // through the dyn-invoke unit, inspect unhandled 'error' payloads and + // create EventEmitter's private symbols. + ["scr_dyn_builtin_class.c", all("dynBuiltinClass", "inspect", "dynInvoke", "symbol")], ["scr_stream.c", "stream"], // The zlib stream classes reference both the zlib engine and the stream unit. ["scr_zlib_stream.c", all("zlib", "stream")], diff --git a/packages/runtime/src/scr_dyn.h b/packages/runtime/src/scr_dyn.h index a3d506a10..91919b641 100644 --- a/packages/runtime/src/scr_dyn.h +++ b/packages/runtime/src/scr_dyn.h @@ -1116,6 +1116,12 @@ ScrDyn *scr_dyn_define_prototype_props(ScrDyn *target, ScrDyn *descs, ScrDyn *pr * must be static literals (the box never frees them; name may be NULL). */ ScrDyn *scr_dyn_new_func(ScrClosure *clo, ScrDynThunk thunk, uint32_t arity, const char *sig, const char *name); ScrDyn *scr_dyn_new_class(ScrClassObj *cls, const char *type_key); +/* EventEmitter and the stream constructors as untyped constructor values, + * and instanceof over untyped constructor functions + * (scr_dyn_builtin_class.c, linked with the dynBuiltinClass feature). */ +ScrDyn *scr_dyn_builtin_class(double kind); +bool scr_dyn_builtin_instanceof(ScrDyn *value, double kind); +bool scr_dyn_function_instanceof(ScrDyn *value, ScrDyn *callee); bool scr_dyn_class_is_key(const ScrDyn *value, const ScrStr *type_key); bool scr_dyn_class_is(const ScrDyn *value, const char *type_key); /* Calls a dyn value: a non-function kind throws the catchable TypeError diff --git a/packages/runtime/src/scr_dyn_builtin_class.c b/packages/runtime/src/scr_dyn_builtin_class.c new file mode 100644 index 000000000..76a7ab15e --- /dev/null +++ b/packages/runtime/src/scr_dyn_builtin_class.c @@ -0,0 +1,1469 @@ +/* EventEmitter and the node:stream constructors as checked-dynamic + * constructor values. JavaScript constructor functions inherit from them + * the pre-class way: + * + * function Bus() { EventEmitter.call(this); } + * util.inherits(Bus, EventEmitter); + * + * A compiled EventEmitter instance keeps its listeners in native state, but + * the receiver here is an ordinary dyn object built by `new Bus()`. So the + * EventEmitter value is a native function whose prototype carries Node's + * lib/events.js methods over that object's own `_events`, `_eventsCount` + * and `_maxListeners` properties, the state Node's own implementation + * keeps there. Calling it with a receiver runs EventEmitter.init. + * + * Stream is Node's legacy constructor: EventEmitter.call(this, opts) and a + * prototype that inherits EventEmitter's. Readable, Writable, Duplex, + * Transform and PassThrough have prototypes chained as in Node, so + * util.inherits onto them and instanceof work, but their native state has + * no dyn object form: calling one with a receiver throws the "not + * supported yet" Error where the construction runs. + * + * One constructor per kind and thread, so every boxing of the same class + * value is the same function. All arguments are borrowed. */ +#include "scr_runtime.h" + +#include +#include +#include + +#ifdef _WIN32 +#include +#define BC_PID() ((long)_getpid()) +#else +#include +#define BC_PID() ((long)getpid()) +#endif + +enum { + BC_EMITTER, + BC_STREAM, + BC_READABLE, + BC_WRITABLE, + BC_DUPLEX, + BC_TRANSFORM, + BC_PASSTHROUGH, + BC_COUNT +}; + +static const char *const bc_names[BC_COUNT] = { + "EventEmitter", "Stream", "Readable", "Writable", "Duplex", "Transform", "PassThrough", +}; + +static SCR_TL ScrDyn *bc_ctors[BC_COUNT]; +static SCR_TL bool bc_teardown_registered; +/* Node's private kShapeMode and kCapture symbols, which util.inspect shows + * on every emitter as Symbol(shapeMode) and Symbol(kCapture). */ +enum { BC_SHAPE_MODE, BC_CAPTURE, BC_SYMBOLS }; +static SCR_TL ScrDyn *bc_symbols[BC_SYMBOLS]; + +static ScrDyn *bc_class(int kind); + +/* ── property helpers ───────────────────────────────────────────────── */ + +static ScrDyn *bc_key(const char *name) { + ScrStr *text = scr_str_new(name, strlen(name)); + ScrDyn *key = scr_dyn_new_str(text); + scr_str_release(text); + return key; +} + +/* object[key] with object as the receiver: +1, or NULL with an exception + * pending. */ +static ScrDyn *bc_get_key(ScrDyn *object, ScrDyn *key) { + ScrDyn *value = scr_dyn_reflect_get(object, key, object); + if (!value && !scr_exc_pending()) value = scr_dyn_retain(scr_dyn_undefined()); + return value; +} + +static ScrDyn *bc_get(ScrDyn *object, const char *name) { + ScrDyn *key = bc_key(name); + ScrDyn *value = bc_get_key(object, key); + scr_dyn_release(key); + return value; +} + +/* object[key] = value; the value moves in. False with an exception + * pending. */ +static bool bc_set_key(ScrDyn *object, ScrDyn *key, ScrDyn *value) { + scr_dyn_key_set_computed(object, key, value); + scr_dyn_release(value); + return !scr_exc_pending(); +} + +static bool bc_set(ScrDyn *object, const char *name, ScrDyn *value) { + ScrDyn *key = bc_key(name); + bool ok = bc_set_key(object, key, value); + scr_dyn_release(key); + return ok; +} + +static bool bc_undefined(const ScrDyn *value) { + return value->kind == SCR_DYN_UNDEF; +} + +static double bc_number(const ScrDyn *value) { + return value->kind == SCR_DYN_NUM ? value->v.num : 0; +} + +/* receiver.method(...args), dispatched through the receiver's own lookup + * like Node's `this.emit(...)`. +1 or NULL. */ +static ScrDyn *bc_invoke(ScrDyn *receiver, const char *method, ScrDyn *const *args, size_t argc) { + ScrDyn *fn = bc_get(receiver, method); + if (!fn) return NULL; + scr_dyn_this_push_dyn(receiver); + ScrDyn *result = scr_dyn_call(fn, args, argc, method); + scr_dyn_this_pop(); + scr_dyn_release(fn); + return result; +} + +static ScrDyn *bc_apply(ScrDyn *fn, ScrDyn *receiver, ScrDyn *const *args, size_t argc) { + scr_dyn_this_push_dyn(receiver); + ScrDyn *result = scr_dyn_call(fn, args, argc, "listener"); + scr_dyn_this_pop(); + return result; +} + +/* Listener lists are replaced rather than mutated: a fresh array with item + * first, or without the item at index. Node mutates its array in place, + * which nothing outside the emitter observes (emit iterates a copy and + * listeners() answers one). A leak warning already given stays given. */ +static ScrDyn *bc_list_copy(ScrDyn *list, ScrDyn *first, double skip) { + ScrDyn *copy = scr_dyn_new_arr(); + if (first) scr_dyn_arr_push(copy, scr_dyn_retain(first)); + double length = scr_dyn_arr_len(list); + for (double i = 0; i < length; i++) { + if (i == skip) continue; + ScrDyn *item = scr_dyn_index_get(list, i); + if (!item) { + scr_dyn_release(copy); + return NULL; + } + scr_dyn_arr_push(copy, item); + } + ScrDyn *warned = bc_get(list, "warned"); + if (!warned || (scr_dyn_truthy(warned) && !bc_set(copy, "warned", scr_dyn_new_bool(true)))) { + if (warned) scr_dyn_release(warned); + scr_dyn_release(copy); + return NULL; + } + scr_dyn_release(warned); + return copy; +} + +static ScrDyn *bc_unused_args[1]; + +static ScrDyn *bc_arg(ScrDyn *const *args, size_t argc, size_t index) { + return index < argc ? args[index] : scr_dyn_undefined(); +} + +/* The receiver of a prototype method call, +1. Methods called without one + * (an extracted `emitter.on`) read `this` as undefined, as in strict code. */ +static ScrDyn *bc_this(void) { + return scr_dyn_this_get(); +} + +static bool bc_require_object(ScrDyn *self, const char *reading) { + if (self->kind != SCR_DYN_UNDEF && self->kind != SCR_DYN_NULL) return true; + char message[96]; + snprintf(message, sizeof message, "Cannot read properties of %s (reading '%s')", + self->kind == SCR_DYN_NULL ? "null" : "undefined", reading); + scr_throw_error_msg(SCR_ERR_TYPE, message, strlen(message)); + return false; +} + +static bool bc_check_listener(const ScrDyn *listener) { + if (scr_dyn_is_callable(listener)) return true; + scr_dyn_arg_type_fail("listener", "of type function", listener); + return false; +} + +static void bc_teardown(void); + +static ScrDyn *bc_symbol(int which) { + if (!bc_symbols[which]) { + if (!bc_teardown_registered) { + bc_teardown_registered = true; + scr_atexit(bc_teardown); + } + const char *name = which == BC_SHAPE_MODE ? "shapeMode" : "kCapture"; + ScrStr *description = scr_str_new(name, strlen(name)); + ScrSym *symbol = scr_sym_new(description); + scr_str_release(description); + bc_symbols[which] = scr_dyn_new_symbol(symbol); + scr_sym_release(symbol); + } + return bc_symbols[which]; +} + +/* ── EventEmitter.init ──────────────────────────────────────────────── */ + +/* `_events` is fresh unless the receiver already has its own (Node's + * shapeMode, a subclass that pre-populates it); `_maxListeners` keeps a + * value set before the call. */ +static bool bc_emitter_init(ScrDyn *self, ScrDyn *opts) { + if (!bc_require_object(self, "_events")) return false; + ScrDyn *events = bc_get(self, "_events"); + if (!events) return false; + bool fresh = bc_undefined(events); + if (!fresh) { + ScrDyn *proto = scr_dyn_get_prototype(self); + if (!proto) { + scr_dyn_release(events); + return false; + } + if (proto->kind != SCR_DYN_NULL) { + ScrDyn *inherited = bc_get(proto, "_events"); + if (!inherited) { + scr_dyn_release(proto); + scr_dyn_release(events); + return false; + } + fresh = scr_dyn_strict_eq(events, inherited); + scr_dyn_release(inherited); + } + scr_dyn_release(proto); + } + scr_dyn_release(events); + if (fresh) { + if (!bc_set(self, "_events", scr_dyn_new_obj_null_proto())) return false; + if (!bc_set(self, "_eventsCount", scr_dyn_new_num(0))) return false; + } + if (!bc_set_key(self, bc_symbol(BC_SHAPE_MODE), scr_dyn_new_bool(!fresh))) return false; + ScrDyn *max = bc_get(self, "_maxListeners"); + if (!max) return false; + bool keep = scr_dyn_truthy(max); + if (!bc_set(self, "_maxListeners", keep ? max : (scr_dyn_release(max), scr_dyn_retain(scr_dyn_undefined())))) + return false; + if (opts->kind != SCR_DYN_UNDEF && opts->kind != SCR_DYN_NULL) { + ScrDyn *capture = bc_get(opts, "captureRejections"); + if (!capture) return false; + bool requested = !bc_undefined(capture); + if (requested && capture->kind != SCR_DYN_BOOL) { + scr_dyn_arg_type_fail("options.captureRejections", "of type boolean", capture); + scr_dyn_release(capture); + return false; + } + bool enabled = requested && capture->v.b; + scr_dyn_release(capture); + if (enabled) { + static const char message[] = "EventEmitter captureRejections on a constructor function is not supported yet"; + scr_throw_error_msg_code(SCR_ERR_ERROR, message, sizeof message - 1, "SC2020"); + return false; + } + } + return bc_set_key(self, bc_symbol(BC_CAPTURE), scr_dyn_new_bool(false)); +} + +/* ── listener bookkeeping (lib/events.js) ───────────────────────────── */ + +static double bc_max_listeners(ScrDyn *self) { + ScrDyn *max = bc_get(self, "_maxListeners"); + if (!max) return -1; + double value; + if (bc_undefined(max)) { + ScrDyn *fallback = bc_get(bc_class(BC_EMITTER), "defaultMaxListeners"); + value = fallback ? bc_number(fallback) : 10; + if (fallback) scr_dyn_release(fallback); + } else { + value = bc_number(max); + } + scr_dyn_release(max); + return value; +} + +static void bc_warn_tick(ScrClosure *self) { + ScrStr *message = (ScrStr *)scr_box_get_ref(self->caps[0]); + if (scr_warning_emit_hook) { + scr_warning_emit_hook("MaxListenersExceededWarning", NULL, message); + } else { + fflush(stdout); + fprintf(stderr, "(node:%ld) MaxListenersExceededWarning: %.*s\n", BC_PID(), + (int)message->len, message->data); + if (!scr_warning_trace_hint_shown) { + scr_warning_trace_hint_shown = true; + fputs("(Use `node --trace-warnings ...` to show where the warning was created)\n", stderr); + } + } + scr_str_release(message); +} + +/* The constructor name util.inspect prints at depth -1: `[Bus]`. */ +static ScrStr *bc_constructor_name(ScrDyn *self) { + ScrDyn *ctor = bc_get(self, "constructor"); + ScrStr *name = NULL; + if (ctor && scr_dyn_is_callable(ctor)) { + ScrDyn *text = bc_get(ctor, "name"); + if (text && text->kind == SCR_DYN_STR && text->v.str->len) name = scr_str_retain(text->v.str); + if (text) scr_dyn_release(text); + } + if (ctor) scr_dyn_release(ctor); + if (scr_exc_pending()) scr_exc_clear(); + return name ? name : scr_str_new("Object", 6); +} + +static void bc_warn_leak(ScrDyn *self, ScrDyn *type, double count, double max) { + char counted[32], limit[32]; + snprintf(counted, sizeof counted, "%g", count); + snprintf(limit, sizeof limit, "%g", max); + ScrStr *event = scr_dyn_string_coerce_js(type); + if (!event) { + scr_exc_clear(); + event = scr_str_new("", 0); + } + ScrStr *cls = bc_constructor_name(self); + ScrJsonBuf text; + scr_jb_init(&text); + scr_jb_puts(&text, "Possible EventEmitter memory leak detected. "); + scr_jb_puts(&text, counted); + scr_jb_putc(&text, ' '); + scr_jb_put_str(&text, event); + scr_jb_puts(&text, " listeners added to ["); + scr_jb_put_str(&text, cls); + scr_jb_puts(&text, "]. MaxListeners is "); + scr_jb_puts(&text, limit); + scr_jb_puts(&text, ". Use emitter.setMaxListeners() to increase limit"); + scr_str_release(event); + scr_str_release(cls); + ScrClosure *tick = scr_closure_new((void *)&bc_warn_tick, 1); + ScrBox *box = scr_box_new(SCR_BOX_STR); + scr_box_set_ref(box, scr_jb_finish(&text)); + tick->caps[0] = box; + scr_next_tick(tick); +} + +/* _addListener(target, type, listener, prepend): +1 target or NULL. */ +static ScrDyn *bc_add_listener(ScrDyn *self, ScrDyn *type, ScrDyn *listener, bool prepend) { + if (!bc_check_listener(listener)) return NULL; + ScrDyn *events = bc_get(self, "_events"); + if (!events) return NULL; + ScrDyn *existing = NULL; + if (bc_undefined(events)) { + scr_dyn_release(events); + events = scr_dyn_new_obj_null_proto(); + if (!bc_set(self, "_events", scr_dyn_retain(events)) || !bc_set(self, "_eventsCount", scr_dyn_new_num(0))) + goto fail; + } else { + ScrDyn *watcher = bc_get(events, "newListener"); + if (!watcher) goto fail; + bool watched = !bc_undefined(watcher); + scr_dyn_release(watcher); + if (watched) { + ScrDyn *inner = bc_get(listener, "listener"); + if (!inner) goto fail; + ScrDyn *name = bc_key("newListener"); + ScrDyn *args[3] = { name, type, bc_undefined(inner) || inner->kind == SCR_DYN_NULL ? listener : inner }; + ScrDyn *emitted = bc_invoke(self, "emit", args, 3); + scr_dyn_release(name); + scr_dyn_release(inner); + if (!emitted) goto fail; + scr_dyn_release(emitted); + /* the listener may have replaced _events */ + scr_dyn_release(events); + events = bc_get(self, "_events"); + if (!events) return NULL; + } + existing = bc_get_key(events, type); + if (!existing) goto fail; + } + if (!existing || bc_undefined(existing)) { + if (!bc_set_key(events, type, scr_dyn_retain(listener))) goto fail; + ScrDyn *count = bc_get(self, "_eventsCount"); + if (!count) goto fail; + double next = bc_number(count) + 1; + scr_dyn_release(count); + if (!bc_set(self, "_eventsCount", scr_dyn_new_num(next))) goto fail; + } else { + if (scr_dyn_is_callable(existing)) { + ScrDyn *list = scr_dyn_new_arr(); + scr_dyn_arr_push(list, scr_dyn_retain(prepend ? listener : existing)); + scr_dyn_arr_push(list, scr_dyn_retain(prepend ? existing : listener)); + if (!bc_set_key(events, type, scr_dyn_retain(list))) { + scr_dyn_release(list); + goto fail; + } + scr_dyn_release(existing); + existing = list; + } else if (prepend) { + ScrDyn *list = bc_list_copy(existing, listener, -1); + if (!list) goto fail; + if (!bc_set_key(events, type, scr_dyn_retain(list))) { + scr_dyn_release(list); + goto fail; + } + scr_dyn_release(existing); + existing = list; + } else if (existing->kind == SCR_DYN_ARR) { + scr_dyn_arr_push(existing, scr_dyn_retain(listener)); + } + double max = bc_max_listeners(self); + if (scr_exc_pending()) goto fail; + double length = existing->kind == SCR_DYN_ARR ? scr_dyn_arr_len(existing) : 0; + if (max > 0 && length > max) { + ScrDyn *warned = bc_get(existing, "warned"); + if (!warned) goto fail; + bool already = scr_dyn_truthy(warned); + scr_dyn_release(warned); + if (!already) { + if (!bc_set(existing, "warned", scr_dyn_new_bool(true))) goto fail; + bc_warn_leak(self, type, length, max); + } + } + } + if (existing) scr_dyn_release(existing); + scr_dyn_release(events); + return scr_dyn_retain(self); +fail: + if (existing) scr_dyn_release(existing); + scr_dyn_release(events); + return NULL; +} + +/* removeListener(type, listener): +1 self or NULL. */ +static ScrDyn *bc_remove_listener(ScrDyn *self, ScrDyn *type, ScrDyn *listener) { + if (!bc_check_listener(listener)) return NULL; + ScrDyn *events = bc_get(self, "_events"); + if (!events) return NULL; + if (bc_undefined(events)) { + scr_dyn_release(events); + return scr_dyn_retain(self); + } + ScrDyn *list = bc_get_key(events, type); + if (!list) goto fail_events; + if (bc_undefined(list)) { + scr_dyn_release(list); + scr_dyn_release(events); + return scr_dyn_retain(self); + } + ScrDyn *wrapped = scr_dyn_is_callable(list) ? bc_get(list, "listener") : NULL; + if (scr_exc_pending()) goto fail_list; + bool match = scr_dyn_is_callable(list) && + (scr_dyn_strict_eq(list, listener) || (wrapped && scr_dyn_strict_eq(wrapped, listener))); + if (match) { + ScrDyn *count = bc_get(self, "_eventsCount"); + if (!count) goto fail_wrapped; + double left = bc_number(count) - 1; + scr_dyn_release(count); + if (!bc_set(self, "_eventsCount", scr_dyn_new_num(left))) goto fail_wrapped; + ScrDyn *shaped = bc_get_key(self, bc_symbol(BC_SHAPE_MODE)); + if (!shaped) goto fail_wrapped; + bool shape_mode = scr_dyn_truthy(shaped); + scr_dyn_release(shaped); + if (shape_mode) { + if (!bc_set_key(events, type, scr_dyn_retain(scr_dyn_undefined()))) goto fail_wrapped; + } else if (left == 0) { + if (!bc_set(self, "_events", scr_dyn_new_obj_null_proto())) goto fail_wrapped; + } else { + scr_dyn_key_delete_computed(events, type, false); + if (scr_exc_pending()) goto fail_wrapped; + ScrDyn *watcher = bc_get(events, "removeListener"); + if (!watcher) goto fail_wrapped; + bool watched = scr_dyn_truthy(watcher); + scr_dyn_release(watcher); + if (watched) { + ScrDyn *name = bc_key("removeListener"); + ScrDyn *args[3] = { name, type, wrapped && scr_dyn_truthy(wrapped) ? wrapped : listener }; + ScrDyn *emitted = bc_invoke(self, "emit", args, 3); + scr_dyn_release(name); + if (!emitted) goto fail_wrapped; + scr_dyn_release(emitted); + } + } + } else if (list->kind == SCR_DYN_ARR) { + double length = scr_dyn_arr_len(list); + double position = -1; + for (double i = length - 1; i >= 0; i--) { + ScrDyn *item = scr_dyn_index_get(list, i); + if (!item) goto fail_wrapped; + bool same = scr_dyn_strict_eq(item, listener); + if (!same && scr_dyn_is_callable(item)) { + ScrDyn *inner = bc_get(item, "listener"); + if (!inner) { + scr_dyn_release(item); + goto fail_wrapped; + } + same = scr_dyn_strict_eq(inner, listener); + scr_dyn_release(inner); + } + scr_dyn_release(item); + if (same) { + position = i; + break; + } + } + if (position >= 0) { + ScrDyn *rest = bc_list_copy(list, NULL, position); + if (!rest) goto fail_wrapped; + ScrDyn *stored = scr_dyn_arr_len(rest) == 1 ? scr_dyn_index_get(rest, 0) : scr_dyn_retain(rest); + scr_dyn_release(rest); + if (!stored || !bc_set_key(events, type, stored)) goto fail_wrapped; + ScrDyn *watcher = bc_get(events, "removeListener"); + if (!watcher) goto fail_wrapped; + bool watched = !bc_undefined(watcher); + scr_dyn_release(watcher); + if (watched) { + ScrDyn *name = bc_key("removeListener"); + ScrDyn *args[3] = { name, type, listener }; + ScrDyn *emitted = bc_invoke(self, "emit", args, 3); + scr_dyn_release(name); + if (!emitted) goto fail_wrapped; + scr_dyn_release(emitted); + } + } + } + if (wrapped) scr_dyn_release(wrapped); + scr_dyn_release(list); + scr_dyn_release(events); + return scr_dyn_retain(self); +fail_wrapped: + if (wrapped) scr_dyn_release(wrapped); +fail_list: + scr_dyn_release(list); +fail_events: + scr_dyn_release(events); + return NULL; +} + +/* ── once wrappers ──────────────────────────────────────────────────── */ + +enum { BC_ONCE_FIRED, BC_ONCE_TARGET, BC_ONCE_TYPE, BC_ONCE_LISTENER, BC_ONCE_SELF, BC_ONCE_CAPS }; + +static ScrBox *bc_box(ScrDyn *value /* moves in */) { + ScrBox *box = scr_box_new_obj(scr_dyn_retain_v, scr_dyn_release_v, scr_dyn_trace_v); + scr_box_set_ref(box, value); + return box; +} + +static ScrDyn *bc_cap(ScrClosure *closure, size_t index) { + return scr_box_get_ref(closure->caps[index]); /* +1 */ +} + +/* onceWrapper: the first call removes the wrapper, then calls the listener + * with the target as its receiver. The wrapper's reference to itself is + * dropped when it fires, so a fired wrapper holds no cycle. */ +static ScrDyn *bc_once_call(ScrClosure *closure, ScrDyn *const *args, size_t argc) { + if (scr_box_get_f64(closure->caps[BC_ONCE_FIRED]) != 0) return scr_dyn_retain(scr_dyn_undefined()); + ScrDyn *target = bc_cap(closure, BC_ONCE_TARGET); + ScrDyn *type = bc_cap(closure, BC_ONCE_TYPE); + ScrDyn *listener = bc_cap(closure, BC_ONCE_LISTENER); + ScrDyn *self = bc_cap(closure, BC_ONCE_SELF); + ScrDyn *result = NULL; + ScrDyn *removal[2] = { type, self }; + ScrDyn *removed = bc_invoke(target, "removeListener", removal, 2); + if (removed) { + scr_dyn_release(removed); + scr_box_set_f64(closure->caps[BC_ONCE_FIRED], 1); + scr_box_set_ref(closure->caps[BC_ONCE_SELF], scr_dyn_retain(scr_dyn_undefined())); + result = bc_apply(listener, target, args, argc); + } + scr_dyn_release(self); + scr_dyn_release(listener); + scr_dyn_release(type); + scr_dyn_release(target); + return result; +} + +static ScrDyn *bc_once_wrap(ScrDyn *target, ScrDyn *type, ScrDyn *listener) { + ScrClosure *closure = scr_closure_new(NULL, BC_ONCE_CAPS); + closure->caps[BC_ONCE_FIRED] = scr_box_new(SCR_BOX_F64); + closure->caps[BC_ONCE_TARGET] = bc_box(scr_dyn_retain(target)); + closure->caps[BC_ONCE_TYPE] = bc_box(scr_dyn_retain(type)); + closure->caps[BC_ONCE_LISTENER] = bc_box(scr_dyn_retain(listener)); + closure->caps[BC_ONCE_SELF] = bc_box(scr_dyn_retain(scr_dyn_undefined())); + scr_closure_retain(closure); + ScrDyn *wrapper = scr_dyn_new_func(closure, bc_once_call, 0, "native:events", "bound onceWrapper"); + scr_box_set_ref(closure->caps[BC_ONCE_SELF], scr_dyn_retain(wrapper)); + scr_closure_release(closure); + if (!bc_set(wrapper, "listener", scr_dyn_retain(listener))) { + scr_dyn_release(wrapper); + return NULL; + } + return wrapper; +} + +/* ── EventEmitter.prototype ─────────────────────────────────────────── */ + +static ScrDyn *bc_on(ScrClosure *closure, ScrDyn *const *args, size_t argc) { + (void)closure; + ScrDyn *self = bc_this(); + ScrDyn *result = bc_require_object(self, "_events") + ? bc_add_listener(self, bc_arg(args, argc, 0), bc_arg(args, argc, 1), false) : NULL; + scr_dyn_release(self); + return result; +} + +static ScrDyn *bc_prepend(ScrClosure *closure, ScrDyn *const *args, size_t argc) { + (void)closure; + ScrDyn *self = bc_this(); + ScrDyn *result = bc_require_object(self, "_events") + ? bc_add_listener(self, bc_arg(args, argc, 0), bc_arg(args, argc, 1), true) : NULL; + scr_dyn_release(self); + return result; +} + +static ScrDyn *bc_once_common(ScrDyn *const *args, size_t argc, bool prepend) { + ScrDyn *self = bc_this(); + ScrDyn *type = bc_arg(args, argc, 0); + ScrDyn *listener = bc_arg(args, argc, 1); + ScrDyn *result = NULL; + if (bc_check_listener(listener) && bc_require_object(self, "_events")) { + ScrDyn *wrapper = bc_once_wrap(self, type, listener); + if (wrapper) { + result = bc_add_listener(self, type, wrapper, prepend); + scr_dyn_release(wrapper); + } + } + scr_dyn_release(self); + return result; +} + +static ScrDyn *bc_once(ScrClosure *closure, ScrDyn *const *args, size_t argc) { + (void)closure; + return bc_once_common(args, argc, false); +} + +static ScrDyn *bc_prepend_once(ScrClosure *closure, ScrDyn *const *args, size_t argc) { + (void)closure; + return bc_once_common(args, argc, true); +} + +static ScrDyn *bc_off(ScrClosure *closure, ScrDyn *const *args, size_t argc) { + (void)closure; + ScrDyn *self = bc_this(); + ScrDyn *result = bc_require_object(self, "_events") + ? bc_remove_listener(self, bc_arg(args, argc, 0), bc_arg(args, argc, 1)) : NULL; + scr_dyn_release(self); + return result; +} + +/* removeAllListeners(type?): with no 'removeListener' watcher the table is + * reset; otherwise every listener is removed through removeListener, last + * first, so the watcher sees each one (and itself last). */ +static ScrDyn *bc_remove_all(ScrClosure *closure, ScrDyn *const *args, size_t argc) { + (void)closure; + ScrDyn *self = bc_this(); + if (!bc_require_object(self, "_events")) { + scr_dyn_release(self); + return NULL; + } + ScrDyn *type = bc_arg(args, argc, 0); + bool all = argc == 0; + ScrDyn *events = bc_get(self, "_events"); + ScrDyn *watcher = NULL; + if (!events) goto fail; + if (bc_undefined(events)) goto done; + watcher = bc_get(events, "removeListener"); + if (!watcher) goto fail; + if (bc_undefined(watcher)) { + if (all) { + if (!bc_set(self, "_events", scr_dyn_new_obj_null_proto()) || !bc_set(self, "_eventsCount", scr_dyn_new_num(0))) + goto fail; + } else { + ScrDyn *present = bc_get_key(events, type); + if (!present) goto fail; + bool has = !bc_undefined(present); + scr_dyn_release(present); + if (has) { + ScrDyn *count = bc_get(self, "_eventsCount"); + if (!count) goto fail; + double left = bc_number(count) - 1; + scr_dyn_release(count); + if (left == 0) { + if (!bc_set(self, "_events", scr_dyn_new_obj_null_proto()) || !bc_set(self, "_eventsCount", scr_dyn_new_num(0))) + goto fail; + } else { + if (!bc_set(self, "_eventsCount", scr_dyn_new_num(left))) goto fail; + scr_dyn_key_delete_computed(events, type, false); + if (scr_exc_pending()) goto fail; + } + } + } + goto done; + } + if (all) { + ScrDyn *keys = scr_dyn_own_keys(events); + if (!keys) goto fail; + double length = scr_dyn_arr_len(keys); + for (double i = 0; i < length; i++) { + ScrDyn *key = scr_dyn_index_get(keys, i); + if (!key) { + scr_dyn_release(keys); + goto fail; + } + bool skip = key->kind == SCR_DYN_STR && key->v.str->len == 14 && memcmp(key->v.str->data, "removeListener", 14) == 0; + ScrDyn *done_key = NULL; + if (!skip) { + ScrDyn *one[1] = { key }; + done_key = bc_invoke(self, "removeAllListeners", one, 1); + } + scr_dyn_release(key); + if (!skip && !done_key) { + scr_dyn_release(keys); + goto fail; + } + if (done_key) scr_dyn_release(done_key); + } + scr_dyn_release(keys); + ScrDyn *name = bc_key("removeListener"); + ScrDyn *one[1] = { name }; + ScrDyn *cleared = bc_invoke(self, "removeAllListeners", one, 1); + scr_dyn_release(name); + if (!cleared) goto fail; + scr_dyn_release(cleared); + if (!bc_set(self, "_events", scr_dyn_new_obj_null_proto()) || !bc_set(self, "_eventsCount", scr_dyn_new_num(0))) + goto fail; + goto done; + } + { + ScrDyn *listeners = bc_get_key(events, type); + if (!listeners) goto fail; + if (scr_dyn_is_callable(listeners)) { + ScrDyn *pair[2] = { type, listeners }; + ScrDyn *removed = bc_invoke(self, "removeListener", pair, 2); + scr_dyn_release(listeners); + if (!removed) goto fail; + scr_dyn_release(removed); + } else if (listeners->kind == SCR_DYN_ARR) { + for (double i = scr_dyn_arr_len(listeners) - 1; i >= 0; i--) { + ScrDyn *item = scr_dyn_index_get(listeners, i); + if (!item) { + scr_dyn_release(listeners); + goto fail; + } + ScrDyn *pair[2] = { type, item }; + ScrDyn *removed = bc_invoke(self, "removeListener", pair, 2); + scr_dyn_release(item); + if (!removed) { + scr_dyn_release(listeners); + goto fail; + } + scr_dyn_release(removed); + } + scr_dyn_release(listeners); + } else { + scr_dyn_release(listeners); + } + } +done: + if (watcher) scr_dyn_release(watcher); + if (events) scr_dyn_release(events); + return self; +fail: + if (watcher) scr_dyn_release(watcher); + if (events) scr_dyn_release(events); + scr_dyn_release(self); + return NULL; +} + +/* emit(type, ...args): Node's unhandled 'error' rule, then each listener + * with the emitter as its receiver over a copy of the list. */ +static ScrDyn *bc_emit(ScrClosure *closure, ScrDyn *const *args, size_t argc) { + (void)closure; + ScrDyn *self = bc_this(); + if (!bc_require_object(self, "_events")) { + scr_dyn_release(self); + return NULL; + } + ScrDyn *type = bc_arg(args, argc, 0); + ScrDyn *const *rest = argc > 1 ? args + 1 : bc_unused_args; + size_t restc = argc > 1 ? argc - 1 : 0; + bool error = type->kind == SCR_DYN_STR && type->v.str->len == 5 && memcmp(type->v.str->data, "error", 5) == 0; + ScrDyn *events = bc_get(self, "_events"); + if (!events) goto fail_self; + if (!bc_undefined(events)) { + if (error) { + ScrDyn *handler = bc_get(events, "error"); + if (!handler) goto fail_events; + error = bc_undefined(handler); + scr_dyn_release(handler); + } + } else if (!error) { + scr_dyn_release(events); + scr_dyn_release(self); + return scr_dyn_new_bool(false); + } + if (error) { + ScrDyn *payload = restc ? rest[0] : scr_dyn_undefined(); + if (scr_dyn_err_instanceof(payload, SCR_ERR_ERROR)) { + scr_dyn_throw(scr_dyn_retain(payload)); + } else { + ScrStr *shown = scr_insp_dyn(payload, 0, 2); + ScrJsonBuf text; + scr_jb_init(&text); + scr_jb_puts(&text, "Unhandled error. ("); + scr_jb_put_str(&text, shown); + scr_jb_putc(&text, ')'); + scr_str_release(shown); + ScrStr *message = scr_jb_finish(&text); + scr_throw_error_msg_code(SCR_ERR_ERROR, message->data, message->len, "ERR_UNHANDLED_ERROR"); + scr_str_release(message); + } + goto fail_events; + } + ScrDyn *handler = bc_get_key(events, type); + if (!handler) goto fail_events; + bool fired = !bc_undefined(handler); + if (scr_dyn_is_callable(handler)) { + ScrDyn *result = bc_apply(handler, self, rest, restc); + scr_dyn_release(handler); + if (!result) goto fail_events; + scr_dyn_release(result); + } else if (handler->kind == SCR_DYN_ARR) { + /* arrayClone: listeners added or removed during the emit run next time */ + size_t length = (size_t)scr_dyn_arr_len(handler); + ScrDyn **listeners = malloc((length ? length : 1) * sizeof *listeners); + if (!listeners) scr_trap("out of memory"); + for (size_t i = 0; i < length; i++) listeners[i] = scr_dyn_index_get(handler, (double)i); + scr_dyn_release(handler); + bool ok = true; + for (size_t i = 0; i < length && ok; i++) { + if (!listeners[i]) { + ok = false; + break; + } + ScrDyn *result = bc_apply(listeners[i], self, rest, restc); + if (!result) ok = false; + else scr_dyn_release(result); + } + for (size_t i = 0; i < length; i++) + if (listeners[i]) scr_dyn_release(listeners[i]); + free(listeners); + if (!ok) goto fail_events; + } else { + scr_dyn_release(handler); + } + scr_dyn_release(events); + scr_dyn_release(self); + return scr_dyn_new_bool(fired); +fail_events: + scr_dyn_release(events); +fail_self: + scr_dyn_release(self); + return NULL; +} + +/* _listeners(target, type, unwrap): a fresh array. */ +static ScrDyn *bc_listeners_of(bool unwrap, ScrDyn *const *args, size_t argc) { + ScrDyn *self = bc_this(); + if (!bc_require_object(self, "_events")) { + scr_dyn_release(self); + return NULL; + } + ScrDyn *result = scr_dyn_new_arr(); + ScrDyn *events = bc_get(self, "_events"); + ScrDyn *list = NULL; + if (!events) goto fail; + if (bc_undefined(events)) goto done; + list = bc_get_key(events, bc_arg(args, argc, 0)); + if (!list) goto fail; + size_t length = scr_dyn_is_callable(list) ? 1 : list->kind == SCR_DYN_ARR ? (size_t)scr_dyn_arr_len(list) : 0; + for (size_t i = 0; i < length; i++) { + ScrDyn *item = scr_dyn_is_callable(list) ? scr_dyn_retain(list) : scr_dyn_index_get(list, (double)i); + if (!item) goto fail; + if (unwrap && scr_dyn_is_callable(item)) { + ScrDyn *inner = bc_get(item, "listener"); + if (!inner) { + scr_dyn_release(item); + goto fail; + } + if (!bc_undefined(inner)) { + scr_dyn_release(item); + item = inner; + } else { + scr_dyn_release(inner); + } + } + scr_dyn_arr_push(result, item); + } +done: + if (list) scr_dyn_release(list); + if (events) scr_dyn_release(events); + scr_dyn_release(self); + return result; +fail: + if (list) scr_dyn_release(list); + if (events) scr_dyn_release(events); + scr_dyn_release(result); + scr_dyn_release(self); + return NULL; +} + +static ScrDyn *bc_listeners(ScrClosure *closure, ScrDyn *const *args, size_t argc) { + (void)closure; + return bc_listeners_of(true, args, argc); +} + +static ScrDyn *bc_raw_listeners(ScrClosure *closure, ScrDyn *const *args, size_t argc) { + (void)closure; + return bc_listeners_of(false, args, argc); +} + +/* listenerCount(type, listener?) over an emitter's _events. */ +static ScrDyn *bc_count_listeners(ScrDyn *self, ScrDyn *type, ScrDyn *only) { + ScrDyn *events = bc_get(self, "_events"); + if (!events) return NULL; + double count = 0; + if (!bc_undefined(events)) { + ScrDyn *list = bc_get_key(events, type); + if (!list) { + scr_dyn_release(events); + return NULL; + } + size_t length = scr_dyn_is_callable(list) ? 1 : list->kind == SCR_DYN_ARR ? (size_t)scr_dyn_arr_len(list) : 0; + for (size_t i = 0; i < length; i++) { + if (!only) { + count++; + continue; + } + ScrDyn *item = scr_dyn_is_callable(list) ? scr_dyn_retain(list) : scr_dyn_index_get(list, (double)i); + if (!item) break; + bool same = scr_dyn_strict_eq(item, only); + if (!same && scr_dyn_is_callable(item)) { + ScrDyn *inner = bc_get(item, "listener"); + if (inner) { + same = scr_dyn_strict_eq(inner, only); + scr_dyn_release(inner); + } + } + scr_dyn_release(item); + if (same) count++; + } + scr_dyn_release(list); + } + scr_dyn_release(events); + if (scr_exc_pending()) return NULL; + return scr_dyn_new_num(count); +} + +static ScrDyn *bc_listener_count(ScrClosure *closure, ScrDyn *const *args, size_t argc) { + (void)closure; + ScrDyn *self = bc_this(); + ScrDyn *result = bc_require_object(self, "_events") + ? bc_count_listeners(self, bc_arg(args, argc, 0), + argc > 1 && !bc_undefined(args[1]) && args[1]->kind != SCR_DYN_NULL ? args[1] : NULL) + : NULL; + scr_dyn_release(self); + return result; +} + +/* EventEmitter.listenerCount(emitter, type): the deprecated static. */ +static ScrDyn *bc_static_listener_count(ScrClosure *closure, ScrDyn *const *args, size_t argc) { + (void)closure; + ScrDyn *emitter = bc_arg(args, argc, 0); + ScrDyn *type = bc_arg(args, argc, 1); + ScrDyn *method = bc_require_object(emitter, "listenerCount") ? bc_get(emitter, "listenerCount") : NULL; + if (!method) return NULL; + ScrDyn *result; + if (scr_dyn_is_callable(method)) result = bc_apply(method, emitter, &type, 1); + else result = bc_count_listeners(emitter, type, NULL); + scr_dyn_release(method); + return result; +} + +static ScrDyn *bc_event_names(ScrClosure *closure, ScrDyn *const *args, size_t argc) { + (void)closure; + (void)args; + (void)argc; + ScrDyn *self = bc_this(); + if (!bc_require_object(self, "_eventsCount")) { + scr_dyn_release(self); + return NULL; + } + ScrDyn *count = bc_get(self, "_eventsCount"); + ScrDyn *result = NULL; + if (count) { + if (bc_number(count) > 0) { + ScrDyn *events = bc_get(self, "_events"); + if (events) { + result = scr_dyn_own_keys(events); + scr_dyn_release(events); + } + } else { + result = scr_dyn_new_arr(); + } + scr_dyn_release(count); + } + scr_dyn_release(self); + return result; +} + +static ScrDyn *bc_set_max(ScrClosure *closure, ScrDyn *const *args, size_t argc) { + (void)closure; + ScrDyn *self = bc_this(); + ScrDyn *n = bc_arg(args, argc, 0); + if (n->kind != SCR_DYN_NUM) { + scr_dyn_arg_type_fail("n", "of type number", n); + scr_dyn_release(self); + return NULL; + } + if (n->v.num < 0 || n->v.num != n->v.num) { + ScrStr *shown = scr_dyn_string_coerce_js(n); + ScrJsonBuf text; + scr_jb_init(&text); + scr_jb_puts(&text, "The value of \"n\" is out of range. It must be a non-negative number. Received "); + if (shown) scr_jb_put_str(&text, shown); + if (shown) scr_str_release(shown); + ScrStr *message = scr_jb_finish(&text); + scr_throw_error_msg_code(SCR_ERR_RANGE, message->data, message->len, "ERR_OUT_OF_RANGE"); + scr_str_release(message); + scr_dyn_release(self); + return NULL; + } + if (!bc_require_object(self, "_maxListeners") || !bc_set(self, "_maxListeners", scr_dyn_retain(n))) { + scr_dyn_release(self); + return NULL; + } + return self; +} + +static ScrDyn *bc_get_max(ScrClosure *closure, ScrDyn *const *args, size_t argc) { + (void)closure; + (void)args; + (void)argc; + ScrDyn *self = bc_this(); + double max = bc_require_object(self, "_maxListeners") ? bc_max_listeners(self) : -1; + scr_dyn_release(self); + if (scr_exc_pending()) return NULL; + return scr_dyn_new_num(max); +} + +/* ── Stream.prototype.pipe (lib/internal/streams/legacy.js) ─────────── */ + +enum { BC_PIPE_SOURCE, BC_PIPE_DEST, BC_PIPE_ENDED, BC_PIPE_HANDLERS, BC_PIPE_CAPS }; +enum { BC_ONDATA, BC_ONDRAIN, BC_ONEND, BC_ONCLOSE, BC_ONERROR, BC_CLEANUP, BC_HANDLER_COUNT }; + +static ScrDyn *bc_pipe_handler(ScrClosure *state, int which); + +static ScrDyn *bc_pipe_get_handler(ScrClosure *state, int which) { + ScrDyn *handlers = bc_cap(state, BC_PIPE_HANDLERS); + ScrDyn *handler = scr_dyn_index_get(handlers, which); + scr_dyn_release(handlers); + return handler; +} + +/* Calls source/dest.removeListener for one pipe handler. */ +static bool bc_pipe_remove(ScrDyn *emitter, const char *event, ScrClosure *state, int which) { + ScrDyn *handler = bc_pipe_get_handler(state, which); + if (!handler) return false; + ScrDyn *name = bc_key(event); + ScrDyn *pair[2] = { name, handler }; + ScrDyn *removed = bc_invoke(emitter, "removeListener", pair, 2); + scr_dyn_release(name); + scr_dyn_release(handler); + if (!removed) return false; + scr_dyn_release(removed); + return true; +} + +static bool bc_pipe_on(ScrDyn *emitter, const char *event, ScrClosure *state, int which) { + ScrDyn *handler = bc_pipe_get_handler(state, which); + if (!handler) return false; + ScrDyn *name = bc_key(event); + ScrDyn *pair[2] = { name, handler }; + ScrDyn *added = bc_invoke(emitter, "on", pair, 2); + scr_dyn_release(name); + scr_dyn_release(handler); + if (!added) return false; + scr_dyn_release(added); + return true; +} + +static bool bc_pipe_cleanup(ScrClosure *state) { + ScrDyn *source = bc_cap(state, BC_PIPE_SOURCE); + ScrDyn *dest = bc_cap(state, BC_PIPE_DEST); + bool ok = bc_pipe_remove(source, "data", state, BC_ONDATA) && + bc_pipe_remove(dest, "drain", state, BC_ONDRAIN) && + bc_pipe_remove(source, "end", state, BC_ONEND) && + bc_pipe_remove(source, "close", state, BC_ONCLOSE) && + bc_pipe_remove(source, "error", state, BC_ONERROR) && + bc_pipe_remove(dest, "error", state, BC_ONERROR) && + bc_pipe_remove(source, "end", state, BC_CLEANUP) && + bc_pipe_remove(source, "close", state, BC_CLEANUP) && + bc_pipe_remove(dest, "close", state, BC_CLEANUP); + scr_dyn_release(dest); + scr_dyn_release(source); + /* the handlers array held the closures that hold this state */ + if (ok) scr_box_set_ref(state->caps[BC_PIPE_HANDLERS], scr_dyn_new_arr()); + return ok; +} + +static ScrDyn *bc_pipe_event(ScrClosure *closure, ScrDyn *const *args, size_t argc) { + ScrClosure *state = (ScrClosure *)scr_box_get_ref(closure->caps[0]); + int which = (int)scr_box_get_f64(closure->caps[1]); + ScrDyn *source = bc_cap(state, BC_PIPE_SOURCE); + ScrDyn *dest = bc_cap(state, BC_PIPE_DEST); + bool ok = true; + switch (which) { + case BC_ONDATA: { + ScrDyn *writable = bc_get(dest, "writable"); + if (!writable) { + ok = false; + break; + } + bool open = scr_dyn_truthy(writable); + scr_dyn_release(writable); + if (!open) break; + ScrDyn *chunk = bc_arg(args, argc, 0); + ScrDyn *written = bc_invoke(dest, "write", &chunk, 1); + if (!written) { + ok = false; + break; + } + bool full = written->kind == SCR_DYN_BOOL && !written->v.b; + scr_dyn_release(written); + if (full) { + ScrDyn *pause = bc_get(source, "pause"); + if (!pause) { + ok = false; + break; + } + if (scr_dyn_is_callable(pause)) { + ScrDyn *paused = bc_apply(pause, source, bc_unused_args, 0); + if (paused) scr_dyn_release(paused); + else ok = false; + } + scr_dyn_release(pause); + } + break; + } + case BC_ONDRAIN: { + ScrDyn *readable = bc_get(source, "readable"); + ScrDyn *resume = readable ? bc_get(source, "resume") : NULL; + if (!readable || !resume) { + ok = false; + } else if (scr_dyn_truthy(readable) && scr_dyn_is_callable(resume)) { + ScrDyn *resumed = bc_apply(resume, source, bc_unused_args, 0); + if (resumed) scr_dyn_release(resumed); + else ok = false; + } + if (readable) scr_dyn_release(readable); + if (resume) scr_dyn_release(resume); + break; + } + case BC_ONEND: + case BC_ONCLOSE: { + if (scr_box_get_f64(state->caps[BC_PIPE_ENDED]) != 0) break; + scr_box_set_f64(state->caps[BC_PIPE_ENDED], 1); + ScrDyn *done = which == BC_ONEND ? bc_invoke(dest, "end", bc_unused_args, 0) : NULL; + if (which == BC_ONCLOSE) { + ScrDyn *destroy = bc_get(dest, "destroy"); + if (!destroy) { + ok = false; + break; + } + if (scr_dyn_is_callable(destroy)) done = bc_apply(destroy, dest, bc_unused_args, 0); + else done = scr_dyn_retain(scr_dyn_undefined()); + scr_dyn_release(destroy); + } + if (done) scr_dyn_release(done); + else ok = false; + break; + } + case BC_ONERROR: { + ok = bc_pipe_cleanup(state); + if (!ok) break; + ScrDyn *rethrow = scr_dyn_this_get(); + ScrDyn *name = bc_key("error"); + ScrDyn *count = rethrow->kind == SCR_DYN_UNDEF ? NULL : bc_invoke(rethrow, "listenerCount", &name, 1); + scr_dyn_release(name); + if (count && bc_number(count) == 0) { + scr_dyn_throw(scr_dyn_retain(bc_arg(args, argc, 0))); + ok = false; + } else if (!count && scr_exc_pending()) { + ok = false; + } + if (count) scr_dyn_release(count); + scr_dyn_release(rethrow); + break; + } + case BC_CLEANUP: + ok = bc_pipe_cleanup(state); + break; + default: + break; + } + scr_dyn_release(dest); + scr_dyn_release(source); + scr_closure_release(state); + return ok ? scr_dyn_retain(scr_dyn_undefined()) : NULL; +} + +static ScrDyn *bc_pipe_handler(ScrClosure *state, int which) { + ScrClosure *closure = scr_closure_new(NULL, 2); + ScrBox *owner = scr_box_new_obj(scr_closure_retain_v, scr_closure_release_v, scr_closure_trace_v); + scr_box_set_ref(owner, scr_closure_retain(state)); + closure->caps[0] = owner; + closure->caps[1] = scr_box_new(SCR_BOX_F64); + scr_box_set_f64(closure->caps[1], which); + return scr_dyn_new_func(closure, bc_pipe_event, 0, "native:stream", which == BC_ONDATA ? "ondata" + : which == BC_ONDRAIN ? "ondrain" : which == BC_ONEND ? "onend" : which == BC_ONCLOSE ? "onclose" + : which == BC_ONERROR ? "onerror" : "cleanup"); +} + +/* Stream.prototype.pipe(dest, options): Node's legacy pipe for streams + * that are only EventEmitters. 'end' and 'close' end or destroy the + * destination unless options.end is false or the destination is stdio. */ +static ScrDyn *bc_pipe(ScrClosure *closure, ScrDyn *const *args, size_t argc) { + (void)closure; + ScrDyn *source = bc_this(); + ScrDyn *dest = bc_arg(args, argc, 0); + ScrDyn *options = bc_arg(args, argc, 1); + if (!bc_require_object(source, "on") || !bc_require_object(dest, "on")) { + scr_dyn_release(source); + return NULL; + } + ScrClosure *state = scr_closure_new(NULL, BC_PIPE_CAPS); + state->caps[BC_PIPE_SOURCE] = bc_box(scr_dyn_retain(source)); + state->caps[BC_PIPE_DEST] = bc_box(scr_dyn_retain(dest)); + state->caps[BC_PIPE_ENDED] = scr_box_new(SCR_BOX_F64); + ScrDyn *handlers = scr_dyn_new_arr(); + for (int i = 0; i < BC_HANDLER_COUNT; i++) scr_dyn_arr_push(handlers, bc_pipe_handler(state, i)); + state->caps[BC_PIPE_HANDLERS] = bc_box(handlers); + bool ok = bc_pipe_on(source, "data", state, BC_ONDATA) && bc_pipe_on(dest, "drain", state, BC_ONDRAIN); + bool end_dest = true; + if (ok && options->kind != SCR_DYN_UNDEF && options->kind != SCR_DYN_NULL) { + ScrDyn *end = bc_get(options, "end"); + if (!end) ok = false; + else { + end_dest = !(end->kind == SCR_DYN_BOOL && !end->v.b); + scr_dyn_release(end); + } + } + if (ok && end_dest) { + ScrDyn *stdio = bc_get(dest, "_isStdio"); + if (!stdio) ok = false; + else { + end_dest = !scr_dyn_truthy(stdio); + scr_dyn_release(stdio); + } + } + if (ok && end_dest) + ok = bc_pipe_on(source, "end", state, BC_ONEND) && bc_pipe_on(source, "close", state, BC_ONCLOSE); + if (ok) { + /* prependListener(source, 'error', onerror) and the same on dest */ + ScrDyn *onerror = bc_pipe_get_handler(state, BC_ONERROR); + ScrDyn *name = bc_key("error"); + ScrDyn *pair[2] = { name, onerror }; + ScrDyn *a = onerror ? bc_invoke(source, "prependListener", pair, 2) : NULL; + ScrDyn *b = a ? bc_invoke(dest, "prependListener", pair, 2) : NULL; + ok = a && b; + if (a) scr_dyn_release(a); + if (b) scr_dyn_release(b); + scr_dyn_release(name); + if (onerror) scr_dyn_release(onerror); + } + if (ok) + ok = bc_pipe_on(source, "end", state, BC_CLEANUP) && bc_pipe_on(source, "close", state, BC_CLEANUP) && + bc_pipe_on(dest, "close", state, BC_CLEANUP); + ScrDyn *result = NULL; + if (ok) { + ScrDyn *name = bc_key("pipe"); + ScrDyn *pair[2] = { name, source }; + ScrDyn *emitted = bc_invoke(dest, "emit", pair, 2); + scr_dyn_release(name); + if (emitted) { + scr_dyn_release(emitted); + result = scr_dyn_retain(dest); + } + } + scr_closure_release(state); + scr_dyn_release(source); + return result; +} + +/* ── the constructors ───────────────────────────────────────────────── */ + +static ScrDyn *bc_construct(ScrClosure *closure, ScrDyn *const *args, size_t argc) { + int kind = (int)scr_box_get_f64(closure->caps[0]); + ScrDyn *self = bc_this(); + ScrDyn *result = NULL; + if (kind == BC_EMITTER || kind == BC_STREAM) { + if (bc_emitter_init(self, bc_arg(args, argc, 0))) result = scr_dyn_retain(scr_dyn_undefined()); + } else { + char message[160]; + snprintf(message, sizeof message, + "'%s' as the base of a constructor function (util.inherits or %s.call(this)) is not supported yet", + bc_names[kind], bc_names[kind]); + scr_throw_error_msg_code(SCR_ERR_ERROR, message, strlen(message), "SC2020"); + } + scr_dyn_release(self); + return result; +} + +static void bc_method(ScrDyn *prototype, const char *name, ScrDynThunk thunk, uint32_t arity) { + ScrDyn *fn = scr_dyn_new_func(scr_closure_new(NULL, 0), thunk, arity, "native:events", name); + bc_set(prototype, name, fn); +} + +static void bc_alias(ScrDyn *prototype, const char *alias, const char *name) { + ScrDyn *fn = bc_get(prototype, name); + if (fn) bc_set(prototype, alias, fn); +} + +static void bc_teardown(void) { + for (int i = 0; i < BC_COUNT; i++) { + if (!bc_ctors[i]) continue; + ScrDyn *ctor = bc_ctors[i]; + /* prototype.constructor, EventEmitter.EventEmitter and Stream.Stream + * point back at the constructor, and Stream.Readable … at constructors + * whose own prototype is Stream: break those cycles first. */ + ScrDyn *prototype = bc_get(ctor, "prototype"); + if (prototype && prototype->kind == SCR_DYN_OBJ) bc_set(prototype, "constructor", scr_dyn_retain(scr_dyn_undefined())); + if (prototype) scr_dyn_release(prototype); + bc_set(ctor, bc_names[i], scr_dyn_retain(scr_dyn_undefined())); + if (i == BC_STREAM) + for (int other = BC_READABLE; other < BC_COUNT; other++) + bc_set(ctor, bc_names[other], scr_dyn_retain(scr_dyn_undefined())); + if (scr_exc_pending()) scr_exc_clear(); + } + for (int i = 0; i < BC_COUNT; i++) { + if (!bc_ctors[i]) continue; + ScrDyn *ctor = bc_ctors[i]; + bc_ctors[i] = NULL; + scr_dyn_release(ctor); + } + for (int i = 0; i < BC_SYMBOLS; i++) { + if (bc_symbols[i]) scr_dyn_release(bc_symbols[i]); + bc_symbols[i] = NULL; + } + if (scr_exc_pending()) scr_exc_clear(); +} + +static ScrDyn *bc_new_class(int kind, ScrDyn *parent) { + ScrClosure *closure = scr_closure_new(NULL, 1); + closure->function_kind = 4; /* an ordinary function with its own prototype */ + closure->caps[0] = scr_box_new(SCR_BOX_F64); + scr_box_set_f64(closure->caps[0], kind); + ScrDyn *ctor = scr_dyn_new_func(closure, bc_construct, 1, "native:events", bc_names[kind]); + ScrDyn *prototype = parent ? NULL : scr_dyn_new_obj(); + if (parent) { + ScrDyn *base = bc_get(parent, "prototype"); + prototype = scr_dyn_obj_create(base); + scr_dyn_release(base); + ScrDyn *linked = scr_dyn_set_prototype(ctor, parent); + if (linked) scr_dyn_release(linked); + } + bc_set(prototype, "constructor", scr_dyn_retain(ctor)); + bc_set(ctor, "prototype", scr_dyn_retain(prototype)); + if (kind == BC_EMITTER) { + bc_set(prototype, "_events", scr_dyn_retain(scr_dyn_undefined())); + bc_set(prototype, "_eventsCount", scr_dyn_new_num(0)); + bc_set(prototype, "_maxListeners", scr_dyn_retain(scr_dyn_undefined())); + bc_set_key(prototype, bc_symbol(BC_CAPTURE), scr_dyn_new_bool(false)); + bc_method(prototype, "setMaxListeners", bc_set_max, 1); + bc_method(prototype, "getMaxListeners", bc_get_max, 0); + bc_method(prototype, "emit", bc_emit, 1); + bc_method(prototype, "addListener", bc_on, 2); + bc_alias(prototype, "on", "addListener"); + bc_method(prototype, "prependListener", bc_prepend, 2); + bc_method(prototype, "once", bc_once, 2); + bc_method(prototype, "prependOnceListener", bc_prepend_once, 2); + bc_method(prototype, "removeListener", bc_off, 2); + bc_alias(prototype, "off", "removeListener"); + bc_method(prototype, "removeAllListeners", bc_remove_all, 1); + bc_method(prototype, "listeners", bc_listeners, 1); + bc_method(prototype, "rawListeners", bc_raw_listeners, 1); + bc_method(prototype, "listenerCount", bc_listener_count, 2); + bc_method(prototype, "eventNames", bc_event_names, 0); + bc_set(ctor, "EventEmitter", scr_dyn_retain(ctor)); + bc_set(ctor, "defaultMaxListeners", scr_dyn_new_num(10)); + ScrDyn *count = scr_dyn_new_func(scr_closure_new(NULL, 0), bc_static_listener_count, 2, "native:events", "listenerCount"); + bc_set(ctor, "listenerCount", count); + } else if (kind == BC_STREAM) { + ScrDyn *pipe = scr_dyn_new_func(scr_closure_new(NULL, 0), bc_pipe, 2, "native:stream", "pipe"); + bc_set(prototype, "pipe", pipe); + } + scr_dyn_release(prototype); + if (scr_exc_pending()) scr_exc_clear(); + return ctor; +} + +/* All seven constructors are created together, each after its parent, so + * Stream's static members name the same constructors every other read + * answers. */ +static ScrDyn *bc_class(int kind) { + if (!bc_ctors[kind]) { + static const int parents[BC_COUNT] = { + -1, BC_EMITTER, BC_STREAM, BC_STREAM, BC_READABLE, BC_DUPLEX, BC_TRANSFORM, + }; + for (int each = 0; each < BC_COUNT; each++) { + if (bc_ctors[each]) continue; + bc_ctors[each] = bc_new_class(each, parents[each] >= 0 ? bc_ctors[parents[each]] : NULL); + } + /* Registered after the constructors exist, so the teardown runs before + * the runtime state their properties were stored in (scr_atexit is + * last in, first out). */ + if (!bc_teardown_registered) { + bc_teardown_registered = true; + scr_atexit(bc_teardown); + } + /* require("stream") is Stream itself, carrying its classes */ + ScrDyn *stream = bc_ctors[BC_STREAM]; + bc_set(stream, "Stream", scr_dyn_retain(stream)); + for (int other = BC_READABLE; other < BC_COUNT; other++) + bc_set(stream, bc_names[other], scr_dyn_retain(bc_ctors[other])); + if (scr_exc_pending()) scr_exc_clear(); + } + return bc_ctors[kind]; +} + +/* The constructor value for kind (the BC_* order), +1. */ +ScrDyn *scr_dyn_builtin_class(double kind) { + int index = (int)kind; + if (index < 0 || index >= BC_COUNT) scr_trap("scriptc: unknown builtin class value\n"); + return scr_dyn_retain(bc_class(index)); +} + +/* OrdinaryHasInstance over the value's prototype chain. */ +static bool bc_chain_has(ScrDyn *value, ScrDyn *prototype) { + if (value->kind != SCR_DYN_OBJ && value->kind != SCR_DYN_ARR && value->kind != SCR_DYN_FUNC && + value->kind != SCR_DYN_PROXY) + return false; + ScrDyn *current = scr_dyn_get_prototype(value); + while (current && current->kind != SCR_DYN_NULL) { + if (scr_dyn_strict_eq(current, prototype)) { + scr_dyn_release(current); + return true; + } + ScrDyn *next = scr_dyn_get_prototype(current); + scr_dyn_release(current); + current = next; + } + if (current) scr_dyn_release(current); + return false; +} + +/* `value instanceof `. Node's Writable also answers true for an + * object whose _writableState is a WritableState, which no untyped object + * has, so the prototype chain answers for every kind. */ +bool scr_dyn_builtin_instanceof(ScrDyn *value, double kind) { + int index = (int)kind; + if (index < 0 || index >= BC_COUNT) scr_trap("scriptc: unknown builtin class value\n"); + ScrDyn *prototype = bc_get(bc_class(index), "prototype"); + if (!prototype) return false; + bool found = bc_chain_has(value, prototype); + scr_dyn_release(prototype); + return found; +} + +/* `value instanceof callee` for an untyped constructor function. */ +bool scr_dyn_function_instanceof(ScrDyn *value, ScrDyn *callee) { + if (!scr_dyn_is_callable(callee)) { + static const char message[] = "Right-hand side of 'instanceof' is not callable"; + scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1); + return false; + } + ScrDyn *prototype = bc_get(callee, "prototype"); + if (!prototype) return false; + bool found = false; + if (prototype->kind == SCR_DYN_OBJ || prototype->kind == SCR_DYN_ARR || prototype->kind == SCR_DYN_FUNC || + prototype->kind == SCR_DYN_PROXY) { + found = bc_chain_has(value, prototype); + } else if (value->kind == SCR_DYN_OBJ || value->kind == SCR_DYN_ARR || value->kind == SCR_DYN_FUNC) { + static const char message[] = "Function has non-object prototype 'undefined' in instanceof check"; + scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1); + } + scr_dyn_release(prototype); + return found; +} diff --git a/tests/corpus/6453-constructor-function-emitter.cjs b/tests/corpus/6453-constructor-function-emitter.cjs new file mode 100644 index 000000000..d07453e14 --- /dev/null +++ b/tests/corpus/6453-constructor-function-emitter.cjs @@ -0,0 +1,114 @@ +// Constructor functions that inherit from EventEmitter the pre-class way: +// util.inherits and EventEmitter.call(this), the call-without-new guard, +// prototype methods that emit, and the listener API over the instance. +"use strict"; +const util = require("util"); +const { EventEmitter } = require("events"); + +util.inherits(Bus, EventEmitter); +function Bus(name) { + if (!(this instanceof Bus)) return new Bus(name); + EventEmitter.call(this); + this.name = name; + this.entries = []; +} +Bus.prototype.add = function (entry) { + this.entries.push(entry); + this.emit("added", entry, this.entries.length); + return this; +}; + +// A second level: Sub inherits Bus, and Bus's constructor runs through call. +function Sub(name) { + Bus.call(this, name); + this.sub = true; +} +util.inherits(Sub, Bus); +Sub.prototype.add = function (entry) { + return Bus.prototype.add.call(this, entry.toUpperCase()); +}; + +// Object.setPrototypeOf instead of util.inherits. +function Legacy() { + EventEmitter.call(this); +} +Object.setPrototypeOf(Legacy.prototype, EventEmitter.prototype); + +function main() { + const bus = Bus("main"); + console.log(bus instanceof Bus, bus instanceof EventEmitter, Bus.super_ === EventEmitter); + console.log(Object.keys(bus).join(","), bus._eventsCount, bus._maxListeners); + bus.on("added", function (entry, count) { + console.log("added", entry, count, this === bus); + }); + bus.once("added", (entry) => console.log("first only", entry)); + bus.prependListener("added", () => console.log("prepended")); + bus.prependOnceListener("added", () => console.log("prepended once")); + bus.add("a").add("b"); + console.log(bus.listenerCount("added"), bus.eventNames(), bus.getMaxListeners()); + + const handler = () => console.log("never"); + bus.on("x", handler); + bus.on("x", () => console.log("kept")); + bus.off("x", handler); + console.log(bus.emit("x"), bus.emit("nothing"), bus.listeners("x").length, bus.rawListeners("added").length); + + try { + bus.emit("error", new Error("boom")); + } catch (err) { + console.log("caught", err.message); + } + try { + bus.emit("error", "plain"); + } catch (err) { + console.log("caught", err.code, err.message); + } + bus.on("error", (err) => console.log("handled", err.message)); + bus.emit("error", new Error("quiet")); + + bus.on("newListener", (event, listener) => console.log("newListener", event, typeof listener)); + bus.on("removeListener", (event) => console.log("removeListener", event)); + bus.on("later", () => {}); + bus.removeAllListeners("added"); + console.log(bus.listenerCount("added"), bus.listenerCount("later")); + bus.removeAllListeners(); + console.log(bus.eventNames().length); + + const sub = new Sub("child"); + sub.on("added", (entry) => console.log("sub added", entry)); + sub.add("c"); + console.log(sub instanceof Sub, sub instanceof Bus, sub instanceof EventEmitter, sub.entries); + + const legacy = new Legacy(); + console.log("legacy", legacy instanceof EventEmitter, legacy.getMaxListeners()); + try { + legacy.setMaxListeners(-1); + } catch (err) { + console.log(err.name, err.code); + } + try { + legacy.on("bad", "not a function"); + } catch (err) { + console.log(err.name, err.code); + } + console.log(new Legacy()); + + // The leak warning goes to stderr with the process id: run it in a child + // and print its output with the id normalized. + const { spawnSync } = require("child_process"); + const child = spawnSync(process.execPath, [process.argv[1], "leak"], { encoding: "utf8" }); + console.log(child.stdout + child.stderr.replace(/\(node:\d+\)/g, "(node:)")); +} + +function leak() { + const legacy = new Legacy(); + legacy.setMaxListeners(1); + legacy.on("many", () => {}); + legacy.on("many", () => {}); + legacy.on("many", () => {}); + console.log("leak", legacy.listenerCount("many")); + setTimeout(() => console.log("leak done"), 1); +} + +if (process.argv[2] === "leak") leak(); +else main(); diff --git a/tests/corpus/6454-constructor-function-stream-bases.cjs b/tests/corpus/6454-constructor-function-stream-bases.cjs new file mode 100644 index 000000000..4b2bb6789 --- /dev/null +++ b/tests/corpus/6454-constructor-function-stream-bases.cjs @@ -0,0 +1,65 @@ +// The legacy Stream constructor as a constructor function's base: its +// EventEmitter state and the legacy pipe between two such streams. The +// stream classes take util.inherits, and instanceof follows the chain. +"use strict"; +const util = require("util"); +const Stream = require("stream"); +const { Readable, Writable, Duplex, Transform, PassThrough } = require("stream"); + +function Source(items) { + Stream.call(this); + this.items = items; + this.readable = true; +} +util.inherits(Source, Stream); +Source.prototype.start = function () { + for (const item of this.items) this.emit("data", item); + this.emit("end"); +}; + +function Sink() { + Stream.call(this); + this.writable = true; + this.chunks = []; +} +util.inherits(Sink, Stream); +Sink.prototype.write = function (chunk) { + this.chunks.push(chunk); + return true; +}; +Sink.prototype.end = function () { + console.log("sink end", this.chunks.join("+")); +}; + +const source = new Source(["a", "b", "c"]); +const sink = new Sink(); +sink.on("pipe", (from) => console.log("piped", from === source)); +console.log(source.pipe(sink) === sink, sink.listenerCount("pipe")); +source.start(); +console.log("after end", source.listenerCount("data"), sink.listenerCount("drain")); + +const kept = new Sink(); +const second = new Source(["x"]); +second.pipe(kept, { end: false }); +second.start(); +console.log("kept open", kept.chunks); + +function Reader() {} +util.inherits(Reader, Readable); +function Both() {} +util.inherits(Both, Duplex); +function Shift() {} +util.inherits(Shift, Transform); +function Through() {} +util.inherits(Through, PassThrough); +function Out() {} +util.inherits(Out, Writable); +console.log(Reader.super_ === Readable, Through.super_ === PassThrough, Out.super_ === Writable); +const reader = Object.create(Reader.prototype); +const through = Object.create(Through.prototype); +const out = Object.create(Out.prototype); +const both = Object.create(Both.prototype); +console.log(reader instanceof Readable, reader instanceof Writable); +console.log(through instanceof Transform, through instanceof Duplex, through instanceof Readable); +console.log(out instanceof Writable, out instanceof Readable, both instanceof Readable); +console.log(typeof Shift.prototype.constructor, Shift.prototype instanceof Duplex); diff --git a/tests/harness/self-hosting-native-toolchain.test.ts b/tests/harness/self-hosting-native-toolchain.test.ts index fa71b5a08..1de960368 100644 --- a/tests/harness/self-hosting-native-toolchain.test.ts +++ b/tests/harness/self-hosting-native-toolchain.test.ts @@ -30,6 +30,7 @@ const features: NativeLinkFeatures = { bigint: false, searchParams: false, qs: false, + dynBuiltinClass: false, parseArgs: false, stream: false, net: false, diff --git a/tests/harness/self-hosting-runtime-features.test.ts b/tests/harness/self-hosting-runtime-features.test.ts index d2c2b589b..8d65d72cd 100644 --- a/tests/harness/self-hosting-runtime-features.test.ts +++ b/tests/harness/self-hosting-runtime-features.test.ts @@ -50,6 +50,7 @@ const featureCalls: readonly [string, IrLibFn][] = [ ["bigint", "bigint.parse"], ["searchParams", "sp.new"], ["qs", "qs.parse"], + ["dynBuiltinClass", "dyn.builtinClass"], ["parseArgs", "util.parseArgs"], ["parseArgs", "util.isDeepStrictEqual"], ["parseArgs", "util.styleText"], diff --git a/tests/harness/self-hosting-runtime-pack.test.ts b/tests/harness/self-hosting-runtime-pack.test.ts index c95d6d9a0..42cf8a729 100644 --- a/tests/harness/self-hosting-runtime-pack.test.ts +++ b/tests/harness/self-hosting-runtime-pack.test.ts @@ -31,6 +31,7 @@ const base: NativeLinkFeatures = { bigint: false, searchParams: false, qs: false, + dynBuiltinClass: false, parseArgs: false, stream: false, net: false, From a64d7f8ce12c36e39c405525098c94a59a6fc18d Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Tue, 20 Oct 2026 00:39:10 -0700 Subject: [PATCH 059/122] Run JavaScript promise executors passed by name, and read `catch (e: any)` bindings dynamically - `new Promise(run)` where `run` is a JavaScript function value whose parameters are untyped (the retry-helper shape `function run(resolve, reject) {...}; return new Promise(run)`) gets an adapter executor: it receives the native resolve/reject pair and calls the original with both boxed as checked-dynamic function values. This was a deferred runtime refusal inside such packages. - A catch binding annotated `any` reads the caught value through its checked-dynamic face, as an unannotated `unknown` one does: member reads, expando writes, `delete` and `in` act on the thrown object, and a rethrow keeps its identity. It was refused (SC1063). Co-Authored-By: Claude Opus 5.5 --- .../src/frontend/lowering/lower-classes.ts | 5 +- .../src/frontend/lowering/lower-exprs.ts | 9 +- .../lowering/promise-executor-adapter.ts | 102 ++++++++++++++++++ .../main.ts.json | 6 ++ .../corpus/6491-catch-any-bindings.ts.json | 6 ++ .../6490-js-named-promise-executor/main.ts | 22 ++++ .../node_modules/later/index.d.ts | 1 + .../node_modules/later/index.js | 31 ++++++ .../node_modules/later/package.json | 6 ++ .../tsconfig.json | 3 + tests/corpus/6491-catch-any-bindings.ts | 34 ++++++ 11 files changed, 222 insertions(+), 3 deletions(-) create mode 100644 packages/compiler/src/frontend/lowering/promise-executor-adapter.ts create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6490-js-named-promise-executor/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6491-catch-any-bindings.ts.json create mode 100644 tests/corpus/6490-js-named-promise-executor/main.ts create mode 100644 tests/corpus/6490-js-named-promise-executor/node_modules/later/index.d.ts create mode 100644 tests/corpus/6490-js-named-promise-executor/node_modules/later/index.js create mode 100644 tests/corpus/6490-js-named-promise-executor/node_modules/later/package.json create mode 100644 tests/corpus/6490-js-named-promise-executor/tsconfig.json create mode 100644 tests/corpus/6491-catch-any-bindings.ts diff --git a/packages/compiler/src/frontend/lowering/lower-classes.ts b/packages/compiler/src/frontend/lowering/lower-classes.ts index 6589d7c04..afac089f8 100644 --- a/packages/compiler/src/frontend/lowering/lower-classes.ts +++ b/packages/compiler/src/frontend/lowering/lower-classes.ts @@ -1,4 +1,5 @@ import { lowerValueReturningConstructorNew } from "./js-dynamic-objects.js"; +import { adaptPromiseExecutor } from "./promise-executor-adapter.js"; import { dynUndefinedExpr, nodeThrowExpr, varRef } from "../../ir/build.js"; import { constructedFromValue, @@ -9856,7 +9857,9 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { // catch-side instanceof and the uncaught printer working). First // settle wins, exactly JS: reject-after-resolve and double-reject // are no-ops, and an executor throw after any settle is swallowed. - const executor = lowerer.lowerExpr(args[0]!); + let executor = lowerer.lowerExpr(args[0]!); + if (executor.type.kind !== "func") lowerer.badType(args[0]!, lowerer.typeOf(args[0]!)); + executor = adaptPromiseExecutor(lowerer, executor, type.inner, loc) ?? executor; if (executor.type.kind !== "func") lowerer.badType(args[0]!, lowerer.typeOf(args[0]!)); if (executor.type.params.length > 1) { const rj = executor.type.params[1]!; diff --git a/packages/compiler/src/frontend/lowering/lower-exprs.ts b/packages/compiler/src/frontend/lowering/lower-exprs.ts index 6cbe5d7a4..49f49987d 100644 --- a/packages/compiler/src/frontend/lowering/lower-exprs.ts +++ b/packages/compiler/src/frontend/lowering/lower-exprs.ts @@ -2132,8 +2132,13 @@ export function caughtRead( } // Unnarrowed reads cross into unknown. The caught-value dispatch pass // retains known native class payloads through live identity capsules; - // other payload kinds use the runtime's ordinary conversion. - if (narrowed?.kind === "dyn") { + // other payload kinds use the runtime's ordinary conversion. A binding + // annotated `any` (`catch (error: any)`) reads the same checked-dynamic + // value: its member reads, writes and deletes go through it. + if ( + narrowed?.kind === "dyn" || + (narrowed === null && (lowerer.typeOf(node).flags & ts.TypeFlags.Any) !== 0) + ) { return { kind: "caughtToDyn", value: ref, type: DYN, loc }; } lowerer.unsupported("SC1063", node); diff --git a/packages/compiler/src/frontend/lowering/promise-executor-adapter.ts b/packages/compiler/src/frontend/lowering/promise-executor-adapter.ts new file mode 100644 index 000000000..09d228b05 --- /dev/null +++ b/packages/compiler/src/frontend/lowering/promise-executor-adapter.ts @@ -0,0 +1,102 @@ +import { varRef } from "../../ir/build.js"; +import { + type IrExpr, + type IrLocal, + type IrParam, + type IrType, + type SrcLoc, + VOID, + funcOf, + typeKey, +} from "../../ir/ir.js"; +import { newFnCtx, type Lowerer } from "./lowerer.js"; + +/** An executor VALUE whose parameters are checked-dynamic (a JavaScript + * function declaration: `function run(resolve, reject) {...}; return new + * Promise(run)`): the promise's native executor is an adapter closure that + * receives the native (resolve, reject) pair and calls the original with + * both boxed into the checked-dynamic tree, so the JavaScript body calls + * them like any function value. Null when the executor's shape is not + * adaptable (more than two parameters, a rest parameter, or a parameter + * that neither is dyn nor already the native closure). */ +export function adaptPromiseExecutor( + lowerer: Lowerer, + executor: IrExpr, + inner: IrType, + loc: SrcLoc, +): IrExpr | null { + const execT = executor.type; + if (execT.kind !== "func" || execT.rest || execT.params.length > 2) return null; + if (!execT.params.some((p) => p.kind === "dyn")) return null; + const resolveT = funcOf(inner.kind === "void" ? [] : [inner], VOID); + const rejectT = funcOf([{ kind: "object", className: "%Error" }], VOID); + const native = [resolveT, rejectT]; + if ( + !execT.params.every((p, i) => p.kind === "dyn" || typeKey(p) === typeKey(native[i]!)) || + !native.slice(0, execT.params.length).every((t) => lowerer.dynConvertible(t)) + ) + return null; + const adaptT = funcOf(native, VOID); + const key = `promise.executor:${typeKey(execT)}:${typeKey(inner)}`; + let name = lowerer.arrHofHelpers.get(key); + if (name === undefined) { + name = `%promise.executor.${lowerer.arrHofHelpers.size}`; + lowerer.arrHofHelpers.set(key, name); + const impl = `${name}.impl`; + const captured: IrLocal = { id: "f.0", name: "f", type: execT, mutable: false, boxed: true }; + const params: IrParam[] = [ + { localId: "resolve.0", name: "resolve", type: resolveT }, + { localId: "reject.0", name: "reject", type: rejectT }, + ]; + const ctx = newFnCtx(false, null, null, VOID); + lowerer.fnStack.push(ctx); + let args: IrExpr[]; + try { + args = execT.params.map((p, i) => + lowerer.coerceToExpected(varRef(params[i]!.localId, native[i]!, loc), p), + ); + } finally { + lowerer.fnStack.pop(); + } + lowerer.liftedFns.push({ + name: impl, + params, + returnType: VOID, + captures: [{ localId: captured.id, name: captured.name, type: execT }], + locals: [ + captured, + ...params.map((p) => ({ id: p.localId, name: p.name, type: p.type, mutable: false })), + ...ctx.locals, + ], + body: [ + { + kind: "exprStmt", + expr: { + kind: "callValue", + callee: varRef(captured.id, execT, loc), + args, + type: execT.ret, + loc, + }, + loc, + }, + ], + loc, + }); + lowerer.liftedFns.push({ + name, + params: [{ localId: captured.id, name: captured.name, type: execT }], + returnType: adaptT, + locals: [captured], + body: [ + { + kind: "return", + value: { kind: "closure", fnName: impl, captures: [captured.id], type: adaptT, loc }, + loc, + }, + ], + loc, + }); + } + return { kind: "call", callee: name, args: [executor], type: adaptT, loc }; +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6490-js-named-promise-executor/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6490-js-named-promise-executor/main.ts.json new file mode 100644 index 000000000..dd95018f0 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6490-js-named-promise-executor/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6490-js-named-promise-executor/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6491-catch-any-bindings.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6491-catch-any-bindings.ts.json new file mode 100644 index 000000000..bc69a66e3 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6491-catch-any-bindings.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6491-catch-any-bindings.ts" + ], + "diags": [] +} diff --git a/tests/corpus/6490-js-named-promise-executor/main.ts b/tests/corpus/6490-js-named-promise-executor/main.ts new file mode 100644 index 000000000..79e2a17d7 --- /dev/null +++ b/tests/corpus/6490-js-named-promise-executor/main.ts @@ -0,0 +1,22 @@ +// A JavaScript library that builds its promise from a named executor +// function (`function run(resolve, reject) {...}; return new Promise(run)`, +// the classic retry helper shape): the executor's parameters are untyped, +// so it receives the native resolve and reject as checked-dynamic function +// values and calls them like any JavaScript function. +import { attempt } from "later"; +async function main(): Promise { + const v = await attempt(async (_bail: (e: Error) => void, n: number): Promise => { + if (n < 3) throw new Error("try " + n); + return "done after " + n; + }, 5); + console.log(v); + try { + await attempt((bail: (e: Error) => void): string => { + bail(new Error("stop")); + return "ignored"; + }, 5); + } catch (e) { + console.log("caught", (e as Error).message); + } +} +main(); diff --git a/tests/corpus/6490-js-named-promise-executor/node_modules/later/index.d.ts b/tests/corpus/6490-js-named-promise-executor/node_modules/later/index.d.ts new file mode 100644 index 000000000..55956f01d --- /dev/null +++ b/tests/corpus/6490-js-named-promise-executor/node_modules/later/index.d.ts @@ -0,0 +1 @@ +export declare function attempt(fn: (bail: (e: Error) => void, n: number) => A | Promise, retries: number): Promise; diff --git a/tests/corpus/6490-js-named-promise-executor/node_modules/later/index.js b/tests/corpus/6490-js-named-promise-executor/node_modules/later/index.js new file mode 100644 index 000000000..deefa6aee --- /dev/null +++ b/tests/corpus/6490-js-named-promise-executor/node_modules/later/index.js @@ -0,0 +1,31 @@ +"use strict"; +function attempt(fn, retries) { + function run(resolve, reject) { + let n = 0; + function bail(err) { + reject(err || new Error("Aborted")); + } + function onError(err) { + if (err.bail) { + bail(err); + return; + } + if (n > retries) reject(err); + else runAttempt(); + } + function runAttempt() { + n++; + let val; + try { + val = fn(bail, n); + } catch (err) { + onError(err); + return; + } + Promise.resolve(val).then(resolve).catch(onError); + } + runAttempt(); + } + return new Promise(run); +} +module.exports = { attempt }; diff --git a/tests/corpus/6490-js-named-promise-executor/node_modules/later/package.json b/tests/corpus/6490-js-named-promise-executor/node_modules/later/package.json new file mode 100644 index 000000000..ce1f29bac --- /dev/null +++ b/tests/corpus/6490-js-named-promise-executor/node_modules/later/package.json @@ -0,0 +1,6 @@ +{ + "name": "later", + "version": "1.0.0", + "main": "./index.js", + "types": "./index.d.ts" +} diff --git a/tests/corpus/6490-js-named-promise-executor/tsconfig.json b/tests/corpus/6490-js-named-promise-executor/tsconfig.json new file mode 100644 index 000000000..27d492dd2 --- /dev/null +++ b/tests/corpus/6490-js-named-promise-executor/tsconfig.json @@ -0,0 +1,3 @@ +{ + "compilerOptions": { "strict": true, "types": ["node"] } +} diff --git a/tests/corpus/6491-catch-any-bindings.ts b/tests/corpus/6491-catch-any-bindings.ts new file mode 100644 index 000000000..9979637b4 --- /dev/null +++ b/tests/corpus/6491-catch-any-bindings.ts @@ -0,0 +1,34 @@ +// A catch binding annotated `any` reads the caught value through its +// checked-dynamic face: member reads, expando writes, `delete` and `in` +// act on the thrown object itself, and a rethrow keeps its identity. +const thrown: Error[] = []; + +function fail(n: number): never { + const e = new Error(`failure ${n}`); + thrown.push(e); + throw e; +} + +for (const n of [1, 2]) { + try { + try { + fail(n); + } catch (error: any) { + error.bail = n === 1; + console.log("marked:", error.bail, "bail" in error, error.message); + if (error.bail) { + delete error.bail; + console.log("deleted:", error.bail, "bail" in error); + } + throw error; + } + } catch (outer) { + console.log("outer:", outer === thrown[n - 1], (outer as Error).message, "bail" in (outer as object)); + } +} + +try { + JSON.parse("{"); +} catch (error: any) { + console.log(error.name, typeof error.message, error instanceof SyntaxError); +} From b74224f31c4f165a951ecaa12e2c01ca7a654bfd Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Mon, 19 Oct 2026 23:56:38 -0700 Subject: [PATCH 060/122] Throw checked-dynamic primitives as primitives so catch typeof tests see them A value thrown from `unknown` storage (`throw values[i]`) that holds a number, string or boolean now throws as that primitive, like a statically typed `throw 5`. Before, it threw as a checked-dynamic reference, so `typeof e === "number"` (and "string"/"boolean") on the catch binding answered false and the narrowed reads were unreachable. A primitive has no identity to keep, and reading the binding as `unknown` converts it back. Co-Authored-By: Claude Opus 5.5 --- .../6480-catch-typeof-untyped-throws.ts.json | 6 +++ packages/runtime/src/scr_json.c | 22 ++++++++++ .../6480-catch-typeof-untyped-throws.ts | 40 +++++++++++++++++++ 3 files changed, 68 insertions(+) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6480-catch-typeof-untyped-throws.ts.json create mode 100644 tests/corpus/6480-catch-typeof-untyped-throws.ts diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6480-catch-typeof-untyped-throws.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6480-catch-typeof-untyped-throws.ts.json new file mode 100644 index 000000000..38b99f9fc --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6480-catch-typeof-untyped-throws.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6480-catch-typeof-untyped-throws.ts" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_json.c b/packages/runtime/src/scr_json.c index 27168dad1..33793c26d 100644 --- a/packages/runtime/src/scr_json.c +++ b/packages/runtime/src/scr_json.c @@ -6359,6 +6359,28 @@ void scr_dyn_throw(ScrDyn *d) { scr_dyn_release(d); return; } + /* A checked-dynamic primitive throws as the primitive itself, as a + * statically typed `throw 5` does: catch bindings test and extract the + * same snapshot kinds (`typeof e === "number"`), and a primitive has no + * identity to keep. */ + if (d->kind == SCR_DYN_NUM) { + double value = d->v.num; + scr_dyn_release(d); + scr_throw_f64(value); + return; + } + if (d->kind == SCR_DYN_BOOL) { + bool value = d->v.b; + scr_dyn_release(d); + scr_throw_bool(value); + return; + } + if (d->kind == SCR_DYN_STR) { + ScrStr *value = scr_str_retain(d->v.str); + scr_dyn_release(d); + scr_throw_str(value); + return; + } scr_throw_ref_classified(d, scr_dyn_retain_v, scr_dyn_release_v, scr_dyn_trace_v, scr_dyn_is_object(d)); } diff --git a/tests/corpus/6480-catch-typeof-untyped-throws.ts b/tests/corpus/6480-catch-typeof-untyped-throws.ts new file mode 100644 index 000000000..98122f851 --- /dev/null +++ b/tests/corpus/6480-catch-typeof-untyped-throws.ts @@ -0,0 +1,40 @@ +// Values thrown from `unknown` storage read back through typeof tests on +// the catch binding, like the same values thrown with static types. +const thrown: unknown[] = [5, 0, -1.5, "text", "", true, false, null, undefined, { kind: "box" }]; +function describe(value: unknown): string { + try { + throw value; + } catch (e) { + const same = e === value; + if (typeof e === "number") return `${same} number ${e + 1}`; + if (typeof e === "string") return `${same} string ${e.length}`; + if (typeof e === "boolean") return `${same} boolean ${!e}`; + if (typeof e === "object") return `${same} object ${e === null ? "null" : "value"}`; + return `${same} ${typeof e}`; + } +} +for (const value of thrown) console.log(describe(value)); +function rethrow(value: unknown): void { + try { + throw value; + } catch (e) { + if (typeof e !== "string") throw e; + console.log("kept string", e); + } +} +for (const value of ["s", 7, false]) { + try { + rethrow(value); + } catch (e) { + console.log("rethrown", typeof e, String(e)); + } +} +let count = 0; +for (const value of thrown) { + try { + throw value; + } catch (e) { + if (typeof e === "number" && e > 0) count += e; + } +} +console.log(count); From ea611bec028d394e80994e8cb4b9b4629681c999 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Tue, 20 Oct 2026 02:52:38 -0500 Subject: [PATCH 061/122] Convert object literals with accessors to unknown again #1490 also took records with accessor slots out of the dyn-convertible domain, which refused passing such a literal where `unknown` is expected. Native APIs read these values through their accessor slots: fileURLToPath, fileURLToPathBuffer and pathToFileURL call the `windows` getter of an options object, so tests/corpus/node24-file-url-options.ts stopped compiling. Records with accessor slots convert to checked-dynamic values again. JSON.stringify of their typed values stays refused, since the typed serializer cannot invoke the getter. --- packages/compiler/src/ir/ir.ts | 3 --- .../object-literal-accessor-values.ts | 14 ++-------- .../object-literal-accessor-values.ts.txt | 26 +++---------------- 3 files changed, 6 insertions(+), 37 deletions(-) diff --git a/packages/compiler/src/ir/ir.ts b/packages/compiler/src/ir/ir.ts index d6ff0fe72..de6bf558c 100644 --- a/packages/compiler/src/ir/ir.ts +++ b/packages/compiler/src/ir/ir.ts @@ -4267,9 +4267,6 @@ function canBoxDynCompositeAt( const shape = getRecord(t.shapeId); if (!shape) return false; if (recordTextCodecClass(shape) !== null) return false; - // Accessor slots hold the getter and setter closures, not property - // values (see accessorSlotProp). - if (shapeHasAccessorSlots(shape)) return false; // Recursive shapes answer coinductively, like isJsonSafeType. if (visiting.has(t.shapeId)) return true; visiting.add(t.shapeId); diff --git a/tests/diagnostics/object-literal-accessor-values.ts b/tests/diagnostics/object-literal-accessor-values.ts index 107ab1f69..e4c456a76 100644 --- a/tests/diagnostics/object-literal-accessor-values.ts +++ b/tests/diagnostics/object-literal-accessor-values.ts @@ -1,11 +1,6 @@ // Object literals with get/set accessors keep their closures in reserved -// slots, so their typed values have no JSON or checked-dynamic form: Node's -// JSON.stringify and dynamic reads invoke the getter, which neither a typed -// serializer nor a converted copy could do. - -function hold(value: unknown): unknown { - return value; -} +// slots, so their typed values have no JSON form: Node's JSON.stringify +// invokes the getter, which the typed serializer cannot do. const counter = { count: 1, @@ -17,11 +12,6 @@ const counter = { // JSON.stringify of the typed value would omit the getter's property. console.log(JSON.stringify(counter)); -// Converting the typed value to `unknown` or `any` would lose the getter. -hold(counter); -const viaAny: any = counter; -console.log(viaAny); - // A literal written into an `any` slot directly keeps its accessor. const direct: any = { get value(): number { diff --git a/tests/harness/__snapshots__/object-literal-accessor-values.ts.txt b/tests/harness/__snapshots__/object-literal-accessor-values.ts.txt index 89bc092aa..372849388 100644 --- a/tests/harness/__snapshots__/object-literal-accessor-values.ts.txt +++ b/tests/harness/__snapshots__/object-literal-accessor-values.ts.txt @@ -1,24 +1,6 @@ -object-literal-accessor-values.ts:18:28 - error SC1090: JSON.stringify of '{ get doubled(): number; count: number }' values (only number, string, boolean, records, arrays, unions of those, and 'unknown' stringify) are not supported yet +object-literal-accessor-values.ts:13:28 - error SC1090: JSON.stringify of '{ get doubled(): number; count: number }' values (only number, string, boolean, records, arrays, unions of those, and 'unknown' stringify) are not supported yet - 17 | // JSON.stringify of the typed value would omit the getter's property. - 18 | console.log(JSON.stringify(counter)); + 12 | // JSON.stringify of the typed value would omit the getter's property. + 13 | console.log(JSON.stringify(counter)); | ^~~~~~~ - 19 | - -object-literal-accessor-values.ts:21:6 - error SC1101: converting typed values to 'unknown' is not supported yet - - 20 | // Converting the typed value to `unknown` or `any` would lose the getter. - 21 | hold(counter); - | ^~~~~~~ - 22 | const viaAny: any = counter; - - hint: numbers, strings, booleans, JSON-safe records/arrays/unions (a deep copy), functions, program class instances, and supported native handles convert into 'unknown' slots; functions, exact-class round trips, and handles preserve identity, while this value's type has no dynamic representation yet - -object-literal-accessor-values.ts:22:21 - error SC1101: converting typed values to 'unknown' is not supported yet - - 21 | hold(counter); - 22 | const viaAny: any = counter; - | ^~~~~~~ - 23 | console.log(viaAny); - - hint: numbers, strings, booleans, JSON-safe records/arrays/unions (a deep copy), functions, program class instances, and supported native handles convert into 'unknown' slots; functions, exact-class round trips, and handles preserve identity, while this value's type has no dynamic representation yet \ No newline at end of file + 14 | \ No newline at end of file From c70d9d7e28d224fc00008696b835cdba213e3477 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Tue, 20 Oct 2026 00:53:19 -0700 Subject: [PATCH 062/122] Refuse returns a generic callback's inferred result would drop members of A callback passed to a generic call without a return annotation can return values the checker unifies into a narrower result type ('return items' beside 'return items.map((i) => ({ ...i, extra }))' infers Item[]). Converting the extended objects into that type copied them and dropped the added members, which Node keeps and a later wider view reads back. Such a return is now refused by name, pointing at the annotation that keeps the members. Co-Authored-By: Claude Opus 5.5 --- .../compiler/src/frontend/lowering/lowerer.ts | 72 +++++++++++++++++++ ...-generic-callback-extended-results.ts.json | 6 ++ .../inferred-callback-drops.ts.json | 6 ++ .../6480-generic-callback-extended-results.ts | 41 +++++++++++ tests/diagnostics/inferred-callback-drops.ts | 13 ++++ .../inferred-callback-drops.ts.txt | 6 ++ 6 files changed, 144 insertions(+) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6480-generic-callback-extended-results.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/inferred-callback-drops.ts.json create mode 100644 tests/corpus/6480-generic-callback-extended-results.ts create mode 100644 tests/diagnostics/inferred-callback-drops.ts create mode 100644 tests/harness/__snapshots__/inferred-callback-drops.ts.txt diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index 27011df24..3edf33d22 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -9264,6 +9264,7 @@ export class Lowerer { * lowering goes through here (via lowerExprExpecting) or calls this * directly when the expression was already lowered. */ coerceInto(node: ts.Node, expr: IrExpr, expected: IrType): IrExpr { + refuseInferredCallbackDrops(this, node, expr.type, expected); // A union destination can accept the binding's tagged storage directly. // The checker's single-arm flow type can be stale after a callback writes // the binding. Extracting that arm and wrapping it again would discard the @@ -12984,3 +12985,74 @@ export class Lowerer { return lowerStdlibMethodCall(this, call, access); } } + +/** A callback passed to a generic call without a return annotation, whose + * returns the checker unified into a narrower result type: `trace(name, + * async () => (cond ? items : items.map((i) => ({ ...i, sum }))))` infers + * `T` from the narrower `Item[]`, though at run time the extended objects + * keep `sum`. Converting such a return into the inferred type copies the + * records and drops the members the narrower type lacks, which Node keeps + * and any later wider view (`Promise`) reads back. Refuse + * that conversion by name rather than lose the members silently; an + * annotation on the callback's return type or on the call's type argument + * names a type that keeps them. */ +function refuseInferredCallbackDrops( + lowerer: Lowerer, + node: ts.Node, + actual: IrType, + expected: IrType, +): void { + if (typeEquals(actual, expected)) return; + const structured = (t: IrType): boolean => + t.kind === "record" || t.kind === "array" || t.kind === "promise"; + if (!structured(actual) || !structured(expected)) return; + let site: ts.Node = node; + while (ts.isParenthesizedExpression(site.parent) || ts.isAwaitExpression(site.parent)) + site = site.parent; + let fn: ts.Node | undefined; + if (ts.isReturnStatement(site.parent) && site.parent.expression === site) { + fn = site.parent.parent; + while (fn && !ts.isFunctionLike(fn)) fn = fn.parent; + } else if (ts.isArrowFunction(site.parent) && site.parent.body === site) { + fn = site.parent; + } + if (!fn || !(ts.isArrowFunction(fn) || ts.isFunctionExpression(fn)) || fn.type) return; + let arg: ts.Node = fn; + while (ts.isParenthesizedExpression(arg.parent)) arg = arg.parent; + const call = arg.parent; + if (!(ts.isCallExpression(call) || ts.isNewExpression(call))) return; + if (!call.arguments?.nodes.includes(arg as ts.Expression)) return; + const dropped = droppedMembers(lowerer, actual, expected, new Set()); + if (dropped.length === 0) return; + lowerer.unsupported( + "SC1090", + node, + `returning '${lowerer.fmt(actual)}' from a callback whose result the checker infers as the narrower '${lowerer.fmt(expected)}' (the conversion would drop ${dropped.map((d) => `'${d}'`).join(", ")}, which the object keeps at run time — annotate the callback's return type or the call's type argument with a type that declares them)`, + ); +} + +/** Member names a structural conversion from `from` into `to` drops: + * declared fields of a source record the target record lacks, through + * array elements and promise payloads. */ +function droppedMembers(lowerer: Lowerer, from: IrType, to: IrType, seen: Set): string[] { + if (typeEquals(from, to)) return []; + if (from.kind === "array" && to.kind === "array") + return droppedMembers(lowerer, from.elem, to.elem, seen); + if (from.kind === "promise" && to.kind === "promise") + return droppedMembers(lowerer, from.inner, to.inner, seen); + if (from.kind !== "record" || to.kind !== "record") return []; + const key = `${from.shapeId}>${to.shapeId}`; + if (seen.has(key)) return []; + seen.add(key); + const source = lowerer.shapes.get(from.shapeId); + const target = lowerer.shapes.get(to.shapeId); + if (!source || !target || source.tuple || target.tuple || target.indexValue) return []; + const out: string[] = []; + for (const f of source.fields) { + if (f.name.startsWith("%")) continue; + const t = target.fields.find((x) => x.name === f.name); + if (!t) out.push(f.name); + else out.push(...droppedMembers(lowerer, f.type, t.type, seen).map((n) => `${f.name}.${n}`)); + } + return out; +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6480-generic-callback-extended-results.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6480-generic-callback-extended-results.ts.json new file mode 100644 index 000000000..d3b5c41b7 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6480-generic-callback-extended-results.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6480-generic-callback-extended-results.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/inferred-callback-drops.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/inferred-callback-drops.ts.json new file mode 100644 index 000000000..a6b592336 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/inferred-callback-drops.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/diagnostics/inferred-callback-drops.ts" + ], + "diags": [] +} diff --git a/tests/corpus/6480-generic-callback-extended-results.ts b/tests/corpus/6480-generic-callback-extended-results.ts new file mode 100644 index 000000000..0d75a1f53 --- /dev/null +++ b/tests/corpus/6480-generic-callback-extended-results.ts @@ -0,0 +1,41 @@ +// A callback whose returns extend the elements it was given keeps the +// added members once its result type names them (an annotation on the +// callback or on the type argument), as Node keeps them on the objects. +type Plain = { name: string; n: number }; +type Item = Plain & { extra?: boolean }; +const plain: Plain[] = [ + { name: "a", n: 1 }, + { name: "b", n: 2 }, +]; + +function run(fn: () => T): T { + return fn(); +} +async function runAsync(fn: () => Promise): Promise { + return fn(); +} + +const annotated = run((): Item[] => { + if (plain.length === 0) return plain; + return plain.map((x) => ({ ...x, extra: x.n > 1 })); +}); +console.log(JSON.stringify(annotated)); + +const viaTypeArgument = run(() => { + if (plain.length > 5) return plain; + return plain.map((x) => ({ ...x, extra: true })); +}); +console.log(JSON.stringify(viaTypeArgument), viaTypeArgument[0]!.extra); + +runAsync(async (): Promise => { + if (plain.length === 0) return plain; + return plain.map((x) => ({ ...x, extra: false })); +}).then((items) => console.log(items)); + +// Callbacks whose results the checker types by their own returns keep +// every member already. +const mapped = plain.map((x) => (x.n > 1 ? x : { ...x, extra: true })); +const asItems: Item[] = mapped; +console.log(JSON.stringify(asItems)); +const summed = plain.reduce((acc, x) => ({ ...acc, total: acc.total + x.n }), { total: 0, label: "sum" }); +console.log(JSON.stringify(summed)); diff --git a/tests/diagnostics/inferred-callback-drops.ts b/tests/diagnostics/inferred-callback-drops.ts new file mode 100644 index 000000000..0d4808b6a --- /dev/null +++ b/tests/diagnostics/inferred-callback-drops.ts @@ -0,0 +1,13 @@ +type Plain = { name: string; n: number }; +type Item = Plain & { extra?: boolean }; +const plain: Plain[] = [{ name: "a", n: 1 }]; +function run(fn: () => T): T { + return fn(); +} +// The checker unifies both returns into Plain[]; the extended objects keep +// `extra` in Node, so converting them into Plain would drop it silently. +const items: Item[] = run(() => { + if (plain.length === 0) return plain; + return plain.map((x) => ({ ...x, extra: true })); +}); +console.log(items.length); diff --git a/tests/harness/__snapshots__/inferred-callback-drops.ts.txt b/tests/harness/__snapshots__/inferred-callback-drops.ts.txt new file mode 100644 index 000000000..daa62f484 --- /dev/null +++ b/tests/harness/__snapshots__/inferred-callback-drops.ts.txt @@ -0,0 +1,6 @@ +inferred-callback-drops.ts:11:10 - error SC1090: returning '{ extra: boolean; n: number; name: string }[]' from a callback whose result the checker infers as the narrower '{ n: number; name: string }[]' (the conversion would drop 'extra', which the object keeps at run time — annotate the callback's return type or the call's type argument with a type that declares them) is not supported yet + + 10 | if (plain.length === 0) return plain; + 11 | return plain.map((x) => ({ ...x, extra: true })); + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 12 | }); \ No newline at end of file From fc9a8650af902da45ac0ac3c42d867edc4c3e72f Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Tue, 20 Oct 2026 03:05:32 -0500 Subject: [PATCH 063/122] Keep an Error's code slot in step with dynamic writes and own properties Writes and deletes of `code` through an error's checked-dynamic node (a `catch (e: any)` binding, a reflective write) changed only the node, so typed `err.code` reads kept the old value. The slot sync that already covered name, message, stack and cause now covers code: a string takes the slot, any other value or a delete clears it. A write, delete or Object.assign through an Error subclass's typed view now syncs the slots the subclass inherits from Error after the class commit, which writes only declared fields. `'code' in err` read the code slot, so an own `code` set to undefined answered false. It now asks the runtime: the slot holds a string, or the error has an own `code` property on its node. --- .../compiler/src/backend/llvm/lib-shared.ts | 1 + .../src/frontend/lowering/lower-exprs.ts | 23 +------ .../compiler/src/ir/builtin-signatures.ts | 1 + packages/compiler/src/ir/validate.ts | 1 + .../6417-error-code-dynamic-writes.ts.json | 6 ++ packages/runtime/src/scr_error.h | 1 + packages/runtime/src/scr_json.c | 67 ++++++++++++++++--- .../corpus/6417-error-code-dynamic-writes.ts | 36 ++++++++++ .../6417-error-code-dynamic-writes.ts.json | 1 + 9 files changed, 109 insertions(+), 28 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6417-error-code-dynamic-writes.ts.json create mode 100644 tests/corpus/6417-error-code-dynamic-writes.ts create mode 100644 tests/harness/corpus-costs/6417-error-code-dynamic-writes.ts.json diff --git a/packages/compiler/src/backend/llvm/lib-shared.ts b/packages/compiler/src/backend/llvm/lib-shared.ts index 46dff20e1..027fde012 100644 --- a/packages/compiler/src/backend/llvm/lib-shared.ts +++ b/packages/compiler/src/backend/llvm/lib-shared.ts @@ -129,6 +129,7 @@ export const LIB_FN_SYMS: Record = { "error.setCodeDyn": "scr_error_set_code_dyn", "error.assign": "scr_error_assign_dyn", "error.hasKey": "scr_error_has_key", + "error.hasCode": "scr_error_has_code", "error.defineCause": "scr_error_define_cause", "error.deleteCause": "scr_error_delete_cause", "error.hasCause": "scr_error_has_cause", diff --git a/packages/compiler/src/frontend/lowering/lower-exprs.ts b/packages/compiler/src/frontend/lowering/lower-exprs.ts index a84e37180..1a1fabdb5 100644 --- a/packages/compiler/src/frontend/lowering/lower-exprs.ts +++ b/packages/compiler/src/frontend/lowering/lower-exprs.ts @@ -14061,27 +14061,10 @@ function lowerInExpression(lowerer: Lowerer, expr: ts.BinaryExpression, loc: Src if (key === "cause") { return { kind: "libCall", fn: "error.hasCause", args: [recv], type: BOOL, loc }; } + // The code slot, or an own `code` with another value + // (`err.code = undefined` is present). if (key === "code") { - const codeType = lowerer.envValueType(); - if (codeType.kind !== "union") - throw new InternalCompilerError("lowerer bug: error code type is not a union"); - const undefTag = lowerer.armTag(codeType.unionId, UNDEFINED_T); - const read: IrExpr = { - kind: "libCall", - fn: "error.code", - args: [recv], - type: codeType, - loc, - }; - return { - kind: "unionIsTag", - unionId: codeType.unionId, - tag: undefTag, - negated: true, - value: read, - type: BOOL, - loc, - }; + return { kind: "libCall", fn: "error.hasCode", args: [recv], type: BOOL, loc }; } // The receiver evaluates for its effects when the answer is static. const always = (): IrExpr => diff --git a/packages/compiler/src/ir/builtin-signatures.ts b/packages/compiler/src/ir/builtin-signatures.ts index 57ecbc8d4..f279d3948 100644 --- a/packages/compiler/src/ir/builtin-signatures.ts +++ b/packages/compiler/src/ir/builtin-signatures.ts @@ -3117,6 +3117,7 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ * (args: the error, the key): a runtime-set own property (Object.assign, * throw-site fields, dynamic writes) or an Object.prototype member. */ "error.hasKey": { argTypes: [null, STRING], result: BOOL }, + "error.hasCode": { argTypes: [null], result: BOOL }, "error.defineCause": { argTypes: [null, DYN], result: VOID }, "error.deleteCause": { argTypes: [null], result: VOID }, /** The compiler-resolved Node-parity throw for always-throwing lowered diff --git a/packages/compiler/src/ir/validate.ts b/packages/compiler/src/ir/validate.ts index 0bbc948a0..4fb8ae2b4 100644 --- a/packages/compiler/src/ir/validate.ts +++ b/packages/compiler/src/ir/validate.ts @@ -4116,6 +4116,7 @@ function validateFunction( e.fn === "error.setCodeDyn" || e.fn === "error.assign" || e.fn === "error.hasKey" || + e.fn === "error.hasCode" || e.fn === "error.defineCause" || e.fn === "error.deleteCause" ) { diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6417-error-code-dynamic-writes.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6417-error-code-dynamic-writes.ts.json new file mode 100644 index 000000000..a5a455a9a --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6417-error-code-dynamic-writes.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6417-error-code-dynamic-writes.ts" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_error.h b/packages/runtime/src/scr_error.h index 597361d59..bc64be4d2 100644 --- a/packages/runtime/src/scr_error.h +++ b/packages/runtime/src/scr_error.h @@ -293,6 +293,7 @@ void scr_error_assign_dyn(ScrError *e, const struct ScrDyn *sources); /* `key in err` for a key no compiled class declares: a runtime-set own * property or an Object.prototype member. Borrows both (scr_json.c). */ bool scr_error_has_key(ScrError *e, const ScrStr *key); +bool scr_error_has_code(ScrError *e); const char *scr_error_message_code(const ScrStr *msg); ScrStr *scr_error_code(ScrError *e); SCR_COLD void scr_throw_error_msg_code(int kind, const char *message, size_t len, const char *code); diff --git a/packages/runtime/src/scr_json.c b/packages/runtime/src/scr_json.c index 56edcf049..a89880e89 100644 --- a/packages/runtime/src/scr_json.c +++ b/packages/runtime/src/scr_json.c @@ -4271,6 +4271,7 @@ static void scr_dyn_descriptor_fields_drop(ScrDyn **fields) { } static bool scr_dyn_key_is_index(const char *key, size_t len, double *out); static void scr_error_sync_cause(ScrDyn *view, const ScrStr *key); +static void scr_error_sync_slots(const ScrDyn *view, ScrDyn *obj, const ScrStr *key); /* Property tables belong to closures, not temporary checked wrappers. Seed * the configurable, non-writable builtin members once so redefinitions and @@ -6893,18 +6894,33 @@ ScrError *scr_errdyn_err_of(const ScrDyn *d) { void scr_errdyn_put(ScrError *e, ScrDyn *d) { scr_errdyn_append(e, d); } -/* Error.cause has one live value even when the Error already crossed a - * checked-value boundary. Reflective data-property edits update its slot. */ -static void scr_error_sync_cause(ScrDyn *view, const ScrStr *key) { +/* An Error's slots (name, message, stack, cause, code) have one live value + * even when the Error already crossed a checked-value boundary: writes and + * deletes of those own properties through its checked-dynamic node update + * the slots typed reads use. `view` is the error's node; `obj` holds the + * properties (the node itself, or a typed view's materialized object). */ +static void scr_error_sync_slots(const ScrDyn *view, ScrDyn *obj, const ScrStr *key) { bool cause = key->len == 5 && memcmp(key->data, "cause", 5) == 0; bool stack = key->len == 5 && memcmp(key->data, "stack", 5) == 0; bool name = key->len == 4 && memcmp(key->data, "name", 4) == 0; bool message = key->len == 7 && memcmp(key->data, "message", 7) == 0; - if (!cause && !stack && !name && !message) return; + bool code = key->len == 4 && memcmp(key->data, "code", 4) == 0; + if (!cause && !stack && !name && !message && !code) return; + if (obj == NULL || obj->kind != SCR_DYN_OBJ) return; size_t hit = scr_errdyn_find(view, false); if (hit == SIZE_MAX) return; - ScrDynEntry *entry = scr_dyn_entry(view, key); + ScrDynEntry *entry = scr_dyn_entry(obj, key); ScrError *e = scr_errdyn_cache[hit].err; + if (code) { + /* A string takes the slot; any other value, or none, clears it and + * stays an own property of the node. */ + ScrStr *next = entry && !entry->accessor && entry->value->kind == SCR_DYN_STR + ? scr_str_retain(entry->value->v.str) + : NULL; + scr_str_release(e->code); + e->code = next; + return; + } if (name || message) { /* The native error's toString and inspect read these fields. */ if (!entry || entry->accessor || entry->value->kind != SCR_DYN_STR) return; @@ -6935,6 +6951,10 @@ static void scr_error_sync_cause(ScrDyn *view, const ScrStr *key) { e->cause_enumerable = entry && entry->enumerable; } +static void scr_error_sync_cause(ScrDyn *view, const ScrStr *key) { + scr_error_sync_slots(view, view, key); +} + /* Error.prototype.toString over the checked-dynamic error encoding (an * object carrying the reserved "%error" marker): `name` defaults to "Error" * and `message` to "", both read through the prototype; either empty leaves @@ -7963,7 +7983,12 @@ void scr_dyn_key_set(ScrDyn *recv, ScrStr *key, ScrDyn *value) { if (scr_event_ref_hooks && scr_event_ref_hooks->set(recv, key, value)) return; ScrDyn *materialized = scr_dyn_typed_ref_materialize(recv); scr_dyn_key_set(materialized, key, value); - if (!scr_exc_pending()) scr_dyn_typed_ref_commit(recv); + if (!scr_exc_pending()) { + scr_dyn_typed_ref_commit(recv); + /* An Error subclass's class commit writes its declared fields; the + * slots it inherits from Error take the write here. */ + scr_error_sync_slots(recv, materialized, key); + } scr_dyn_release(materialized); return; } @@ -9737,7 +9762,10 @@ void scr_dyn_key_delete(ScrDyn *recv, const ScrStr *key, bool strict) { scr_dyn_entry(materialized, key) != NULL; if (!scr_exc_pending()) scr_dyn_key_delete(materialized, key, strict); bool deleted = own && !scr_exc_pending(); - if (!scr_exc_pending()) scr_dyn_typed_ref_commit(recv); + if (!scr_exc_pending()) { + scr_dyn_typed_ref_commit(recv); + scr_error_sync_slots(recv, materialized, key); + } scr_dyn_release(materialized); if (deleted && scr_exc_pending()) { /* The native layout requires this field, so the commit rejected the @@ -12094,7 +12122,17 @@ ScrDyn *scr_dyn_assign(ScrDyn *target, const ScrDyn *src) { if (target->kind == SCR_DYN_TYPED_REF) { ScrDyn *materialized = scr_dyn_typed_ref_materialize(target); ScrDyn *assigned = scr_dyn_assign(materialized, src); - if (!scr_exc_pending()) scr_dyn_typed_ref_commit(target); + if (!scr_exc_pending()) { + scr_dyn_typed_ref_commit(target); + /* An Error subclass's inherited slots take the copied values. */ + static const char *const slots[] = { "name", "message", "stack", "cause", "code" }; + for (size_t i = 0; i < sizeof slots / sizeof slots[0]; i++) { + ScrStr *key = scr_str_new(slots[i], strlen(slots[i])); + if (materialized->kind == SCR_DYN_OBJ && scr_dyn_entry(materialized, key) != NULL) + scr_error_sync_slots(target, materialized, key); + scr_str_release(key); + } + } scr_dyn_release(assigned); scr_dyn_release(materialized); return scr_exc_pending() ? NULL : scr_dyn_retain(target); @@ -14146,6 +14184,19 @@ void scr_error_assign_dyn(ScrError *e, const ScrDyn *sources) { scr_dyn_release(view); } +/* `'code' in err`: the code slot holds a string, or the error has an own + * `code` property with another value (`err.code = undefined`), which lives + * on its checked-dynamic node. Borrows; never throws. */ +bool scr_error_has_code(ScrError *e) { + if (e->code != NULL) return true; + const ScrDyn *view = scr_errdyn_view_of(e); + if (view == NULL) return false; + ScrStr *key = scr_str_new("code", 4); + bool own = scr_dyn_has_own(view, key); + scr_str_release(key); + return own; +} + /* `key in err` for a key no compiled class of the receiver declares: an own * property set at runtime, which lives on the error's checked-dynamic node * (that node exists once anything reflected or wrote through it), or a diff --git a/tests/corpus/6417-error-code-dynamic-writes.ts b/tests/corpus/6417-error-code-dynamic-writes.ts new file mode 100644 index 000000000..2eb23cbd7 --- /dev/null +++ b/tests/corpus/6417-error-code-dynamic-writes.ts @@ -0,0 +1,36 @@ +// An Error's code written through a dynamic view (a `catch (e: any)` +// binding), copied onto an Error subclass with Object.assign, set to +// undefined, or deleted is what typed reads and `'code' in err` see. +class CodedError extends Error {} + +const original = new Error("a") as NodeJS.ErrnoException; +try { + throw original; +} catch (e: any) { + e.code = "E_ANY"; +} +console.log("any write", original.code, "code" in original); + +try { + throw original; +} catch (e: any) { + delete e.code; +} +console.log("any delete", original.code, "code" in original); + +function codeOf(err: NodeJS.ErrnoException): string | undefined { + return err.code; +} +const sub = new CodedError("b"); +Object.assign(sub, { code: "E_ASSIGN" }); +console.log("subclass assign", codeOf(sub), "code" in sub); + +const plain = new Error("c") as NodeJS.ErrnoException; +Object.assign(plain, { code: "E_PLAIN" }); +console.log("assign", plain.code, "code" in plain); + +const cleared = new Error("d") as NodeJS.ErrnoException; +cleared.code = undefined; +console.log("undefined", cleared.code, "code" in cleared); +const fresh = new Error("e") as NodeJS.ErrnoException; +console.log("fresh", fresh.code, "code" in fresh); diff --git a/tests/harness/corpus-costs/6417-error-code-dynamic-writes.ts.json b/tests/harness/corpus-costs/6417-error-code-dynamic-writes.ts.json new file mode 100644 index 000000000..fb3867563 --- /dev/null +++ b/tests/harness/corpus-costs/6417-error-code-dynamic-writes.ts.json @@ -0,0 +1 @@ +{"plain":3425} From 66d2010388cb572fc5617c322555e2e8120f7eb5 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Tue, 20 Oct 2026 03:08:09 -0500 Subject: [PATCH 064/122] Remove lazy loaders together and keep their receiver rule Lazy loaders that share a var statement each removed their declarators on their own, and declarators of one statement removed separately leave overlapping edits, of which applyEdits keeps the first. A later loader in the statement (one using __toESM, say) stayed behind and the interop pass refused the package. Every removed declarator of the pass (the interop requires, the loaders and the unused export tables) now goes into one set, removed once. A call through a loader (`load().fn()`) respelled the callee as the export's live read, which drops the namespace receiver. Loader calls now take the rule namespace calls already use: the callee must be a top-level function that never reads `this`; other calls keep the package refused. --- .../frontend/npm-static-bundled-esm.test.ts | 32 +++++++++++++++ .../src/frontend/npm-static-bundled-esm.ts | 39 +++++++++++-------- 2 files changed, 55 insertions(+), 16 deletions(-) diff --git a/packages/compiler/src/frontend/npm-static-bundled-esm.test.ts b/packages/compiler/src/frontend/npm-static-bundled-esm.test.ts index 1e2ba24a3..98decbb41 100644 --- a/packages/compiler/src/frontend/npm-static-bundled-esm.test.ts +++ b/packages/compiler/src/frontend/npm-static-bundled-esm.test.ts @@ -152,6 +152,38 @@ test("minified initializers sharing a var statement lower the same way", () => { ); }); +test("memoized lazy loaders sharing a var statement lower together", () => { + const lowered = expectSameBehavior(`${helpers}var a_exports = {}; +__export(a_exports, { v: () => v }); +var v, init_a = __esm({ "a.ts"() { console.log("a init"); v = 1; } }); +var b_exports = {}; +__export(b_exports, { default: () => w }); +var w, init_b = __esm({ "b.ts"() { console.log("b init"); w = 2; } }); +var nsA, loadA = () => nsA ??= (init_a(), __toCommonJS(a_exports)), nsB, loadB = () => nsB ??= __toESM((init_b(), __toCommonJS(b_exports))); +console.log(loadA().v, loadB().default, loadA().v); +`); + expect(lowered).not.toContain("loadA"); + expect(lowered).not.toContain("loadB"); +}); + +test("calls through a memoized lazy loader keep the receiver rule", () => { + const source = (use: string): string => `${helpers}var w_exports = {}; +__export(w_exports, { who: () => who, plain: () => plain }); +function who() { return this.__esModule; } +function plain(x) { return "plain " + x; } +var init_w = __esm({ "w.ts"() {} }); +var ns, load = () => ns ??= (init_w(), __toCommonJS(w_exports)); +console.log(${use}); +`; + expect(lower(source("load().who()"))).toEqual({ + degrade: expect.stringContaining( + "calls only of exported functions that do not read 'this'", + ) as string, + }); + const lowered = expectSameBehavior(source("load().plain(1), (load().plain)(2)")); + expect(lowered).not.toContain("load()"); +}); + test.each([ ["is asynchronous", `var init_a = __esm({ async "a.ts"() { await 0; } });\ninit_a();`], [ diff --git a/packages/compiler/src/frontend/npm-static-bundled-esm.ts b/packages/compiler/src/frontend/npm-static-bundled-esm.ts index 6e9c4f315..9c77128e1 100644 --- a/packages/compiler/src/frontend/npm-static-bundled-esm.ts +++ b/packages/compiler/src/frontend/npm-static-bundled-esm.ts @@ -449,7 +449,6 @@ export function lowerBundledEsmInits( } } } - edits.push(...removeDeclarators(sf, deadDeclarators)); /* Namespaces of lazily initialized modules read through members. */ let mainTarget: string | null = null; @@ -500,6 +499,17 @@ export function lowerBundledEsmInits( }; const getterText = (value: ts.Expression): string => applyEdits(source, value.getStart(sf), value.getEnd(), edits); + /** A call through the namespace (`ns.fn()`, `L().fn()`) passes the object + * the read came from as `this`; the lowered object differs from Node's + * module object (`__esModule`, property attributes), so it must stay + * unobservable: the callee is a top-level function that never reads + * `this`. */ + const receiverHidden = (values: ReadonlyMap, name: string): boolean => { + const value = values.get(name); + if (value === undefined || !ts.isIdentifier(value) || !stable(value.text)) return false; + const decl = top.get(value.text)?.node; + return decl !== undefined && ts.isFunctionDeclaration(decl) && !readsThis(decl); + }; /* Memoized lazy loaders of those namespaces, `L = () => M ??= NS` (a * bundle plugin's lazy re-exports): every `L().name` becomes the @@ -564,7 +574,7 @@ export function lowerBundledEsmInits( const table = tables.get(target); if (table === undefined) continue; const loaderWhy = { - degrade: `its memoized lazy loader '${name}' returns the namespace of a bundled ES module as a value, a module object the static rewrite cannot represent (only member reads of its result compile)`, + degrade: `its memoized lazy loader '${name}' returns the namespace of a bundled ES module as a value, a module object the static rewrite cannot represent (only member reads of its result compile, and calls only of exported functions that do not read 'this')`, }; const memo = memoize.left.text; const memoDecl = top.get(memo)?.node; @@ -612,6 +622,10 @@ export function lowerBundledEsmInits( const member = (access as ts.PropertyAccessExpression).name.text; const value = entries.get(member); if (value === undefined || shadowed(access, table.entries)) return loaderWhy; + // The rewrite reads the export without the namespace object, so a + // call through it (`L().fn()`) loses its receiver. + if (calledWithReceiver(access as ts.Expression) && !receiverHidden(entries, member)) + return loaderWhy; const init = initCall === null ? "" : `${initCall.getText(sf)}, `; edits.push({ start: access.getStart(sf), @@ -619,7 +633,7 @@ export function lowerBundledEsmInits( text: `(${init}${getterText(value)})`, }); } - edits.push(...removeDeclarators(sf, new Set([d, memoDecl]))); + deadDeclarators.add(d).add(memoDecl); loaderSpans.push({ start: d.getStart(sf), end: d.getEnd() }); lowered.add(target); } @@ -664,24 +678,13 @@ export function lowerBundledEsmInits( else break; } const values = new Map(table.entries); - // A call through the namespace (`ns.fn()`) passes the object the read - // came from as `this`; the lowered object differs from Node's module - // object (`__esModule`, property attributes), so it must stay - // unobservable: the callee is a top-level function that never reads - // `this`. - const receiverHidden = (name: string): boolean => { - const value = values.get(name); - if (value === undefined || !ts.isIdentifier(value) || !stable(value.text)) return false; - const decl = top.get(value.text)?.node; - return decl !== undefined && ts.isFunctionDeclaration(decl) && !readsThis(decl); - }; const memberRead = (access: ts.Node | undefined, of: ts.Node): boolean => access !== undefined && ts.isPropertyAccessExpression(access) && access.expression === of && values.has(access.name.text) && plainRead(access) && - (!calledWithReceiver(access) || receiverHidden(access.name.text)); + (!calledWithReceiver(access) || receiverHidden(values, access.name.text)); const holder = ns.parent; // `__reExport(main, ns, module.exports)`: a star re-export of the // module into the bundle's own exports (`export * from` in its entry). @@ -792,8 +795,12 @@ export function lowerBundledEsmInits( ) return { degrade: ESM_SHAPE_DEGRADE }; edits.push({ start: table.stmt.getStart(sf), end: table.stmt.getEnd(), text: "" }); - edits.push(...removeDeclarators(sf, new Set([decl]))); + deadDeclarators.add(decl); } + // Every removed declarator in one pass: declarators of one statement + // removed separately would leave overlapping edits, and applyEdits keeps + // only the first of them. + edits.push(...removeDeclarators(sf, deadDeclarators)); /* The run-once closures. */ for (const init of inits.values()) { From 603c6720f9f7dd8e155bbf3e3bbfd62d848da059 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Tue, 20 Oct 2026 03:13:40 -0500 Subject: [PATCH 065/122] Keep function export rewrites from capturing names or keys The pass respells `exports.x` and `module.exports.x` through the exported function's name. A parameter, local, nested function or catch binding of that name between a respelled read and the top level would capture it, so the pass now applies only when the file binds the name once, at its top-level declaration. `{ exports }` after the rebinding respelled to `{ SemVer }`, which renames the property; the shorthand now becomes `{ exports: SemVer }`. --- .../npm-static-cjs-exports-alias.test.ts | 81 +++++++++++++++++++ .../frontend/npm-static-cjs-exports-alias.ts | 20 ++++- 2 files changed, 97 insertions(+), 4 deletions(-) create mode 100644 packages/compiler/src/frontend/npm-static-cjs-exports-alias.test.ts diff --git a/packages/compiler/src/frontend/npm-static-cjs-exports-alias.test.ts b/packages/compiler/src/frontend/npm-static-cjs-exports-alias.test.ts new file mode 100644 index 000000000..c3704ea29 --- /dev/null +++ b/packages/compiler/src/frontend/npm-static-cjs-exports-alias.test.ts @@ -0,0 +1,81 @@ +import { spawnSync } from "node:child_process"; +import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { afterAll, expect, test } from "vitest"; +import { nodeFrontendServices } from "./services-node.js"; + +const dirs: string[] = []; +afterAll(() => { + for (const dir of dirs) rmSync(dir, { recursive: true, force: true }); +}); + +function rewrite(source: string): string | null { + return nodeFrontendServices().cjsFunctionExports(source, "/pkg/index.js"); +} + +function run(file: string) { + const result = spawnSync(process.execPath, [file], { encoding: "utf8" }); + return { status: result.status, stdout: result.stdout, stderr: result.stderr }; +} + +/** The rewritten module prints what the original prints. */ +function expectSameBehavior(source: string): string { + const result = rewrite(source); + expect(typeof result).toBe("string"); + const dir = mkdtempSync(join(tmpdir(), "scriptc-cjs-function-exports-")); + dirs.push(dir); + const original = join(dir, "original.cjs"); + const rewritten = join(dir, "rewritten.cjs"); + writeFileSync(original, source); + writeFileSync(rewritten, result as string); + const oracle = run(original); + expect(oracle.status).toBe(0); + expect(run(rewritten)).toEqual(oracle); + return result as string; +} + +const report = ` +console.log(typeof module.exports, module.exports === Version, Object.keys(Version).join(",")); +`; + +test("members of a function export object read and write the function", () => { + const rewritten = expectSameBehavior(`exports = module.exports = Version; +exports.parse = parse; +module.exports.major = (v) => parse(v)[0]; +function Version(v) { return { v }; } +function parse(v) { return v.split(".").map(Number); } +console.log(exports.major("2.1.0"), module.exports.parse("1.2").length); +${report}`); + expect(rewritten).toContain("Version.parse = parse"); + expect(rewritten).not.toContain("exports = module.exports"); +}); + +test.each([ + ["a parameter", "function inner(Version) { return exports.parse; }"], + ["a local", "function inner() { const Version = {}; return exports.parse; }"], + ["a nested function", "function inner() { function Version() {} return exports.parse; }"], + [ + "a catch binding", + "function inner() { try { throw 0; } catch (Version) { return exports.parse; } }", + ], +])("a nested binding of the function's name (%s) keeps the module as it is", (_, inner) => { + expect( + rewrite(`exports = module.exports = Version; +exports.parse = parse; +function Version() {} +function parse() {} +${inner} +`), + ).toBeNull(); +}); + +test("an `exports` shorthand property keeps its key", () => { + const rewritten = expectSameBehavior(`exports = module.exports = Version; +exports.tag = "v"; +function Version() {} +const holder = { exports }; +console.log(Object.keys(holder).join(","), holder.exports === Version, holder.exports.tag); +${report}`); + expect(rewritten).toContain("{ exports: Version }"); +}); diff --git a/packages/compiler/src/frontend/npm-static-cjs-exports-alias.ts b/packages/compiler/src/frontend/npm-static-cjs-exports-alias.ts index c88c9949e..dfe45b862 100644 --- a/packages/compiler/src/frontend/npm-static-cjs-exports-alias.ts +++ b/packages/compiler/src/frontend/npm-static-cjs-exports-alias.ts @@ -15,12 +15,14 @@ * runs and nothing replaces the export object again: the statement is a * top-level statement preceded only by inert declarations, it is the * file's only write of `module.exports` and `exports`, the function is a - * top-level declaration nothing reassigns, and `exports`/`module` are the - * CommonJS globals (never declared in the file). Other shapes answer + * top-level declaration nothing reassigns and the file's only binding of + * its name, and `exports`/`module` are the CommonJS globals (never + * declared in the file). Other shapes answer * null and keep their existing treatment. */ import * as ts from "./ts7/syntax.js"; import { applyEdits, + countDeclarations, declaredNames, isWritten, references, @@ -105,7 +107,10 @@ export function normalizeCjsFunctionExports(sf: ts.SourceFile): string | null { const fnDecls = stmts.filter( (st) => ts.isFunctionDeclaration(st) && st.name?.text === fnName && st.body !== undefined, ); - if (fnDecls.length !== 1) return null; + // The respelled reads name the function from wherever they sit, so no + // other binding of its name (a parameter, a local, a nested function) + // may exist anywhere in the file. + if (fnDecls.length !== 1 || countDeclarations(sf, fnName) !== 1) return null; const edits: Edit[] = []; let unsafe = false; @@ -131,7 +136,14 @@ export function normalizeCjsFunctionExports(sf: ts.SourceFile): string | null { unsafe = true; continue; } - edits.push({ start: r.getStart(sf), end: r.getEnd(), text: fnName }); + // `{ exports }` keeps its key: the shorthand spells out the value. + const shorthand = + r.parent !== undefined && ts.isShorthandPropertyAssignment(r.parent) && r.parent.name === r; + edits.push({ + start: r.getStart(sf), + end: r.getEnd(), + text: shorthand ? `exports: ${fnName}` : fnName, + }); continue; } if (r.text === "module") { From 08fb421628ea11b7c94855f5dd2e51399129174f Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Tue, 20 Oct 2026 01:27:17 -0700 Subject: [PATCH 066/122] Compile NodeJS stream interfaces as general streams when other streams flow into them Co-Authored-By: Claude Opus 5.5 --- .../ambient/scriptc-node-fallback.d.ts | 59 +++++++++--- .../src/backend/llvm/expr-stream-callbacks.ts | 42 ++++++++- .../compiler/src/backend/llvm/lib-process.ts | 22 +++++ .../compiler/src/backend/llvm/lib-shared.ts | 1 + .../frontend/lowering/builtins/properties.ts | 38 +++++++- .../src/frontend/lowering/general-streams.ts | 94 +++++++++++++++++++ .../frontend/lowering/lower-event-emitter.ts | 14 ++- .../src/frontend/lowering/lower-modules.ts | 8 ++ .../src/frontend/lowering/lower-stdlib.ts | 11 ++- .../src/frontend/lowering/lower-stream.ts | 62 +++++++++++- .../compiler/src/frontend/lowering/lowerer.ts | 55 ++++++++++- packages/compiler/src/frontend/type-mapper.ts | 66 +++++++++---- packages/compiler/src/ir/builtin-effects.ts | 1 + .../compiler/src/ir/builtin-signatures.ts | 12 +++ packages/compiler/src/ir/runtime-features.ts | 2 + packages/compiler/src/ir/validate.ts | 14 +++ packages/runtime/src/scr_stream.c | 39 ++++++++ packages/runtime/src/scr_stream.h | 5 + .../6463-node-readable-stream-producers.ts | 75 +++++++++++++++ .../6464-node-writable-stream-producers.ts | 68 ++++++++++++++ .../6465-node-stream-js-boundary/consume.js | 12 +++ .../6465-node-stream-js-boundary/main.ts | 33 +++++++ .../node-types/node-stream-interfaces.ts | 61 ++++++++++++ tests/harness/project-config.test.ts | 18 ++++ 24 files changed, 774 insertions(+), 38 deletions(-) create mode 100644 packages/compiler/src/frontend/lowering/general-streams.ts create mode 100644 tests/corpus/6463-node-readable-stream-producers.ts create mode 100644 tests/corpus/6464-node-writable-stream-producers.ts create mode 100644 tests/corpus/6465-node-stream-js-boundary/consume.js create mode 100644 tests/corpus/6465-node-stream-js-boundary/main.ts create mode 100644 tests/fixtures/node-types/node-stream-interfaces.ts diff --git a/packages/compiler/ambient/scriptc-node-fallback.d.ts b/packages/compiler/ambient/scriptc-node-fallback.d.ts index 436bcace3..28e481b9d 100644 --- a/packages/compiler/ambient/scriptc-node-fallback.d.ts +++ b/packages/compiler/ambient/scriptc-node-fallback.d.ts @@ -80,21 +80,56 @@ declare namespace NodeJS { path?: string | undefined; syscall?: string | undefined; } - /* The piped child-output stream (child.stdout / child.stderr under - * stdio ["ignore", "pipe", "pipe"]): 'data' fires one Buffer chunk per - * read (≤64KB, consumer-driven — no listener, no read), 'end' fires - * once at EOF and always BEFORE the child's 'exit'. A flowing stream - * keeps the loop alive. */ + /* The readable-stream interface every Node stream implements. The + * piped child-output stream (child.stdout / child.stderr under stdio + * ["ignore", "pipe", "pipe"]) is declared with it: 'data' fires one + * Buffer chunk per read (≤64KB, consumer-driven — no listener, no + * read), 'end' fires once at EOF and always BEFORE the child's 'exit'. A + * flowing stream keeps the loop alive. A program that also passes + * other streams (fs, zlib, node:stream) where this interface is + * expected compiles it as the node:stream Readable, with child output + * bridged into one. */ interface ReadableStream { on(event: "data", listener: (chunk: Buffer) => void): void; on(event: "data", listener: (chunk: string) => void): void; on(event: "end", listener: () => void): void; + on(event: "close", listener: () => void): void; + on(event: "error", listener: (err: Error) => void): void; once(event: "data", listener: (chunk: Buffer) => void): void; once(event: "data", listener: (chunk: string) => void): void; once(event: "end", listener: () => void): void; + once(event: "close", listener: () => void): void; + once(event: "error", listener: (err: Error) => void): void; /* setEncoding('utf8') makes subsequent data chunks strings, including * multibyte sequences split across native pipe reads. */ setEncoding(encoding: string): this; + readonly readable: boolean; + pause(): this; + resume(): this; + isPaused(): boolean; + pipe(destination: T, options?: { end?: boolean }): T; + unpipe(destination?: WritableStream): this; + } + /* The writable-stream interface every Node stream implements: + * process.stdout/process.stderr, fs.WriteStream and the node:stream + * Writable family. Written only to the process streams, it compiles as + * their descriptor; with other streams flowing into it, as the + * node:stream Writable (the process streams convert into one). */ + interface WritableStream { + readonly writable: boolean; + write(data: string | Uint8Array, callback?: (error?: Error | null) => void): boolean; + write( + data: string | Uint8Array, + encoding: BufferEncoding, + callback?: (error?: Error | null) => void, + ): boolean; + end(callback?: () => void): this; + end(data: string | Uint8Array, callback?: () => void): this; + end(data: string | Uint8Array, encoding: BufferEncoding, callback?: () => void): this; + on(event: "drain" | "finish" | "close", listener: () => void): void; + on(event: "error", listener: (err: Error) => void): void; + once(event: "drain" | "finish" | "close", listener: () => void): void; + once(event: "error", listener: (err: Error) => void): void; } } @@ -6317,7 +6352,7 @@ declare module "node:cluster" { /* node:tty — the fd probe (the same isatty(3) behind process.*.isTTY) * and the supported structural view of a process output stream. */ declare module "tty" { - export interface WriteStream { + export interface WriteStream extends NodeJS.WritableStream { write(data: string | Uint8Array, callback?: (error?: Error | null) => void): boolean; write( data: string | Uint8Array, @@ -6567,8 +6602,8 @@ declare module "stream" { resume(): this; isPaused(): boolean; setEncoding(encoding: string): this; - pipe(destination: T, options?: { end?: boolean }): T; - unpipe(destination?: Writable): this; + pipe(destination: T, options?: { end?: boolean }): T; + unpipe(destination?: NodeJS.WritableStream): this; destroy(error?: Error): this; readonly readable: boolean; readonly readableEnded: boolean; @@ -6701,12 +6736,12 @@ declare module "stream" { readonly closed: boolean; readonly destroyed: boolean; readonly errored: Error | null; - on(event: "drain" | "finish" | "close", listener: () => void): this; - on(event: "error", listener: (err: Error) => void): this; + on(event: "drain" | "finish" | "close", listener: () => void): void; + on(event: "error", listener: (err: Error) => void): void; on(event: "pipe" | "unpipe", listener: (src: Readable) => void): this; on(event: string, listener: (...args: any[]) => void): this; - once(event: "drain" | "finish" | "close", listener: () => void): this; - once(event: "error", listener: (err: Error) => void): this; + once(event: "drain" | "finish" | "close", listener: () => void): void; + once(event: "error", listener: (err: Error) => void): void; once(event: "pipe" | "unpipe", listener: (src: Readable) => void): this; once(event: string, listener: (...args: any[]) => void): this; addListener(event: "drain" | "finish" | "close", listener: () => void): this; diff --git a/packages/compiler/src/backend/llvm/expr-stream-callbacks.ts b/packages/compiler/src/backend/llvm/expr-stream-callbacks.ts index 3f7961163..9be757a20 100644 --- a/packages/compiler/src/backend/llvm/expr-stream-callbacks.ts +++ b/packages/compiler/src/backend/llvm/expr-stream-callbacks.ts @@ -17,7 +17,7 @@ export function streamDataAdapter( const p = cbT.params[0]; if ( cbT.params.length > 1 || - (p && p.kind !== "bytes" && p.kind !== "string" && p.kind !== "dyn") + (p && p.kind !== "bytes" && p.kind !== "string" && p.kind !== "dyn" && p.kind !== "union") ) { throw new InternalCompilerError( "llvm emitter bug: stream data listener param shape (frontend must fence)", @@ -73,6 +73,46 @@ export function streamDataAdapter( ); finish("%r0"); d.push(`}`, ``); + } else if (p.kind === "union") { + // A `Buffer | string` chunk: wrap whichever slot the stream filled at + // its arm of the (boxed) union. + const def = host.unionsById.get(p.unionId); + const bytesTag = def ? def.arms.findIndex((a) => a.kind === "bytes" && a.elem === "u8") : -1; + const strTag = def ? def.arms.findIndex((a) => a.kind === "string") : -1; + if ( + !def || + host.nullableUnions.has(p.unionId) || + bytesTag < 0 || + strTag < 0 || + def.arms.length !== 2 + ) + throw new InternalCompilerError( + "llvm emitter bug: stream data listener union is not Buffer | string (frontend must fence)", + ); + host.declare(`declare ptr @scr_union_new_ref(i32, ptr, ptr, ptr, ptr)`); + host.declare(`declare ptr @scr_bytes_retain_v(ptr)`); + host.declare(`declare void @scr_bytes_release_v(ptr)`); + host.declare(`declare ptr @scr_str_retain_v(ptr)`); + host.declare(`declare void @scr_str_release_v(ptr)`); + d.push( + ` %uslot = alloca ptr`, + ` %isb = icmp ne ptr %a0, null`, + ` br i1 %isb, label %buf, label %str`, + `buf:`, + ` %rb = call ptr @scr_bytes_retain_v(ptr %a0)`, + ` %ub = call ptr @scr_union_new_ref(i32 ${bytesTag}, ptr %rb, ptr @scr_bytes_retain_v, ptr @scr_bytes_release_v, ptr null)`, + ` store ptr %ub, ptr %uslot`, + ` br label %go`, + `str:`, + ` %rs = call ptr @scr_str_retain_v(ptr %a1)`, + ` %us = call ptr @scr_union_new_ref(i32 ${strTag}, ptr %rs, ptr @scr_str_retain_v, ptr @scr_str_release_v, ptr null)`, + ` store ptr %us, ptr %uslot`, + ` br label %go`, + `go:`, + ` %uv = load ptr, ptr %uslot`, + ); + finish("%uv"); + d.push(`}`, ``); } else { // dyn: box by runtime tag — the JS lane's adapter parameter. host.declare(`declare ptr @scr_dyn_new_buffer(ptr)`); diff --git a/packages/compiler/src/backend/llvm/lib-process.ts b/packages/compiler/src/backend/llvm/lib-process.ts index eaf7f31cd..4a7be1a35 100644 --- a/packages/compiler/src/backend/llvm/lib-process.ts +++ b/packages/compiler/src/backend/llvm/lib-process.ts @@ -305,6 +305,28 @@ export function emitChildProcessLibCall(host: LlvmEmitterContext, e: LibCallExpr B.line(`${raw} = call ptr @${get}(ptr ${recv.name}) ; +1 or NULL`); return host.wrapNullable(raw, raw, def!.arms[streamTag]!, streamTag, e.type, nullTag); } + if (e.fn === "child.stdioStream") { + // The general stream representation's stdio read: the child's bridged + // runtime stream (+1) or NULL for an unpiped slot. + if (e.type.kind !== "union") + throw new InternalCompilerError(`llvm emitter bug: ${e.fn} result is not a union`); + const def = host.unionsById.get(e.type.unionId); + const streamTag = def ? def.arms.findIndex((a) => a.kind === "object") : -1; + const nullTag = def ? def.arms.findIndex((a) => a.kind === "nullT") : -1; + const fd = e.args[1]; + if (streamTag < 0 || nullTag < 0 || fd?.kind !== "numLit") + throw new InternalCompilerError(`llvm emitter bug: ${e.fn} lacks its arms or slot`); + const recv = host.emitExpr(e.args[0]!); + const key = fd.value === 0 ? "stdin" : fd.value === 1 ? "stdout" : "stderr"; + host.declare(`declare ptr @scr_stream_child_stdio(ptr, ptr, ${host.sizeType})`); + const raw = B.tmp(); + B.line( + `${raw} = call ptr @scr_stream_child_stdio(ptr ${recv.name}, ptr ${host.cstr(key)}, ${host.sizeType} ${key.length}) ; +1 or NULL`, + ); + const out = host.wrapNullable(raw, raw, def!.arms[streamTag]!, streamTag, e.type, nullTag); + host.emitPendingCheck(); + return out; + } if (e.fn === "writer.writeString" || e.fn === "writer.writeBytes") { const writer = host.emitExpr(e.args[0]!); const data = host.emitExpr(e.args[1]!); diff --git a/packages/compiler/src/backend/llvm/lib-shared.ts b/packages/compiler/src/backend/llvm/lib-shared.ts index 294128107..61d31435c 100644 --- a/packages/compiler/src/backend/llvm/lib-shared.ts +++ b/packages/compiler/src/backend/llvm/lib-shared.ts @@ -726,6 +726,7 @@ export const LIB_FN_SYMS: Record = { "child.unref": "scr_child_unref", "child.ref": "scr_child_ref", "procStream.write": "scr_proc_stream_write", + "stream.stdioWritable": "scr_stream_stdio_writable", "process.kill": "scr_process_kill_named", "process.killNum": "scr_process_kill", // fs.promises (scr_bytes_io.c / scr_lib.c): the promise forms settle diff --git a/packages/compiler/src/frontend/lowering/builtins/properties.ts b/packages/compiler/src/frontend/lowering/builtins/properties.ts index b0caa0456..3c0486f82 100644 --- a/packages/compiler/src/frontend/lowering/builtins/properties.ts +++ b/packages/compiler/src/frontend/lowering/builtins/properties.ts @@ -1,5 +1,5 @@ import { isBuiltinClassInstance } from "./receiver-types.js"; -import { strLit } from "../../../ir/build.js"; +import { numLit, strLit } from "../../../ir/build.js"; import * as ts from "../../ts7/adapter.js"; import { type Lowerer } from "../lowerer.js"; import { locOf } from "../../program.js"; @@ -192,6 +192,25 @@ export function lowerBuiltinExtraProperty( // +1-or-NULL runtime pair. if (kind === "child" && (name === "stdin" || name === "stdout" || name === "stderr")) { const receiver = lowerer.lowerExpr(expr.expression); + // Under the general stream representation (general-streams.ts) the + // declared stream type maps to a runtime stream class: the read + // answers the child's bridged stream (one per stdio slot, so repeated + // reads keep identity), fed by the same pipe. + const general = lowerer.generalNodeStreams ? generalChildStdioType(lowerer, expr, name) : null; + if (general) { + const type: IrType = { + kind: "union", + unionId: lowerer.unions.intern([general, { kind: "nullT" }]), + }; + const read: IrExpr = { + kind: "libCall", + fn: "child.stdioStream", + args: [receiver, numLit(name === "stdin" ? 0 : name === "stdout" ? 1 : 2, loc)], + type, + loc, + }; + return lowerer.maybeNarrow(read, expr); + } const streamType = name === "stdin" ? CHILDWRITER_T : CHILDSTREAM_T; const type: IrType = { kind: "union", @@ -259,3 +278,20 @@ export function lowerBuiltinExtraProperty( } return null; } + +/** The runtime stream class a child stdio read produces under the general + * stream representation, or null when the declared type keeps the stdio + * handle (the fallback declarations' ChildStdin). */ +function generalChildStdioType( + lowerer: Lowerer, + expr: ts.PropertyAccessExpression, + name: "stdin" | "stdout" | "stderr", +): IrType | null { + const className = name === "stdin" ? "%Writable" : "%Readable"; + const mapped = lowerer.mapTypeOf(lowerer.typeOf(expr)); + if (!mapped) return null; + const arms = mapped.kind === "union" ? (lowerer.unions.get(mapped.unionId)?.arms ?? []) : [mapped]; + return arms.some((arm) => arm.kind === "object" && arm.className === className) + ? { kind: "object", className } + : null; +} diff --git a/packages/compiler/src/frontend/lowering/general-streams.ts b/packages/compiler/src/frontend/lowering/general-streams.ts new file mode 100644 index 000000000..6b51ce545 --- /dev/null +++ b/packages/compiler/src/frontend/lowering/general-streams.ts @@ -0,0 +1,94 @@ +/* The general stream representation (TypeMapperCtx.generalNodeStreams). + * + * NodeJS.ReadableStream and NodeJS.WritableStream (and @types/node's + * stream.Readable / stream.Writable, which also type child stdio) name the + * interface every Node stream implements. By default they map to the + * child/process stdio handles: the specialized representation of the + * values that commonly fill them (child.stdout, process.stdout). A program + * that moves another stream — fs.createReadStream(), a zlib Transform, a + * Readable subclass — into one of those slots needs the general + * representation instead: the runtime stream classes, with the stdio + * handles converted into them where they are read or flow. + * + * The decision is per program. Reachable emit lowers without it; a flow + * that needs it marks the lowerer (needsGeneralNodeStreams) and lowerToIr + * lowers the whole program again with it. Programs without such a flow + * never take the second pass, so their IR is unchanged. */ +import type { IrExpr, IrType } from "../../ir/ir.js"; +import { everyTypeChild } from "../../ir/traverse.js"; +import type { Lowerer } from "./lowerer.js"; +import { streamSidesOf } from "./lower-stream.js"; + +const STDIO_STREAM_KINDS: ReadonlySet = new Set([ + "childStream", + "childWriter", + "procStream", +]); + +/** Whether `type` (through union arms, record fields and the structural + * type children) contains a part satisfying `test`. */ +function containsPart(lowerer: Lowerer, type: IrType, test: (part: IrType) => boolean): boolean { + const seen = new Set(); + const walk = (part: IrType): boolean => { + if (test(part)) return true; + if (part.kind === "union") { + if (seen.has(`u:${part.unionId}`)) return false; + seen.add(`u:${part.unionId}`); + return (lowerer.unions.get(part.unionId)?.arms ?? []).some(walk); + } + if (part.kind === "record") { + if (seen.has(`r:${part.shapeId}`)) return false; + seen.add(`r:${part.shapeId}`); + return (lowerer.shapes.get(part.shapeId)?.fields ?? []).some((f) => walk(f.type)); + } + let found = false; + everyTypeChild(part, (child) => { + found = walk(child); + return !found; + }); + return found; + }; + return walk(type); +} + +function isGeneralStream(lowerer: Lowerer, part: IrType): boolean { + return ( + part.kind === "object" && streamSidesOf(lowerer, lowerer.classes.get(part.className)) !== null + ); +} + +/** A mismatched flow (requireExactShape's input) that the general stream + * representation resolves: a general stream into a stdio-represented + * slot, a stdio handle into an 'unknown' slot, or an 'unknown' value + * checked into a stdio-represented slot. */ +export function needsGeneralNodeStreams( + lowerer: Lowerer, + actual: IrType, + expected: IrType, +): boolean { + const stdio = (part: IrType): boolean => STDIO_STREAM_KINDS.has(part.kind); + const pipeHandle = (part: IrType): boolean => + part.kind === "childStream" || part.kind === "childWriter"; + if (containsPart(lowerer, expected, stdio)) { + return actual.kind === "dyn" || containsPart(lowerer, actual, (p) => isGeneralStream(lowerer, p)); + } + return expected.kind === "dyn" && containsPart(lowerer, actual, pipeHandle); +} + +/** Under the general representation, process.stdout / process.stderr (the + * procStream fd scalar) flowing into a runtime Writable slot: an + * fd-backed Writable whose writes take the same synchronous path as + * process.stdout.write. Null when the flow is not that one. */ +export function stdioIntoGeneralStream( + lowerer: Lowerer, + expr: IrExpr, + expected: IrType, +): IrExpr | null { + if (!lowerer.generalNodeStreams || expr.type.kind !== "procStream") return null; + const writable: IrType = { kind: "object", className: "%Writable" }; + const accepts = + (expected.kind === "object" && expected.className === "%Writable") || + (expected.kind === "union" && lowerer.armTag(expected.unionId, writable) >= 0); + if (!accepts) return null; + return { kind: "libCall", fn: "stream.stdioWritable", args: [expr], type: writable, loc: expr.loc }; +} diff --git a/packages/compiler/src/frontend/lowering/lower-event-emitter.ts b/packages/compiler/src/frontend/lowering/lower-event-emitter.ts index 06e9c4ab1..4b8b9ea14 100644 --- a/packages/compiler/src/frontend/lowering/lower-event-emitter.ts +++ b/packages/compiler/src/frontend/lowering/lower-event-emitter.ts @@ -1245,7 +1245,19 @@ export function lowerEmitterMethodCall( ); } const p = cbT.params[0]; - if (p !== undefined && !(p.kind === "bytes" && p.elem === "u8") && p.kind !== "string") { + // A `Buffer | string` chunk (NodeJS.ReadableStream's listener + // shape) takes whichever side the stream delivers. + const chunkUnion = + p?.kind === "union" && + (lowerer.unions.get(p.unionId)?.arms ?? []).every( + (arm) => (arm.kind === "bytes" && arm.elem === "u8") || arm.kind === "string", + ); + if ( + p !== undefined && + !(p.kind === "bytes" && p.elem === "u8") && + p.kind !== "string" && + !chunkUnion + ) { lowerer.noLowering( `a 'data' listener whose chunk parameter is '${lowerer.fmt(p)}'`, args[1]!, diff --git a/packages/compiler/src/frontend/lowering/lower-modules.ts b/packages/compiler/src/frontend/lowering/lower-modules.ts index fa4c98372..aac5883cd 100644 --- a/packages/compiler/src/frontend/lowering/lower-modules.ts +++ b/packages/compiler/src/frontend/lowering/lower-modules.ts @@ -872,6 +872,14 @@ export function moduleArtifacts( valueInstanceOfRoots.add(root.def.name); } break; + case "libCall": + // The general stream representation's child stdio read bridges + // through the runtime install that boxes both stream classes. + if (node.fn === "child.stdioStream") { + visitClass("%Readable"); + visitClass("%Writable"); + } + break; case "dynFrom": if (boxedChild(node.value.type)) { // A generic child can expose native stdio without importing stream. diff --git a/packages/compiler/src/frontend/lowering/lower-stdlib.ts b/packages/compiler/src/frontend/lowering/lower-stdlib.ts index 2f0e1c66f..4ddfbe5ba 100644 --- a/packages/compiler/src/frontend/lowering/lower-stdlib.ts +++ b/packages/compiler/src/frontend/lowering/lower-stdlib.ts @@ -4,6 +4,7 @@ import { InternalCompilerError } from "../../errors.js"; import * as ts from "../ts7/adapter.js"; import { lowerToNumberArgument } from "./lower-exprs.js"; import type { Lowerer } from "./lowerer.js"; +import { isDeclaredInAmbientNamespace } from "../type-mapper.js"; import { arrayOf, BOOL, @@ -1021,7 +1022,15 @@ export function fenceStaticAbortControllerMemberRead( function isStdlibFetchInterface(lowerer: Lowerer, node: ts.Node, owner: string): ts.Symbol | null { const type = lowerer.checker.getBaseTypeOfLiteralType(lowerer.typeOf(node)); for (const symbol of [type.getSymbol(), type.getAliasSymbol()]) { - if (symbol?.name === owner && lowerer.isStdlibSymbol(symbol)) return symbol; + // NodeJS.ReadableStream shares the WHATWG interface's name. + if ( + symbol?.name === owner && + lowerer.isStdlibSymbol(symbol) && + !symbol.declarations.some( + (d) => ts.isInterfaceDeclaration(d) && isDeclaredInAmbientNamespace(d, "NodeJS"), + ) + ) + return symbol; } return null; } diff --git a/packages/compiler/src/frontend/lowering/lower-stream.ts b/packages/compiler/src/frontend/lowering/lower-stream.ts index dd0ddcc0e..66595c473 100644 --- a/packages/compiler/src/frontend/lowering/lower-stream.ts +++ b/packages/compiler/src/frontend/lowering/lower-stream.ts @@ -3296,6 +3296,62 @@ function pipeEndOption(lowerer: Lowerer, options: ts.Expression | undefined, loc return end; } +/** `readable.pipe(process.stdout)`: the process stream joins the pipe as + * an fd-backed Writable (stream.stdioWritable), and — like Node, which + * never ends process.stdout or process.stderr from a pipe — the source's + * end does not end it. pipe() answers the process stream itself. */ +function pipeIntoStdio( + lowerer: Lowerer, + call: ts.CallExpression, + access: ts.PropertyAccessExpression, + target: IrExpr, +): IrExpr { + const loc = locOf(call); + // JS order: the receiver, then the destination, then the options. + const receiverValue = lowerer.lowerExpr(access.expression); + const source = lowerer.declareHiddenLocal("%pipeSource", receiverValue.type); + const stdio = lowerer.declareHiddenLocal("%pipeStdio", target.type); + const options = call.arguments.nodes[1]; + const stmts: IrStmt[] = [ + { kind: "varDecl", localId: source.id, init: receiverValue, loc }, + { kind: "varDecl", localId: stdio.id, init: target, loc }, + ]; + // The options literal still evaluates; its end flag has no effect here. + if (options !== undefined) { + stmts.push({ kind: "exprStmt", expr: pipeEndOption(lowerer, options, loc), loc }); + } + const receiver: IrExpr = { kind: "varRef", localId: source.id, type: receiverValue.type, loc }; + const writable: IrType = { kind: "object", className: "%Writable" }; + stmts.push({ + kind: "exprStmt", + expr: { + kind: "libCall", + fn: "readable.pipe", + args: [ + receiver, + { + kind: "libCall", + fn: "stream.stdioWritable", + args: [{ kind: "varRef", localId: stdio.id, type: target.type, loc }], + type: writable, + loc, + }, + boolLit(false, loc), + ], + type: writable, + loc, + }, + loc, + }); + return { + kind: "seqExpr", + stmts, + result: { kind: "varRef", localId: stdio.id, type: target.type, loc }, + type: target.type, + loc, + }; +} + export function lowerStreamMethodCall( lowerer: Lowerer, call: ts.CallExpression, @@ -3545,7 +3601,9 @@ export function lowerStreamMethodCall( loc, }; } - const dst = lowerer.lowerExpr(args[0]!); + const target = lowerer.lowerExpr(args[0]!); + if (target.type.kind === "procStream") return pipeIntoStdio(lowerer, call, access, target); + const dst = target; const dstInfo = dst.type.kind === "object" ? lowerer.classes.get(dst.type.className) : undefined; const dstSides = streamSidesOf(lowerer, dstInfo); @@ -3553,7 +3611,7 @@ export function lowerStreamMethodCall( lowerer.noLowering( `pipe into a '${lowerer.fmt(dst.type)}'`, args[0]!, - "the destination must be a stream Writable/Duplex (process streams and sockets are not pipe destinations yet)", + "the destination must be a stream Writable/Duplex, process.stdout or process.stderr (sockets are not pipe destinations yet)", ); } const end = pipeEndOption(lowerer, args[1], loc); diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index 1361b346f..2973498e5 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -1,3 +1,4 @@ +import { needsGeneralNodeStreams, stdioIntoGeneralStream } from "./general-streams.js"; import { extendAssertedRecordMembers } from "./assertion-members.js"; import { jsDynamicDeclarations } from "./js-dynamic-objects.js"; import { @@ -877,6 +878,10 @@ export interface LowererMode { /** Statically resolved child_process.fork program roots, in stable target-id order. */ forkTargets?: readonly ts.SourceFile[]; frontendServices?: FrontendServices | undefined; + /** The program's general stream representation (TypeMapperCtx. + * generalNodeStreams). lowerToIr turns it on only after a pass without + * it saw a stream flow that needs it (requiresGeneralNodeStreams). */ + generalNodeStreams?: boolean; /** LoadResult.inlineRequireCycleCalls. */ inlineRequireCycleCalls?: ReadonlySet | undefined; /** LowerOptions.libraryCallbacks (see there). */ @@ -1033,10 +1038,40 @@ export function lowerToIr( for (const d of extraModuleCycleDiags) reachableEmit.pushDiag(d); for (const d of ffiValidation.diagnostics) reachableEmit.pushDiag(d); const emitted = reachableEmit.emitReachable(options.libRoots); - const { reachable } = emitted; + let { reachable } = emitted; let result = emitted.result; let resultLowerer = reachableEmit; timing("reachable-emit", { reachable: reachable.size }); + // The general stream representation (TypeMapperCtx.generalNodeStreams) + // is a per-program decision: only a program whose reachable code moved + // a general stream into a stdio-represented stream slot lowers again + // with it. Every other program keeps the single pass above unchanged. + const generalNodeStreams = reachableEmit.requiresGeneralNodeStreams; + if (generalNodeStreams) { + const general = new Lowerer(program, entry, moduleOrder, { + frontendServices: options.frontendServices, + projectCheckJs: options.projectCheckJs ?? false, + targetPlatform, + startupCrash, + inlineRequireCycleCalls: options.inlineRequireCycleCalls, + ffiImports, + libraryCallbacks, + forkTargets, + workerTargets, + externalTypes, + externalTypeSpecifiersByFile, + tierMode, + ffiBindingSymbols: ffiValidation.symbolsByName, + generalNodeStreams: true, + }); + for (const d of extraModuleCycleDiags) general.pushDiag(d); + for (const d of ffiValidation.diagnostics) general.pushDiag(d); + const generalEmitted = general.emitReachable(options.libRoots); + reachable = generalEmitted.reachable; + result = generalEmitted.result; + resultLowerer = general; + timing("general-streams-relower", { reachable: reachable.size }); + } // A generic class-rest support decision can depend on record metadata // whose historical owner is discovered only while retained bodies lower. // If the settled answer is a fence, rerun the ordinary reachable emit so @@ -1044,10 +1079,11 @@ export function lowerToIr( // declarators stay unvisited, bindings block, cascades and stats match the // historical compiler. This is a rare compatibility fallback; programs // without such a settled fence retain checker-backed IR exactly once. - if (reachableEmit.requiresHistoricalOrderRelower) { + if (resultLowerer.requiresHistoricalOrderRelower) { const emit = new Lowerer(program, entry, moduleOrder, { frontendServices: options.frontendServices, projectCheckJs: options.projectCheckJs ?? false, + ...(generalNodeStreams ? { generalNodeStreams: true } : {}), reachable, targetPlatform, startupCrash, @@ -1107,6 +1143,7 @@ export function lowerToIr( const remainder = new Lowerer(program, entry, moduleOrder, { frontendServices: options.frontendServices, projectCheckJs: options.projectCheckJs ?? false, + ...(generalNodeStreams ? { generalNodeStreams: true } : {}), reachable, remainder: true, alreadyFlushed: resultLowerer.flushedSymbols, @@ -2496,6 +2533,12 @@ export class Lowerer { /** Settled generic class-rest metadata requires the historical emit * fallback so its fence can poison the original statement atomically. */ requiresHistoricalOrderRelower = false; + /** A general stream value (a runtime stream class instance) reached a + * slot the child/process stdio handles represent — NodeJS.ReadableStream, + * NodeJS.WritableStream, or @types/node's stream.Readable/Writable — or + * a stdio handle reached an 'unknown' slot. lowerToIr then lowers the + * program again under the general stream representation. */ + requiresGeneralNodeStreams = false; /** Helpers that snapshot shape declaration order into their bodies. * Reachability lowers inits before the declarations they discover, so * these rebuild after the worklist restores historical shape metadata. */ @@ -2943,6 +2986,7 @@ export class Lowerer { * refuse and fence exactly as before. Stage S6 demotes the units of * committed refusals in "auto" and keeps them refusals in "static-only". */ readonly tierMode: TierMode; + readonly generalNodeStreams: boolean; /** Functions finishModule placed in the dynamic tier (ir/tiers.ts). */ dynamicFunctions: DynamicFunction[] = []; /** Their IR, for coverage's dynamic statement count. */ @@ -3071,6 +3115,7 @@ export class Lowerer { this.startupCrash = mode.startupCrash ?? null; this.targetPlatform = mode.targetPlatform ?? process.platform; this.tierMode = mode.tierMode ?? "auto"; + this.generalNodeStreams = mode.generalNodeStreams ?? false; this.workerTargets = mode.workerTargets ?? []; this.workerTargets.forEach((sf, id) => { this.workerTargetIdByPath.set(tsgoPath(resolve(sf.fileName)), id); @@ -3167,6 +3212,7 @@ export class Lowerer { isExternalTypeFile: (sf) => this.externalTypeSpecifiersByFile.has(tsgoPath(resolve(sf.fileName))), typeMemo: this.typeMemo, + ...(this.generalNodeStreams ? { generalNodeStreams: true } : {}), canMemoizeType: () => this.typeParamBindings === null && this.typeParamTsBindings === null && @@ -8451,6 +8497,8 @@ export class Lowerer { * validator ICEs on them as the usual backstop. */ requireExactShape(node: ts.Node, actual: IrType, expected: IrType): void { if (typeEquals(actual, expected)) return; + if (!this.generalNodeStreams && needsGeneralNodeStreams(this, actual, expected)) + this.requiresGeneralNodeStreams = true; // dyn mismatches first. A dyn ('unknown') value flowing into a typed // slot needs a CHECKED cast — the hint points at `as `; tsc // usually rejects this before we do, so the fence is mostly defensive. @@ -8657,6 +8705,9 @@ export class Lowerer { } if (expr.type.kind === "void" && expected.kind !== "void" && this.neverValued.has(expr)) return this.divergentValue(expr, expected); + // process.stdout/stderr into a general Writable slot (general-streams.ts). + const stdioStream = stdioIntoGeneralStream(this, expr, expected); + if (stdioStream) return this.coerceToExpected(stdioStream, expected); // Iterator-typed slots: native iterators and differently typed // generators step through an adapter generator. if (expected.kind === "generator" && !typeEquals(expr.type, expected)) { diff --git a/packages/compiler/src/frontend/type-mapper.ts b/packages/compiler/src/frontend/type-mapper.ts index 94506f034..afa9f4311 100644 --- a/packages/compiler/src/frontend/type-mapper.ts +++ b/packages/compiler/src/frontend/type-mapper.ts @@ -980,7 +980,7 @@ function isDeclaredInAmbientModule(d: ts.Declaration, name: string): boolean { /** True when the declaration's nearest enclosing namespace is `name` * (`declare global { namespace NodeJS { ... } }` — the NodeJS-global * interfaces @types/node declares outside any ambient module). */ -function isDeclaredInAmbientNamespace(d: ts.Declaration, name: string): boolean { +export function isDeclaredInAmbientNamespace(d: ts.Declaration, name: string): boolean { let node: ts.Node | undefined = d.parent; while (node) { if (ts.isModuleDeclaration(node) && ts.isIdentifier(node.name)) { @@ -1146,6 +1146,13 @@ export interface TypeMapperCtx { * type descriptions for project-owned values, but their imported runtime * bindings are fenced separately by the Lowerer. */ isExternalTypeFile: (sf: ts.SourceFile) => boolean; + /** The program's general stream representation (lowerToIr decides it + * per program): NodeJS.ReadableStream / NodeJS.WritableStream and + * @types/node's stream.Readable / stream.Writable map to the runtime + * stream classes instead of the child/process stdio handles, so values + * from every stream producer share their slots. Off (the default), the + * stdio handles keep those spellings. */ + generalNodeStreams?: boolean; /** Successful context-free mappings, keyed by the * TypeScript server's stable type id. A generated facade can reference the * same large record/function type thousands of times; remapping its full @@ -2998,6 +3005,20 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // global-namespace interface real code writes in its own signatures — // prefixStream's `output` param). The representation is the raw fd // scalar; the lowered surface is write(string). + // Under the program's general stream representation (generalNodeStreams) + // NodeJS.WritableStream is the runtime Writable instead: the process + // streams convert into it where they flow (process.stdioWritable). + const nodeWritableInterface = + psym?.name === "WritableStream" && + psym.declarations.some( + (d) => + ts.isInterfaceDeclaration(d) && + ctx.isStdlibFile(d.getSourceFile()) && + isDeclaredInAmbientNamespace(d, "NodeJS"), + ); + if (nodeWritableInterface && ctx.generalNodeStreams === true) { + return { kind: "object", className: "%Writable" }; + } if ( (psym?.name === "WriteStream" && psym.declarations.some( @@ -3007,13 +3028,7 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { (isDeclaredInAmbientModule(d, "tty") || (isDeclaredInAmbientModule(d, "process") && isDeclaredInAmbientNamespace(d, "NodeJS"))), )) || - (psym?.name === "WritableStream" && - psym.declarations.some( - (d) => - ts.isInterfaceDeclaration(d) && - ctx.isStdlibFile(d.getSourceFile()) && - isDeclaredInAmbientNamespace(d, "NodeJS"), - )) + nodeWritableInterface ) { return PROCSTREAM_T; } @@ -3030,6 +3045,7 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { isDeclaredInAmbientModule(d, "child_process"), )) || (psym?.name === "Writable" && + ctx.generalNodeStreams !== true && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && @@ -3047,6 +3063,9 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // branch: the fallback's Readable must map to the runtime class, and // under the fallback child.stdout is NodeJS.ReadableStream (its own // branch below), so the two never collide. + // @types/node's Duplex/Transform/PassThrough classes have no child-stdio + // reading, so they map to the runtime classes too; its Readable and + // Writable join them under the general representation. for (const [irName, rec] of RUNTIME_STREAM_CLASSES) { if ( psym?.name === rec.lib && @@ -3054,15 +3073,17 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { (d) => ts.isClassDeclaration(d) && ctx.isStdlibFile(d.getSourceFile()) && - !isNodeTypesPath(d.getSourceFile().fileName) && + (!isNodeTypesPath(d.getSourceFile().fileName) || + ctx.generalNodeStreams === true || + (rec.lib !== "Readable" && rec.lib !== "Writable")) && isDeclaredInAmbientModule(d, "stream"), ) ) { return { kind: "object", className: irName }; } } - // node:zlib's stream classes (the shipped fallback declarations): their - // instances are runtime Transform streams. + // node:zlib's stream classes (the shipped fallback declarations and + // @types/node's): their instances are runtime Transform streams. if ( psym !== undefined && ZLIB_STREAM_MODES.has(psym.name) && @@ -3070,22 +3091,20 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { (d) => ts.isClassDeclaration(d) && ctx.isStdlibFile(d.getSourceFile()) && - !isNodeTypesPath(d.getSourceFile().fileName) && isDeclaredInAmbientModule(d, "zlib"), ) ) { return { kind: "object", className: "%Transform" }; } - // fs.ReadStream / fs.WriteStream (the shipped fallback declarations): - // their instances are runtime Readable / Writable streams - // (scr_fs_stream.c). + // fs.ReadStream / fs.WriteStream (the shipped fallback declarations and + // @types/node's): their instances are runtime Readable / Writable + // streams (scr_fs_stream.c). if ( (psym?.name === "ReadStream" || psym?.name === "WriteStream") && psym.declarations.some( (d) => ts.isClassDeclaration(d) && ctx.isStdlibFile(d.getSourceFile()) && - !isNodeTypesPath(d.getSourceFile().fileName) && isDeclaredInAmbientModule(d, "fs"), ) ) { @@ -3107,6 +3126,9 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { isDeclaredInAmbientNamespace(d, "NodeJS"), )) ) { + // The general representation: the runtime Readable, which child + // stdio reads produce through the child stream bridge. + if (ctx.generalNodeStreams === true) return { kind: "object", className: "%Readable" }; return { kind: "childStream" }; } // dgram.Socket: the same ambiguous "Socket" name disambiguated by its @@ -4125,10 +4147,18 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { const mapped = mapType(checker.getTypeOfSymbol(p), ctx); if (!mapped) return false; const arms = mapped.kind === "union" ? ctx.unions.get(mapped.unionId)?.arms : [mapped]; + // Under the general stream representation the stdio members are the + // runtime stream classes. + const isExpected = (arm: IrType): boolean => + arm.kind === expected || + (ctx.generalNodeStreams === true && + arm.kind === "object" && + ((expected === "childStream" && arm.className === "%Readable") || + (expected === "childWriter" && arm.className === "%Writable"))); return ( arms !== undefined && - arms.some((arm) => arm.kind === expected) && - arms.every((arm) => arm.kind === expected || arm.kind === unit) + arms.some(isExpected) && + arms.every((arm) => isExpected(arm) || arm.kind === unit) ); }; const childShaped = diff --git a/packages/compiler/src/ir/builtin-effects.ts b/packages/compiler/src/ir/builtin-effects.ts index 6757851d9..2973a9aa2 100644 --- a/packages/compiler/src/ir/builtin-effects.ts +++ b/packages/compiler/src/ir/builtin-effects.ts @@ -75,6 +75,7 @@ export const MAY_THROW_LIB_FNS: ReadonlySet = new Set([ "process.stdoutWriteBytes", "process.stderrWriteBytes", "procStream.write", + "child.stdioStream", "weakMap.new", "weakSet.new", "dyn.fromEntries", diff --git a/packages/compiler/src/ir/builtin-signatures.ts b/packages/compiler/src/ir/builtin-signatures.ts index 2cbb35185..2e4faedb4 100644 --- a/packages/compiler/src/ir/builtin-signatures.ts +++ b/packages/compiler/src/ir/builtin-signatures.ts @@ -2534,6 +2534,11 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ * queue, end/destroy settle it, writable is a pure state read, and * listener callbacks move into the writer registry. */ "child.stdin": { argTypes: [CHILD_T], result: VOID }, + /** Under the general stream representation (general-streams.ts), a + * stdio read (slot 0, 1 or 2) answers the child's bridged runtime stream + * — `Writable | null` for stdin, `Readable | null` otherwise; the bridge + * throws when the program lacks the stream unit's boxing helpers. */ + "child.stdioStream": { argTypes: [CHILD_T, F64], result: VOID }, // Listener registrations: callback func shapes are program-dependent // (zero-param / Buffer / Buffer-armed union) — the slots pin arity, // the stream receiver, and the once-flag. @@ -2555,6 +2560,13 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ * stderrWrite paths so buffering and ordering stay identical. Data * borrowed; the bool is Node's always-true backpressure signal. */ "procStream.write": { argTypes: [PROCSTREAM_T, STRING], result: BOOL }, + /** process.stdout/stderr flowing into a runtime Writable slot under the + * general stream representation: an fd-backed Writable (+1) whose + * writes take process.stdout.write's synchronous path. */ + "stream.stdioWritable": { + argTypes: [PROCSTREAM_T], + result: { kind: "object", className: "%Writable" }, + }, "child.killed": { argTypes: [CHILD_T], result: BOOL }, "child.kill": { argTypes: [CHILD_T, STRING], result: BOOL }, "child.killNum": { argTypes: [CHILD_T, F64], result: BOOL }, diff --git a/packages/compiler/src/ir/runtime-features.ts b/packages/compiler/src/ir/runtime-features.ts index 4d59a6241..572a93273 100644 --- a/packages/compiler/src/ir/runtime-features.ts +++ b/packages/compiler/src/ir/runtime-features.ts @@ -144,6 +144,7 @@ const ABORT_SIGNAL_LIB_FNS: ReadonlySet = new Set([ /** Entry points that link the stream unit (with STREAM_LIB_PREFIXES). */ const STREAM_LIB_FNS: ReadonlySet = new Set([ + "child.stdioStream", "console.bind", "crypto.hashStream", "crypto.hashStreamPiped", @@ -162,6 +163,7 @@ const STREAM_LIB_FNS: ReadonlySet = new Set([ "stream.destroyErr", "stream.errored", "stream.prop", + "stream.stdioWritable", "stream.workerStdio", ]); diff --git a/packages/compiler/src/ir/validate.ts b/packages/compiler/src/ir/validate.ts index 50ba236b8..63078ae4a 100644 --- a/packages/compiler/src/ir/validate.ts +++ b/packages/compiler/src/ir/validate.ts @@ -5224,6 +5224,20 @@ function validateFunction( } return true; } + if (e.fn === "child.stdioStream") { + const def = e.type.kind === "union" ? unions.get(e.type.unionId) : undefined; + const slot = e.args[1]; + const className = + slot?.kind === "numLit" ? (slot.value === 0 ? "%Writable" : "%Readable") : null; + const ok = + def && + className !== null && + def.arms.length === 2 && + def.arms.some((a) => a.kind === "object" && a.className === className) && + def.arms.some((a) => a.kind === "nullT"); + if (!ok) err(`libCall ${e.fn} must return the stream class of its stdio slot or null`, e.loc); + return true; + } if (e.fn === "child.stdin" || e.fn === "child.stdout" || e.fn === "child.stderr") { const def = e.type.kind === "union" ? unions.get(e.type.unionId) : undefined; const handleKind = e.fn === "child.stdin" ? "childWriter" : "childStream"; diff --git a/packages/runtime/src/scr_stream.c b/packages/runtime/src/scr_stream.c index 5f0bb493d..b7d99984a 100644 --- a/packages/runtime/src/scr_stream.c +++ b/packages/runtime/src/scr_stream.c @@ -6619,3 +6619,42 @@ ScrStream *scr_crypto_hash_stream_piped(ScrCryptoHash *hash) { if (s) scr_crypto_hash_stream_hold(hash); return s; } + +/* ── the compiler's general stream representation ───────────────────── + * NodeJS.ReadableStream / NodeJS.WritableStream slots that also hold other + * streams compile as this unit's classes; the stdio handles convert into + * them where they are read or flow. Both entry points sit last in the unit + * and reference only global symbols and no new constant data, so programs + * that never convert link the same bytes as before. */ + +/* A child stdio read (the child.stdioStream libCall): the bridged stream + * the checked-dynamic ChildProcess view answers for the same key ("stdin", + * "stdout" or "stderr", passed by the compiled program), so both views + * share one stream per slot. +1, or NULL for an unpiped slot or a throw. */ +ScrStream *scr_stream_child_stdio(void *child, const char *key, size_t key_len) { + ScrDyn *handle = scr_dyn_new_handle(child, SCR_DYNH_CHILD); + ScrDyn *box = scr_dyn_handle_ops_of(handle)->get(child, key, key_len); + scr_dyn_release(handle); + if (box == NULL) return NULL; + ScrStream *stream = box->kind == SCR_DYN_TYPED_REF ? scr_dyn_typed_ref_unbox(box) : NULL; + scr_dyn_release(box); + return stream; +} + +/* process.stdout / process.stderr flowing into a Writable slot (the + * stream.stdioWritable libCall): a Writable whose _write hands each chunk + * to the descriptor through process.stdout.write's synchronous path and + * completes at once, so ordering against console output and direct writes + * is unchanged. The closure keeps the fd as given (1 or 2). */ +static void scr_stream_stdio_write(ScrClosure *cb, ScrStream *s, ScrBytes *chunk) { + int fd = (int)scr_box_get_f64(cb->caps[0]); + (void)scr_stdio_write(fd == 2 ? 2 : 1, chunk->data, chunk->len * scr_bytes_elem_size(chunk->elem)); + scr_stream_write_done(s, NULL); +} +ScrStream *scr_stream_stdio_writable(double fd) { + ScrClosure *write = scr_closure_new((void *)scr_stream_stdio_write, 1); + write->caps[0] = scr_box_new(SCR_BOX_F64); + scr_box_set_f64(write->caps[0], fd); + return scr_stream_new_writable(-1, true, true, write, scr_stream_stdio_write, + NULL, NULL, NULL, NULL); +} diff --git a/packages/runtime/src/scr_stream.h b/packages/runtime/src/scr_stream.h index 82727c1dc..f6c3c24e5 100644 --- a/packages/runtime/src/scr_stream.h +++ b/packages/runtime/src/scr_stream.h @@ -125,6 +125,11 @@ void scr_stream_init_passthrough(ScrStream *s, double rhwm, double whwm, bool au ScrStream *scr_stream_new_readable_dyn(struct ScrDyn *opts); void scr_stream_worker_stdio(void); ScrStream *scr_stream_new_writable_dyn(struct ScrDyn *opts); +/* The general stream representation's conversions (+1): a child's bridged + * stdio stream by key (NULL when unpiped), and process.stdout/stderr as a + * Writable (fd 1 or 2). */ +ScrStream *scr_stream_child_stdio(void *child, const char *key, size_t key_len); +ScrStream *scr_stream_stdio_writable(double fd); ScrStream *scr_stream_new_duplex_dyn(struct ScrDyn *opts); ScrStream *scr_stream_new_transform_dyn(struct ScrDyn *opts); ScrStream *scr_stream_new_passthrough_dyn(struct ScrDyn *opts); diff --git a/tests/corpus/6463-node-readable-stream-producers.ts b/tests/corpus/6463-node-readable-stream-producers.ts new file mode 100644 index 000000000..4d06fac94 --- /dev/null +++ b/tests/corpus/6463-node-readable-stream-producers.ts @@ -0,0 +1,75 @@ +// NodeJS.ReadableStream is the interface every Node readable implements. +// One helper typed with it consumes a file stream, a Readable subclass, +// Readable.from, a gunzip Transform fed through pipe(), and a child's +// stdout. A program that mixes those producers compiles the interface as +// the node:stream Readable, with the child's pipe bridged into one. +import { spawn } from "node:child_process"; +import * as fs from "node:fs"; +import * as os from "node:os"; +import * as path from "node:path"; +import { Readable } from "node:stream"; +import * as zlib from "node:zlib"; + +function drain(label: string, src: NodeJS.ReadableStream): Promise { + return new Promise((resolve, reject) => { + let text = ""; + let chunks = 0; + src.on("data", (chunk: Buffer | string) => { + chunks++; + text += typeof chunk === "string" ? `[s]${chunk}` : chunk.toString("utf8"); + }); + src.on("error", (err) => reject(err)); + src.on("end", () => { + console.log(label, "end", chunks > 0, src.readable); + resolve(text); + }); + }); +} + +class Countdown extends Readable { + private left = 3; + _read(): void { + this.push(this.left > 0 ? `t-${this.left--};` : null); + } +} + +function openText(file: string): NodeJS.ReadableStream { + return fs.createReadStream(file); +} + +function openDecoded(file: string): NodeJS.ReadableStream { + const stream = fs.createReadStream(file); + stream.setEncoding("utf8"); + return stream; +} + +async function main(): Promise { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "scriptc-node-readable-")); + const plain = path.join(dir, "plain.txt"); + const packed = path.join(dir, "packed.gz"); + fs.writeFileSync(plain, "hello from a file\n"); + fs.writeFileSync(packed, zlib.gzipSync(Buffer.from("inflated text\n"))); + + console.log(JSON.stringify(await drain("file", openText(plain)))); + console.log(JSON.stringify(await drain("decoded", openDecoded(plain)))); + console.log(JSON.stringify(await drain("subclass", new Countdown()))); + console.log(JSON.stringify(await drain("from", Readable.from(["a", "b", "c"])))); + const gunzip = fs.createReadStream(packed).pipe(zlib.createGunzip()); + console.log(JSON.stringify(await drain("gunzip", gunzip))); + + const child = spawn("sh", ["-c", "printf 'child line\\n'"], { + stdio: ["ignore", "pipe", "inherit"], + }); + const out = child.stdout; + if (out !== null) console.log(JSON.stringify(await drain("child", out))); + console.log("same stdout", child.stdout === out); + + try { + await drain("missing", openText(path.join(dir, "missing.txt"))); + } catch (err) { + console.log("missing", (err as NodeJS.ErrnoException).code); + } + fs.rmSync(dir, { recursive: true }); +} + +void main(); diff --git a/tests/corpus/6464-node-writable-stream-producers.ts b/tests/corpus/6464-node-writable-stream-producers.ts new file mode 100644 index 000000000..bc9986b9b --- /dev/null +++ b/tests/corpus/6464-node-writable-stream-producers.ts @@ -0,0 +1,68 @@ +// NodeJS.WritableStream is the interface every Node writable implements: +// process.stdout/stderr, fs.WriteStream and the node:stream Writable +// family. Helpers typed with it accept all of them, a copy helper pipes a +// file stream into one, and readable.pipe(process.stdout) leaves the +// process stream open afterwards, as in Node. +import * as fs from "node:fs"; +import * as os from "node:os"; +import * as path from "node:path"; +import { Readable, Writable } from "node:stream"; + +function emit(dst: NodeJS.WritableStream, line: string): boolean { + return dst.write(`${line}\n`); +} + +function copy(src: NodeJS.ReadableStream, dst: NodeJS.WritableStream): Promise { + return new Promise((resolve, reject) => { + src.on("error", reject); + dst.on("error", reject); + dst.on("finish", () => resolve()); + src.pipe(dst); + }); +} + +class Collector extends Writable { + readonly parts: string[] = []; + _write(chunk: Buffer, _encoding: string, callback: (error?: Error | null) => void): void { + this.parts.push(chunk.toString("utf8")); + callback(); + } +} + +function finished(stream: NodeJS.WritableStream): Promise { + return new Promise((resolve) => { + stream.end(() => resolve()); + }); +} + +async function main(): Promise { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "scriptc-node-writable-")); + const target = path.join(dir, "out.txt"); + const file = fs.createWriteStream(target); + console.log("file write", emit(file, "to a file")); + console.log("stdout write", emit(process.stdout, "to stdout")); + emit(process.stderr, "to stderr"); + const collector = new Collector(); + emit(collector, "to a collector"); + await finished(file); + await finished(collector); + console.log(JSON.stringify(fs.readFileSync(target, "utf8"))); + console.log(JSON.stringify(collector.parts)); + + const source = path.join(dir, "source.txt"); + const copied = path.join(dir, "copied.txt"); + fs.writeFileSync(source, "copied through pipe\n"); + await copy(fs.createReadStream(source), fs.createWriteStream(copied)); + console.log(JSON.stringify(fs.readFileSync(copied, "utf8"))); + + await new Promise((resolve) => { + const piped = Readable.from(["piped ", "to ", "stdout\n"]); + piped.on("end", () => resolve()); + console.log("pipe answers stdout", piped.pipe(process.stdout) === process.stdout); + }); + // The pipe did not end process.stdout. + emit(process.stdout, "still writable"); + fs.rmSync(dir, { recursive: true }); +} + +void main(); diff --git a/tests/corpus/6465-node-stream-js-boundary/consume.js b/tests/corpus/6465-node-stream-js-boundary/consume.js new file mode 100644 index 000000000..ceb8f3abf --- /dev/null +++ b/tests/corpus/6465-node-stream-js-boundary/consume.js @@ -0,0 +1,12 @@ +export function streamToText(stream) { + return new Promise((resolve, reject) => { + const chunks = []; + stream.on("data", (chunk) => chunks.push(Buffer.from(chunk))); + stream.on("error", reject); + stream.on("end", () => resolve(Buffer.concat(chunks).toString("utf8"))); + }); +} + +export function describeStream(stream) { + console.log(typeof stream.on, typeof stream.pipe, stream.readable); +} diff --git a/tests/corpus/6465-node-stream-js-boundary/main.ts b/tests/corpus/6465-node-stream-js-boundary/main.ts new file mode 100644 index 000000000..0079c0733 --- /dev/null +++ b/tests/corpus/6465-node-stream-js-boundary/main.ts @@ -0,0 +1,33 @@ +// NodeJS.ReadableStream values crossing into untyped JavaScript and into +// 'unknown': a file stream and a child's stdout share one helper with a +// JavaScript stream consumer, which sees ordinary Node streams. +import { spawn } from "node:child_process"; +import * as fs from "node:fs"; +import * as os from "node:os"; +import * as path from "node:path"; +import { describeStream, streamToText } from "./consume.js"; + +function open(file: string): NodeJS.ReadableStream { + return fs.createReadStream(file); +} + +function textOf(stream: NodeJS.ReadableStream): Promise { + return streamToText(stream); +} + +async function main(): Promise { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "scriptc-node-stream-js-")); + const file = path.join(dir, "input.txt"); + fs.writeFileSync(file, "through javascript\n"); + const held: unknown = open(file); + console.log(typeof held); + describeStream(held); + console.log(JSON.stringify(await textOf(open(file)))); + const child = spawn("sh", ["-c", "printf 'child output\\n'"], { + stdio: ["ignore", "pipe", "inherit"], + }); + if (child.stdout !== null) console.log(JSON.stringify(await textOf(child.stdout))); + fs.rmSync(dir, { recursive: true }); +} + +void main(); diff --git a/tests/fixtures/node-types/node-stream-interfaces.ts b/tests/fixtures/node-types/node-stream-interfaces.ts new file mode 100644 index 000000000..2809dc315 --- /dev/null +++ b/tests/fixtures/node-types/node-stream-interfaces.ts @@ -0,0 +1,61 @@ +// @types/node's stream interfaces fed by every producer: NodeJS.ReadableStream +// and stream.Readable slots take file streams, a gunzip Transform and a +// child's stdout; NodeJS.WritableStream and stream.Writable slots take file +// streams and process.stdout. +import { spawn } from "node:child_process"; +import * as fs from "node:fs"; +import * as os from "node:os"; +import * as path from "node:path"; +import { Readable, Writable } from "node:stream"; +import * as zlib from "node:zlib"; + +function drain(label: string, src: NodeJS.ReadableStream): Promise { + return new Promise((resolve, reject) => { + let text = ""; + src.on("data", (chunk: Buffer | string) => { + text += chunk.toString(); + }); + src.on("error", reject); + src.on("end", () => { + console.log(label, "end"); + resolve(text); + }); + }); +} + +function emit(dst: NodeJS.WritableStream, line: string): void { + dst.write(`${line}\n`); +} + +function finish(dst: Writable): Promise { + return new Promise((resolve) => dst.end(() => resolve())); +} + +async function main(): Promise { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "scriptc-node-types-streams-")); + const plain = path.join(dir, "plain.txt"); + const packed = path.join(dir, "packed.gz"); + fs.writeFileSync(plain, "from a file\n"); + fs.writeFileSync(packed, zlib.gzipSync(Buffer.from("from gunzip\n"))); + const file: Readable = fs.createReadStream(plain); + console.log(JSON.stringify(await drain("file", file))); + console.log(JSON.stringify(await drain("gunzip", fs.createReadStream(packed).pipe(zlib.createGunzip())))); + const child = spawn("sh", ["-c", "printf 'from a child\\n'"], { stdio: ["ignore", "pipe", "inherit"] }); + if (child.stdout) console.log(JSON.stringify(await drain("child", child.stdout))); + + const target = path.join(dir, "out.txt"); + const out: Writable = fs.createWriteStream(target); + emit(out, "to a file"); + emit(process.stdout, "to stdout"); + await finish(out); + console.log(JSON.stringify(fs.readFileSync(target, "utf8"))); + await new Promise((resolve) => { + const piped = fs.createReadStream(plain); + piped.on("end", () => resolve()); + piped.pipe(process.stdout); + }); + emit(process.stdout, "stdout still open"); + fs.rmSync(dir, { recursive: true }); +} + +void main(); diff --git a/tests/harness/project-config.test.ts b/tests/harness/project-config.test.ts index 14e4c27af..34063aa9c 100644 --- a/tests/harness/project-config.test.ts +++ b/tests/harness/project-config.test.ts @@ -461,6 +461,24 @@ test("node-types: stream subclasses extend the @types/node stream classes", asyn expect(comparableStderr(native.stderr)).toBe(node.stderr); }); +test("node-types: NodeJS stream interfaces and stream.Readable/Writable take every stream producer", async () => { + const outDir = outDirFor("node-stream-interfaces"); + const entry = join(nodeTypesDir, "node-stream-interfaces.ts"); + const result = await compile(entry, { + outPath: join(outDir, "node-stream-interfaces"), + outDir, + sanitize, + }); + expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true); + if (!result.ok) return; + const [native, node] = await Promise.all([ + execFileAsync(result.binaryPath), + execFileAsync(process.execPath, ["--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", entry]), + ]); + expect(native.stdout).toBe(node.stdout); + expect(comparableStderr(native.stderr)).toBe(node.stderr); +}); + test("node-types: promisified setTimeout and setImmediate lower as timers/promises", async () => { const outDir = outDirFor("promisify-timers"); const entry = join(nodeTypesDir, "promisify-timers.ts"); From 915c0f63c5e30bbee77662e894ae409348073618 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Tue, 20 Oct 2026 01:40:28 -0700 Subject: [PATCH 067/122] Request the general stream representation where a stdio handle meets the stream surface child.stdout.pipe(), stream events and writes beyond write(string) on a NodeJS stream interface slot, and pipes into a child's stdin now lower the program with the general representation instead of fencing. The fallback ChildStdin is a NodeJS.WritableStream, and the coverage remainder relowers its unreached code the same way. Co-Authored-By: Claude Opus 5.5 --- .../builtin-apis/streams-and-readline.mdx | 2 + .../console-and-process-streams.mdx | 3 +- .../ambient/scriptc-node-fallback.d.ts | 10 ++-- .../builtins/child-process-methods.ts | 7 +++ .../src/frontend/lowering/builtins/process.ts | 3 ++ .../frontend/lowering/builtins/properties.ts | 3 +- .../src/frontend/lowering/general-streams.ts | 30 +++++++++--- .../src/frontend/lowering/lower-stream.ts | 2 + .../compiler/src/frontend/lowering/lowerer.ts | 47 +++++++++++-------- packages/compiler/src/frontend/type-mapper.ts | 40 ++++++++++++++++ .../corpus/6466-node-stdio-stream-surface.ts | 45 ++++++++++++++++++ 11 files changed, 160 insertions(+), 32 deletions(-) create mode 100644 tests/corpus/6466-node-stdio-stream-surface.ts diff --git a/docs/content/limitations/builtin-apis/streams-and-readline.mdx b/docs/content/limitations/builtin-apis/streams-and-readline.mdx index 0ac0ca3a6..860daa5d8 100644 --- a/docs/content/limitations/builtin-apis/streams-and-readline.mdx +++ b/docs/content/limitations/builtin-apis/streams-and-readline.mdx @@ -1,4 +1,6 @@ - **Streams and readline:** Stream chunks are Buffers or strings. + `NodeJS.ReadableStream` and `NodeJS.WritableStream`, and `stream.Readable` and `stream.Writable` under `@types/node`, accept every stream: when a program passes file, zlib, or `node:stream` streams through them, child-process pipes and `process.stdout` and `process.stderr` join those streams as `Readable` and `Writable` instances. + `readable.pipe(process.stdout)` and `readable.pipe(process.stderr)` compile and, as in Node, do not end the process stream. `Readable.from` accepts arrays, Sets, Map and Set iterators, generators, async generators, and class iterables and async iterables whose values are strings or Buffers, and in JavaScript any iterable or async iterable of them; a stream that mixes strings and Buffers is unsupported. In JavaScript, a sync iterator value with a `then` method that is not a promise, and an object whose `[Symbol.asyncIterator]` or `[Symbol.iterator]` method is a generator method, are unsupported. Object-mode streams of other values (including `Readable.from` over a `Map`, whose values are entry arrays) are unsupported. diff --git a/docs/content/limitations/console-and-process-streams.mdx b/docs/content/limitations/console-and-process-streams.mdx index 0d7cd8f30..c62b82f0c 100644 --- a/docs/content/limitations/console-and-process-streams.mdx +++ b/docs/content/limitations/console-and-process-streams.mdx @@ -9,4 +9,5 @@ JavaScript can store `process.stdin`, `stdout`, and `stderr`, replace output `wr Input supports data/end/error listeners, pause/resume, buffered reads, and TTY raw mode. Output writes are synchronous, with successful callbacks on the next-tick queue; queued backpressure is not modeled. Stored streams do not support piping, encoding changes, arbitrary properties, or other events. -TypeScript output parameters support `write(string)`. +A TypeScript `NodeJS.WritableStream` parameter that only receives `process.stdout` or `process.stderr` supports `write(string)`. +When other streams flow into the same parameter, the process streams join them as `Writable` instances with the full writable surface. diff --git a/packages/compiler/ambient/scriptc-node-fallback.d.ts b/packages/compiler/ambient/scriptc-node-fallback.d.ts index 28e481b9d..0df5aceeb 100644 --- a/packages/compiler/ambient/scriptc-node-fallback.d.ts +++ b/packages/compiler/ambient/scriptc-node-fallback.d.ts @@ -123,9 +123,9 @@ declare namespace NodeJS { encoding: BufferEncoding, callback?: (error?: Error | null) => void, ): boolean; - end(callback?: () => void): this; - end(data: string | Uint8Array, callback?: () => void): this; - end(data: string | Uint8Array, encoding: BufferEncoding, callback?: () => void): this; + end(callback?: () => void): void; + end(data: string | Uint8Array, callback?: () => void): void; + end(data: string | Uint8Array, encoding: BufferEncoding, callback?: () => void): void; on(event: "drain" | "finish" | "close", listener: () => void): void; on(event: "error", listener: (err: Error) => void): void; once(event: "drain" | "finish" | "close", listener: () => void): void; @@ -3092,9 +3092,9 @@ declare module "child_process" { write(chunk: string | Uint8Array): boolean; end(): void; destroy(): void; - on(event: "drain" | "finish", listener: () => void): void; + on(event: "drain" | "finish" | "close", listener: () => void): void; on(event: "error", listener: (err: Error) => void): void; - once(event: "drain" | "finish", listener: () => void): void; + once(event: "drain" | "finish" | "close", listener: () => void): void; once(event: "error", listener: (err: Error) => void): void; } diff --git a/packages/compiler/src/frontend/lowering/builtins/child-process-methods.ts b/packages/compiler/src/frontend/lowering/builtins/child-process-methods.ts index a435bf740..7f0c2cde0 100644 --- a/packages/compiler/src/frontend/lowering/builtins/child-process-methods.ts +++ b/packages/compiler/src/frontend/lowering/builtins/child-process-methods.ts @@ -1,5 +1,6 @@ import { boolLit, strLit } from "../../../ir/build.js"; import * as ts from "../../ts7/adapter.js"; +import { requestGeneralStreamSurface } from "../general-streams.js"; import { type Lowerer } from "../lowerer.js"; import { isJsSourceFile, locOf } from "../../program.js"; import { isChildSurfaceMember } from "../surfaces.js"; @@ -335,6 +336,7 @@ export function lowerChildStreamMethodCall( const evT = lowerer.typeOf(call.arguments.nodes[0]!); const event = evT.isStringLiteralType() ? evT.value : null; if (event !== "data" && event !== "end") { + requestGeneralStreamSurface(lowerer, access.expression); lowerer.noLowering( `stream.${name}(${event === null ? "non-literal event" : `"${event}"`}, ...)`, call.arguments.nodes[0]!, @@ -342,6 +344,7 @@ export function lowerChildStreamMethodCall( ); } if (!ts.isExpressionStatement(call.parent)) { + requestGeneralStreamSurface(lowerer, access.expression); lowerer.unsupported( "SC1090", call, @@ -393,6 +396,7 @@ export function lowerChildStreamMethodCall( loc, }; } + requestGeneralStreamSurface(lowerer, access.expression); lowerer.noLowering( `ReadableStream.${name}`, call, @@ -418,6 +422,7 @@ export function lowerChildWriterMethodCall( if (name === "write") { if (call.arguments.nodes.length !== 1 || call.arguments.nodes.some(ts.isSpreadElement)) { + requestGeneralStreamSurface(lowerer, access.expression); lowerer.noLowering( `child stdin write with ${call.arguments.nodes.length} arguments`, call, @@ -461,6 +466,7 @@ export function lowerChildWriterMethodCall( const eventType = lowerer.typeOf(call.arguments.nodes[0]!); const event = eventType.isStringLiteralType() ? eventType.value : null; if (event !== "drain" && event !== "finish" && event !== "error") { + requestGeneralStreamSurface(lowerer, access.expression); lowerer.noLowering( `child stdin ${name}(${event === null ? "non-literal event" : `"${event}"`}, ...)`, call.arguments.nodes[0]!, @@ -507,6 +513,7 @@ export function lowerChildWriterMethodCall( return { kind: "libCall", fn, args: [receiver, cb, once], type: VOID, loc }; } + requestGeneralStreamSurface(lowerer, access.expression); lowerer.noLowering( `Writable.${name}`, call, diff --git a/packages/compiler/src/frontend/lowering/builtins/process.ts b/packages/compiler/src/frontend/lowering/builtins/process.ts index dc2f5ee89..5dd747065 100644 --- a/packages/compiler/src/frontend/lowering/builtins/process.ts +++ b/packages/compiler/src/frontend/lowering/builtins/process.ts @@ -3,6 +3,7 @@ import { envSnapshotHelper, lowerProcessLoadEnvFile } from "./environment.js"; import { dynUndefinedExpr, boolLit, numLit, strLit, varRef } from "../../../ir/build.js"; import { timerStyleCallback } from "../lower-timers.js"; import * as ts from "../../ts7/adapter.js"; +import { requestGeneralStreamSurface } from "../general-streams.js"; import { type Lowerer, own } from "../lowerer.js"; import { isJsSourceFile, locOf } from "../../program.js"; import { isSafeToDiscard } from "../expressions/evaluation-safety.js"; @@ -224,6 +225,7 @@ export function lowerProcStreamMethodCall( const receiver = lowerer.lowerExpr(access.expression); const data = lowerer.lowerExpr(call.arguments.nodes[0]!); if (data.type.kind !== "string") { + requestGeneralStreamSurface(lowerer, access.expression); lowerer.noLowering( `write of '${lowerer.fmt(data.type)}' data on a stream value`, call.arguments.nodes[0]!, @@ -232,6 +234,7 @@ export function lowerProcStreamMethodCall( } return { kind: "libCall", fn: "procStream.write", args: [receiver, data], type: BOOL, loc }; } + requestGeneralStreamSurface(lowerer, access.expression); lowerer.noLowering( `WritableStream.${name}`, call, diff --git a/packages/compiler/src/frontend/lowering/builtins/properties.ts b/packages/compiler/src/frontend/lowering/builtins/properties.ts index 3c0486f82..e60a14c0c 100644 --- a/packages/compiler/src/frontend/lowering/builtins/properties.ts +++ b/packages/compiler/src/frontend/lowering/builtins/properties.ts @@ -290,7 +290,8 @@ function generalChildStdioType( const className = name === "stdin" ? "%Writable" : "%Readable"; const mapped = lowerer.mapTypeOf(lowerer.typeOf(expr)); if (!mapped) return null; - const arms = mapped.kind === "union" ? (lowerer.unions.get(mapped.unionId)?.arms ?? []) : [mapped]; + const arms = + mapped.kind === "union" ? (lowerer.unions.get(mapped.unionId)?.arms ?? []) : [mapped]; return arms.some((arm) => arm.kind === "object" && arm.className === className) ? { kind: "object", className } : null; diff --git a/packages/compiler/src/frontend/lowering/general-streams.ts b/packages/compiler/src/frontend/lowering/general-streams.ts index 6b51ce545..98993798c 100644 --- a/packages/compiler/src/frontend/lowering/general-streams.ts +++ b/packages/compiler/src/frontend/lowering/general-streams.ts @@ -16,6 +16,8 @@ * never take the second pass, so their IR is unchanged. */ import type { IrExpr, IrType } from "../../ir/ir.js"; import { everyTypeChild } from "../../ir/traverse.js"; +import * as ts from "../ts7/adapter.js"; +import { isGeneralNodeStreamInterface } from "../type-mapper.js"; import type { Lowerer } from "./lowerer.js"; import { streamSidesOf } from "./lower-stream.js"; @@ -59,8 +61,7 @@ function isGeneralStream(lowerer: Lowerer, part: IrType): boolean { /** A mismatched flow (requireExactShape's input) that the general stream * representation resolves: a general stream into a stdio-represented - * slot, a stdio handle into an 'unknown' slot, or an 'unknown' value - * checked into a stdio-represented slot. */ + * slot, or a child pipe handle into an 'unknown' slot. */ export function needsGeneralNodeStreams( lowerer: Lowerer, actual: IrType, @@ -69,9 +70,8 @@ export function needsGeneralNodeStreams( const stdio = (part: IrType): boolean => STDIO_STREAM_KINDS.has(part.kind); const pipeHandle = (part: IrType): boolean => part.kind === "childStream" || part.kind === "childWriter"; - if (containsPart(lowerer, expected, stdio)) { - return actual.kind === "dyn" || containsPart(lowerer, actual, (p) => isGeneralStream(lowerer, p)); - } + if (containsPart(lowerer, expected, stdio)) + return containsPart(lowerer, actual, (p) => isGeneralStream(lowerer, p)); return expected.kind === "dyn" && containsPart(lowerer, actual, pipeHandle); } @@ -90,5 +90,23 @@ export function stdioIntoGeneralStream( (expected.kind === "object" && expected.className === "%Writable") || (expected.kind === "union" && lowerer.armTag(expected.unionId, writable) >= 0); if (!accepts) return null; - return { kind: "libCall", fn: "stream.stdioWritable", args: [expr], type: writable, loc: expr.loc }; + return { + kind: "libCall", + fn: "stream.stdioWritable", + args: [expr], + type: writable, + loc: expr.loc, + }; +} + +/** A stdio-handle receiver whose member has no lowering on the handle + * (`out.on("error", ...)`, `child.stdout.pipe(...)`, `output.end()`): when + * its declared type is one the general representation maps to a runtime + * stream class, the program lowers again with it, where the full stream + * surface applies. The caller still reports its fence for this pass. */ +export function requestGeneralStreamSurface(lowerer: Lowerer, receiver: ts.Expression): void { + if (lowerer.generalNodeStreams) return; + const type = lowerer.checker.getNonNullableType(lowerer.typeOf(receiver)); + if (isGeneralNodeStreamInterface(type, lowerer.typeCtx)) + lowerer.requiresGeneralNodeStreams = true; } diff --git a/packages/compiler/src/frontend/lowering/lower-stream.ts b/packages/compiler/src/frontend/lowering/lower-stream.ts index 66595c473..c73945d2b 100644 --- a/packages/compiler/src/frontend/lowering/lower-stream.ts +++ b/packages/compiler/src/frontend/lowering/lower-stream.ts @@ -49,6 +49,7 @@ import { sideEffectFreeOptionValue } from "./surfaces.js"; import { lowerDynObjectLiteral } from "./expressions/object-literals.js"; import { dynUndefinedExpr, varRef } from "../../ir/build.js"; import { genResultRecord } from "../type-mapper.js"; +import { requestGeneralStreamSurface } from "./general-streams.js"; import { extractIteratorValue, genStep, genValue, stepDrivable } from "./lower-generators.js"; import { BOOL, @@ -3608,6 +3609,7 @@ export function lowerStreamMethodCall( dst.type.kind === "object" ? lowerer.classes.get(dst.type.className) : undefined; const dstSides = streamSidesOf(lowerer, dstInfo); if (dstSides !== "w" && dstSides !== "rw") { + requestGeneralStreamSurface(lowerer, args[0]!); lowerer.noLowering( `pipe into a '${lowerer.fmt(dst.type)}'`, args[0]!, diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index 2973498e5..c9a0c6435 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -1140,25 +1140,34 @@ export function lowerToIr( }; else if (contracts.count > 0) result = { ...result, contracts }; if (options.coverage !== true) return result; - const remainder = new Lowerer(program, entry, moduleOrder, { - frontendServices: options.frontendServices, - projectCheckJs: options.projectCheckJs ?? false, - ...(generalNodeStreams ? { generalNodeStreams: true } : {}), - reachable, - remainder: true, - alreadyFlushed: resultLowerer.flushedSymbols, - inlineRequireCycleCalls: options.inlineRequireCycleCalls, - targetPlatform, - ffiImports, - libraryCallbacks, - forkTargets, - workerTargets, - ffiBindingSymbols: ffiValidation.symbolsByName, - externalTypes, - externalTypeSpecifiersByFile, - tierMode, - }); - const rem = remainder.run(); + const remainderPass = (general: boolean): Lowerer => + new Lowerer(program, entry, moduleOrder, { + frontendServices: options.frontendServices, + projectCheckJs: options.projectCheckJs ?? false, + ...(general ? { generalNodeStreams: true } : {}), + reachable, + remainder: true, + alreadyFlushed: resultLowerer.flushedSymbols, + inlineRequireCycleCalls: options.inlineRequireCycleCalls, + targetPlatform, + ffiImports, + libraryCallbacks, + forkTargets, + workerTargets, + ffiBindingSymbols: ffiValidation.symbolsByName, + externalTypes, + externalTypeSpecifiersByFile, + tierMode, + }); + let remainder = remainderPass(generalNodeStreams); + let rem = remainder.run(); + // Unreached code reports what it would compile to once reached: a + // stream flow there that needs the general representation lowers the + // remainder again with it, as reachable emit would. + if (!generalNodeStreams && remainder.requiresGeneralNodeStreams) { + remainder = remainderPass(true); + rem = remainder.run(); + } timing("coverage-remainder"); // Tiers (tier-analysis.ts): reached statements in dynamic-tier functions, // and with tierReport the report-only demotion analysis. diff --git a/packages/compiler/src/frontend/type-mapper.ts b/packages/compiler/src/frontend/type-mapper.ts index afa9f4311..08e603211 100644 --- a/packages/compiler/src/frontend/type-mapper.ts +++ b/packages/compiler/src/frontend/type-mapper.ts @@ -991,6 +991,31 @@ export function isDeclaredInAmbientNamespace(d: ts.Declaration, name: string): b return false; } +/** Whether `type` is one of the stream spellings the general stream + * representation (TypeMapperCtx.generalNodeStreams) maps differently: + * NodeJS.ReadableStream / NodeJS.WritableStream, @types/node's + * stream.Readable / stream.Writable, or the fallback declarations' + * ChildStdin. */ +export function isGeneralNodeStreamInterface( + type: ts.Type, + ctx: Pick, +): boolean { + const psym = type.getSymbol() ?? type.getAliasSymbol(); + if (psym === undefined) return false; + return psym.declarations.some((d) => { + if (!(ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d))) return false; + if (!ctx.isStdlibFile(d.getSourceFile())) return false; + if (psym.name === "ReadableStream" || psym.name === "WritableStream") + return ts.isInterfaceDeclaration(d) && isDeclaredInAmbientNamespace(d, "NodeJS"); + if (psym.name === "ChildStdin") return isDeclaredInAmbientModule(d, "child_process"); + return ( + (psym.name === "Readable" || psym.name === "Writable") && + isNodeTypesPath(d.getSourceFile().fileName) && + isDeclaredInAmbientModule(d, "stream") + ); + }); +} + /** A type declared in the shipped WebAssembly namespace (scriptc.d.ts): * its modules, instances, memories, tables, globals, error classes and the * Exports/Imports records. */ @@ -3038,6 +3063,7 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // of the broad @types class fence where they are constructed. if ( (psym?.name === "ChildStdin" && + ctx.generalNodeStreams !== true && psym.declarations.some( (d) => ts.isInterfaceDeclaration(d) && @@ -3056,6 +3082,20 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { ) { return { kind: "childWriter" }; } + // Under the general stream representation the fallback declarations' + // ChildStdin is the runtime Writable too (child.stdioStream bridges it). + if ( + ctx.generalNodeStreams === true && + psym?.name === "ChildStdin" && + psym.declarations.some( + (d) => + ts.isInterfaceDeclaration(d) && + ctx.isStdlibFile(d.getSourceFile()) && + isDeclaredInAmbientModule(d, "child_process"), + ) + ) { + return { kind: "object", className: "%Writable" }; + } // The static node:stream classes (the shipped fallback declarations — // NOT @types/node, whose stream.Readable also types child stdio; under // @types/node the childStream mapping below keeps priority and the diff --git a/tests/corpus/6466-node-stdio-stream-surface.ts b/tests/corpus/6466-node-stdio-stream-surface.ts new file mode 100644 index 000000000..d3726360f --- /dev/null +++ b/tests/corpus/6466-node-stdio-stream-surface.ts @@ -0,0 +1,45 @@ +// The stdio handles reach the full stream surface when a program uses it: +// child.stdout piped to process.stdout, a file stream piped into a +// child's stdin, and an 'error' listener on a NodeJS.WritableStream +// parameter that only receives the process streams. Each of these +// compiles the NodeJS stream interfaces as the node:stream classes. +import { spawn } from "node:child_process"; +import * as fs from "node:fs"; +import * as os from "node:os"; +import * as path from "node:path"; + +function report(out: NodeJS.WritableStream, text: string): void { + out.on("error", (err) => console.log("error", err.message)); + out.write(`${text}\n`); +} + +function echo(text: string): Promise { + return new Promise((resolve) => { + const child = spawn("sh", ["-c", `echo ${text}`], { stdio: ["ignore", "pipe", "inherit"] }); + child.stdout?.pipe(process.stdout); + child.on("close", (code) => resolve(code)); + }); +} + +function cat(input: string): Promise { + return new Promise((resolve) => { + const child = spawn("cat", [], { stdio: ["pipe", "pipe", "inherit"] }); + child.stdout?.pipe(process.stdout); + child.on("close", (code) => resolve(code)); + if (child.stdin) fs.createReadStream(input).pipe(child.stdin); + }); +} + +async function main(): Promise { + report(process.stdout, "reported"); + report(process.stderr, "reported to stderr"); + console.log("echo exit", await echo("piped from a child")); + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "scriptc-node-stdio-")); + const input = path.join(dir, "input.txt"); + fs.writeFileSync(input, "file contents through cat\n"); + console.log("cat exit", await cat(input)); + report(process.stdout, "stdout stays open"); + fs.rmSync(dir, { recursive: true }); +} + +void main(); From e1e45f12b9a86803c51c83e52a685080295aa61c Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Tue, 20 Oct 2026 03:55:43 -0500 Subject: [PATCH 068/122] Give converted object literals their accessors An object literal with get/set accessors converted to `any` or `unknown` listed its reserved closure slots (`%get:x`) as data keys, so a dynamic read of the accessor answered undefined without calling the getter, and Object.keys and JSON.stringify listed the slot instead of the property. The checked-dynamic copy now defines each accessor as one enumerable, configurable property whose getter and setter are the slot closures, in declaration order: the record shape records its accessor key order (`accessorOrder`). Live views of such records build that copy, and checking a copy back into the record reads the closures from the accessor's getter and setter. Reads, writes, Object.keys, Object.entries, JSON.stringify and property descriptors answer as in Node. --- packages/compiler/src/backend/llvm/dyn.ts | 63 +++++++++++++++++-- .../src/backend/llvm/expr-stream-bridges.ts | 2 + packages/compiler/src/frontend/type-mapper.ts | 43 ++++++++----- .../src/ir/function-references.test.ts | 1 + packages/compiler/src/ir/ir.ts | 4 ++ .../object-literal-accessors-dyn.ts.json | 6 ++ packages/runtime/src/scr_dyn.h | 5 ++ packages/runtime/src/scr_json.c | 25 ++++++++ tests/corpus/object-literal-accessors-dyn.ts | 31 +++++++++ .../object-literal-accessors-dyn.ts.json | 1 + 10 files changed, 162 insertions(+), 19 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/object-literal-accessors-dyn.ts.json create mode 100644 tests/corpus/object-literal-accessors-dyn.ts create mode 100644 tests/harness/corpus-costs/object-literal-accessors-dyn.ts.json diff --git a/packages/compiler/src/backend/llvm/dyn.ts b/packages/compiler/src/backend/llvm/dyn.ts index 86b4f5be8..7e414daee 100644 --- a/packages/compiler/src/backend/llvm/dyn.ts +++ b/packages/compiler/src/backend/llvm/dyn.ts @@ -34,6 +34,7 @@ import { InternalCompilerError } from "../../errors.js"; * ScrDynPath { parent, key, index } — the %ScrDynPath type. */ import type { IrRecordShape, IrType, IrUnionDef } from "../../ir/ir.js"; import { + accessorSlotProp, DYN_HANDLE_KINDS, isDirentShape, isDynTypedRefType, @@ -613,6 +614,18 @@ export class LlDyn { * tracking does not count the read's reference. Returns the value and * the matching release. */ private memberRead(B: BlockBuilder, key: string, t: IrType): { value: string; release: string } { + // An accessor slot reads the getter or setter of the accessor property + // the checked-dynamic copy holds for it (see accessorSlotProp). + const slot = accessorSlotProp(key); + if (slot !== null) { + this.host.declare(`declare ptr @scr_dyn_obj_accessor_fn(ptr, ptr, ${this.S}, i1 zeroext)`); + this.host.declare(`declare void @scr_dyn_release(ptr)`); + const fn = B.tmp(); + B.line( + `${fn} = call ptr @scr_dyn_obj_accessor_fn(ptr %d, ptr ${this.host.cstr(slot.prop)}, ${this.S} ${Buffer.byteLength(slot.prop, "utf8")}, i1 ${slot.kind === "set" ? "true" : "false"})`, + ); + return { value: fn, release: "scr_dyn_release" }; + } const held = this.isRecursiveShape(t) || this.holdsRecursiveShape(t); const release = held ? "scr_dyn_release_held" : "scr_dyn_release"; this.host.declare(`declare void @${release}(ptr)`); @@ -2687,13 +2700,53 @@ export class LlDyn { // Keys insert in DECLARED order (JS insertion order); internal // '%'-fields follow so a record→dyn→record round trip keeps them. const byName = new Map(shape.fields.map((f) => [f.name, f])); - const order = shape.declaredOrder ?? shape.fields.map((f) => f.name); + // An accessor property is one key whose getter and setter are the + // slot closures (see accessorSlotProp), in accessorOrder's place. + const order = shape.accessorOrder ?? shape.declaredOrder ?? shape.fields.map((f) => f.name); const inOrder = new Set(order); - const dynFields = [ - ...order.map((n) => byName.get(n)).filter((f) => f !== undefined), - ...shape.fields.filter((f) => !inOrder.has(f.name)), + const accessors = new Set( + shape.accessorOrder === undefined + ? [] + : shape.fields.flatMap((f) => { + const slot = accessorSlotProp(f.name); + return slot === null ? [] : [slot.prop]; + }), + ); + type DynField = { name: string; type: IrType } | { accessor: string }; + const dynFields: DynField[] = [ + ...order.flatMap((n) => { + if (accessors.has(n)) return [{ accessor: n } as DynField]; + const f = byName.get(n); + return f === undefined ? [] : [f as DynField]; + }), + ...shape.fields.filter( + (f) => + !inOrder.has(f.name) && !(accessors.size > 0 && accessorSlotProp(f.name) !== null), + ), ]; - for (const f of dynFields) { + for (const entry of dynFields) { + if ("accessor" in entry) { + host.declare( + `declare void @scr_dyn_obj_set_accessor(ptr, ptr, ${host.sizeType}, ptr, ptr)`, + ); + const boxed = (kind: "get" | "set"): string => { + const slot = byName.get(`%${kind}:${entry.accessor}`); + if (slot === undefined) return "null"; + const fv = loadFieldOf(slot.name, slot.type); + const conv = B.tmp(); + B.line( + `${conv} = call ptr @${this.toDynHelper(slot.type)}(${this.valTy(slot.type)} ${fv})`, + ); + return conv; + }; + const getter = boxed("get"); + const setter = boxed("set"); + B.line( + `call void @scr_dyn_obj_set_accessor(ptr ${d}, ptr ${host.cstr(entry.accessor)}, ${host.sizeType} ${Buffer.byteLength(entry.accessor, "utf8")}, ptr ${getter}, ptr ${setter}) ; ${llvmCommentText(entry.accessor)}`, + ); + continue; + } + const f = entry; const klen = Buffer.byteLength(f.name, "utf8"); const fv = loadFieldOf(f.name, f.type); // An ABSENT optional field contributes no key. diff --git a/packages/compiler/src/backend/llvm/expr-stream-bridges.ts b/packages/compiler/src/backend/llvm/expr-stream-bridges.ts index 134c74b92..1841053df 100644 --- a/packages/compiler/src/backend/llvm/expr-stream-bridges.ts +++ b/packages/compiler/src/backend/llvm/expr-stream-bridges.ts @@ -15,6 +15,7 @@ import { isDirentShape, isDynTypedRefType, isRefCounted, + shapeHasAccessorSlots, typeKey, } from "../../ir/ir.js"; import { mangleFunction, mangleGlobal, mangleRecordStruct } from "../mangle.js"; @@ -965,6 +966,7 @@ export function streamTypedRefMaterializeAdapter( * Declared-field records and tuples cover the live stream values. */ const ordinary = shape.indexValue || + shapeHasAccessorSlots(shape) || shape.fields.some((field) => field.name === "handleEvent" && field.type.kind === "func"); if (ordinary) { const out = B.tmp(); diff --git a/packages/compiler/src/frontend/type-mapper.ts b/packages/compiler/src/frontend/type-mapper.ts index 94506f034..b095ecbb8 100644 --- a/packages/compiler/src/frontend/type-mapper.ts +++ b/packages/compiler/src/frontend/type-mapper.ts @@ -5895,6 +5895,7 @@ interface RecordShapeParts { fields: { name: string; type: IrType }[]; indexValue?: IrType; declaredOrder?: string[]; + accessorOrder?: string[]; } function mapRecordType(widened: ts.Type, ctx: TypeMapperCtx): IrType | null { @@ -5931,15 +5932,14 @@ function mapRecordType(widened: ts.Type, ctx: TypeMapperCtx): IrType | null { // (generic type parameters, mixin instantiations) cannot intern by // checker-type identity — fence recursive generic-open shapes. if (contextResolutions !== sensitivityAtEntry) return null; - return { - kind: "record", - shapeId: shapes.finalizeRecursive( - widened, - inner.fields, - inner.indexValue, - inner.declaredOrder, - ), - }; + const recursive = shapes.finalizeRecursive( + widened, + inner.fields, + inner.indexValue, + inner.declaredOrder, + ); + adoptAccessorOrder(shapes.get(recursive), inner.accessorOrder); + return { kind: "record", shapeId: recursive }; } // A record with no typed payload contract is a view of an existing // object, including across parameters and returns. Building a native @@ -5954,15 +5954,20 @@ function mapRecordType(widened: ts.Type, ctx: TypeMapperCtx): IrType | null { return DYN; const refined = refinedConstituentShape(widened, inner, ctx); if (refined !== null) return { kind: "record", shapeId: refined }; - return { - kind: "record", - shapeId: shapes.intern(inner.fields, false, inner.indexValue, inner.declaredOrder), - }; + const shapeId = shapes.intern(inner.fields, false, inner.indexValue, inner.declaredOrder); + adoptAccessorOrder(shapes.get(shapeId), inner.accessorOrder); + return { kind: "record", shapeId }; } finally { shapes.inProgress.delete(widened); } } +/** The first-seen accessor key order of a shape with accessor slots. */ +function adoptAccessorOrder(shape: IrRecordShape | undefined, order: string[] | undefined): void { + if (shape !== undefined && order !== undefined && shape.accessorOrder === undefined) + shape.accessorOrder = order; +} + /** A refinement intersection (`Expr & { kind: "mul" }`, which the checker * reduces to `Mul & { kind: "mul" }`) keeps the shape of the constituent it * refines when the refinement changes no field's representation. Recursive @@ -6322,8 +6327,18 @@ function mapRecordTypeInner( const declaredOrder = esOwnKeyOrder( fields.filter((f) => accessorSlotProp(f.name) === null).map((f) => f.name), ); + // With accessors: every own key in that order, an accessor property + // named once for its get/set pair (the checked-dynamic copy lists it). + const accessorOrder = fields.some((f) => accessorSlotProp(f.name) !== null) + ? esOwnKeyOrder([...new Set(fields.map((f) => accessorSlotProp(f.name)?.prop ?? f.name))]) + : undefined; fields.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0)); - return { fields, ...(indexValue ? { indexValue } : {}), declaredOrder }; + return { + fields, + ...(indexValue ? { indexValue } : {}), + declaredOrder, + ...(accessorOrder ? { accessorOrder } : {}), + }; } } diff --git a/packages/compiler/src/ir/function-references.test.ts b/packages/compiler/src/ir/function-references.test.ts index c2d461e09..2c5a45dd5 100644 --- a/packages/compiler/src/ir/function-references.test.ts +++ b/packages/compiler/src/ir/function-references.test.ts @@ -170,6 +170,7 @@ const MODULE_FIELDS: Record>, Role> = { "records[].id": "other", "records[].fields[].name": "other", "records[].declaredOrder[]": "other", + "records[].accessorOrder[]": "other", "unions[].id": "other", "unions[].discriminant.field": "other", "unions[].discriminant.cases[].values[]": "other", diff --git a/packages/compiler/src/ir/ir.ts b/packages/compiler/src/ir/ir.ts index de6bf558c..34e70f3f0 100644 --- a/packages/compiler/src/ir/ir.ts +++ b/packages/compiler/src/ir/ir.ts @@ -1621,6 +1621,10 @@ export interface IrRecordShape { * fields carry that declaredOrder OMITS are internal '%'-fields (Dirent's * %dtype) — hidden from every key-order surface, JSON included. */ declaredOrder?: string[]; + /** Shapes with accessor slots: every own key in declaration order, with + * an accessor property named once (see accessorSlotProp). The + * checked-dynamic copy lists its keys in this order. */ + accessorOrder?: string[]; } /** fs.Dirent rows: the hidden %dtype entry kind (libuv's UV_DIRENT diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/object-literal-accessors-dyn.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/object-literal-accessors-dyn.ts.json new file mode 100644 index 000000000..ffe0c75d3 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/object-literal-accessors-dyn.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/object-literal-accessors-dyn.ts" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_dyn.h b/packages/runtime/src/scr_dyn.h index 6fdbbb408..ff6763cda 100644 --- a/packages/runtime/src/scr_dyn.h +++ b/packages/runtime/src/scr_dyn.h @@ -1076,6 +1076,11 @@ extern SCR_TL size_t scr_uncloneable_count; ScrDyn *scr_dyn_from_error(const ScrError *e); void scr_error_register_dyn(const ScrVt *vt, ScrDyn *(*box)(void *)); ScrDyn *scr_error_dyn_fields(const ScrError *e); +/* An accessor property of a fresh object: enumerable and configurable, + * with the given getter and setter (either may be NULL). Moves both. */ +void scr_dyn_obj_set_accessor(ScrDyn *obj, const char *key, size_t key_len, ScrDyn *getter, ScrDyn *setter); +/* The getter or setter of an own accessor property, +1 (undefined when none). */ +ScrDyn *scr_dyn_obj_accessor_fn(const ScrDyn *obj, const char *key, size_t key_len, bool setter); void scr_error_commit_dyn(ScrError *e, const ScrDyn *view); /* The reverse extraction, riding the same identity cache: a dyn error * that came from a runtime ScrError answers THAT instance (+1; diff --git a/packages/runtime/src/scr_json.c b/packages/runtime/src/scr_json.c index c42e66734..131514d4b 100644 --- a/packages/runtime/src/scr_json.c +++ b/packages/runtime/src/scr_json.c @@ -4356,6 +4356,31 @@ void scr_dyn_obj_set(ScrDyn *obj, const char *key, size_t key_len, ScrDyn *value scr_dyn_obj_put(obj, key, key_len, value); } +/* The getter (or, with `setter`, the setter) of the own accessor property + * `key`: +1, or the undefined value when the property is not an accessor + * or has none. */ +ScrDyn *scr_dyn_obj_accessor_fn(const ScrDyn *obj, const char *key, size_t key_len, bool setter) { + const ScrDynEntry *entry = obj->kind == SCR_DYN_OBJ ? scr_dyn_find_entry(obj, key, key_len) : NULL; + ScrDyn *fn = entry && entry->accessor ? (setter ? entry->setter : entry->getter) : NULL; + return scr_dyn_retain(fn ? fn : scr_dyn_undefined()); +} + +/* An object literal's get/set accessor property on a fresh copy: + * enumerable and configurable, with the given getter and setter functions + * (either NULL when the literal declares none). Moves both. */ +void scr_dyn_obj_set_accessor(ScrDyn *obj, const char *key, size_t key_len, ScrDyn *getter, ScrDyn *setter) { + scr_dyn_obj_put(obj, key, key_len, scr_dyn_retain(scr_dyn_undefined())); + ScrDynEntry *entry = scr_dyn_find_entry(obj, key, key_len); + if (!entry) { + scr_dyn_release(getter); + scr_dyn_release(setter); + return; + } + entry->accessor = true; + entry->getter = getter; + entry->setter = setter; +} + /* A class instance's view takes its native field values whatever the * attributes copied into it (a frozen instance's view is built the same way * as any other). Consumes value. */ diff --git a/tests/corpus/object-literal-accessors-dyn.ts b/tests/corpus/object-literal-accessors-dyn.ts new file mode 100644 index 000000000..3b7dc95e4 --- /dev/null +++ b/tests/corpus/object-literal-accessors-dyn.ts @@ -0,0 +1,31 @@ +// Object literals with get/set accessors held as `any` or `unknown` keep +// their accessors, as in Node: a read calls the getter, a write calls the +// setter, Object.keys, Object.entries and JSON.stringify list the accessor +// in declaration order and call the getter, and the property descriptor +// has a get and set function. Writes to data properties reach the +// original object. + +function dyn(value: unknown): any { + return value; +} + +let count = 0; +const lit = { + a: 1, + get g(): number { count++; return 42; }, + set g(v: number) { console.log("set", v); }, + b: 2, + get only(): string { return "read-only"; }, +}; +const d = dyn(lit); +console.log("read", d.g, d.only, count); +console.log("keys", Object.keys(d).join(","), JSON.stringify(d), count); +console.log("entries", Object.entries(d).map((e) => e.join("=")).join(";")); +console.log("in", "g" in d, Object.hasOwn(d, "g")); +d.g = 7; +d.a = 5; +console.log("write", lit.a, d.a); +const desc = Object.getOwnPropertyDescriptor(d, "g")!; +console.log("descriptor", typeof desc.get, typeof desc.set, desc.enumerable, desc.configurable); +const u: unknown = lit; +console.log("unknown", (u as any).g, JSON.stringify(u)); diff --git a/tests/harness/corpus-costs/object-literal-accessors-dyn.ts.json b/tests/harness/corpus-costs/object-literal-accessors-dyn.ts.json new file mode 100644 index 000000000..9643a761b --- /dev/null +++ b/tests/harness/corpus-costs/object-literal-accessors-dyn.ts.json @@ -0,0 +1 @@ +{ "plain": 2903, "san": 3777 } From 1ccda45f7dcef77db7727a6a929ecf71ceea03d4 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Tue, 20 Oct 2026 03:59:25 -0500 Subject: [PATCH 069/122] Decode an AST list's position only when it is read Every read of an AST list field built an AstNodeList whose pos and end were decoded from the wire first, though most readers only use `nodes`. The list now keeps the wire file and the list's index, and pos and end decode when they are read. The allocation per read stays the same size, and a decoded file still holds no list objects. --- packages/compiler/src/frontend/ts7/ast-node.ts | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/packages/compiler/src/frontend/ts7/ast-node.ts b/packages/compiler/src/frontend/ts7/ast-node.ts index 92ab5e4b8..3760fff8d 100644 --- a/packages/compiler/src/frontend/ts7/ast-node.ts +++ b/packages/compiler/src/frontend/ts7/ast-node.ts @@ -19,13 +19,22 @@ import { SyntaxKind, NodeFlags } from "./enums.js"; /** A list of child nodes with the list's own position, the shape of tsc-ts's * NodeList. The file keeps the nodes array, not this object: each read of a * list field returns a new AstNodeList over the same array, so `nodes` keeps - * its identity and a decoded file holds no list objects. */ + * its identity and a decoded file holds no list objects. The position is + * decoded from the wire when it is read; most readers only use `nodes`. */ export class AstNodeList { constructor( - readonly pos: number, - readonly end: number, + private readonly wire: AstWireFile, + private readonly index: number, readonly nodes: readonly T[], ) {} + + get pos(): number { + return this.wire.pos(this.index) >>> 0; + } + + get end(): number { + return this.wire.end(this.index) >>> 0; + } } /** One immutable AST response owns one object per node and one array per @@ -83,7 +92,7 @@ export class AstFile { this.lists[index] = nodes; this.materialized?.(); } - return new AstNodeList(this.wire.pos(index) >>> 0, this.wire.end(index) >>> 0, nodes); + return new AstNodeList(this.wire, index, nodes); } namedChild(node: AstNode, name: string): number { From d968975f346840a3bdb84ca5c4914977de6f03ff Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Tue, 20 Oct 2026 01:14:02 -0700 Subject: [PATCH 070/122] Build dict-spreading literals with other members as checked-dynamic objects An object literal that spreads an index-signature record but has members outside the scalar join (an array of labels), an unknown-valued spread (Record), or a member a later spread may overwrite with another type now maps to the checked-dynamic value: it is built like a JavaScript object literal, so spreads copy their own keys in order and later keyed writes land on the same object. 'c && { k: v }' spreads contribute k exactly when c is truthy even where the checker's union of the two does not map (an AbortSignal member), and a conditional spread onto an 'unknown' field leaves the checked-dynamic undefined in its empty arm. Co-Authored-By: Claude Opus 5.5 --- .../lowering/expressions/object-literals.ts | 34 +++++++- packages/compiler/src/frontend/type-mapper.ts | 77 ++++++++++++++++--- .../6475-dynamic-spread-literals.ts.json | 6 ++ tests/corpus/6475-dynamic-spread-literals.ts | 63 +++++++++++++++ 4 files changed, 167 insertions(+), 13 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6475-dynamic-spread-literals.ts.json create mode 100644 tests/corpus/6475-dynamic-spread-literals.ts diff --git a/packages/compiler/src/frontend/lowering/expressions/object-literals.ts b/packages/compiler/src/frontend/lowering/expressions/object-literals.ts index 5a9d0ea10..77c4c8fc5 100644 --- a/packages/compiler/src/frontend/lowering/expressions/object-literals.ts +++ b/packages/compiler/src/frontend/lowering/expressions/object-literals.ts @@ -1700,7 +1700,11 @@ export function lowerObjectLiteral( } shapeMismatch(prop); } - const absent = lowerer.absentFieldValue(fieldType, locOf(prop)); + // An 'unknown' slot holds the checked-dynamic undefined for the + // empty arm, the absent-property read the width copy uses too. + const absent = + lowerer.absentFieldValue(fieldType, locOf(prop)) ?? + (fieldType.kind === "dyn" ? dynUndefinedExpr(locOf(prop)) : null); if (!absent) { lowerer.unsupported( "SC1090", @@ -2628,6 +2632,14 @@ export function andSpreadOf( while (ts.isParenthesizedExpression(right)) right = right.expression; if (!ts.isObjectLiteralExpression(right)) return null; const spreadType = lowerer.mapTypeOf(lowerer.typeOf(e)); + // Every falsy value is a primitive, which spreads nothing, so `c && { + // ... }` contributes the literal's members exactly when c is truthy, + // whatever c's type. The checker's union of the two decides the + // representation where it maps; where it does not (a member typed by a + // checked-dynamic value such as an AbortSignal), the syntax decides. + if (spreadType === null || spreadType.kind === "dyn") { + return syntacticAndSpread(right, e.left); + } if (spreadType?.kind !== "union") return null; const arms = lowerer.unions.get(spreadType.unionId)?.arms ?? []; if ( @@ -2659,6 +2671,26 @@ export function andSpreadOf( return { cond: e.left, props, whenTrue: true }; } +function syntacticAndSpread( + right: ts.ObjectLiteralExpression, + cond: ts.Expression, +): { cond: ts.Expression; props: CondSpreadProp[]; whenTrue: boolean } | "unsupported" { + if (right.properties.nodes.length === 0) return "unsupported"; + const props: CondSpreadProp[] = []; + for (const p of right.properties.nodes) { + if ( + (ts.isPropertyAssignment(p) || ts.isShorthandPropertyAssignment(p)) && + p.name && + (ts.isIdentifier(p.name) || ts.isStringLiteral(p.name)) + ) { + props.push(p as CondSpreadProp); + } else { + return "unsupported"; + } + } + return { cond, props, whenTrue: true }; +} + /** A conditional spread's carrier property: `name: value` or shorthand, * with an identifier/string-literal name. */ export type CondSpreadProp = (ts.PropertyAssignment | ts.ShorthandPropertyAssignment) & { diff --git a/packages/compiler/src/frontend/type-mapper.ts b/packages/compiler/src/frontend/type-mapper.ts index de4e13de0..f2440a521 100644 --- a/packages/compiler/src/frontend/type-mapper.ts +++ b/packages/compiler/src/frontend/type-mapper.ts @@ -5861,6 +5861,7 @@ function mapRecordType(widened: ts.Type, ctx: TypeMapperCtx): IrType | null { ) return DYN; const merged = dictSpreadLiteralShape(widened, inner, ctx); + if (merged === "dyn") return DYN; if (merged !== null) return { kind: "record", shapeId: merged }; const refined = refinedConstituentShape(widened, inner, ctx); if (refined !== null) return { kind: "record", shapeId: refined }; @@ -5889,7 +5890,7 @@ function dictSpreadLiteralShape( widened: ts.Type, parts: RecordShapeParts, ctx: TypeMapperCtx, -): string | null { +): string | "dyn" | null { if (parts.indexValue) return null; const symbol = widened.getSymbol(); if (!symbol || (symbol.flags & ts.SymbolFlags.ObjectLiteral) === 0) return null; @@ -5908,16 +5909,29 @@ function dictSpreadLiteralShape( // mapping. const contextualRaw = checker.getContextualType(literal); const contextual = contextualRaw ? checker.getNonNullableType(contextualRaw) : undefined; - // An unknown-valued slot (`Record`) is the checked-dynamic - // value, which has no typed merge of its own: the literal still needs its - // own record. + // A literal written into a declared slot lowers into that slot (a typed + // index-signature record merges into it, a declared record converts), + // and its own type keeps its mapping. Only a literal with no declared + // destination (an unannotated binding, a type parameter, `any` or + // `unknown`, an unknown-valued record) is built by its own type. + // A generic argument's context is the instantiated parameter: the + // literal's own type when the type argument was inferred from it. + const ownContext = + contextual !== undefined && + (contextual === widened || contextual.getSymbol()?.declarations.includes(literal) === true); if ( contextual && - checker - .getIndexInfosOfType(contextual) - .some((info) => (info.valueType.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) === 0) - ) - return null; + !ownContext && + !(contextual.flags & (ts.TypeFlags.TypeParameter | ts.TypeFlags.Any | ts.TypeFlags.Unknown)) + ) { + const infos = checker.getIndexInfosOfType(contextual); + const unknownValued = + infos.length > 0 && + infos.every( + (info) => (info.valueType.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) !== 0, + ); + if (!unknownValued) return null; + } const kinds = props.map((p) => ts.isSpreadAssignment(p) ? spreadSourceKindTs(checker, p.expression) : "member", ); @@ -5934,6 +5948,7 @@ function dictSpreadLiteralShape( // pure index-signature record. if (t === widened) return null; const mapped = mapType(t, ctx); + if (mapped?.kind === "dyn") return DYN; const shape = mapped?.kind === "record" ? shapes.get(mapped.shapeId) : undefined; return shape?.indexValue && shape.fields.length === 0 && !shape.tuple ? shape.indexValue : null; }; @@ -5947,6 +5962,11 @@ function dictSpreadLiteralShape( return true; }; const before: string[] = []; + // An unknown-valued spread (`Record`, or another such + // literal) makes the object the checked-dynamic value: it is built like + // a JavaScript object literal (copy each spread's own keys in order), and + // member reads check their declared types. + let unknownValued = false; for (const [i, p] of props.entries()) { // A fixed-shape or conditional spread contributes its own members, // which the checker already types exactly (they are in `parts`). @@ -5958,11 +5978,32 @@ function dictSpreadLiteralShape( } if (ts.isSpreadAssignment(p)) { const slot = dictSlotOf(p.expression); - if (!slot || !addArms(slot)) return null; - // A member written before this spread may be overwritten by it. + if (slot?.kind === "dyn") { + unknownValued = true; + continue; + } + if (!slot) return null; + if (!addArms(slot)) { + // A slot outside the scalar join still has the checked-dynamic + // representation when it converts to it. + if ( + !canConvertToDyn( + slot, + (id) => shapes.get(id), + (id) => unions.get(id), + ) + ) + return null; + unknownValued = true; + } + // A member written before this spread may be overwritten by it: the + // typed join keeps member types only where the slot is that type; + // otherwise the checked-dynamic object (whose member reads check + // their types) holds whatever the spread wrote. for (const name of before) { const field = parts.fields.find((f) => f.name === name); - if (!field || !typeEquals(field.type, slot)) return null; + if (!field) return null; + if (!typeEquals(field.type, slot)) unknownValued = true; } continue; } @@ -5972,6 +6013,18 @@ function dictSpreadLiteralShape( before.push(name.text); } if (parts.fields.some((f) => f.name.startsWith("%"))) return null; + // Members outside the scalar join (an array of labels beside the tags) + // make the object the checked-dynamic value too. + if (!unknownValued && !parts.fields.every((f) => addArms(f.type))) unknownValued = true; + if (unknownValued) { + const getRecord = (id: string) => shapes.get(id); + const getUnion = (id: string) => unions.get(id); + return parts.fields.every( + (f) => f.type.kind === "dyn" || canConvertToDyn(f.type, getRecord, getUnion), + ) + ? "dyn" + : null; + } for (const field of parts.fields) if (!addArms(field.type)) return null; if (![...arms.values()].some((arm) => !isUnitType(arm))) return null; const sorted = [...arms.values()].sort((a, b) => (typeKey(a) < typeKey(b) ? -1 : 1)); diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6475-dynamic-spread-literals.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6475-dynamic-spread-literals.ts.json new file mode 100644 index 000000000..29207c929 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6475-dynamic-spread-literals.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6475-dynamic-spread-literals.ts" + ], + "diags": [] +} diff --git a/tests/corpus/6475-dynamic-spread-literals.ts b/tests/corpus/6475-dynamic-spread-literals.ts new file mode 100644 index 000000000..eaf76598e --- /dev/null +++ b/tests/corpus/6475-dynamic-spread-literals.ts @@ -0,0 +1,63 @@ +// Object literals spreading index-signature records whose members are not +// all scalars, or whose spread values are unknown, are checked-dynamic +// objects built like JavaScript's: each spread copies its own keys in +// order, conditional spreads add nothing when they do not apply, and later +// keyed writes land on the same object. +function flatten(obj: unknown, prefix: string): Record { + const out: Record = {}; + if (typeof obj === "object" && obj !== null) { + for (const [k, v] of Object.entries(obj)) out[`${prefix}.${k}`] = v; + } + return out; +} +function summary(status: string, ms: number, bytes: number | undefined, err: string | undefined) { + const milestones = { connect: 3, done: 9 }; + const resultTags = { + "git.fetch.status": status, + "git.fetch.durationMs": ms, + ...flatten(milestones, "git.fetch"), + ...(bytes !== undefined && { "git.fetch.packBytes": bytes }), + ...(err !== undefined && { "git.fetch.error": err }), + }; + console.log(JSON.stringify({ event: "git.fetch.summary", version: "v1", ...resultTags })); +} +summary("ok", 12, 100, undefined); +summary("failed", 3, undefined, "boom"); +type SendOptions = { method: string; headersTimeout?: number; signal?: AbortSignal }; +function send(url: string, options: SendOptions): string { + return `${url} ${options.method} ${options.headersTimeout ?? "-"} ${options.signal ? options.signal.aborted : "none"}`; +} +function callWith(timeout: number | undefined, attemptTimeout: number | undefined): string { + return send("u", { + headersTimeout: timeout, + method: "GET", + ...(attemptTimeout && { signal: AbortSignal.timeout(attemptTimeout) }), + }); +} +console.log(callWith(5, 1000)); +console.log(callWith(undefined, undefined)); +console.log(callWith(5, 0)); +function report(tags: string[], from: string, logs: [string, string][]) { + const ddTags = { + "errClass.from": from, + "errClass.tagLabels": Array.from(new Set(tags)).sort(), + "errClass.tags": "", + ...tags.reduce((obj: Record, tag: string) => { + obj[`errClass.tags.${tag}`] = true; + return obj; + }, {}), + "errClass.logLinesSent": logs.length, + "errClass.lastRule": "none", + }; + if (tags.length > 1) { + ddTags["errClass.lastRule"] = "rule-2"; + } + for (const tag of ["late"]) { + // @ts-expect-error -- keys outside the literal's declared ones + ddTags[`errClass.tags.${tag}`] = true; + } + ddTags["errClass.tags"] = tags.join(","); + console.log(JSON.stringify(ddTags), ddTags["errClass.logLinesSent"] + 1); +} +report(["b", "a"], "build", [["stdout", "x"]]); +report([], "init", []); From 7cc8179a22a93218fe42bb21173e9717c8842ff3 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Tue, 20 Oct 2026 02:31:27 -0700 Subject: [PATCH 071/122] Close a bridged child stdin on destroy and keep stream listener typing untyped The fallback NodeJS stream interfaces declare Node's EventEmitter listener signature for events beyond data/end, so checked JavaScript keeps its contextual typing for process stream listeners. The child stdin fence fixture becomes a differential test of the forms that now compile. Co-Authored-By: Claude Opus 5.5 --- .../ambient/scriptc-node-fallback.d.ts | 19 +++++---- ...463-node-readable-stream-producers.ts.json | 6 +++ ...464-node-writable-stream-producers.ts.json | 6 +++ .../6465-node-stream-js-boundary/main.ts.json | 7 ++++ .../6466-node-stdio-stream-surface.ts.json | 6 +++ .../node-types/child-stdin-fences.ts.json | 6 --- .../node-types/child-stdin-streams.ts.json | 6 +++ .../node-types/node-stream-interfaces.ts.json | 6 +++ packages/runtime/src/scr_stream.c | 22 +++++++++- .../fixtures/node-types/child-stdin-fences.ts | 6 --- .../node-types/child-stdin-streams.ts | 40 +++++++++++++++++++ tests/harness/project-config.test.ts | 26 +++++------- 12 files changed, 118 insertions(+), 38 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6463-node-readable-stream-producers.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6464-node-writable-stream-producers.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6465-node-stream-js-boundary/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6466-node-stdio-stream-surface.ts.json delete mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/child-stdin-fences.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/child-stdin-streams.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/node-stream-interfaces.ts.json delete mode 100644 tests/fixtures/node-types/child-stdin-fences.ts create mode 100644 tests/fixtures/node-types/child-stdin-streams.ts diff --git a/packages/compiler/ambient/scriptc-node-fallback.d.ts b/packages/compiler/ambient/scriptc-node-fallback.d.ts index 0df5aceeb..037801338 100644 --- a/packages/compiler/ambient/scriptc-node-fallback.d.ts +++ b/packages/compiler/ambient/scriptc-node-fallback.d.ts @@ -93,13 +93,11 @@ declare namespace NodeJS { on(event: "data", listener: (chunk: Buffer) => void): void; on(event: "data", listener: (chunk: string) => void): void; on(event: "end", listener: () => void): void; - on(event: "close", listener: () => void): void; - on(event: "error", listener: (err: Error) => void): void; + on(event: string, listener: (...args: any[]) => void): void; once(event: "data", listener: (chunk: Buffer) => void): void; once(event: "data", listener: (chunk: string) => void): void; once(event: "end", listener: () => void): void; - once(event: "close", listener: () => void): void; - once(event: "error", listener: (err: Error) => void): void; + once(event: string, listener: (...args: any[]) => void): void; /* setEncoding('utf8') makes subsequent data chunks strings, including * multibyte sequences split across native pipe reads. */ setEncoding(encoding: string): this; @@ -126,10 +124,9 @@ declare namespace NodeJS { end(callback?: () => void): void; end(data: string | Uint8Array, callback?: () => void): void; end(data: string | Uint8Array, encoding: BufferEncoding, callback?: () => void): void; - on(event: "drain" | "finish" | "close", listener: () => void): void; - on(event: "error", listener: (err: Error) => void): void; - once(event: "drain" | "finish" | "close", listener: () => void): void; - once(event: "error", listener: (err: Error) => void): void; + /* Node's EventEmitter signature: listeners take untyped arguments. */ + on(event: string, listener: (...args: any[]) => void): void; + once(event: string, listener: (...args: any[]) => void): void; } } @@ -3092,10 +3089,12 @@ declare module "child_process" { write(chunk: string | Uint8Array): boolean; end(): void; destroy(): void; - on(event: "drain" | "finish" | "close", listener: () => void): void; + on(event: "drain" | "finish", listener: () => void): void; on(event: "error", listener: (err: Error) => void): void; - once(event: "drain" | "finish" | "close", listener: () => void): void; + on(event: string, listener: (...args: any[]) => void): void; + once(event: "drain" | "finish", listener: () => void): void; once(event: "error", listener: (err: Error) => void): void; + once(event: string, listener: (...args: any[]) => void): void; } export interface ChildProcess extends Disposable { diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6463-node-readable-stream-producers.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6463-node-readable-stream-producers.ts.json new file mode 100644 index 000000000..e413951ae --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6463-node-readable-stream-producers.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6463-node-readable-stream-producers.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6464-node-writable-stream-producers.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6464-node-writable-stream-producers.ts.json new file mode 100644 index 000000000..fd805a2af --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6464-node-writable-stream-producers.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6464-node-writable-stream-producers.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6465-node-stream-js-boundary/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6465-node-stream-js-boundary/main.ts.json new file mode 100644 index 000000000..95278eab8 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6465-node-stream-js-boundary/main.ts.json @@ -0,0 +1,7 @@ +{ + "order": [ + "/tests/corpus/6465-node-stream-js-boundary/consume.js", + "/tests/corpus/6465-node-stream-js-boundary/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6466-node-stdio-stream-surface.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6466-node-stdio-stream-surface.ts.json new file mode 100644 index 000000000..8ebcddd22 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6466-node-stdio-stream-surface.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6466-node-stdio-stream-surface.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/child-stdin-fences.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/child-stdin-fences.ts.json deleted file mode 100644 index cfa602a2e..000000000 --- a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/child-stdin-fences.ts.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "order": [ - "/tests/fixtures/node-types/child-stdin-fences.ts" - ], - "diags": [] -} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/child-stdin-streams.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/child-stdin-streams.ts.json new file mode 100644 index 000000000..2bb443d2a --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/child-stdin-streams.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/node-types/child-stdin-streams.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/node-stream-interfaces.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/node-stream-interfaces.ts.json new file mode 100644 index 000000000..cfb5c1a10 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/node-stream-interfaces.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/node-types/node-stream-interfaces.ts" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_stream.c b/packages/runtime/src/scr_stream.c index b7d99984a..3b2f7c8f6 100644 --- a/packages/runtime/src/scr_stream.c +++ b/packages/runtime/src/scr_stream.c @@ -6627,10 +6627,20 @@ ScrStream *scr_crypto_hash_stream_piped(ScrCryptoHash *hash) { * and reference only global symbols and no new constant data, so programs * that never convert link the same bytes as before. */ +/* destroy() on a bridged child stdin closes the pipe, as Node's does. */ +static void scr_stream_child_stdin_destroy(ScrClosure *cb, ScrStream *s, ScrError *err) { + ScrChildWriter *writer = scr_box_get_ref(cb->caps[0]); + scr_child_writer_destroy(writer); + scr_child_writer_release(writer); + scr_stream_destroy_done(s, err ? scr_error_retain(err) : NULL); +} + /* A child stdio read (the child.stdioStream libCall): the bridged stream * the checked-dynamic ChildProcess view answers for the same key ("stdin", * "stdout" or "stderr", passed by the compiled program), so both views - * share one stream per slot. +1, or NULL for an unpiped slot or a throw. */ + * share one stream per slot. The stdin stream (the only five-byte key) + * gains the destroy hook that closes the pipe. +1, or NULL for an unpiped + * slot or a throw. */ ScrStream *scr_stream_child_stdio(void *child, const char *key, size_t key_len) { ScrDyn *handle = scr_dyn_new_handle(child, SCR_DYNH_CHILD); ScrDyn *box = scr_dyn_handle_ops_of(handle)->get(child, key, key_len); @@ -6638,6 +6648,16 @@ ScrStream *scr_stream_child_stdio(void *child, const char *key, size_t key_len) if (box == NULL) return NULL; ScrStream *stream = box->kind == SCR_DYN_TYPED_REF ? scr_dyn_typed_ref_unbox(box) : NULL; scr_dyn_release(box); + if (stream != NULL && key_len == 5 && stream->st->destroy_cb == NULL) { + ScrChildWriter *writer = scr_child_stdin(child); + if (writer != NULL) { + ScrClosure *destroy = scr_closure_new((void *)scr_stream_child_stdin_destroy, 1); + destroy->caps[0] = scr_box_new_obj(scr_child_writer_retain_v, scr_child_writer_release_v, NULL); + scr_box_set_ref(destroy->caps[0], writer); + stream->st->destroy_cb = destroy; + stream->st->destroy_inv = scr_stream_child_stdin_destroy; + } + } return stream; } diff --git a/tests/fixtures/node-types/child-stdin-fences.ts b/tests/fixtures/node-types/child-stdin-fences.ts deleted file mode 100644 index 0859d8513..000000000 --- a/tests/fixtures/node-types/child-stdin-fences.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { spawn } from "node:child_process"; - -const child = spawn("node", []); -child.stdin.write("x", "utf8"); -child.stdin.end("x"); -child.stdin.destroy(new Error("stop")); diff --git a/tests/fixtures/node-types/child-stdin-streams.ts b/tests/fixtures/node-types/child-stdin-streams.ts new file mode 100644 index 000000000..c433d7bfb --- /dev/null +++ b/tests/fixtures/node-types/child-stdin-streams.ts @@ -0,0 +1,40 @@ +// child.stdin forms beyond write(string): a write with an encoding, end +// with a final chunk, and destroy with an error observed by an 'error' +// listener. They compile through the general stream representation, where +// child.stdin is the child's bridged Writable. +import { spawn } from "node:child_process"; + +function feed(): Promise { + return new Promise((resolve) => { + const child = spawn("cat", [], { stdio: ["pipe", "pipe", "inherit"] }); + let out = ""; + child.stdout.on("data", (chunk: Buffer) => { + out += chunk.toString(); + }); + child.on("close", (code) => { + console.log("fed", JSON.stringify(out), code); + resolve(); + }); + child.stdin.write("first ", "utf8"); + child.stdin.end("second\n"); + }); +} + +function abort(): Promise { + return new Promise((resolve) => { + const child = spawn("cat", [], { stdio: ["pipe", "ignore", "inherit"] }); + child.stdin.on("error", (err) => console.log("stdin error", err.message)); + child.on("close", (code) => { + console.log("aborted", code); + resolve(); + }); + child.stdin.destroy(new Error("stop")); + }); +} + +async function main(): Promise { + await feed(); + await abort(); +} + +void main(); diff --git a/tests/harness/project-config.test.ts b/tests/harness/project-config.test.ts index 34063aa9c..3635c88ed 100644 --- a/tests/harness/project-config.test.ts +++ b/tests/harness/project-config.test.ts @@ -551,26 +551,22 @@ test("node-types: promisified dns lookups settle with the lookup objects", async expect(comparableStderr(native.stderr)).toBe(node.stderr); }); -test("node-types: unsupported child stdin forms remain named fences", async () => { - const dir = outDirFor("node-child-stdin-fences"); - const entry = join(nodeTypesDir, "child-stdin-fences.ts"); +test("node-types: child stdin forms beyond write(string) compile through the general stream representation", async () => { + const dir = outDirFor("node-child-stdin-streams"); + const entry = join(nodeTypesDir, "child-stdin-streams.ts"); const result = await compile(entry, { - outPath: join(dir, "child-stdin-fences"), + outPath: join(dir, "child-stdin-streams"), outDir: dir, sanitize, }); - expect(result.ok).toBe(false); - if (result.ok) return; - expect(result.diagnostics.map((diagnostic) => diagnostic.code)).toEqual([ - "SC2020", - "SC2020", - "SC2020", - ]); - expect(result.diagnostics.map((diagnostic) => diagnostic.message)).toEqual([ - expect.stringContaining("child stdin write with 2 arguments"), - expect.stringContaining("Writable.end"), - expect.stringContaining("Writable.destroy"), + expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true); + if (!result.ok) return; + const [native, node] = await Promise.all([ + execFileAsync(result.binaryPath), + execFileAsync(process.execPath, ["--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", entry]), ]); + expect(native.stdout).toBe(node.stdout); + expect(comparableStderr(native.stderr)).toBe(node.stderr); }); test("node-types: callback exec uses the typed ExecException overloads", async () => { From cb26c95f618ad0b55708345b47b9afd64bba0ebc Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Mon, 19 Oct 2026 22:09:41 -0700 Subject: [PATCH 072/122] Read http.request options at run time, speak HTTP over Unix sockets, and fill the boot path's net and fetch gaps http.request, https.request and get now accept options the compiler cannot see as an object literal: a RequestOptions variable, a literal that spreads one, or a literal that sets socketPath. The runtime reads the record in the ClientRequest constructor's order with Node's errors; documented options it does not implement throw the runtime fence when they carry a value. https adds rejectUnauthorized, ca and servername. socketPath dials a Unix domain socket in the net unit; the agent keys its sockets by the path as Node's getName does, and the Host header defaults to localhost. server.listen(path[, cb]) binds a Unix socket and removes the socket file when the server closes; a numeric string is still a port. Errors name the path ("connect ENOENT /path", "listen EADDRINUSE: address already in use /path"). Windows reports ENOTSUP. A TypeScript object literal with no contextual type that spreads a checked-dynamic value maps to the checked dynamic, so `const options = { ...location, path, timeout }` compiles. A socket's 'lookup' event reaches its listeners with (err, address, family, host) for each address a host name resolves to, or once with the lookup error, before 'connect'. Only programs that listen for 'lookup' install the lookup records; elsewhere the hook sites are a NULL check. server.close((err) => ...) runs the callback with undefined at 'close', or with ERR_SERVER_NOT_RUNNING when the server was not listening; a server that never listened now emits 'close'. IncomingMessage 'data' listeners may take string chunks after setEncoding, and res.end() accepts a string-or-undefined chunk. fetch's BodyInit and HeadersInit unions, as RequestInit-derived option types spell them under @types/node, map to the checked dynamic, and a URLSearchParams body is sent form-encoded, as by Node. Co-Authored-By: Claude Opus 5.5 --- docs/content/docs/limitations.mdx | 4 + .../limitations/unix-domain-sockets.mdx | 2 + .../ambient/scriptc-node-fallback.d.ts | 29 +- packages/compiler/src/backend/llvm/emitter.ts | 3 + .../compiler/src/backend/llvm/lib-network.ts | 47 +++ .../compiler/src/backend/llvm/lib-shared.ts | 8 + .../src/frontend/lowering/lower-server.ts | 170 +++++++- packages/compiler/src/frontend/type-mapper.ts | 140 +++++++ packages/compiler/src/ir/builtin-effects.ts | 6 + .../compiler/src/ir/builtin-signatures.ts | 26 ++ packages/compiler/src/ir/runtime-features.ts | 9 + packages/compiler/src/ir/validate.ts | 16 +- .../corpus/6440-http-socket-path.ts.json | 6 + .../6441-http-request-options-record.ts.json | 6 + .../6442-https-request-options-record.ts.json | 6 + .../6443-net-lookup-http-close-text.ts.json | 6 + .../node-types/fetch-init-members.ts.json | 6 + .../node-types/http-socket-path.ts.json | 6 + packages/runtime/src/scr_fetch.c | 21 +- packages/runtime/src/scr_http.c | 394 +++++++++++++++++- packages/runtime/src/scr_http.h | 34 ++ packages/runtime/src/scr_net.c | 352 +++++++++++++++- packages/runtime/src/scr_net.h | 8 + packages/runtime/src/scr_tls.c | 68 +++ tests/corpus/6440-http-socket-path.ts | 109 +++++ .../6441-http-request-options-record.ts | 68 +++ .../6442-https-request-options-record.ts | 61 +++ .../corpus/6443-net-lookup-http-close-text.ts | 73 ++++ .../fixtures/node-types/fetch-init-members.ts | 68 +++ tests/fixtures/node-types/http-socket-path.ts | 68 +++ tests/harness/project-config.test.ts | 38 ++ 31 files changed, 1840 insertions(+), 18 deletions(-) create mode 100644 docs/content/limitations/unix-domain-sockets.mdx create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6440-http-socket-path.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6441-http-request-options-record.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6442-https-request-options-record.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6443-net-lookup-http-close-text.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/fetch-init-members.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/http-socket-path.ts.json create mode 100644 tests/corpus/6440-http-socket-path.ts create mode 100644 tests/corpus/6441-http-request-options-record.ts create mode 100644 tests/corpus/6442-https-request-options-record.ts create mode 100644 tests/corpus/6443-net-lookup-http-close-text.ts create mode 100644 tests/fixtures/node-types/fetch-init-members.ts create mode 100644 tests/fixtures/node-types/http-socket-path.ts diff --git a/docs/content/docs/limitations.mdx b/docs/content/docs/limitations.mdx index c64c5cebd..353fa42d1 100644 --- a/docs/content/docs/limitations.mdx +++ b/docs/content/docs/limitations.mdx @@ -224,6 +224,10 @@ title: "Limitations" ../limitations/udp-socket-options.mdx +### Unix domain sockets and request options + +../limitations/unix-domain-sockets.mdx + ### Process arguments and errors ../limitations/process-arguments-and-errors.mdx diff --git a/docs/content/limitations/unix-domain-sockets.mdx b/docs/content/limitations/unix-domain-sockets.mdx new file mode 100644 index 000000000..70a8a47e4 --- /dev/null +++ b/docs/content/limitations/unix-domain-sockets.mdx @@ -0,0 +1,2 @@ +`server.listen(path)` and the `socketPath` option of `http.request()` and `https.request()` use Unix domain sockets on Linux and macOS. On Windows, where Node maps these paths to named pipes, they fail with `ENOTSUP`. `server.address()` on a server listening on a Unix socket returns `{ address: , family: "IPv4", port: 0 }`, where Node returns the socket path as a string: compiled code holds `address()` as the `{ address, family, port }` record. `listen(path, backlog, callback)` is refused at compile time; pass `listen(path, callback)`. +When the options of `http.request()` come from a variable, or from an object literal that spreads one, they are read at run time with Node's validation errors. The `auth`, `createConnection`, `family`, `hints`, `insecureHTTPParser`, `joinDuplicateHeaders`, `localAddress`, `localPort`, `lookup` and `uniqueHeaders` options then throw a runtime error when they hold a value, and so do `setHost: false` and `setDefaultHeaders: false`. diff --git a/packages/compiler/ambient/scriptc-node-fallback.d.ts b/packages/compiler/ambient/scriptc-node-fallback.d.ts index 1b0d1fd36..c0d8476a6 100644 --- a/packages/compiler/ambient/scriptc-node-fallback.d.ts +++ b/packages/compiler/ambient/scriptc-node-fallback.d.ts @@ -4681,6 +4681,11 @@ declare module "net" { on(event: "close", listener: (hadError: boolean) => void): void; on(event: "error", listener: (err: Error) => void): void; on(event: "session", listener: (session: Buffer) => void): void; + /* The host name's resolution, before the connect. */ + on( + event: "lookup", + listener: (err: Error | null, address: string, family: number, host: string) => void, + ): void; /* addListener IS on (Node aliases them) — the suite spells both. */ addListener(event: "data", listener: (chunk: Buffer) => void): void; /* After setEncoding the chunk is a string. */ @@ -4692,6 +4697,11 @@ declare module "net" { addListener(event: "close", listener: (hadError: boolean) => void): void; addListener(event: "error", listener: (err: Error) => void): void; addListener(event: "session", listener: (session: Buffer) => void): void; + /* The host name's resolution, before the connect. */ + addListener( + event: "lookup", + listener: (err: Error | null, address: string, family: number, host: string) => void, + ): void; once(event: "data", listener: (chunk: Buffer) => void): void; /* After setEncoding the chunk is a string. */ once(event: "data", listener: (chunk: string) => void): void; @@ -4702,6 +4712,11 @@ declare module "net" { once(event: "close", listener: (hadError: boolean) => void): void; once(event: "error", listener: (err: Error) => void): void; once(event: "session", listener: (session: Buffer) => void): void; + /* The host name's resolution, before the connect. */ + once( + event: "lookup", + listener: (err: Error | null, address: string, family: number, host: string) => void, + ): void; } export interface Server { /* Whether the server keeps the event loop alive (ref'd by default). */ @@ -4720,6 +4735,8 @@ declare module "net" { /* The positional bind address (the options form's host in argument * spelling). */ listen(port: number, host: string, callback?: () => void): Server; + /* A Unix-socket server; the socket file is removed when it closes. */ + listen(path: string, callback?: () => void): Server; /* The explicit-interface bind: host is an IP literal (absent = the * host-less dual-stack any); ipv6Only sets IPV6_V6ONLY and reusePort * requests kernel connection distribution where Node supports it. */ @@ -5043,10 +5060,11 @@ declare module "http" { * callback receives the response as an IncomingMessage (statusCode, * headers, body via on("data"/"end")). */ export interface RequestOptions { - hostname?: string; - host?: string; - port?: number; - path?: string; + hostname?: string | null; + host?: string | null; + /* A string port (a URL's .port) is accepted, as by Node. */ + port?: number | string | null; + path?: string | null; method?: string; timeout?: number; headers?: OutgoingHttpHeaders; @@ -5060,6 +5078,8 @@ declare module "http" { agent?: Agent | boolean | null; /* Aborting destroys the request with an AbortError. */ signal?: AbortSignal; + /* HTTP over the Unix socket at this path instead of host:port. */ + socketPath?: string; /* The options-record stance: Node ignores an options record's * unknown keys, so every key typechecks here — the option WALK in * the lowering decides per key (lower, fence by name, or drop @@ -5333,6 +5353,7 @@ declare module "https" { agent?: Agent | boolean | null; /* Aborting destroys the request with an AbortError. */ signal?: AbortSignal; + socketPath?: string; /* The options-record stance (see http.RequestOptions). */ [option: string]: unknown; } diff --git a/packages/compiler/src/backend/llvm/emitter.ts b/packages/compiler/src/backend/llvm/emitter.ts index e86b447d9..617d528fe 100644 --- a/packages/compiler/src/backend/llvm/emitter.ts +++ b/packages/compiler/src/backend/llvm/emitter.ts @@ -2154,6 +2154,7 @@ export class LlEmitter { // programs additionally stamp the httpReq/httpRes ops — the C main's // install lines, gated on the same predicates native/executable.ts links by. const usesNet = runtimeFeatures.net; + const usesNetLookup = runtimeFeatures.netLookup; const usesDgram = runtimeFeatures.dgram; const usesHttp2 = runtimeFeatures.http2; const usesHttp = runtimeFeatures.http; @@ -2207,6 +2208,7 @@ export class LlEmitter { this.declare(`declare void @scr_net_install()`); this.declare(`declare void @scr_net_dyn_install()`); } + if (usesNetLookup) this.declare(`declare void @scr_net_lookup_install()`); if (usesDgram) this.declare(`declare void @scr_dgram_install()`); if (usesHttp2) this.declare(`declare void @scr_http2_dyn_install()`); if (usesHttp) this.declare(`declare void @scr_http_dyn_install()`); @@ -2811,6 +2813,7 @@ export class LlEmitter { ...(snapshotsTlsCa ? [` call void @scr_tls_ca_install()`] : []), ...(usesFetch ? [` call void @scr_fetch_install()`] : []), ...(usesNet ? [` call void @scr_net_install()`, ` call void @scr_net_dyn_install()`] : []), + ...(usesNetLookup ? [` call void @scr_net_lookup_install()`] : []), ...(usesHttp ? [` call void @scr_http_dyn_install()`] : []), ...(usesDgram ? [` call void @scr_dgram_install()`] : []), ...(usesHttp2 ? [` call void @scr_http2_dyn_install()`] : []), diff --git a/packages/compiler/src/backend/llvm/lib-network.ts b/packages/compiler/src/backend/llvm/lib-network.ts index ea37fc2cf..d49bbde14 100644 --- a/packages/compiler/src/backend/llvm/lib-network.ts +++ b/packages/compiler/src/backend/llvm/lib-network.ts @@ -60,7 +60,54 @@ export function emitNetworkHttpLibCall(host: LlvmEmitterContext, e: LibCallExpr) ...handler, ]); } + if ( + e.fn === "http.requestDyn" || + e.fn === "http.requestDynCb" || + e.fn === "https.requestDyn" || + e.fn === "https.requestDynCb" + ) { + // [options, autoEnd(, cb)]: the runtime walks the options record and + // attaches its signal through events.addAbortListener (the event-target + // unit, linked for these calls by runtime-features). + const args = e.args.map((arg) => host.emitExpr(arg)); + host.usesTimers = true; + const cb = args[2]; + const handler = + cb === undefined + ? [ptr(), ptr()] + : callbackAdapter( + host, + cb, + callbackType(cb).params.length === 0 + ? "scr_http_resp_thunk0" + : "scr_http_resp_thunk_res", + ["ptr", "ptr"], + ); + host.declare(`declare ptr @scr_events_add_abort_listener(ptr, ptr)`); + return finishRuntimeCall( + host, + e, + e.fn.startsWith("https.") ? "scr_https_request_dyn" : "scr_http_request_dyn", + [ + abiValue(host, args[0]!), + abiValue(host, args[1]!), + ...handler, + ptr("@scr_events_add_abort_listener"), + ], + ); + } const B = host.B; + if (e.fn === "net.listenPath" || e.fn === "net.listenPathCb") { + const args = e.args.map((a) => host.emitExpr(a)); + let cb = "null"; + if (e.fn === "net.listenPathCb") { + host.toHeap(args[2]!); + cb = args[2]!.name; + } + host.declare(`declare void @scr_net_listen_path(ptr, ptr, ptr)`); + B.line(`call void @scr_net_listen_path(ptr ${args[0]!.name}, ptr ${args[1]!.name}, ptr ${cb})`); + return { name: "", type: e.type }; + } if (e.fn === "net.connectOptsChk") { return emitAlwaysThrowLibCall(host, e, "scr_net_connect_opts_chk"); } diff --git a/packages/compiler/src/backend/llvm/lib-shared.ts b/packages/compiler/src/backend/llvm/lib-shared.ts index be34ad886..bb61bfba2 100644 --- a/packages/compiler/src/backend/llvm/lib-shared.ts +++ b/packages/compiler/src/backend/llvm/lib-shared.ts @@ -1139,6 +1139,8 @@ export const LIB_FN_SYMS: Record = { "net.sockReadyState": "scr_net_sock_ready_state", "net.sockSetKeepAlive": "scr_net_sock_set_keepalive_ret", "net.sockOnCloseDyn": "scr_net_sock_on_close_dyn", + "net.sockOnLookup": "scr_net_sock_on_lookup", + "net.serverCloseDyn": "scr_net_server_close_dyn", "net.sockOnWriteFlushDyn": "scr_net_sock_on_write_flush_dyn", "net.sockOnFinishDyn": "scr_net_sock_on_finish_dyn", "net.isIP": "scr_net_is_ip", @@ -1614,6 +1616,8 @@ export const USES_TIMERS_LIB_FNS = new Set([ "sc.arrayBuffer", "net.listen", "net.listenCb", + "net.listenPath", + "net.listenPathCb", "net.listenOpts", "net.listenOptsCb", "net.listenOptsReusePort", @@ -1632,6 +1636,10 @@ export const USES_TIMERS_LIB_FNS = new Set([ "http.requestCb", "http.requestUrl", "http.requestUrlCb", + "http.requestDyn", + "http.requestDynCb", + "https.requestDyn", + "https.requestDynCb", "https.request", "https.requestCb", "https.requestUrl", diff --git a/packages/compiler/src/frontend/lowering/lower-server.ts b/packages/compiler/src/frontend/lowering/lower-server.ts index 0e1438fc9..1a43485eb 100644 --- a/packages/compiler/src/frontend/lowering/lower-server.ts +++ b/packages/compiler/src/frontend/lowering/lower-server.ts @@ -53,7 +53,7 @@ import { sideEffectFreeOptionValue, TLS_SERVER_DOCUMENTED_OPTIONS, } from "./surfaces.js"; -import { conditionalSpreadOf } from "./expressions/object-literals.js"; +import { conditionalSpreadOf, lowerDynObjectLiteral } from "./expressions/object-literals.js"; import { resultIsDiscarded } from "./call-position.js"; import { lowerCallbackArg as lowerCallbackArgShared } from "./callback-arg.js"; import { pairsSnapshotHelper } from "./pairs-snapshot.js"; @@ -1627,6 +1627,29 @@ function lowerNetServerMethodCall( : [receiver, port, host, v6only, reusePort, cbV], ); } + // listen(path[, cb]): a string first argument names a Unix socket + // (the runtime takes a numeric string as a port, Node's isPipeName). + if (lowerer.mapTypeOf(lowerer.typeOf(args[0]!))?.kind === "string") { + if (args.length > 2) { + lowerer.noLowering( + `listen(path) with ${args.length} arguments`, + call, + "the supported form is listen(path[, callback])", + ); + } + const path = lowerer.lowerExprExpecting(args[0]!, STRING); + if (args.length === 1) return listenResult("net.listenPath", [receiver, path]); + const { cb } = lowerCallbackArg( + lowerer, + args[1]!, + "listen callbacks", + 0, + () => false, + "use ()", + [], + ); + return listenResult("net.listenPathCb", [receiver, path, cb]); + } const port = lowerer.lowerExprExpecting(args[0]!, F64); // The optional middle host — listen(port, '127.0.0.1'[, cb]): a // string-typed second argument is the bind address (the net.connect @@ -1682,6 +1705,21 @@ function lowerNetServerMethodCall( if (args.length === 0) { return { kind: "libCall", fn: "net.serverClose", args: [receiver], type: VOID, loc }; } + if ( + lowerer.checker + .getCallSignatures(lowerer.typeOf(args[0]!)) + .some((sig) => sig.getParameters().length > 0) + ) { + // close((err) => ...): undefined once the server closes, or Node's + // ERR_SERVER_NOT_RUNNING when it was not listening. + return { + kind: "libCall", + fn: "net.serverCloseDyn", + args: [receiver, lowerSocketDynListener(lowerer, args[0]!, "close callbacks")], + type: VOID, + loc, + }; + } const { cb } = lowerCallbackArg( lowerer, args[0]!, @@ -2722,6 +2760,17 @@ function lowerNetSocketMethodCall( : "net.sockOnConnect"; return { kind: "libCall", fn, args: [receiver, cb, once], type: VOID, loc }; } + if (event === "lookup") { + // (err, address, family, host) after the host name resolves, before + // the connect: once per resolved address, or once with the error. + return { + kind: "libCall", + fn: "net.sockOnLookup", + args: [receiver, lowerSocketDynListener(lowerer, args[1]!, "lookup listeners"), once], + type: VOID, + loc, + }; + } if (event === "finish") { // Fires once when the FIN goes out — once either way (the event // happens at most once per socket). @@ -2781,7 +2830,7 @@ function lowerNetSocketMethodCall( lowerer.noLowering( `socket.${name}(${event === null ? "non-literal event" : `"${event}"`}, ...)`, args[0]!, - '"data", "end", "close", "error", "connect", "ready", "timeout", "readable", "secureConnect", and "session" are the supported socket events (as literals)', + '"data", "end", "close", "error", "connect", "lookup", "ready", "timeout", "readable", "secureConnect", and "session" are the supported socket events (as literals)', ); } lowerer.noLowering( @@ -5813,6 +5862,44 @@ function lowerHttp2ModuleCall( ); } +/** The checked-dynamic options value of a request whose options the + * runtime reads: a non-literal options value (a RequestOptions variable — + * checked-dynamic storage — or a record that converts to one), or a + * literal the static walk cannot lower because it spreads another options + * value or sets socketPath. Null keeps the static literal walk (and its + * fences), which every other literal takes unchanged. */ +function runtimeRequestOptions( + lowerer: Lowerer, + optsNode: ts.Expression, + secureish: boolean, +): IrExpr | null { + if (ts.isObjectLiteralExpression(optsNode)) { + const needsRuntime = optsNode.properties.nodes.some((prop) => { + if (ts.isSpreadAssignment(prop)) { + // the static walk's one spread: `...(c ? { rejectUnauthorized } : {})` on https + const cs = conditionalSpreadOf(prop.expression); + return !( + secureish && + cs !== null && + cs !== "unsupported" && + cs.props.length === 1 && + cs.props[0]!.name.text === "rejectUnauthorized" + ); + } + return ( + (ts.isPropertyAssignment(prop) || ts.isShorthandPropertyAssignment(prop)) && + (ts.isIdentifier(prop.name) || ts.isStringLiteral(prop.name)) && + prop.name.text === "socketPath" + ); + }); + if (!needsRuntime) return null; + return lowerDynObjectLiteral(lowerer, optsNode); + } + const t = lowerer.mapTypeOf(lowerer.typeOf(optsNode)); + if (!t || !(t.kind === "dyn" || (t.kind === "record" && lowerer.dynConvertible(t)))) return null; + return lowerer.coerceToExpected(lowerer.lowerExpr(optsNode), DYN); +} + /** http.request(options[, cb]) / http.get(options[, cb]) — the client. * The options argument must be an OBJECT LITERAL; the lowered keys are * hostname/host, port, path, method, timeout, and headers (a nested @@ -5855,6 +5942,40 @@ function lowerHttpClientCall( ); } const optsNode = args[0]!; + const runtimeOptions = + binding === null ? runtimeRequestOptions(lowerer, optsNode, secureish) : null; + if (runtimeOptions !== null) { + // The options record is read at run time (the ClientRequest + // constructor's walk in the runtime): a RequestOptions variable, or a + // literal that spreads one or sets socketPath. + const autoEnd = boolLit(member === "get", loc); + const secureDyn = secure === true; + if (args.length === 1) { + return { + kind: "libCall", + fn: secureDyn ? "https.requestDyn" : "http.requestDyn", + args: [runtimeOptions, autoEnd], + type: HTTPCLIENTREQ_T, + loc, + }; + } + const { cb } = lowerCallbackArg( + lowerer, + args[1]!, + "response callbacks", + 1, + (p) => p.kind === "httpReq", + "use (res) or ()", + [HTTPREQ_T], + ); + return { + kind: "libCall", + fn: secureDyn ? "https.requestDynCb" : "http.requestDynCb", + args: [runtimeOptions, autoEnd, cb], + type: HTTPCLIENTREQ_T, + loc, + }; + } if (!ts.isObjectLiteralExpression(optsNode)) { const t = lowerer.mapTypeOf(lowerer.typeOf(optsNode)); if ((t?.kind === "string" || t?.kind === "url") && binding === null) { @@ -7135,6 +7256,27 @@ function lowerHttpReqMethodCall( const event = evT.isStringLiteralType() ? evT.value : null; const receiver = coerceToHandle(lowerer, access.expression, HTTPREQ_T); if (event === "data") { + const sigs = lowerer.checker.getCallSignatures(lowerer.typeOf(args[1]!)); + const firstParam = sigs.length === 1 ? sigs[0]!.getParameters()[0] : undefined; + const param = + firstParam !== undefined + ? lowerer.mapTypeOf(lowerer.checker.getTypeOfSymbol(firstParam)) + : null; + if (param !== null && param.kind === "string") { + // A chunk typed string (the setEncoding('utf8') shape), as on a + // socket: the listener takes the chunk through a checked + // boundary, so a string arrives after setEncoding and a Buffer + // without it fails the check instead of being decoded. + const boxed = lowerSocketDynListener(lowerer, args[1]!, "data listeners"); + const cb: IrExpr = { kind: "dynCheck", value: boxed, type: funcOf([DYN], VOID), loc }; + return { + kind: "libCall", + fn: "http.reqOnData", + args: [receiver, cb, once], + type: VOID, + loc, + }; + } const { cb } = lowerCallbackArg( lowerer, args[1]!, @@ -7697,6 +7839,30 @@ function lowerHttpResMethodCall( if (cbArg !== null) return endWithCb(fn, [receiver, data], cbArg); return { kind: "libCall", fn, args: [receiver, data], type: VOID, loc }; } + // end(payload ? text : undefined): an absent chunk ends without data, + // as end() does. The checked-dynamic end takes each arm at run time. + if (name === "end" && data.type.kind === "union" && lowerer.dynConvertible(data.type)) { + const arms = lowerer.unions.get(data.type.unionId)?.arms ?? []; + if ( + arms.every( + (arm) => + arm.kind === "string" || + (arm.kind === "bytes" && arm.elem === "u8") || + arm.kind === "undefinedT" || + arm.kind === "nullT", + ) + ) { + const dynData: IrExpr = { kind: "dynFrom", value: data, type: DYN, loc: locOf(dataNode) }; + if (cbArg !== null) return endWithCb("http.resEndDyn", [receiver, dynData], cbArg); + return { + kind: "libCall", + fn: "http.resEndDyn", + args: [receiver, dynData], + type: VOID, + loc, + }; + } + } lowerer.noLowering( `${name} of '${lowerer.fmt(data.type)}' data`, dataNode ?? call, diff --git a/packages/compiler/src/frontend/type-mapper.ts b/packages/compiler/src/frontend/type-mapper.ts index 1d7ef7364..652a0d710 100644 --- a/packages/compiler/src/frontend/type-mapper.ts +++ b/packages/compiler/src/frontend/type-mapper.ts @@ -1307,6 +1307,116 @@ function isWorkerHandleSymbol( /** The `{ message: any }` object type (or its union with undefined) that * worker_threads declares as receiveMessageOnPort's result. */ +/** The binary and stream members of fetch's BodyInit (undici-types): + * NodeJS.ArrayBufferView expands to the typed arrays and DataView. */ +const FETCH_BODY_MEMBERS: ReadonlySet = new Set([ + "ArrayBuffer", + "SharedArrayBuffer", + "AsyncIterable", + "Iterable", + "Blob", + "FormData", + "URLSearchParams", + "ReadableStream", + "DataView", + "Buffer", + "Int8Array", + "Uint8Array", + "Uint8ClampedArray", + "Int16Array", + "Uint16Array", + "Int32Array", + "Uint32Array", + "Float16Array", + "Float32Array", + "Float64Array", + "BigInt64Array", + "BigUint64Array", +]); + +/** fetch's request body type, BodyInit, however TypeScript spells it + * (`RequestInit["body"]`, `NonNullable<...>`, an optional member): a + * union of strings, the stdlib binary and stream types and nullish + * values that includes both FormData and URLSearchParams. Native fetch + * reads such a body at run time, so it rides checked-dynamic storage. */ +function isFetchBodyUnion(type: ts.Type, ctx: Pick): boolean { + if (!type.isUnionType()) return false; + let formData = false; + let searchParams = false; + for (const member of ts.constituentTypes(type)) { + if (member.flags & (ts.TypeFlags.StringLike | ts.TypeFlags.Null | ts.TypeFlags.Undefined)) + continue; + const symbol = member.getSymbol(); + if ( + symbol === undefined || + !FETCH_BODY_MEMBERS.has(symbol.name) || + !symbol.declarations.some((d) => ctx.isStdlibFile(d.getSourceFile())) + ) + return false; + if (symbol.name === "FormData") formData = true; + if (symbol.name === "URLSearchParams") searchParams = true; + } + return formData && searchParams; +} + +/** A mapped object type (Omit, Pick, Partial) whose members all come from + * one stdlib interface that maps to the checked dynamic. */ +function isDynInterfaceSubset(type: ts.Type, ctx: TypeMapperCtx): boolean { + if (!(type.flags & ts.TypeFlags.Object) || type.isClassOrInterface()) return false; + const alias = type.getAliasSymbol(); + if (alias === undefined) return false; + const props = ctx.checker.getPropertiesOfType(type); + if (props.length === 0) return false; + let owner: ts.InterfaceDeclaration | undefined; + for (const prop of props) { + const decl = prop.declarations[0]; + const parent = decl?.parent; + if ( + parent === undefined || + !ts.isInterfaceDeclaration(parent) || + !ctx.isStdlibFile(parent.getSourceFile()) || + (owner !== undefined && owner !== parent) + ) + return false; + owner = parent; + } + return ( + owner !== undefined && mapType(ctx.checker.getTypeAtLocation(owner.name), ctx)?.kind === "dyn" + ); +} + +/** fetch's HeadersInit (undici-types): a union of the stdlib Headers + * class with header-pair arrays and/or a header record, plus nullish + * values. Native fetch reads it at run time like the init around it. */ +function isFetchHeadersUnion(type: ts.Type, ctx: Pick): boolean { + if (!type.isUnionType()) return false; + let headers = false; + let other = false; + for (const member of ts.constituentTypes(type)) { + if (member.flags & (ts.TypeFlags.Null | ts.TypeFlags.Undefined)) continue; + const symbol = member.getSymbol(); + if ( + symbol?.name === "Headers" && + symbol.declarations.some((d) => ctx.isStdlibFile(d.getSourceFile())) + ) { + headers = true; + continue; + } + const alias = member.getAliasSymbol()?.name; + if ( + symbol?.name === "Array" || + symbol?.name === "ReadonlyArray" || + alias === "Record" || + alias === "HeaderRecord" + ) { + other = true; + continue; + } + return false; + } + return headers && other; +} + function isReceiveMessageResult( type: ts.Type, ctx: Pick, @@ -1474,6 +1584,25 @@ function classExprNeedsContext(decl: ts.ClassLikeDeclaration): boolean { return false; } +/** True for the type of a TypeScript object literal with no contextual + * type (`const options = { ...location, path }`) that spreads a value + * mapping to the checked dynamic (a stdlib options type such as + * http.RequestOptions). A literal with a contextual type is built in that + * type's representation instead, and JavaScript literals keep their own + * rules. */ +function spreadsCheckedDynamic(symbol: ts.Symbol | undefined, ctx: TypeMapperCtx): boolean { + if (symbol === undefined || (symbol.flags & ts.SymbolFlags.ObjectLiteral) === 0) return false; + const decl = symbol.declarations[0]; + if (decl === undefined || !ts.isObjectLiteralExpression(decl)) return false; + if (isJsSourceFile(decl.getSourceFile())) return false; + if (ctx.checker.getContextualType(decl)) return false; + return decl.properties.nodes.some( + (prop) => + ts.isSpreadAssignment(prop) && + mapType(ctx.checker.getTypeAtLocation(prop.expression), ctx)?.kind === "dyn", + ); +} + function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { const { checker, unions, classNamer, resolveTypeParam } = ctx; // TypeScript's intrinsic globalThis symbol has no declaration. A stored @@ -1494,9 +1623,20 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { const moduleId = ctx.moduleNamespaceId?.(type) ?? null; if (moduleId !== null) return { kind: "moduleNs", moduleId }; } + // A TypeScript object literal that spreads a checked-dynamic value + // (`{ ...requestOptions, path, timeout }`): its keys are the source's + // runtime keys, which no record shape holds, so the literal rides + // checked-dynamic storage like its source. + if (spreadsCheckedDynamic(intrinsic, ctx)) return DYN; // worker_threads.receiveMessageOnPort's `{ message: any } | undefined`: // the runtime's { message } object, a checked-dynamic value. if (isReceiveMessageResult(type, ctx)) return DYN; + if (isFetchBodyUnion(type, ctx) || isFetchHeadersUnion(type, ctx)) return DYN; + // Omit/Pick/Partial of a stdlib interface that rides checked-dynamic + // storage (`Omit`): every member comes + // from that interface, so the value is a subset of the same runtime-read + // options object and rides the same storage. + if (isDynInterfaceSubset(type, ctx)) return DYN; // @types/node spells child_process.ExecFileException as an intersection // type alias over two Error-derived metadata interfaces. The value is // still one ordinary Error; recognize the owned alias before generic diff --git a/packages/compiler/src/ir/builtin-effects.ts b/packages/compiler/src/ir/builtin-effects.ts index 8b94646f2..51a5ed6a2 100644 --- a/packages/compiler/src/ir/builtin-effects.ts +++ b/packages/compiler/src/ir/builtin-effects.ts @@ -263,6 +263,12 @@ export const MAY_THROW_LIB_FNS: ReadonlySet = new Set([ // ("Invalid URL") or a non-http scheme (ERR_INVALID_PROTOCOL). "http.requestUrl", "http.requestUrlCb", + // The runtime options walk throws Node's validation errors and the + // fence for documented options it does not implement. + "http.requestDyn", + "http.requestDynCb", + "https.requestDyn", + "https.requestDynCb", // The h2 client's throwing entries: connect on a bad/non-http // authority (ERR_INVALID_URL / ERR_INVALID_PROTOCOL), request on a // closed/destroyed session (ERR_HTTP2_INVALID_SESSION), setEncoding on diff --git a/packages/compiler/src/ir/builtin-signatures.ts b/packages/compiler/src/ir/builtin-signatures.ts index 1225b2293..778a509f5 100644 --- a/packages/compiler/src/ir/builtin-signatures.ts +++ b/packages/compiler/src/ir/builtin-signatures.ts @@ -1241,6 +1241,13 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ "net.createServer": { argTypes: [], result: NETSERVER_T }, "net.createServerCb": { argTypes: [null], result: NETSERVER_T }, "net.listen": { argTypes: [NETSERVER_T, F64], result: VOID }, + /** listen(path[, cb]): a Unix-socket server; the socket file is removed + * when the server closes. Failures are the async 'error'. */ + "net.listenPath": { argTypes: [NETSERVER_T, STRING], result: VOID }, + "net.listenPathCb": { + argTypes: [NETSERVER_T, STRING, { kind: "func", params: [], ret: VOID }], + result: VOID, + }, "net.listenCb": { argTypes: [NETSERVER_T, F64, { kind: "func", params: [], ret: VOID }], result: VOID, @@ -1283,6 +1290,10 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ * answers null (the serverPort stance). */ "net.serverAddress": { argTypes: [NETSERVER_T], result: VOID }, "net.serverClose": { argTypes: [NETSERVER_T], result: VOID }, + /** server.close((err) => ...): the callback (a dyn function) gets + * undefined once the server closes, or Node's ERR_SERVER_NOT_RUNNING + * when it was not listening. */ + "net.serverCloseDyn": { argTypes: [NETSERVER_T, DYN], result: VOID }, "net.serverCloseCb": { argTypes: [NETSERVER_T, { kind: "func", params: [], ret: VOID }], result: VOID, @@ -1865,6 +1876,17 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ * non-http scheme. */ "http.requestUrl": { argTypes: [STRING, STRING, BOOL], result: HTTPCLIENTREQ_T }, "http.requestUrlCb": { argTypes: [STRING, STRING, BOOL, null], result: HTTPCLIENTREQ_T }, + /** request/get with a runtime options record: a RequestOptions + * variable, or a literal that spreads one or sets socketPath. Args + * (options, autoEnd[, cb]). The runtime reads the record in the + * ClientRequest constructor's order (host/port or socketPath, path, + * method, headers, timeout, agent, signal, maxHeaderSize) with Node's + * errors, and documented options it does not implement throw the + * runtime fence. The "https." pair adds the TLS options. MAY THROW. */ + "http.requestDyn": { argTypes: [DYN, BOOL], result: HTTPCLIENTREQ_T }, + "http.requestDynCb": { argTypes: [DYN, BOOL, null], result: HTTPCLIENTREQ_T }, + "https.requestDyn": { argTypes: [DYN, BOOL], result: HTTPCLIENTREQ_T }, + "https.requestDynCb": { argTypes: [DYN, BOOL, null], result: HTTPCLIENTREQ_T }, /** The paused-mode demux surface (portless's first-byte TLS peek: * once('readable') + read(1) + unshift + emit('connection')). * sockOnReadable registers a zero-param listener (a consumer: arrived @@ -1910,6 +1932,10 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ /** Listeners with a parameter, as dyn functions: close(hadError), * write's cb(err) and end's cb. */ "net.sockOnCloseDyn": { argTypes: [NETSOCKET_T, DYN, BOOL], result: VOID }, + /** socket.on/once('lookup', listener): the listener (a dyn function) + * gets (err, address, family, host) once per resolved address, or once + * with the lookup error, before the connect. */ + "net.sockOnLookup": { argTypes: [NETSOCKET_T, DYN, BOOL], result: VOID }, "net.sockOnWriteFlush": { argTypes: [NETSOCKET_T, { kind: "func", params: [], ret: VOID }], result: VOID, diff --git a/packages/compiler/src/ir/runtime-features.ts b/packages/compiler/src/ir/runtime-features.ts index cc3d08148..d8e388054 100644 --- a/packages/compiler/src/ir/runtime-features.ts +++ b/packages/compiler/src/ir/runtime-features.ts @@ -48,6 +48,9 @@ export interface RuntimeFeatures { qs: boolean; parseArgs: boolean; fsWatch: boolean; + /** The program listens for a socket's 'lookup' event: the C main + * installs the net unit's lookup records. */ + netLookup: boolean; nodeTest: boolean; /** The program calls mock.timers members: its timer calls go through * scr_test.c's mock-aware entry points. */ @@ -183,6 +186,10 @@ const LEGACY_TEXT_DECODER_LIB_FNS: ReadonlySet = new Set([ /** Signal-taking readline, stream and timers/promises entry points, which register abort listeners. */ const ABORT_SIGNAL_LIB_FNS: ReadonlySet = new Set([ "http.clientSignal", + "http.requestDyn", + "http.requestDynCb", + "https.requestDyn", + "https.requestDynCb", "rl.createSignal", "rl.questionAsyncSignal", "sp.finishedOptsSignal", @@ -438,6 +445,7 @@ function scanRuntimeFeatures(mod: IrModule, stopAt?: keyof RuntimeFeatures): Run qs: false, parseArgs: false, fsWatch: false, + netLookup: false, nodeTest: false, mockTimers: false, dgram: false, @@ -540,6 +548,7 @@ function scanRuntimeFeatures(mod: IrModule, stopAt?: keyof RuntimeFeatures): Run features.regex = true; } if (fn.startsWith("fs.watch") || fn.startsWith("watcher.")) features.fsWatch = true; + if (fn === "net.sockOnLookup") features.netLookup = true; // scr_test.c renders thrown strings in the failing-tests section with // the inspect unit's string formatting. if (fn.startsWith("test.")) { diff --git a/packages/compiler/src/ir/validate.ts b/packages/compiler/src/ir/validate.ts index 4a961dfca..5d372664f 100644 --- a/packages/compiler/src/ir/validate.ts +++ b/packages/compiler/src/ir/validate.ts @@ -4426,7 +4426,9 @@ function validateFunction( e.fn === "http.clientOnResponse" || e.fn === "http.requestAgentCb" || e.fn === "https.requestAgentCb" || - e.fn === "https.requestUrlCb" + e.fn === "https.requestUrlCb" || + e.fn === "http.requestDynCb" || + e.fn === "https.requestDynCb" ) { // The response listener: void, no params or exactly (res: httpReq). const cbT = @@ -4437,11 +4439,13 @@ function validateFunction( ? 9 : e.fn === "http.requestUrlCb" || e.fn === "https.requestUrlCb" ? 3 - : e.fn === "http.requestAgentCb" - ? 8 - : e.fn === "https.requestAgentCb" - ? 10 - : 1 + : e.fn === "http.requestDynCb" || e.fn === "https.requestDynCb" + ? 2 + : e.fn === "http.requestAgentCb" + ? 8 + : e.fn === "https.requestAgentCb" + ? 10 + : 1 ]?.type; let ok = cbT?.kind === "func" && cbT.ret.kind === "void" && cbT.params.length <= 1; if (ok && cbT?.kind === "func" && cbT.params.length === 1) { diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6440-http-socket-path.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6440-http-socket-path.ts.json new file mode 100644 index 000000000..63142a38a --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6440-http-socket-path.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6440-http-socket-path.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6441-http-request-options-record.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6441-http-request-options-record.ts.json new file mode 100644 index 000000000..d596d3442 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6441-http-request-options-record.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6441-http-request-options-record.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6442-https-request-options-record.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6442-https-request-options-record.ts.json new file mode 100644 index 000000000..ccc648aee --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6442-https-request-options-record.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6442-https-request-options-record.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6443-net-lookup-http-close-text.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6443-net-lookup-http-close-text.ts.json new file mode 100644 index 000000000..cad9675b2 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6443-net-lookup-http-close-text.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6443-net-lookup-http-close-text.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/fetch-init-members.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/fetch-init-members.ts.json new file mode 100644 index 000000000..3d8a7bef5 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/fetch-init-members.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/node-types/fetch-init-members.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/http-socket-path.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/http-socket-path.ts.json new file mode 100644 index 000000000..9147244bf --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/http-socket-path.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/node-types/http-socket-path.ts" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_fetch.c b/packages/runtime/src/scr_fetch.c index 17beb9122..735183b89 100644 --- a/packages/runtime/src/scr_fetch.c +++ b/packages/runtime/src/scr_fetch.c @@ -5179,7 +5179,21 @@ ScrPromise *scr_fetch_static(ScrStr *url, ScrDyn *init) { * arrays, and plain objects (with the ordinary JS object coercion * protocol), while null and undefined continue to mean no body. */ ScrDyn *coerced_body = NULL; - if (body_present && body->kind != SCR_DYN_STR && + /* A URLSearchParams body is its serialization, sent as + * application/x-www-form-urlencoded;charset=UTF-8 (extract a body). */ + bool form_body = false; + if (body_present && body->kind == SCR_DYN_HANDLE && + body->v.handle.tag == SCR_DYNH_SEARCH_PARAMS) { + coerced_body = scr_dyn_handle_ops_of(body)->invoke(body->v.handle.ptr, (ScrDyn *)body, + "toString", NULL, 0, "URLSearchParams"); + if (!coerced_body || coerced_body->kind != SCR_DYN_STR) { + scr_dyn_release(coerced_body); + return sf_reject_now(promise, "fetch failed"); + } + body = coerced_body; + form_body = true; + } + if (body_present && !form_body && body->kind != SCR_DYN_STR && body->kind != SCR_DYN_BYTES && !body_stream) { ScrStr *text = scr_dyn_string_coerce_js(body); if (!text) { @@ -5322,7 +5336,10 @@ ScrPromise *scr_fetch_static(ScrStr *url, ScrDyn *init) { /* Fetch Metadata is controlled by the fetch implementation. Undici * replaces a caller-provided sec-fetch-mode with "cors". */ sf_strip_header(&headers, "sec-fetch-mode"); - if (body && body->kind == SCR_DYN_STR && + if (form_body && !sf_pairs_have(headers, "content-type")) { + sf_push_header_text(headers, "content-type", + "application/x-www-form-urlencoded;charset=UTF-8", 47); + } else if (body && body->kind == SCR_DYN_STR && !sf_pairs_have(headers, "content-type")) { sf_push_header_text(headers, "content-type", "text/plain;charset=UTF-8", 24); diff --git a/packages/runtime/src/scr_http.c b/packages/runtime/src/scr_http.c index 83a1b3583..9a2541d24 100644 --- a/packages/runtime/src/scr_http.c +++ b/packages/runtime/src/scr_http.c @@ -5582,6 +5582,18 @@ static void scr_http_agent_destroy(ScrHttpAgent *a) { } } +/* The socketPath option of the next request built through + * scr_http_request_agent_ex (borrowed; the caller keeps it alive across + * the call), like https's servername: that request dials the Unix socket + * instead of host:port, and the agent keys its sockets by the path, as + * Node's getName does. */ +static SCR_TL ScrStr *scr_http_next_socket_path = NULL; +/* The Unix-socket dialer, set by the runtime options path that sets the + * path; a pointer (not static) so ordinary requests do not link the + * Unix-socket half of the net unit. */ +extern SCR_TL ScrNetSocket *(*scr_http_socket_dialer)(ScrStr *path, bool deferred); +SCR_TL ScrNetSocket *(*scr_http_socket_dialer)(ScrStr *path, bool deferred) = NULL; + ScrHttpClientReq *scr_http_request_agent_ex(ScrStr *host /*borrowed*/, double port, ScrStr *path /*borrowed*/, ScrStr *method /*borrowed*/, double timeout_ms, ScrArr *header_pairs /*borrowed*/, @@ -5590,6 +5602,8 @@ ScrHttpClientReq *scr_http_request_agent_ex(ScrStr *host /*borrowed*/, double po ScrHttpRespFn fn, int default_port, void (*wrap)(ScrNetSocket *, void *), void *wrap_ctx, ScrStr *pool_key /*borrowed, nullable*/) { + ScrStr *socket_path = scr_http_next_socket_path; + scr_http_next_socket_path = NULL; /* the null/undefined arms: the scheme's global agent, like Node */ if (agent == NULL || agent->kind == SCR_DYN_UNDEF || agent->kind == SCR_DYN_NULL) agent = scr_http_global_agent(default_port == 443); @@ -5619,8 +5633,10 @@ ScrHttpClientReq *scr_http_request_agent_ex(ScrStr *host /*borrowed*/, double po scr_arr_push_ref(copy, scr_str_new("close", 5)); pairs = copy; } + ScrNetSocket *presock = socket_path != NULL ? scr_http_socket_dialer(socket_path, false) : NULL; ScrHttpClientReq *c = scr_http_request_impl(host, p, path, method, timeout_ms, pairs, - auto_end, cb, fn, default_port, wrap, wrap_ctx, NULL); + auto_end, cb, fn, default_port, wrap, wrap_ctx, + presock); if (copy) scr_arr_release(copy); return c; } @@ -5636,11 +5652,14 @@ ScrHttpClientReq *scr_http_request_agent_ex(ScrStr *host /*borrowed*/, double po char portbuf[16]; int portn = snprintf(portbuf, sizeof portbuf, "%d", (int)p); ScrStr *name = scr_http_agent_name(host->data, host->len, portbuf, (size_t)portn, - NULL, 0, 0, NULL, 0); + NULL, 0, 0, socket_path ? socket_path->data : NULL, + socket_path ? socket_path->len : 0); /* a free pooled socket for the name serves the request right away */ ScrHttpAgentFree *reuse = ag->keep_alive ? scr_http_agent_free_take(ag, name, pool_key) : NULL; bool queue = reuse == NULL && scr_http_agent_active(ag, name) >= ag->max_sockets; - ScrNetSocket *presock = queue || reuse ? scr_net_connect_deferred(p, host) : NULL; + ScrNetSocket *presock = socket_path != NULL ? scr_http_socket_dialer(socket_path, queue || reuse) + : queue || reuse ? scr_net_connect_deferred(p, host) + : NULL; ScrHttpClientReq *c = scr_http_request_impl(host, p, path, method, timeout_ms, header_pairs, auto_end, cb, fn, default_port, wrap, wrap_ctx, presock); @@ -6968,3 +6987,372 @@ ScrHttpClientReq *scr_http_request_url(ScrStr *url /*borrowed*/, ScrStr *method scr_str_release(path); return c; } + +/* ── http.request(options) with a runtime options record ───────────────── + * + * The options value of http.request/get when the compiler cannot see an + * object literal: a RequestOptions variable, or a literal that spreads one + * or sets socketPath. Node's ClientRequest constructor reads it at run + * time; so does this walk, in Node's validation order and with its error + * shapes. Documented options outside the implemented set throw the + * runtime fence when they carry a value, rather than being ignored; + * undocumented keys are ignored, as Node ignores them. */ + +/* The documented options this walk does not implement. A truthy value + * fences; setHost and setDefaultHeaders fence only when false. */ +static const char *const SCR_HTTP_REQ_FENCED_OPTIONS[] = { + "auth", "createConnection", "family", "hints", "httpValidation", "insecureHTTPParser", + "joinDuplicateHeaders", "localAddress", "localPort", "lookup", "uniqueHeaders", NULL, +}; + +static const char SCR_HTTP_REQ_SUPPORTED[] = + "hostname/host, port, path, method, headers, timeout, agent, signal, socketPath, " + "defaultPort, protocol, and maxHeaderSize are the supported options"; + +void scr_http_req_opts_fence(const char *api, const char *key, const char *hint) { + ScrJsonBuf b; + scr_jb_init(&b); + scr_jb_puts(&b, api); + scr_jb_puts(&b, " option '"); + scr_jb_puts(&b, key); + scr_jb_puts(&b, "' has no static lowering — "); + scr_jb_puts(&b, hint); + scr_throw_error(SCR_ERR_ERROR, scr_jb_finish(&b)); +} + +void scr_http_req_opts_free(ScrHttpReqOpts *o) { + scr_str_release(o->host); + scr_str_release(o->path); + scr_str_release(o->method); + scr_str_release(o->socket_path); + if (o->headers) scr_arr_release(o->headers); + scr_dyn_release(o->agent); + scr_dyn_release(o->signal); + scr_dyn_release(o->view); + memset(o, 0, sizeof *o); +} + +/* A member of the options record, NULL when absent or undefined. */ +static const ScrDyn *scr_http_req_opt(const ScrDyn *opts, const char *key) { + const ScrDyn *v = scr_dyn_obj_get(opts, key, strlen(key)); + return v == NULL || v->kind == SCR_DYN_UNDEF ? NULL : v; +} + +static bool scr_http_req_truthy(const ScrDyn *v) { return v != NULL && scr_dyn_truthy(v); } + +/* validatePort's accepted values: an integer in [0, 65535] or a string + * holding one. NaN when the value is not a valid port. */ +static double scr_http_req_port_value(const ScrDyn *v) { + if (v->kind == SCR_DYN_NUM) { + double n = v->v.num; + return n == n && trunc(n) == n && n >= 0 && n <= 65535 ? n : NAN; + } + if (v->kind == SCR_DYN_STR) { + bool blank = true; + for (size_t i = 0; i < v->v.str->len && blank; i++) + blank = isspace((unsigned char)v->v.str->data[i]) != 0; + if (blank) return NAN; + ScrStr *s = scr_str_retain(v->v.str); + double n = scr_string_to_number(s); + scr_str_release(s); + return n == n && trunc(n) == n && n >= 0 && n <= 65535 ? n : NAN; + } + return NAN; +} + +/* Reads `opts` (borrowed) into `o` in the ClientRequest constructor's + * order. False with the exception pending (`o` holds nothing). */ +bool scr_http_req_opts_read(const ScrDyn *opts, bool secure, const char *api, ScrHttpReqOpts *o) { + memset(o, 0, sizeof *o); + o->port = -1; + o->default_port = secure ? 443 : 80; + if (opts != NULL && opts->kind == SCR_DYN_TYPED_REF) { + o->view = scr_dyn_typed_ref_materialize(opts); + opts = o->view; + } + if (opts == NULL || opts->kind == SCR_DYN_UNDEF || opts->kind == SCR_DYN_NULL) { + opts = NULL; /* Node spreads it: every option takes its default */ + } else if (opts->kind != SCR_DYN_OBJ) { + scr_http_req_opts_fence(api, "options", "pass an options object or a URL string"); + scr_http_req_opts_free(o); + return false; + } +#define OPT(key) (opts != NULL ? scr_http_req_opt(opts, key) : NULL) + /* agent: false, null/undefined (the global agent), or an Agent */ + const ScrDyn *agent = OPT("agent"); + if (agent != NULL && agent->kind != SCR_DYN_NULL && + !(agent->kind == SCR_DYN_BOOL && !agent->v.b) && + !(agent->kind == SCR_DYN_HANDLE && agent->v.handle.tag == SCR_DYNH_HTTP_AGENT)) { + scr_dyn_prop_type_fail("options.agent", "one of Agent-like Object, undefined, or false", agent); + scr_http_req_opts_free(o); + return false; + } + if (agent != NULL) o->agent = scr_dyn_retain((ScrDyn *)agent); + bool agent_secure = secure; + if (agent != NULL && agent->kind == SCR_DYN_HANDLE) + agent_secure = ((ScrHttpAgent *)agent->v.handle.ptr)->secure; + /* path: String(options.path) when truthy, without unescaped characters */ + const ScrDyn *path = OPT("path"); + if (scr_http_req_truthy(path)) { + o->path = scr_dyn_string_coerce_js(path); + if (o->path == NULL) { + scr_http_req_opts_free(o); + return false; + } + bool bad = false; + for (size_t i = 0; i < o->path->len && !bad; i++) { + unsigned char ch = (unsigned char)o->path->data[i]; + if (ch < 0x21) bad = true; + else if (ch >= 0x80) { + /* U+0080..U+00FF (lead byte C2/C3) pass; anything above is unescaped */ + if (ch != 0xc2 && ch != 0xc3) bad = true; + else i++; + } + } + if (bad) { + static const char msg[] = "Request path contains unescaped characters"; + scr_throw_error_msg_code(SCR_ERR_TYPE, msg, sizeof msg - 1, "ERR_UNESCAPED_CHARACTERS"); + scr_http_req_opts_free(o); + return false; + } + } else { + o->path = scr_str_new("/", 1); + } + /* protocol: options.protocol || the agent's, which must match */ + const ScrDyn *protocol = OPT("protocol"); + const char *expected = agent_secure ? "https:" : "http:"; + if (scr_http_req_truthy(protocol)) { + ScrStr *p = scr_dyn_string_coerce_js(protocol); + if (p == NULL) { + scr_http_req_opts_free(o); + return false; + } + if (p->len != strlen(expected) || memcmp(p->data, expected, p->len) != 0) { + char msg[192]; + int n = snprintf(msg, sizeof msg, "Protocol \"%.*s\" not supported. Expected \"%s\"", + (int)(p->len < 64 ? p->len : 64), p->data, expected); + scr_str_release(p); + scr_throw_error_msg_code(SCR_ERR_TYPE, msg, (size_t)n, "ERR_INVALID_PROTOCOL"); + scr_http_req_opts_free(o); + return false; + } + scr_str_release(p); + } + /* defaultPort, then port (validated when the dial is TCP) */ + const ScrDyn *default_port = OPT("defaultPort"); + if (scr_http_req_truthy(default_port) && default_port->kind == SCR_DYN_NUM) + o->default_port = (int)default_port->v.num; + else if (agent != NULL && agent->kind == SCR_DYN_HANDLE) + o->default_port = (int)((ScrHttpAgent *)agent->v.handle.ptr)->default_port; + const ScrDyn *port = OPT("port"); + /* host: validateHost(hostname) || validateHost(host) || 'localhost' */ + static const char *const host_keys[] = { "hostname", "host" }; + for (size_t i = 0; i < 2; i++) { + const ScrDyn *h = OPT(host_keys[i]); + if (h == NULL || h->kind == SCR_DYN_NULL) continue; + if (h->kind != SCR_DYN_STR) { + char name[24]; + snprintf(name, sizeof name, "options.%s", host_keys[i]); + scr_dyn_prop_type_fail(name, "of type string or one of undefined or null", h); + scr_http_req_opts_free(o); + return false; + } + if (o->host == NULL && h->v.str->len > 0) o->host = scr_str_retain(h->v.str); + } + if (o->host == NULL) o->host = scr_str_new("localhost", 9); + const ScrDyn *set_host = OPT("setHost"); + if (set_host != NULL && set_host->kind == SCR_DYN_BOOL && !set_host->v.b) { + scr_http_req_opts_fence(api, "setHost", "the Host header is always sent — leave setHost at its true default"); + scr_http_req_opts_free(o); + return false; + } + const ScrDyn *set_default = OPT("setDefaultHeaders"); + if (set_default != NULL && set_default->kind == SCR_DYN_BOOL && !set_default->v.b) { + scr_http_req_opts_fence(api, "setDefaultHeaders", "the default headers are always sent — leave setDefaultHeaders at its true default"); + scr_http_req_opts_free(o); + return false; + } + const ScrDyn *socket_path = OPT("socketPath"); + /* timeout: getTimerDuration(options.timeout, 'timeout') */ + const ScrDyn *timeout = OPT("timeout"); + if (timeout != NULL) { + if (timeout->kind != SCR_DYN_NUM) { + scr_dyn_arg_type_fail("timeout", "of type number", timeout); + scr_http_req_opts_free(o); + return false; + } + double t = timeout->v.num; + if (!(t >= 0 && isfinite(t))) { + char recv[48], msg[192]; + scr_num_received(t, recv); + int n = snprintf(msg, sizeof msg, + "The value of \"timeout\" is out of range. It must be a non-negative finite number. Received %s", + recv); + scr_throw_error_msg_code(SCR_ERR_RANGE, msg, (size_t)n, "ERR_OUT_OF_RANGE"); + scr_http_req_opts_free(o); + return false; + } + o->timeout = t; + } + /* method: a string token, upper-cased; GET when absent or empty */ + const ScrDyn *method = OPT("method"); + if (method != NULL && method->kind != SCR_DYN_NULL && method->kind != SCR_DYN_STR) { + scr_dyn_prop_type_fail("options.method", "of type string", method); + scr_http_req_opts_free(o); + return false; + } + if (method != NULL && method->kind == SCR_DYN_STR && method->v.str->len > 0) { + o->method = scr_str_new(method->v.str->data, method->v.str->len); + for (size_t i = 0; i < o->method->len; i++) { + if (!scr_http_token_char((unsigned char)o->method->data[i])) { + ScrJsonBuf b; + scr_jb_init(&b); + scr_jb_puts(&b, "Method must be a valid HTTP token [\""); + scr_jb_write(&b, o->method->data, o->method->len); + scr_jb_puts(&b, "\"]"); + ScrStr *msg = scr_jb_finish(&b); + scr_throw_error_msg_code(SCR_ERR_TYPE, msg->data, msg->len, "ERR_INVALID_HTTP_TOKEN"); + scr_str_release(msg); + scr_http_req_opts_free(o); + return false; + } + } + for (size_t i = 0; i < o->method->len; i++) { + unsigned char ch = (unsigned char)o->method->data[i]; + if (ch >= 'a' && ch <= 'z') o->method->data[i] = (char)(ch - 'a' + 'A'); + } + } else { + o->method = scr_str_new("GET", 3); + } + const ScrDyn *max_header = OPT("maxHeaderSize"); + if (max_header != NULL) { + if (max_header->kind != SCR_DYN_NUM) { + scr_dyn_arg_type_fail("maxHeaderSize", "of type number", max_header); + scr_http_req_opts_free(o); + return false; + } + o->max_header_size = max_header->v.num; + o->has_max_header_size = true; + } + for (size_t i = 0; SCR_HTTP_REQ_FENCED_OPTIONS[i] != NULL; i++) { + if (scr_http_req_truthy(OPT(SCR_HTTP_REQ_FENCED_OPTIONS[i]))) { + scr_http_req_opts_fence(api, SCR_HTTP_REQ_FENCED_OPTIONS[i], SCR_HTTP_REQ_SUPPORTED); + scr_http_req_opts_free(o); + return false; + } + } + /* headers: an object (String(number) values, arrays as repeated lines) + * or the flat [name, value, ...] array */ + const ScrDyn *headers = OPT("headers"); + ScrDyn *headers_view = NULL; + if (headers != NULL && headers->kind == SCR_DYN_TYPED_REF) { + headers_view = scr_dyn_typed_ref_materialize(headers); + headers = headers_view; + } + if (headers != NULL && headers->kind == SCR_DYN_OBJ) { + for (size_t i = 0; i < headers->v.obj.len; i++) { + const ScrDynEntry *e = &headers->v.obj.entries[i]; + ScrStr *name = scr_str_new(e->key, e->key_len); + ScrStr *label = scr_str_new("", 0); + scr_http_validate_header_name(name, label); + scr_str_release(label); + const ScrDyn *v = e->value; + if (!scr_exc_pending()) { + if (v->kind == SCR_DYN_ARR) { + for (size_t k = 0; k < v->v.arr.len && !scr_exc_pending(); k++) + scr_http_validate_header_value(name, v->v.arr.items[k]); + } else { + scr_http_validate_header_value(name, v); + } + } + scr_str_release(name); + if (scr_exc_pending()) break; + } + if (!scr_exc_pending()) o->headers = scr_http_dynh_pairs(headers); + } else if (headers != NULL && headers->kind == SCR_DYN_ARR) { + o->headers = scr_http_dynh_flat_pairs(headers); + } else if (headers != NULL && headers->kind != SCR_DYN_NULL) { + scr_http_req_opts_fence(api, "headers", "headers is an object of string or number values here"); + } + scr_dyn_release(headers_view); + if (scr_exc_pending()) { + scr_http_req_opts_free(o); + return false; + } + if (o->headers == NULL) o->headers = scr_arr_new(SCR_ELEM_STR, 0); + const ScrDyn *signal = OPT("signal"); + if (signal != NULL) o->signal = scr_dyn_retain((ScrDyn *)signal); + /* the connection: a Unix socket (net validates the path) or host:port */ + if (scr_http_req_truthy(socket_path)) { + if (socket_path->kind != SCR_DYN_STR) { + scr_dyn_prop_type_fail("options.path", "of type string", socket_path); + scr_http_req_opts_free(o); + return false; + } + o->socket_path = scr_str_retain(socket_path->v.str); + if (scr_http_req_truthy(port)) { + double n = scr_http_req_port_value(port); + if (n == n) o->port = n; + } + } else if (scr_http_req_truthy(port)) { + double n = scr_http_req_port_value(port); + if (n != n) { + char detail[96], msg[192]; + const char *d = scr_dyn_specific_type(port, detail, sizeof detail); + int len = snprintf(msg, sizeof msg, "Port should be >= 0 and < 65536. Received %s.", d); + scr_throw_error_msg_code(SCR_ERR_RANGE, msg, (size_t)len, "ERR_SOCKET_BAD_PORT"); + scr_http_req_opts_free(o); + return false; + } + o->port = n; + } +#undef OPT + return true; +} + +/* Builds the request `o` describes (its references stay with `o`). The + * signal is checked before construction and attached after it, like the + * literal lowering's sequence. +1, or NULL with the exception pending. */ +ScrHttpClientReq *scr_http_req_opts_send(ScrHttpReqOpts *o, bool auto_end, + ScrClosure *cb /*moves, nullable*/, ScrHttpRespFn fn, + ScrDyn *(*add_abort)(ScrDyn *signal, ScrDyn *listener), + void (*wrap)(ScrNetSocket *, void *), void *wrap_ctx, + ScrStr *pool_key /*borrowed, nullable*/) { + if (o->signal != NULL) { + scr_http_client_signal_check(o->signal); + if (scr_exc_pending()) { + if (cb) scr_closure_release(cb); + return NULL; + } + } + /* without a port option the agent's default port applies (< 0), and + * without an agent the scheme's */ + double port = o->port >= 0 ? o->port : o->agent != NULL && o->agent->kind == SCR_DYN_HANDLE ? -1 + : o->default_port; + scr_http_next_socket_path = o->socket_path; + scr_http_socket_dialer = &scr_net_connect_path; + ScrHttpClientReq *c = scr_http_request_agent_ex( + o->host, port, o->path, o->method, o->timeout, o->headers, auto_end, o->agent, cb, fn, + o->default_port, wrap, wrap_ctx, pool_key); + if (c == NULL) return NULL; + if (o->has_max_header_size) { + scr_http_client_max_header_size(c, o->max_header_size); + if (scr_exc_pending()) return c; + } + if (o->signal != NULL) scr_http_client_add_abort_signal(c, o->signal, add_abort); + return c; +} + +/* http.request/http.get(options[, cb]) with a runtime options record. */ +ScrHttpClientReq *scr_http_request_dyn(const ScrDyn *opts /*borrowed*/, bool auto_end, + ScrClosure *cb /*moves, nullable*/, ScrHttpRespFn fn, + ScrDyn *(*add_abort)(ScrDyn *signal, ScrDyn *listener)) { + ScrHttpReqOpts o; + if (!scr_http_req_opts_read(opts, false, "http.request", &o)) { + if (cb) scr_closure_release(cb); + return NULL; + } + ScrHttpClientReq *c = scr_http_req_opts_send(&o, auto_end, cb, fn, add_abort, NULL, NULL, NULL); + scr_http_req_opts_free(&o); + return c; +} diff --git a/packages/runtime/src/scr_http.h b/packages/runtime/src/scr_http.h index a4dcc44ce..654362c06 100644 --- a/packages/runtime/src/scr_http.h +++ b/packages/runtime/src/scr_http.h @@ -371,6 +371,40 @@ ScrHttpClientReq *scr_http_request_agent(ScrStr *host /*borrowed*/, double port, double timeout_ms, ScrArr *header_pairs /*borrowed*/, bool auto_end, const ScrDyn *agent /*borrowed*/, ScrClosure *cb /*moves, nullable*/, ScrHttpRespFn fn); /* +1 */ +/* http.request/get with a runtime options record (a RequestOptions + * variable, or a literal spreading one or setting socketPath). The walk + * reads the record in the ClientRequest constructor's order; https adds + * its TLS options (scr_tls.c) and sends through the same path. */ +typedef struct ScrHttpReqOpts { + ScrStr *host; /* +1: hostname || host || "localhost" */ + double port; /* < 0: no port option */ + int default_port; /* defaultPort, the agent's, or the scheme's */ + ScrStr *path; /* +1 */ + ScrStr *method; /* +1, upper-cased */ + double timeout; /* 0 = none */ + ScrArr *headers; /* +1 name/value pairs */ + ScrDyn *agent; /* +1, NULL = the global agent */ + ScrDyn *signal; /* +1, NULL = none */ + ScrStr *socket_path; /* +1, NULL = dial host:port */ + double max_header_size; + bool has_max_header_size; + ScrDyn *view; /* +1 materialized typed record, if any */ +} ScrHttpReqOpts; +bool scr_http_req_opts_read(const ScrDyn *opts /*borrowed*/, bool secure, const char *api, + ScrHttpReqOpts *o); /* false = exception pending */ +void scr_http_req_opts_free(ScrHttpReqOpts *o); +void scr_http_req_opts_fence(const char *api, const char *key, const char *hint); /* throws */ +ScrHttpClientReq *scr_http_req_opts_send(ScrHttpReqOpts *o, bool auto_end, + ScrClosure *cb /*moves, nullable*/, ScrHttpRespFn fn, + ScrDyn *(*add_abort)(ScrDyn *signal, ScrDyn *listener), + void (*wrap)(ScrNetSocket *, void *), void *wrap_ctx, + ScrStr *pool_key /*borrowed, nullable*/); /* +1 */ +ScrHttpClientReq *scr_http_request_dyn(const ScrDyn *opts /*borrowed*/, bool auto_end, + ScrClosure *cb /*moves, nullable*/, ScrHttpRespFn fn, + ScrDyn *(*add_abort)(ScrDyn *signal, ScrDyn *listener)); /* +1 */ +ScrHttpClientReq *scr_https_request_dyn(const ScrDyn *opts /*borrowed*/, bool auto_end, + ScrClosure *cb /*moves, nullable*/, ScrHttpRespFn fn, + ScrDyn *(*add_abort)(ScrDyn *signal, ScrDyn *listener)); /* +1 */ #ifdef SCR_RC_AUDIT long scr_http_live_count(void); #endif diff --git a/packages/runtime/src/scr_net.c b/packages/runtime/src/scr_net.c index f13906454..a92f738bd 100644 --- a/packages/runtime/src/scr_net.c +++ b/packages/runtime/src/scr_net.c @@ -21,6 +21,7 @@ #include #include #include +#include /* AF_UNIX: listen(path) and http's socketPath */ #if defined(__FreeBSD__) || defined(__DragonFly__) || defined(_AIX) || defined(__sun) #include #endif @@ -442,6 +443,7 @@ struct ScrNetServer { * '::' when bound_v6, else '0.0.0.0' — Node's own * address().address answers) */ ScrStr *pending_err; /* deferred listen failure */ + ScrStr *unix_path; /* listen(path): the bound socket file, removed at close */ size_t nconns; ScrNetLs conn_ls, err_ls, close_ls, listening_cbs, timeout_ls; /* server.maxConnections (unset = undefined) and its 'drop' listeners */ @@ -532,6 +534,14 @@ struct ScrNetSocket { /* the in-flight dial chain's LAST failure — surfaced (moved into * pending_err) only when the list exhausts; a later success drops it */ ScrStr *dial_err; + /* a dial that is not the TCP sockaddr path (the Unix-socket connect): + * scr_net_sock_dial_peer hands the dial to it; unix_path is its target */ + void (*dial_hook)(ScrNetSocket *s); + ScrStr *unix_path; + /* 'lookup' listeners and the resolutions waiting to reach them (the + * lookup unit's records; see scr_net_lookup_ops) */ + ScrNetLs lookup_ls; + struct ScrNetLookupEv *lookup_evs; ScrNetLs data_ls, end_ls, close_ls, err_ls, conn_ls, timeout_ls, readable_ls; /* Flow control (pause()/resume()): user_paused holds reads OFF even * with consumers (kernel/TCP backpressure is the buffer); flowing @@ -608,6 +618,21 @@ typedef struct ScrNetTunnel { size_t in_len; } ScrNetTunnel; +/* The 'lookup' event's half of the runtime, installed by the first + * 'lookup' registration: programs without a 'lookup' listener never link + * it, and the hook sites below are a NULL check. */ +typedef struct ScrNetLookupOps { + /* a host name resolved (ip/family) or failed (err_msg, ip NULL) */ + void (*record)(ScrNetSocket *s, const char *host, size_t host_len, const char *ip, int family, + const char *err_msg); + void (*flush)(ScrNetSocket *s); /* deliver the recorded resolutions */ + void (*free)(ScrNetSocket *s); +} ScrNetLookupOps; +/* Not static: the compiler must not resolve the calls through it to the + * one implementation, which would link that half into every program. */ +extern SCR_TL const ScrNetLookupOps *scr_net_lookup_ops; +SCR_TL const ScrNetLookupOps *scr_net_lookup_ops = NULL; + static void scr_net_tunnel_free(ScrNetTunnel *t) { if (t == NULL) return; free(t->out); @@ -648,6 +673,7 @@ void scr_net_server_release(ScrNetServer *s) { scr_dyn_release(s->drop_max_connection); scr_str_release(s->bound_host); scr_str_release(s->pending_err); + scr_str_release(s->unix_path); if (s->fd >= 0) scr_net_close_fd_raw(s->fd); #ifdef SCR_RC_AUDIT scr_net_live--; @@ -685,6 +711,9 @@ void scr_net_sock_release(ScrNetSocket *s) { free(s->dial_ips); scr_str_release(s->dial_err); scr_str_release(s->dial_host); + scr_str_release(s->unix_path); + scr_net_ls_drop(&s->lookup_ls); + if (s->lookup_evs != NULL) scr_net_lookup_ops->free(s); scr_net_tunnel_free(s->tunnel); free(s->wbuf); free(s->rbuf); @@ -1385,6 +1414,10 @@ static void scr_net_sock_connect_done(ScrNetSocket *s) { * one): the transport handshake starts, or 'connect' fires and buffered * writes flush. */ static void scr_net_sock_connected(ScrNetSocket *s) { + if (s->lookup_evs != NULL) { + scr_net_lookup_ops->flush(s); + if (scr_exc_pending()) return; + } scr_net_sock_update_write(s); /* keeps the filter iff bytes are buffered */ scr_net_sock_update_read(s); scr_net_sock_touch(s); @@ -1892,6 +1925,11 @@ void scr_net_server_close_direct(ScrNetServer *s, ScrClosure *cb /*moves, nullab scr_net_close_fd_raw(s->fd); /* stops accepting NOW (registrations dropped first) */ s->fd = -1; s->listening = false; +#ifndef _WIN32 + /* a Unix-socket server removes its socket file as it closes, like + * libuv's pipe close */ + if (s->unix_path != NULL) unlink(s->unix_path->data); +#endif } /* A close BEFORE the deferred 'listening' delivered cancels it — Node's * emitListeningNT checks the handle close() nulled, so the listen @@ -1905,6 +1943,9 @@ void scr_net_server_close_direct(ScrNetServer *s, ScrClosure *cb /*moves, nullab s->close_seq = ++scr_net_close_seq; } s->closing = true; /* the sweep fires 'close' once nconns drains */ + /* a server that never listened is not registered yet: Node still emits + * its 'close' (and runs close callbacks) */ + scr_net_server_register(s); /* Node's http server.close() first closes its idle keep-alive * connections; busy ones end after their response (scr_http.c). Only * HTTP connections carry an idle probe, so plain net servers keep @@ -2207,7 +2248,10 @@ ScrStr *scr_net_blocking_lookup(ScrStr *host /*borrowed*/) { * autoselect), and any other name is resolved when the dial starts. */ static void scr_net_sock_set_peer(ScrNetSocket *s, ScrStr *host /*borrowed, nullable*/) { const char *h = host && host->len > 0 ? host->data : "localhost"; - if (strcmp(h, "localhost") == 0) h = "127.0.0.1"; + if (strcmp(h, "localhost") == 0) { + h = "127.0.0.1"; + if (scr_net_lookup_ops != NULL) scr_net_lookup_ops->record(s, "localhost", 9, h, 4, NULL); + } snprintf(s->peer_ip, sizeof s->peer_ip, "%s", h); struct in_addr a4; struct in6_addr a6; @@ -2253,6 +2297,9 @@ static void scr_net_sock_dial_host(ScrNetSocket *s) { out = inet_ntop(AF_INET6, &((struct sockaddr_in6 *)ai->ai_addr)->sin6_addr, ip, sizeof ip); } if (out == NULL) continue; + if (scr_net_lookup_ops != NULL) + scr_net_lookup_ops->record(s, host->data, host->len, ip, ai->ai_family == AF_INET6 ? 6 : 4, + NULL); if (first == AF_UNSPEC) first = ai->ai_family; size_t f = ai->ai_family == first ? 0 : 1; if (fam[f] == NULL) { @@ -2283,6 +2330,7 @@ static void scr_net_sock_dial_host(ScrNetSocket *s) { char msg[320]; snprintf(msg, sizeof msg, "getaddrinfo %s %.*s", code, (int)(host->len < 255 ? host->len : 255), host->data); + if (scr_net_lookup_ops != NULL) scr_net_lookup_ops->record(s, host->data, host->len, NULL, 0, msg); free(ips); scr_str_release(host); s->pending_err = scr_str_new(msg, strlen(msg)); @@ -2304,6 +2352,10 @@ static void scr_net_sock_dial_host(ScrNetSocket *s) { * queue — the socket exists, buffers writes, and dials when a slot * frees). */ static void scr_net_sock_dial_peer(ScrNetSocket *s) { + if (s->dial_hook != NULL) { + s->dial_hook(s); + return; + } if (s->dial_host != NULL) { scr_net_sock_dial_host(s); return; @@ -2552,6 +2604,167 @@ void scr_net_sock_dial_start(ScrNetSocket *s) { scr_net_sock_dial_peer(s); } +/* ── Unix domain sockets ─────────────────────────────────────────────── + * + * listen(path) and the http client's socketPath option. Node (libuv) + * reports these failures with the path where a TCP error names + * host:port: "connect ENOENT /run/x.sock", "listen EADDRINUSE: address + * already in use /run/x.sock". Windows has no Unix-socket support here + * (Node maps paths to named pipes there), so both forms fail with ENOTSUP. */ + +static const char *scr_net_unix_errname(int err) { + switch (err) { + case ENOENT: return "ENOENT"; + case ENOTDIR: return "ENOTDIR"; + case ENAMETOOLONG: return "ENAMETOOLONG"; + case EAGAIN: return "EAGAIN"; + case EPROTOTYPE: return "EPROTOTYPE"; + default: return scr_net_errname(err); + } +} + +static const char *scr_net_unix_reason(int err) { + switch (err) { + case EADDRINUSE: return "address already in use"; + case EACCES: return "permission denied"; + case ENOENT: return "no such file or directory"; + case ENOTDIR: return "not a directory"; + case ENAMETOOLONG: return "name too long"; + case EINVAL: return "invalid argument"; + default: return "operation not supported"; + } +} + +/* Fills a sockaddr_un for `path`; false (with errno set) when it does not + * fit, which libuv reports as ENAMETOOLONG too. */ +#ifndef _WIN32 +static bool scr_net_unix_addr(const ScrStr *path, struct sockaddr_un *sa) { + memset(sa, 0, sizeof *sa); + sa->sun_family = AF_UNIX; + if (path->len >= sizeof sa->sun_path || memchr(path->data, '\0', path->len) != NULL) { + errno = ENAMETOOLONG; + return false; + } + memcpy(sa->sun_path, path->data, path->len); + return true; +} +#endif + +/* The dial_hook of a Unix-socket client: a nonblocking connect that + * completes through the same write-readiness path as TCP. A local + * connect usually settles at once; its failure is the deferred 'error'. */ +static void scr_net_sock_dial_unix(ScrNetSocket *s) { + int err = ENOTSUP; +#ifndef _WIN32 + struct sockaddr_un sa; + if (scr_net_unix_addr(s->unix_path, &sa)) { + int fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd < 0) { + fputs("scriptc: socket() failed\n", stderr); + abort(); + } + scr_net_nonblock(fd); + s->fd = fd; + if (connect(fd, (struct sockaddr *)&sa, sizeof sa) == 0 || errno == EINPROGRESS) { + s->connecting = true; + scr_net_sock_update_write(s); + return; + } + } + err = errno; +#endif + ScrJsonBuf b; + scr_jb_init(&b); + scr_jb_puts(&b, "connect "); + scr_jb_puts(&b, scr_net_unix_errname(err)); + scr_jb_putc(&b, ' '); + scr_jb_write(&b, s->unix_path->data, s->unix_path->len); + s->pending_err = scr_jb_finish(&b); + s->had_error = true; + s->connecting = false; + scr_net_sock_close_fd(s); +} + +/* A client socket for the Unix socket at `path` (borrowed). `deferred` + * creates it the way scr_net_connect_deferred does: registered and + * buffering, dialed by scr_net_sock_dial_start. +1. */ +ScrNetSocket *scr_net_connect_path(ScrStr *path, bool deferred) { + ScrNetSocket *s = scr_net_sock_new(); + s->unix_path = scr_str_retain(path); + s->dial_hook = &scr_net_sock_dial_unix; + if (!scr_net_poller_init()) { + fputs("scriptc: event poller init failed\n", stderr); + abort(); + } + if (deferred) { + s->connecting = true; + s->dial_deferred = true; + } else { + scr_net_sock_dial_unix(s); + } + scr_net_sock_register(s); + return s; +} + +/* server.listen(path[, cb]): binds a Unix socket now; a failure is the + * deferred 'error', success the deferred 'listening', as for a port. */ +void scr_net_listen_path(ScrNetServer *s, ScrStr *path /*borrowed*/, + ScrClosure *cb /*moves, nullable*/) { + /* Node's isPipeName: a string that reads as a non-negative number is a port */ + double port = scr_string_to_number(path); + if (port >= 0) { + scr_net_listen_any(s, port, false, cb); + return; + } + if (cb) scr_net_ls_add(&s->listening_cbs, cb, NULL, true); + if (s->listening || s->close_emitted) return; + if (!scr_net_poller_init()) { + fputs("scriptc: event poller init failed\n", stderr); + abort(); + } + int err = ENOTSUP; +#ifndef _WIN32 + struct sockaddr_un sa; + if (scr_net_unix_addr(path, &sa)) { + int fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd < 0) { + fputs("scriptc: socket() failed\n", stderr); + abort(); + } + scr_net_nonblock(fd); + if (bind(fd, (struct sockaddr *)&sa, sizeof sa) == 0 && listen(fd, 511) == 0) { + s->fd = fd; + s->listening = true; + s->emit_listening = true; + scr_str_release(s->unix_path); + s->unix_path = scr_str_retain(path); + /* address() carries the path in its address field (Node answers + * the path string itself) */ + scr_str_release(s->bound_host); + s->bound_host = scr_str_retain(path); + scr_net_watch_read(fd, s, true); + scr_net_server_register(s); + return; + } + err = errno; + close(fd); + } else { + err = errno; + } +#endif + ScrJsonBuf b; + scr_jb_init(&b); + scr_jb_puts(&b, "listen "); + scr_jb_puts(&b, scr_net_unix_errname(err)); + scr_jb_puts(&b, ": "); + scr_jb_puts(&b, scr_net_unix_reason(err)); + scr_jb_putc(&b, ' '); + scr_jb_write(&b, path->data, path->len); + if (!s->pending_err) s->pending_err = scr_jb_finish(&b); + else scr_str_release(scr_jb_finish(&b)); + scr_net_server_register(s); /* the sweep delivers the failure */ +} + /* ── the caller-lookup dial (net.connect with a lookup option) ───────── * * portless's createLoopbackConnection: connect({ host, port, @@ -4094,6 +4307,136 @@ void scr_net_sock_on_close_dyn(ScrNetSocket *s, const ScrDyn *listener /*borrowe (void *)&scr_net_dyn_fire_arg, once); } +/* ── 'lookup' ─────────────────────────────────────────────────────────── */ + +typedef struct ScrNetLookupEv { + struct ScrNetLookupEv *next; + ScrStr *host; + ScrStr *ip; /* NULL with err_msg */ + int family; + ScrStr *err_msg; /* "getaddrinfo ENOTFOUND host" */ +} ScrNetLookupEv; + +static void scr_net_lookup_record(ScrNetSocket *s, const char *host, size_t host_len, + const char *ip, int family, const char *err_msg) { + ScrNetLookupEv *ev = calloc(1, sizeof *ev); + if (!ev) scr_net_oom(); + ev->host = scr_str_new(host, host_len); + ev->ip = ip != NULL ? scr_str_new(ip, strlen(ip)) : NULL; + ev->family = family; + ev->err_msg = err_msg != NULL ? scr_str_new(err_msg, strlen(err_msg)) : NULL; + ScrNetLookupEv **link = &s->lookup_evs; + while (*link) link = &(*link)->next; + *link = ev; +} + +static void scr_net_lookup_ev_free(ScrNetLookupEv *ev) { + scr_str_release(ev->host); + scr_str_release(ev->ip); + scr_str_release(ev->err_msg); + free(ev); +} + +static void scr_net_lookup_free(ScrNetSocket *s) { + while (s->lookup_evs) { + ScrNetLookupEv *ev = s->lookup_evs; + s->lookup_evs = ev->next; + scr_net_lookup_ev_free(ev); + } +} + +/* Node emits ('lookup', err, address, family, host) from the resolver's + * answer, before it dials: here the resolutions recorded at the dial reach + * the listeners just before 'connect' (or the lookup's 'error'). */ +static void scr_net_lookup_flush(ScrNetSocket *s) { + while (s->lookup_evs && !scr_exc_pending()) { + ScrNetLookupEv *ev = s->lookup_evs; + s->lookup_evs = ev->next; + if (s->lookup_ls.n > 0) { + ScrDyn *args[4]; + if (ev->err_msg != NULL) { + ScrError *e = scr_error_new(SCR_ERR_ERROR, ev->err_msg); + const char *code = strchr(ev->err_msg->data, ' '); + if (code != NULL) { + char buf[32]; + size_t n = strcspn(code + 1, " "); + if (n < sizeof buf) { + memcpy(buf, code + 1, n); + buf[n] = '\0'; + scr_error_set_code(e, buf); + } + } + args[0] = scr_dyn_from_error(e); + scr_error_release(e); + args[1] = scr_dyn_retain(scr_dyn_undefined()); + args[2] = scr_dyn_retain(scr_dyn_undefined()); + } else { + args[0] = scr_dyn_new_null(); + args[1] = scr_dyn_new_str(ev->ip); + args[2] = scr_dyn_new_num((double)ev->family); + } + args[3] = scr_dyn_new_str(ev->host); + ScrNetL *snap; + size_t n = scr_net_ls_snapshot(&s->lookup_ls, &snap); + scr_dyn_this_push(s, SCR_DYNH_NET_SOCKET); + for (size_t i = 0; i < n; i++) { + if (!scr_exc_pending()) { + ScrDyn *fn = scr_dyn_listener_fn(snap[i].cb); + scr_dyn_release(scr_dyn_call(fn, args, 4, "listener")); + scr_dyn_release(fn); + } + scr_closure_release(snap[i].cb); + } + scr_dyn_this_pop(); + free(snap); + for (size_t i = 0; i < 4; i++) scr_dyn_release(args[i]); + } + scr_net_lookup_ev_free(ev); + } +} + +static const ScrNetLookupOps scr_net_lookup_ops_impl = { + &scr_net_lookup_record, + &scr_net_lookup_flush, + &scr_net_lookup_free, +}; + +/* The C main of a program with a 'lookup' listener installs the lookup + * records before any socket dials. */ +void scr_net_lookup_install(void) { scr_net_lookup_ops = &scr_net_lookup_ops_impl; } + +/* socket.on/once('lookup', listener) with a dyn function listener. The + * socket resolved when net.connect ran, before the listener was added; it + * delivers its recorded answers at the connect, like Node's asynchronous + * lookup. */ +void scr_net_sock_on_lookup(ScrNetSocket *s, const ScrDyn *listener /*borrowed*/, bool once) { + if (s->close_emitted) return; + scr_net_ls_add(&s->lookup_ls, scr_dyn_listener_closure_fn(listener, (void *)&scr_net_dyn_fire_arg), + (void *)&scr_net_dyn_fire_arg, once); +} + +/* server.close((err) => ...): the callback runs at 'close' with undefined, + * or with Node's ERR_SERVER_NOT_RUNNING when the server was not listening. */ +static void scr_net_close_not_running_fire(ScrClosure *cb) { + ScrDyn *fn = scr_dyn_listener_fn(cb); + static const char msg[] = "Server is not running."; + ScrStr *text = scr_str_new(msg, sizeof msg - 1); + ScrError *e = scr_error_new(SCR_ERR_ERROR, text); + scr_str_release(text); + scr_error_set_code(e, "ERR_SERVER_NOT_RUNNING"); + ScrDyn *err = scr_dyn_from_error(e); + scr_error_release(e); + scr_dyn_release(scr_dyn_call(fn, &err, 1, "listener")); + scr_dyn_release(err); + scr_dyn_release(fn); +} + +void scr_net_server_close_dyn(ScrNetServer *s, const ScrDyn *listener /*borrowed*/) { + ScrClosure *cb = scr_dyn_listener_closure_fn( + listener, s->listening ? (void *)&scr_dyn_listener_fire0 : (void *)&scr_net_close_not_running_fire); + scr_net_server_close(s, cb); +} + void scr_net_sock_on_write_flush_dyn(ScrNetSocket *s, const ScrDyn *listener /*borrowed*/) { if (s->close_emitted) return; scr_net_ls_add(&s->wcb_ls, scr_dyn_listener_closure_fn(listener, (void *)&scr_net_dyn_fire_arg), @@ -4752,6 +5095,13 @@ static void scr_net_sweep(void) { return; } } + if (sock->lookup_evs != NULL && sock->pending_err) { + scr_net_lookup_ops->flush(sock); + if (scr_exc_pending()) { + scr_net_sock_release(sock); + return; + } + } if (sock->pending_err) { ScrStr *msg = sock->pending_err; sock->pending_err = NULL; diff --git a/packages/runtime/src/scr_net.h b/packages/runtime/src/scr_net.h index f76613a0e..385704654 100644 --- a/packages/runtime/src/scr_net.h +++ b/packages/runtime/src/scr_net.h @@ -164,6 +164,9 @@ void scr_net_server_timeout_option_set(ScrNetServer *s, double field, const stru ScrStr *scr_net_server_addr_ip(ScrNetServer *s); /* +1 — address().address */ ScrStr *scr_net_server_addr_family(ScrNetServer *s); /* +1 — address().family */ void scr_net_server_close(ScrNetServer *s, ScrClosure *cb /*moves, nullable*/); +void scr_net_server_close_dyn(ScrNetServer *s, const ScrDyn *listener /*borrowed*/); +void scr_net_sock_on_lookup(ScrNetSocket *s, const ScrDyn *listener /*borrowed*/, bool once); +void scr_net_lookup_install(void); void scr_net_server_close_all_connections(ScrNetServer *s); void scr_net_server_close_idle_connections(ScrNetServer *s); ScrDyn *scr_net_server_max_connections_get(ScrNetServer *s); /* +1 */ @@ -237,6 +240,11 @@ bool scr_net_sock_readable(ScrNetSocket *s); /* The deferred dial (the http agent's maxSockets queue): the socket * registers "connecting" and buffers writes; dial_start runs the dial. */ ScrNetSocket *scr_net_connect_deferred(double port, ScrStr *host /*borrowed, nullable*/); /* +1 */ +/* A client socket for the Unix socket at `path`, dialed now or (deferred) + * by scr_net_sock_dial_start. A failure is the deferred 'error'. */ +ScrNetSocket *scr_net_connect_path(ScrStr *path /*borrowed*/, bool deferred); /* +1 */ +/* server.listen(path[, cb]): a Unix-socket server, its file removed at close. */ +void scr_net_listen_path(ScrNetServer *s, ScrStr *path /*borrowed*/, ScrClosure *cb /*moves, nullable*/); void scr_net_sock_dial_start(ScrNetSocket *s); void scr_net_sock_set_timeout(ScrNetSocket *s, double ms); void scr_net_sock_on_timeout(ScrNetSocket *s, ScrClosure *cb /*moves*/, bool once); diff --git a/packages/runtime/src/scr_tls.c b/packages/runtime/src/scr_tls.c index 464b19736..804e51d69 100644 --- a/packages/runtime/src/scr_tls.c +++ b/packages/runtime/src/scr_tls.c @@ -1512,6 +1512,74 @@ static ScrBytes *scr_tls_pem_dyn(const ScrDyn *v, const char *what, bool concat) return NULL; } +/* https.request/get with a runtime options record: the http walk, then + * the TLS options the literal https path lowers (rejectUnauthorized, ca, + * servername). Other tls.connect options fence when they carry a value. */ +static const char *const SCR_HTTPS_REQ_FENCED_OPTIONS[] = { + "ALPNProtocols", "cert", "checkServerIdentity", "ciphers", "crl", "dhparam", "ecdhCurve", + "honorCipherOrder", "key", "maxVersion", "minVersion", "passphrase", "pfx", "secureContext", + "secureOptions", "secureProtocol", "session", "sessionIdContext", "sigalgs", NULL, +}; + +ScrHttpClientReq *scr_https_request_dyn(const ScrDyn *opts /*borrowed*/, bool auto_end, + ScrClosure *cb /*moves, nullable*/, ScrHttpRespFn fn, + ScrDyn *(*add_abort)(ScrDyn *signal, ScrDyn *listener)) { + ScrHttpReqOpts o; + if (!scr_http_req_opts_read(opts, true, "https.request", &o)) { + if (cb) scr_closure_release(cb); + return NULL; + } + const ScrDyn *rec = o.view != NULL ? o.view : opts; + bool reject = true; + ScrBytes *ca = NULL; + ScrStr *servername = NULL; + bool ok = true; + if (rec != NULL && rec->kind == SCR_DYN_OBJ) { + for (size_t i = 0; ok && SCR_HTTPS_REQ_FENCED_OPTIONS[i] != NULL; i++) { + const char *key = SCR_HTTPS_REQ_FENCED_OPTIONS[i]; + const ScrDyn *v = scr_dyn_obj_get(rec, key, strlen(key)); + if (v != NULL && scr_tls_dyn_truthy(v)) { + scr_http_req_opts_fence("https.request", key, + "rejectUnauthorized, ca, and servername are the supported TLS options"); + ok = false; + } + } + const ScrDyn *r = ok ? scr_dyn_obj_get(rec, "rejectUnauthorized", 18) : NULL; + if (r != NULL && r->kind == SCR_DYN_BOOL && !r->v.b) reject = false; + const ScrDyn *c = ok ? scr_dyn_obj_get(rec, "ca", 2) : NULL; + if (c != NULL && c->kind != SCR_DYN_UNDEF && c->kind != SCR_DYN_NULL) { + ca = scr_tls_pem_dyn(c, "an https.request 'ca' option", true); + if (ca == NULL) ok = false; + } + const ScrDyn *sn = ok ? scr_dyn_obj_get(rec, "servername", 10) : NULL; + if (sn != NULL && sn->kind != SCR_DYN_UNDEF && sn->kind != SCR_DYN_NULL) { + if (sn->kind != SCR_DYN_STR) { + scr_dyn_prop_type_fail("options.servername", "of type string", sn); + ok = false; + } else { + servername = scr_str_retain(sn->v.str); + } + } + } + if (!ok) { + if (ca) scr_bytes_release(ca); + scr_http_req_opts_free(&o); + if (cb) scr_closure_release(cb); + return NULL; + } + const char *ca_data = ca ? (const char *)ca->data : NULL; + size_t ca_len = ca ? ca->len : 0; + ScrTlsCli *cli = scr_tls_cli_for(o.host, reject, ca_data, ca_len, servername); + ScrStr *key = scr_tls_pool_key(o.host, reject, ca_data, ca_len, servername); + scr_str_release(servername); + ScrHttpClientReq *req = scr_http_req_opts_send(&o, auto_end, cb, fn, add_abort, + &scr_tls_cli_wrap, cli, key); + scr_str_release(key); + if (ca) scr_bytes_release(ca); + scr_http_req_opts_free(&o); + return req; +} + /* The literal path's runtime-valued cert/key extraction (the compiler's * tls.pemDyn libCall): `what` arrives precomposed. undefined throws — the * literal walk required the member's presence. */ diff --git a/tests/corpus/6440-http-socket-path.ts b/tests/corpus/6440-http-socket-path.ts new file mode 100644 index 000000000..f782cf2a4 --- /dev/null +++ b/tests/corpus/6440-http-socket-path.ts @@ -0,0 +1,109 @@ +// HTTP over a Unix domain socket: http.createServer().listen(path) and +// http.request / http.get with a socketPath option, from an object literal, +// from a RequestOptions variable, and from a literal that spreads one. The +// Host header defaults to localhost. A missing socket fails the request +// with ENOENT, a second listen on a bound path fails with EADDRINUSE, and +// closing the server removes the socket file. +import * as http from "node:http"; +import { existsSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; + +const sock = join(tmpdir(), `scriptc-http-${process.pid}.sock`); +const shown = (text: string): string => text.split(sock).join(""); + +const server = http.createServer((req, res) => { + let body = ""; + req.on("data", (chunk: Buffer) => { + body += chunk.toString(); + }); + req.on("end", () => { + res.setHeader("content-type", "text/plain"); + res.end(`${req.method} ${req.url} host=${req.headers.host} x=${req.headers["x-n"]} body=${body}`); + }); +}); + +function collect(res: http.IncomingMessage, label: string, done: () => void): void { + let text = ""; + res.on("data", (chunk: Buffer) => { + text += chunk.toString(); + }); + res.on("end", () => { + console.log(label, res.statusCode, res.headers["content-type"], text); + done(); + }); +} + +function literal(): Promise { + return new Promise((resolve) => { + const req = http.request( + { socketPath: sock, path: "/literal", method: "POST", headers: { "x-n": "1" } }, + (res) => collect(res, "literal", resolve), + ); + req.end("one"); + }); +} + +const base: http.RequestOptions = { socketPath: sock }; + +function fromVariable(): Promise { + return new Promise((resolve) => { + const headers: Record = { "x-n": 2, "content-type": "text/plain" }; + const options: http.RequestOptions = { ...base, path: "/variable", method: "PUT", headers, timeout: 5000 }; + const req = http.request(options, (res) => collect(res, "variable", resolve)); + req.write("two"); + req.end(); + }); +} + +function spread(): Promise { + return new Promise((resolve) => { + const req = http.request({ ...base, path: "/spread", method: "POST", headers: { "x-n": "3" } }, (res) => + collect(res, "spread", resolve), + ); + req.end("three"); + }); +} + +function get(): Promise { + return new Promise((resolve) => { + const options: http.RequestOptions = { socketPath: sock, path: "/get?q=4" }; + http.get(options, (res) => collect(res, "get", resolve)); + }); +} + +function missing(): Promise { + return new Promise((resolve) => { + const options: http.RequestOptions = { socketPath: `${sock}.missing`, path: "/" }; + const req = http.request(options, () => console.log("unexpected response")); + req.on("error", (err: NodeJS.ErrnoException) => { + console.log("missing", err.code, shown(err.message)); + resolve(); + }); + req.end(); + }); +} + +function busy(): Promise { + return new Promise((resolve) => { + const other = http.createServer(); + other.on("error", (err: NodeJS.ErrnoException) => { + console.log("busy", err.code, shown(err.message)); + resolve(); + }); + other.listen(sock); + }); +} + +server.listen(sock, async () => { + console.log("listening", existsSync(sock)); + await literal(); + await fromVariable(); + await spread(); + await get(); + await missing(); + await busy(); + server.close(() => { + console.log("closed", existsSync(sock)); + }); +}); diff --git a/tests/corpus/6441-http-request-options-record.ts b/tests/corpus/6441-http-request-options-record.ts new file mode 100644 index 000000000..f2eba1a02 --- /dev/null +++ b/tests/corpus/6441-http-request-options-record.ts @@ -0,0 +1,68 @@ +// http.request options read at run time: a RequestOptions value chosen by +// a runtime condition (a TCP endpoint with a string port, or a socket +// path), spread into an options object with a timeout and an optional +// signal; agent: false; an aborted signal; and Node's validation errors +// for the method, port, host, timeout, path, protocol and agent options. +import * as http from "node:http"; + +const server = http.createServer((req, res) => { + const host = (req.headers.host ?? "").replace(/:\d+$/, ":"); + res.end(`${req.method} ${req.url} host=${host} connection=${req.headers.connection} n=${req.headers["x-n"]}`); +}); + +function send(label: string, options: http.RequestOptions): Promise { + return new Promise((resolve) => { + const req = http.request(options, (res) => { + let text = ""; + res.on("data", (chunk: Buffer) => { + text += chunk.toString(); + }); + res.on("end", () => { + console.log(label, res.statusCode, text); + resolve(); + }); + }); + req.on("error", (err: NodeJS.ErrnoException) => { + console.log(label, "error", err.name, err.code, err.message); + resolve(); + }); + req.end(); + }); +} + +function invalid(label: string, options: http.RequestOptions): void { + try { + const req = http.request(options); + req.on("error", () => {}); + req.destroy(); + console.log(label, "accepted"); + } catch (err) { + const e = err as NodeJS.ErrnoException; + console.log(label, e.name, e.code, e.message); + } +} + +server.listen(0, "127.0.0.1", async () => { + const port = (server.address() as { port: number }).port; + const endpoint = process.env.NO_SUCH_ENDPOINT_VAR ?? `http://127.0.0.1:${port}`; + const url = new URL(endpoint); + const location: http.RequestOptions = process.env.NO_SUCH_SOCKET_VAR + ? { socketPath: process.env.NO_SUCH_SOCKET_VAR } + : { port: url.port, host: url.hostname }; + const headers: Record = { "x-n": 1 }; + const controller: AbortController | undefined = undefined; + const options = { ...location, path: "/rpc", method: "post", headers, timeout: 2000, signal: controller }; + await send("spread", options); + await send("agent false", { ...location, path: "/close", agent: false }); + const aborted = new AbortController(); + aborted.abort(); + await send("aborted", { ...location, path: "/never", signal: aborted.signal }); + invalid("method", { ...location, method: "g t" }); + invalid("port", { host: "127.0.0.1", port: "http" }); + invalid("host", { hostname: 5 } as unknown as http.RequestOptions); + invalid("timeout", { ...location, timeout: -1 }); + invalid("path", { ...location, path: "/a b" }); + invalid("protocol", { ...location, protocol: "https:" }); + invalid("agent", { ...location, agent: 5 } as unknown as http.RequestOptions); + server.close(); +}); diff --git a/tests/corpus/6442-https-request-options-record.ts b/tests/corpus/6442-https-request-options-record.ts new file mode 100644 index 000000000..4148e015b --- /dev/null +++ b/tests/corpus/6442-https-request-options-record.ts @@ -0,0 +1,61 @@ +// https.request with options read at run time: a RequestOptions value +// spread with the CA, over TCP and over a Unix socket, an unverifiable +// certificate with and without rejectUnauthorized, and the protocol check. +import * as https from "node:https"; +import { readFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; + +const ca = readFileSync("tests/fixtures/server/certs/ca.pem"); +const sock = join(tmpdir(), `scriptc-https-${process.pid}.sock`); + +const server = https.createServer( + { + cert: readFileSync("tests/fixtures/server/certs/localhost.pem"), + key: readFileSync("tests/fixtures/server/certs/localhost-key.pem"), + }, + (req, res) => res.end(`${req.method} ${req.url} host=${(req.headers.host ?? "").replace(/:\d+$/, ":")}`), +); +const unix = https.createServer( + { + cert: readFileSync("tests/fixtures/server/certs/localhost.pem"), + key: readFileSync("tests/fixtures/server/certs/localhost-key.pem"), + }, + (req, res) => res.end(`unix ${req.method} ${req.url} host=${req.headers.host}`), +); + +function send(label: string, options: https.RequestOptions): Promise { + return new Promise((resolve) => { + const req = https.request(options, (res) => { + let body = ""; + res.on("data", (c: Buffer) => (body += c.toString())); + res.on("end", () => { + console.log(label, res.statusCode, body); + resolve(); + }); + }); + req.on("error", (err: NodeJS.ErrnoException) => { + console.log(label, "error", err.code); + resolve(); + }); + req.end(); + }); +} + +server.listen(0, () => { + const port = (server.address() as { port: number }).port; + unix.listen(sock, async () => { + const tcp: https.RequestOptions = { host: "localhost", port }; + await send("tcp", { ...tcp, path: "/a", ca, agent: false }); + await send("unix", { socketPath: sock, path: "/b", ca, agent: false }); + await send("unverified", { ...tcp, path: "/c", agent: false }); + await send("unchecked", { ...tcp, path: "/d", rejectUnauthorized: false, agent: false }); + try { + https.request({ ...tcp, protocol: "http:" }); + } catch (err) { + console.log("protocol", (err as NodeJS.ErrnoException).code, (err as Error).message); + } + server.close(); + unix.close(); + }); +}); diff --git a/tests/corpus/6443-net-lookup-http-close-text.ts b/tests/corpus/6443-net-lookup-http-close-text.ts new file mode 100644 index 000000000..1d50d3580 --- /dev/null +++ b/tests/corpus/6443-net-lookup-http-close-text.ts @@ -0,0 +1,73 @@ +// A socket's 'lookup' event for a host name (none for an IP address), +// string 'data' listeners after setEncoding on both sides of an HTTP +// exchange, res.end() of a string or undefined, and the close callback of +// a server that never listened. +import * as http from "node:http"; +import * as net from "node:net"; + +function lookups(): Promise { + return new Promise((resolve) => { + const tcp = net.createServer((s) => s.end("hi")); + tcp.listen(0, "127.0.0.1", () => { + const port = (tcp.address() as net.AddressInfo).port; + const named = net.connect({ host: "localhost", port }); + let reported = false; + named.on("lookup", (err: Error | null, address: string, family: number, host: string) => { + // macOS also answers ::1; report the IPv4 answer the dial uses. + if (family === 4 && !reported) { + reported = true; + console.log("lookup", err, address, family, host); + } + }); + named.once("connect", () => console.log("connect after lookup", reported)); + named.on("data", () => {}); + named.on("close", () => { + const direct = net.connect({ host: "127.0.0.1", port }); + direct.once("lookup", () => console.log("unexpected lookup")); + direct.once("connect", () => console.log("connect without lookup")); + direct.on("data", () => {}); + direct.on("close", () => tcp.close(() => resolve())); + }); + }); + }); +} + +const server = http.createServer((req, res) => { + req.setEncoding("utf8"); + const chunks: string[] = []; + req.on("data", (chunk: string) => chunks.push(chunk)); + req.on("end", () => { + const body = chunks.join(""); + res.writeHead(200, { "content-type": "text/plain" }); + const payload = body ? body.toUpperCase() : undefined; + res.end(payload); + }); +}); + +function post(port: number, body: string): Promise { + return new Promise((resolve) => { + const req = http.request({ host: "127.0.0.1", port, method: "POST", path: "/" }, (res) => { + res.setEncoding("utf8"); + let text = ""; + res.on("data", (part: string) => { + text += part; + }); + res.on("end", () => { + console.log("response", JSON.stringify(body), "->", JSON.stringify(text)); + resolve(); + }); + }); + req.end(body); + }); +} + +lookups().then(() => { + server.listen(0, "127.0.0.1", async () => { + const port = (server.address() as net.AddressInfo).port; + await post(port, "hello"); + await post(port, ""); + server.close(() => console.log("closed")); + const idle = http.createServer(); + idle.close(() => console.log("never listened closed")); + }); +}); diff --git a/tests/fixtures/node-types/fetch-init-members.ts b/tests/fixtures/node-types/fetch-init-members.ts new file mode 100644 index 000000000..371941e5a --- /dev/null +++ b/tests/fixtures/node-types/fetch-init-members.ts @@ -0,0 +1,68 @@ +// Options derived from RequestInit under @types/node: an interface that +// extends Omit holds fetch's BodyInit and HeadersInit +// members, which are destructured, narrowed and passed back into fetch, +// with the remaining members carried as an Omit<...> of RequestInit. +import * as http from "node:http"; +import type { AddressInfo } from "node:net"; + +type PassthroughRequestInit = Omit; +type FetchBody = NonNullable; + +interface PostOptions extends PassthroughRequestInit { + timeoutMs?: number; +} + +function describe(body: FetchBody | null | undefined): string { + return typeof body === "string" ? `text(${body.length})` : body == null ? "none" : "binary or form"; +} + +async function send(params: { + port: number; + method: string; + body: FetchBody | null | undefined; + headers: RequestInit["headers"]; + init: Omit; + timeoutMs: number; +}): Promise { + const { port, method, body, headers, init, timeoutMs } = params; + const fetchInit: RequestInit = { + ...init, + method, + headers, + body: body ?? undefined, + signal: AbortSignal.timeout(timeoutMs), + }; + return fetch(`http://127.0.0.1:${port}/`, fetchInit); +} + +async function post(port: number, options: PostOptions = {}): Promise { + const { timeoutMs, body, method, headers, ...rest } = options; + const res = await send({ + port, + method: method ?? "POST", + body, + headers, + init: rest, + timeoutMs: timeoutMs ?? 5000, + }); + return `${describe(body)} -> ${res.status} ${await res.text()}`; +} + +const server = http.createServer((req, res) => { + let data = ""; + req.on("data", (chunk: Buffer) => { + data += chunk.toString(); + }); + req.on("end", () => { + res.end(`${req.method} type=${req.headers["content-type"]} x=${req.headers["x-n"]} body=${data}`); + }); +}); + +server.listen(0, "127.0.0.1", async () => { + const port = (server.address() as AddressInfo).port; + console.log(await post(port, { body: "plain", headers: { "x-n": "1" } })); + console.log(await post(port, { body: new URLSearchParams("a=1&b=2"), headers: [["x-n", "2"]] })); + console.log(await post(port, { body: Buffer.from("bytes"), method: "PUT", timeoutMs: 2000 })); + console.log(await post(port, { method: "DELETE", headers: new Headers({ "x-n": "4" }) })); + server.close(); +}); diff --git a/tests/fixtures/node-types/http-socket-path.ts b/tests/fixtures/node-types/http-socket-path.ts new file mode 100644 index 000000000..1b7ff96b5 --- /dev/null +++ b/tests/fixtures/node-types/http-socket-path.ts @@ -0,0 +1,68 @@ +// An HTTP client that reaches its API over a Unix socket unless an +// endpoint URL is configured, typed by @types/node: the location is an +// http.RequestOptions chosen at run time, spread into each request's +// options with a path, method, headers, timeout and an optional signal. +// server.close callbacks take Node's ERR_SERVER_NOT_RUNNING error. +import * as http from "node:http"; +import { existsSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; + +const socketPath: string = join(tmpdir(), `scriptc-api-${process.pid}.sock`); +const endpoint = process.env.NO_SUCH_API_ENDPOINT_VAR ? new URL(process.env.NO_SUCH_API_ENDPOINT_VAR) : undefined; +const location: http.RequestOptions = endpoint + ? { port: endpoint.port, host: endpoint.hostname } + : { socketPath }; + +function call(path: string, body: string, timeout: number, ac?: AbortController): Promise { + return new Promise((resolve, reject) => { + const headers: Record = { + "content-type": "application/json", + "content-length": Buffer.byteLength(body), + }; + const options = { + ...location, + path, + method: "POST", + headers, + timeout, + signal: ac?.signal, + }; + const request = http.request(options, (res) => { + let data = ""; + res.on("data", (part: Buffer) => { + data += part.toString(); + }); + res.on("error", reject); + res.on("end", () => resolve(`${res.statusCode} ${data}`)); + }); + request.on("error", reject); + request.on("timeout", () => request.destroy(new Error("timed out"))); + request.end(body); + }); +} + +const server = http.createServer((req, res) => { + let body = ""; + req.on("data", (part: Buffer) => { + body += part.toString(); + }); + req.on("end", () => { + res.end(`${req.method} ${req.url} host=${req.headers.host} body=${body}`); + }); +}); + +server.listen(socketPath, async () => { + console.log(await call("/v1/start", JSON.stringify({ id: 1 }), 5000)); + console.log(await call("/v1/status", JSON.stringify({ id: 2 }), 5000, new AbortController())); + try { + const ac = new AbortController(); + ac.abort(); + await call("/v1/never", "{}", 5000, ac); + } catch (err) { + console.log("aborted", (err as Error).name); + } + server.close((err) => console.log("closed", err === undefined, existsSync(socketPath))); + server.close((err) => console.log("closed again", err?.name, (err as NodeJS.ErrnoException)?.code, err?.message)); + http.createServer().close((err) => console.log("never listened", (err as NodeJS.ErrnoException)?.code)); +}); diff --git a/tests/harness/project-config.test.ts b/tests/harness/project-config.test.ts index 1479cea1a..ea8cbf79b 100644 --- a/tests/harness/project-config.test.ts +++ b/tests/harness/project-config.test.ts @@ -422,6 +422,44 @@ test("node-types: util.promisify over typed callback-last functions settles like expect(comparableStderr(native.stderr)).toBe(node.stderr); }); +test("node-types: http.request over a Unix socket with RequestOptions read at run time", async () => { + const outDir = outDirFor("http-socket-path"); + const entry = join(nodeTypesDir, "http-socket-path.ts"); + const result = await compile(entry, { + outPath: join(outDir, "http-socket-path"), + outDir, + sanitize, + }); + expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true); + if (!result.ok) return; + const native = await execFileAsync(result.binaryPath); + const node = await execFileAsync(process.execPath, [ + "--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", + entry, + ]); + expect(native.stdout).toBe(node.stdout); + expect(comparableStderr(native.stderr)).toBe(node.stderr); +}); + +test("node-types: RequestInit-derived options carry BodyInit and HeadersInit into fetch", async () => { + const outDir = outDirFor("fetch-init-members"); + const entry = join(nodeTypesDir, "fetch-init-members.ts"); + const result = await compile(entry, { + outPath: join(outDir, "fetch-init-members"), + outDir, + sanitize, + }); + expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true); + if (!result.ok) return; + const native = await execFileAsync(result.binaryPath); + const node = await execFileAsync(process.execPath, [ + "--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", + entry, + ]); + expect(native.stdout).toBe(node.stdout); + expect(comparableStderr(native.stderr)).toBe(node.stderr); +}); + test("node-types: stream subclasses extend the @types/node stream classes", async () => { const outDir = outDirFor("stream-subclass-hooks"); const entry = join(nodeTypesDir, "stream-subclass-hooks.ts"); From 1e96179f3a4e6b81d881688bc4250168fffd662c Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Tue, 20 Oct 2026 03:13:15 -0700 Subject: [PATCH 073/122] Carry untyped streams, casts, for await and pipeline stages into the general stream representation An 'unknown' value flowing or cast into a NodeJS.ReadableStream slot and 'for await' over one now select the general representation, where the checked cast to Readable applies. pipeline() stages that arrive untyped unwrap boxed native streams at run time. @types/node's zlib interface declarations map to the runtime Transform. Co-Authored-By: Claude Opus 5.5 --- .../ambient/scriptc-node-fallback.d.ts | 1 + .../compiler/src/backend/llvm/lib-shared.ts | 1 + .../lowering/expressions/narrowing.ts | 4 ++ .../src/frontend/lowering/general-streams.ts | 4 +- .../src/frontend/lowering/lower-stmts.ts | 3 ++ .../src/frontend/lowering/lower-stream.ts | 18 +++++++ packages/compiler/src/frontend/type-mapper.ts | 3 +- packages/compiler/src/ir/builtin-effects.ts | 1 + .../compiler/src/ir/builtin-signatures.ts | 8 +++ packages/compiler/src/ir/runtime-features.ts | 1 + packages/runtime/src/scr_stream.c | 17 +++++++ packages/runtime/src/scr_stream.h | 3 ++ .../6467-node-stream-untyped-sources/main.ts | 51 +++++++++++++++++++ .../sources.js | 20 ++++++++ 14 files changed, 133 insertions(+), 2 deletions(-) create mode 100644 tests/corpus/6467-node-stream-untyped-sources/main.ts create mode 100644 tests/corpus/6467-node-stream-untyped-sources/sources.js diff --git a/packages/compiler/ambient/scriptc-node-fallback.d.ts b/packages/compiler/ambient/scriptc-node-fallback.d.ts index 037801338..3885e0fef 100644 --- a/packages/compiler/ambient/scriptc-node-fallback.d.ts +++ b/packages/compiler/ambient/scriptc-node-fallback.d.ts @@ -107,6 +107,7 @@ declare namespace NodeJS { isPaused(): boolean; pipe(destination: T, options?: { end?: boolean }): T; unpipe(destination?: WritableStream): this; + [Symbol.asyncIterator](): AsyncIterableIterator; } /* The writable-stream interface every Node stream implements: * process.stdout/process.stderr, fs.WriteStream and the node:stream diff --git a/packages/compiler/src/backend/llvm/lib-shared.ts b/packages/compiler/src/backend/llvm/lib-shared.ts index 61d31435c..4602a2bdd 100644 --- a/packages/compiler/src/backend/llvm/lib-shared.ts +++ b/packages/compiler/src/backend/llvm/lib-shared.ts @@ -727,6 +727,7 @@ export const LIB_FN_SYMS: Record = { "child.ref": "scr_child_ref", "procStream.write": "scr_proc_stream_write", "stream.stdioWritable": "scr_stream_stdio_writable", + "stream.unboxDyn": "scr_stream_unbox_dyn", "process.kill": "scr_process_kill_named", "process.killNum": "scr_process_kill", // fs.promises (scr_bytes_io.c / scr_lib.c): the promise forms settle diff --git a/packages/compiler/src/frontend/lowering/expressions/narrowing.ts b/packages/compiler/src/frontend/lowering/expressions/narrowing.ts index b54da8180..6b75755b1 100644 --- a/packages/compiler/src/frontend/lowering/expressions/narrowing.ts +++ b/packages/compiler/src/frontend/lowering/expressions/narrowing.ts @@ -26,6 +26,7 @@ import { jsBindingHasOpenWrites } from "../lower-stmts.js"; import { narrowClassUnion, narrowStoredClassValue, structuralNarrowing } from "../class-unions.js"; import { checkedClassAssertion, checkedClassUnionAssertion } from "../class-assertions.js"; import { isOptionalProcessStreamProperty } from "../builtins/process.js"; +import { requestGeneralStreamSurface } from "../general-streams.js"; import { literalValues } from "../../literal-values.js"; import { literalUnionArm } from "../../union-discriminants.js"; import { checkedArmExtraction } from "../coercions/unions.js"; @@ -1450,6 +1451,9 @@ export function lowerAsExpression( if (DYN_HANDLE_KINDS.has(target.kind)) { return { kind: "dynCheck", value: inner, type: target, loc: locOf(expr) }; } + // `value as NodeJS.ReadableStream`: the general stream classes take + // the checked cast (general-streams.ts). + requestGeneralStreamSurface(lowerer, expr); lowerer.unsupported( "SC1090", expr, diff --git a/packages/compiler/src/frontend/lowering/general-streams.ts b/packages/compiler/src/frontend/lowering/general-streams.ts index 98993798c..0113a9958 100644 --- a/packages/compiler/src/frontend/lowering/general-streams.ts +++ b/packages/compiler/src/frontend/lowering/general-streams.ts @@ -61,7 +61,8 @@ function isGeneralStream(lowerer: Lowerer, part: IrType): boolean { /** A mismatched flow (requireExactShape's input) that the general stream * representation resolves: a general stream into a stdio-represented - * slot, or a child pipe handle into an 'unknown' slot. */ + * slot, a child pipe handle into an 'unknown' slot, or an 'unknown' value + * into a child-pipe slot (the general classes take a checked cast). */ export function needsGeneralNodeStreams( lowerer: Lowerer, actual: IrType, @@ -70,6 +71,7 @@ export function needsGeneralNodeStreams( const stdio = (part: IrType): boolean => STDIO_STREAM_KINDS.has(part.kind); const pipeHandle = (part: IrType): boolean => part.kind === "childStream" || part.kind === "childWriter"; + if (actual.kind === "dyn") return containsPart(lowerer, expected, pipeHandle); if (containsPart(lowerer, expected, stdio)) return containsPart(lowerer, actual, (p) => isGeneralStream(lowerer, p)); return expected.kind === "dyn" && containsPart(lowerer, actual, pipeHandle); diff --git a/packages/compiler/src/frontend/lowering/lower-stmts.ts b/packages/compiler/src/frontend/lowering/lower-stmts.ts index 064cd7a7b..afc329f40 100644 --- a/packages/compiler/src/frontend/lowering/lower-stmts.ts +++ b/packages/compiler/src/frontend/lowering/lower-stmts.ts @@ -1,4 +1,5 @@ import { hostsAll } from "./expressions/hosted-narrowing.js"; +import { requestGeneralStreamSurface } from "./general-streams.js"; import { matchAllRowsReadByElements, storedMatchAllRowsReadByElements, @@ -8916,6 +8917,8 @@ export function lowerForOf(lowerer: Lowerer, stmt: ts.ForOfStatement): IrStmt { } const builtin = lowerForAwaitBuiltin(lowerer, stmt); if (builtin) return builtin; + // A NodeJS.ReadableStream iterates as the general Readable. + requestGeneralStreamSurface(lowerer, stmt.expression); lowerer.unsupported( "SC1070", stmt, diff --git a/packages/compiler/src/frontend/lowering/lower-stream.ts b/packages/compiler/src/frontend/lowering/lower-stream.ts index c73945d2b..400fd79ac 100644 --- a/packages/compiler/src/frontend/lowering/lower-stream.ts +++ b/packages/compiler/src/frontend/lowering/lower-stream.ts @@ -2045,6 +2045,24 @@ function lowerStreamArg( const v = isHashValueType(lowered.type) ? hashStreamOf(lowered, what === "pipeline" || what === "compose") : lowered; + // A checked-dynamic pipeline stage (an untyped source or destination): + // a boxed native stream unwraps at run time; other values (iterables, + // functions) throw the not-supported Error there. + if (v.type.kind === "dyn" && what === "pipeline") { + return { + kind: "libCall", + fn: "stream.unboxDyn", + args: [ + v, + strLit( + "pipeline stages other than native streams (iterables, generators, plain functions, and { readable, writable } pairs) are not supported yet", + locOf(node), + ), + ], + type: { kind: "object", className: "%Duplex" }, + loc: locOf(node), + }; + } const info = v.type.kind === "object" ? lowerer.classes.get(v.type.className) : undefined; if (!streamSidesOf(lowerer, info)) { // A provably-non-stream value in a JS source (the invalid-input diff --git a/packages/compiler/src/frontend/type-mapper.ts b/packages/compiler/src/frontend/type-mapper.ts index 08e603211..7acb539d1 100644 --- a/packages/compiler/src/frontend/type-mapper.ts +++ b/packages/compiler/src/frontend/type-mapper.ts @@ -3129,7 +3129,8 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { ZLIB_STREAM_MODES.has(psym.name) && psym.declarations.some( (d) => - ts.isClassDeclaration(d) && + (ts.isClassDeclaration(d) || + (ts.isInterfaceDeclaration(d) && isNodeTypesPath(d.getSourceFile().fileName))) && ctx.isStdlibFile(d.getSourceFile()) && isDeclaredInAmbientModule(d, "zlib"), ) diff --git a/packages/compiler/src/ir/builtin-effects.ts b/packages/compiler/src/ir/builtin-effects.ts index 2973a9aa2..1c390bbe4 100644 --- a/packages/compiler/src/ir/builtin-effects.ts +++ b/packages/compiler/src/ir/builtin-effects.ts @@ -76,6 +76,7 @@ export const MAY_THROW_LIB_FNS: ReadonlySet = new Set([ "process.stderrWriteBytes", "procStream.write", "child.stdioStream", + "stream.unboxDyn", "weakMap.new", "weakSet.new", "dyn.fromEntries", diff --git a/packages/compiler/src/ir/builtin-signatures.ts b/packages/compiler/src/ir/builtin-signatures.ts index 2e4faedb4..597076cb1 100644 --- a/packages/compiler/src/ir/builtin-signatures.ts +++ b/packages/compiler/src/ir/builtin-signatures.ts @@ -2563,6 +2563,14 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ /** process.stdout/stderr flowing into a runtime Writable slot under the * general stream representation: an fd-backed Writable (+1) whose * writes take process.stdout.write's synchronous path. */ + /** A pipeline() stage that is a checked-dynamic value: a boxed native + * stream unwraps by identity (+1); anything else throws the given + * not-supported message. Typed as a Duplex so every stage position + * accepts it; the runtime pipeline checks the stream's real sides. */ + "stream.unboxDyn": { + argTypes: [DYN, STRING], + result: { kind: "object", className: "%Duplex" }, + }, "stream.stdioWritable": { argTypes: [PROCSTREAM_T], result: { kind: "object", className: "%Writable" }, diff --git a/packages/compiler/src/ir/runtime-features.ts b/packages/compiler/src/ir/runtime-features.ts index 572a93273..99ba76f79 100644 --- a/packages/compiler/src/ir/runtime-features.ts +++ b/packages/compiler/src/ir/runtime-features.ts @@ -164,6 +164,7 @@ const STREAM_LIB_FNS: ReadonlySet = new Set([ "stream.errored", "stream.prop", "stream.stdioWritable", + "stream.unboxDyn", "stream.workerStdio", ]); diff --git a/packages/runtime/src/scr_stream.c b/packages/runtime/src/scr_stream.c index 3b2f7c8f6..1c5e339a0 100644 --- a/packages/runtime/src/scr_stream.c +++ b/packages/runtime/src/scr_stream.c @@ -6678,3 +6678,20 @@ ScrStream *scr_stream_stdio_writable(double fd) { return scr_stream_new_writable(-1, true, true, write, scr_stream_stdio_write, NULL, NULL, NULL, NULL); } + +/* A pipeline() stage that arrives as a checked-dynamic value (the + * stream.unboxDyn libCall): a boxed native stream unwraps by identity + * (+1); any other value throws the compiled program's not-supported + * message (Node also takes iterables and functions there). */ +ScrStream *scr_stream_unbox_dyn(const ScrDyn *value, const ScrStr *message) { + if (value->kind == SCR_DYN_TYPED_REF && value->v.typed_ref.type_key_len > 7 && + !memcmp(value->v.typed_ref.type_key, "object:", 7)) { + ScrStream *stream = scr_dyn_typed_ref_unbox(value); + const ScrVt *vt = stream->vt; + if ((vt->pre >= scr_readable_vt.pre && vt->pre <= scr_readable_vt.post) || + (vt->pre >= scr_writable_vt.pre && vt->pre <= scr_writable_vt.post)) return stream; + value->v.typed_ref.release(stream); + } + scr_throw_error_msg_code(SCR_ERR_ERROR, message->data, message->len, "SC2020"); + return NULL; +} diff --git a/packages/runtime/src/scr_stream.h b/packages/runtime/src/scr_stream.h index f6c3c24e5..63ed0c28a 100644 --- a/packages/runtime/src/scr_stream.h +++ b/packages/runtime/src/scr_stream.h @@ -130,6 +130,9 @@ ScrStream *scr_stream_new_writable_dyn(struct ScrDyn *opts); * Writable (fd 1 or 2). */ ScrStream *scr_stream_child_stdio(void *child, const char *key, size_t key_len); ScrStream *scr_stream_stdio_writable(double fd); +/* A boxed native stream out of a checked-dynamic pipeline stage (+1), or + * NULL with `message` thrown as the not-supported Error. */ +ScrStream *scr_stream_unbox_dyn(const struct ScrDyn *value, const ScrStr *message); ScrStream *scr_stream_new_duplex_dyn(struct ScrDyn *opts); ScrStream *scr_stream_new_transform_dyn(struct ScrDyn *opts); ScrStream *scr_stream_new_passthrough_dyn(struct ScrDyn *opts); diff --git a/tests/corpus/6467-node-stream-untyped-sources/main.ts b/tests/corpus/6467-node-stream-untyped-sources/main.ts new file mode 100644 index 000000000..bab7e8cc7 --- /dev/null +++ b/tests/corpus/6467-node-stream-untyped-sources/main.ts @@ -0,0 +1,51 @@ +// Streams that arrive untyped meet NodeJS stream interface slots: an +// untyped JavaScript function's stream returned as NodeJS.ReadableStream, +// a checked cast from 'unknown', pipeline() over untyped source and +// destination stages, and 'for await' over a NodeJS.ReadableStream +// parameter fed by a file stream and a child's stdout. +import { spawn } from "node:child_process"; +import * as fs from "node:fs"; +import * as os from "node:os"; +import * as path from "node:path"; +import { pipeline } from "node:stream/promises"; +import * as zlib from "node:zlib"; +import { collector, joined, openUntyped } from "./sources.js"; + +function open(file: string): NodeJS.ReadableStream { + return openUntyped(file); +} + +function held(file: string): unknown { + return fs.createReadStream(file); +} + +async function collect(label: string, src: NodeJS.ReadableStream): Promise { + let text = ""; + for await (const chunk of src) text += chunk.toString(); + console.log(label, JSON.stringify(text)); +} + +async function main(): Promise { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "scriptc-node-stream-untyped-")); + const plain = path.join(dir, "plain.txt"); + const packed = path.join(dir, "packed.gz"); + fs.writeFileSync(plain, "plain text\n"); + fs.writeFileSync(packed, zlib.gzipSync(Buffer.from("packed text\n"))); + + await collect("returned", open(plain)); + await collect("cast", held(plain) as NodeJS.ReadableStream); + const child = spawn("sh", ["-c", "printf 'child text\\n'"], { stdio: ["ignore", "pipe", "inherit"] }); + if (child.stdout) await collect("child", child.stdout); + + const sink = collector(); + await pipeline(openUntyped(packed), zlib.createGunzip(), sink); + console.log("pipeline", JSON.stringify(joined())); + try { + await pipeline(openUntyped(path.join(dir, "missing.gz")), zlib.createGunzip(), collector()); + } catch (err) { + console.log("pipeline error", (err as NodeJS.ErrnoException).code); + } + fs.rmSync(dir, { recursive: true }); +} + +void main(); diff --git a/tests/corpus/6467-node-stream-untyped-sources/sources.js b/tests/corpus/6467-node-stream-untyped-sources/sources.js new file mode 100644 index 000000000..ce4b5dcb7 --- /dev/null +++ b/tests/corpus/6467-node-stream-untyped-sources/sources.js @@ -0,0 +1,20 @@ +import * as fs from "node:fs"; +import { PassThrough } from "node:stream"; + +const parts = []; + +/** @returns {any} */ +export function openUntyped(file) { + return fs.createReadStream(file); +} + +/** @returns {any} */ +export function collector() { + const sink = new PassThrough(); + sink.on("data", (chunk) => parts.push(chunk.toString("utf8"))); + return sink; +} + +export function joined() { + return parts.splice(0).join(""); +} From 51b001444fee6bb48dc466eac166d22285af6659 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Tue, 20 Oct 2026 05:15:24 -0500 Subject: [PATCH 074/122] Count a computed key as a stack read only when it can be "stack" A program that spelled "stack" anywhere treated every computed-key read on a checked-dynamic value as a possible read of an error's stack, so it kept a source frame in every function. The key of such a read now counts only when it can be "stack": a string literal answers by its value; a number, bigint or boolean key, or one converted from such a value, cannot be "stack"; an immutable local answers with its one initializer, and a parameter of a function the program only calls by name answers with the arguments its call sites pass. Any other key counts when the program spells "stack", as before. --- .../src/backend/stack-observations.test.ts | 49 ++++++ .../src/backend/stack-observations.ts | 142 +++++++++++++++--- 2 files changed, 169 insertions(+), 22 deletions(-) diff --git a/packages/compiler/src/backend/stack-observations.test.ts b/packages/compiler/src/backend/stack-observations.test.ts index 0e1178ce9..4a5a5de13 100644 --- a/packages/compiler/src/backend/stack-observations.test.ts +++ b/packages/compiler/src/backend/stack-observations.test.ts @@ -84,6 +84,55 @@ test("a computed key reads the stack when the program spells it", () => { expect(observesStacks(mod([fn("%main", [computed])]))).toBe(false); }); +test('a computed key that cannot be "stack" reads no stack', () => { + const value: IrExpr = { loc, kind: "varRef", localId: "d", type: DYN }; + const spelled: IrStmt = { + loc, + kind: "exprStmt", + expr: { loc, kind: "strLit", value: "stack", type: STRING }, + }; + const read = (key: IrExpr): IrStmt => ({ + loc, + kind: "exprStmt", + expr: { loc, kind: "dynKeyGet", value, key, type: DYN }, + }); + const index: IrExpr = { loc, kind: "varRef", localId: "i", type: F64 }; + const indexText: IrExpr = { loc, kind: "toString", operand: index, type: STRING }; + // A number key, and a key converted from one. + expect(observesStacks(mod([fn("%main", [read(index), spelled])]))).toBe(false); + expect(observesStacks(mod([fn("%main", [read(indexText), spelled])]))).toBe(false); + // An immutable local answers with its initializer (the shape of the + // dispatch helpers' formatted index keys). + const key: IrExpr = { loc, kind: "varRef", localId: "key", type: STRING }; + const local = (init: IrExpr, mutable = false): IrFunction => ({ + ...fn("%main", [{ loc, kind: "varDecl", localId: "key", init }, read(key), spelled]), + locals: [{ id: "key", name: "key", type: STRING, mutable }], + }); + expect(observesStacks(mod([local(indexText)]))).toBe(false); + expect(observesStacks(mod([local(indexText, true)]))).toBe(true); + // A parameter of a function called by name answers with its arguments. + const helper: IrFunction = { + ...fn("readKey", [read(key)]), + params: [{ localId: "key", name: "key", type: STRING }], + locals: [{ id: "key", name: "key", type: STRING, mutable: false }], + }; + const callWith = (arg: IrExpr): IrStmt => ({ + loc, + kind: "exprStmt", + expr: { loc, kind: "call", callee: "readKey", args: [arg], type: DYN }, + }); + const numbers = mod([fn("%main", [callWith(indexText), spelled]), helper]); + expect(observesStacks(numbers)).toBe(false); + const stack = mod([ + fn("%main", [ + callWith(indexText), + callWith({ loc, kind: "strLit", value: "stack", type: STRING }), + ]), + helper, + ]); + expect(observesStacks(stack)).toBe(true); +}); + test("programs without stack reads are not", () => { expect(observesStacks(mod([fn("%main", [call("helper")]), fn("helper", [ret])]))).toBe(false); }); diff --git a/packages/compiler/src/backend/stack-observations.ts b/packages/compiler/src/backend/stack-observations.ts index cc6d109e4..fbb1c23cf 100644 --- a/packages/compiler/src/backend/stack-observations.ts +++ b/packages/compiler/src/backend/stack-observations.ts @@ -3,7 +3,7 @@ * it answers true. */ import { exprFunctionReference, moduleFunctionReferences } from "../ir/function-references.js"; import { MAY_THROW_LIB_FNS } from "../ir/builtin-effects.js"; -import type { IrExpr, IrFunction, IrModule, IrStmt } from "../ir/ir.js"; +import type { IrExpr, IrFunction, IrModule, IrStmt, IrType } from "../ir/ir.js"; import { MAY_THROW_ARR_METHODS } from "../ir/ir.js"; import { everyExprChild, everyStmtChild, everyStmtList } from "../ir/traverse.js"; import { unreachableStatements } from "./may-throw.js"; @@ -13,16 +13,122 @@ import { writesPublished } from "./publication.js"; /** Lib calls that read a captured stack trace. */ const STACK_READS: ReadonlySet = new Set(["error.stack"]); +/** Key types whose property names are never `stack`: a number, bigint or + * boolean key names its decimal or `true`/`false` property. */ +const NON_STACK_KEY_TYPES: ReadonlySet = new Set(["f64", "bigint", "bool"]); + +/** Whether a computed key can be the string "stack", the question a read + * on a checked-dynamic value asks before it counts as a stack read. + * - A string literal answers by its value; a key of a number, bigint or + * boolean type, or one converted from such a value, cannot be "stack". + * - An immutable local answers with its one initializer, and a parameter of + * a function the program only calls by name answers with the arguments + * its call sites pass. + * - Any other key can be "stack" when the program spells the literal + * "stack" somewhere, since the literal can reach the key through fields, + * containers or calls; a key the program assembles from other strings or + * from data it reads keeps the static answer. */ +class StackKeys { + private readonly spelled: boolean; + private readonly calls = new Map(); + private readonly named: Set; + private readonly inits = new Map>(); + private readonly visiting = new Set(); + + constructor(mod: IrModule) { + this.spelled = spellsStack(mod); + this.named = new Set(moduleFunctionReferences(mod)); + // Class methods, accessors and constructors are called through dispatch. + const classNames = new Set((mod.classes ?? []).map((cls) => cls.name)); + for (const fn of mod.functions) { + if (isClassMember(fn.name, classNames)) this.named.add(fn.name); + everyStmtList(fn.body, { + stmt: () => true, + expr: (expr) => { + if (expr.kind === "call") { + const sites = this.calls.get(expr.callee) ?? []; + sites.push({ caller: fn, args: expr.args }); + this.calls.set(expr.callee, sites); + } + const referenced = exprFunctionReference(expr); + if (referenced !== undefined) this.named.add(referenced); + return true; + }, + }); + } + } + + mayBeStack(fn: IrFunction, key: IrExpr): boolean { + if (key.kind === "strLit") return key.value === "stack"; + if (!this.spelled) return false; + if (NON_STACK_KEY_TYPES.has(key.type.kind)) return false; + if (key.kind === "toString" && NON_STACK_KEY_TYPES.has(key.operand.type.kind)) return false; + if (key.kind === "dynFrom" && NON_STACK_KEY_TYPES.has(key.value.type.kind)) return false; + if (key.kind !== "varRef") return true; + const visit = `${fn.name}\0${key.localId}`; + // A cycle adds no value of its own: the answer comes from its entries. + if (this.visiting.has(visit)) return false; + this.visiting.add(visit); + try { + return this.localMayBeStack(fn, key.localId); + } finally { + this.visiting.delete(visit); + } + } + + private localMayBeStack(fn: IrFunction, localId: string): boolean { + const local = fn.locals.find((candidate) => candidate.id === localId); + if (local === undefined || local.mutable) return true; + const index = fn.params.findIndex((param) => param.localId === localId); + if (index !== -1) { + const sites = this.calls.get(fn.name); + if (this.named.has(fn.name) || sites === undefined) return true; + return sites.some( + (site) => + site.args.length !== fn.params.length || this.mayBeStack(site.caller, site.args[index]!), + ); + } + const init = this.initializerOf(fn, localId); + return init === null || this.mayBeStack(fn, init); + } + + /** The one initializer of an immutable local, or null when it has none + * or more than one declaration. */ + private initializerOf(fn: IrFunction, localId: string): IrExpr | null { + let inits = this.inits.get(fn); + if (inits === undefined) { + inits = new Map(); + everyStmtList(fn.body, { + stmt: (stmt) => { + if (stmt.kind === "varDecl") + inits!.set(stmt.localId, inits!.has(stmt.localId) ? null : (stmt.init ?? null)); + return true; + }, + expr: () => true, + }); + this.inits.set(fn, inits); + } + return inits.get(localId) ?? null; + } +} + +/** Whether `name` follows the `%.` convention of class + * methods, accessors and constructors for one of `classNames`. */ +function isClassMember(name: string, classNames: ReadonlySet): boolean { + if (!name.startsWith("%")) return false; + for (let dot = name.indexOf("."); dot !== -1; dot = name.indexOf(".", dot + 1)) { + if (classNames.has(name.slice(1, dot))) return true; + } + return false; +} + /** A typed `error.stack` read, or a read on a checked-dynamic value that - * can reach an error's own `stack` accessor: the constant key `stack`, or - * a computed key when `computedKeys` (the program spells "stack", so a - * computed key can name it). A key the program assembles from other - * strings or from data it reads keeps the static answer. */ -function readsStack(expr: IrExpr, computedKeys: boolean): boolean { + * can reach an error's own `stack` accessor: one whose key can be "stack" + * (StackKeys). */ +function readsStack(fn: IrFunction, expr: IrExpr, keys: StackKeys): boolean { if (expr.kind === "libCall") return STACK_READS.has(expr.fn); if (expr.kind !== "dynKeyGet") return false; - if (expr.key.kind === "strLit") return expr.key.value === "stack"; - return computedKeys; + return keys.mayBeStack(fn, expr.key); } /** Whether the module contains the string literal "stack". */ @@ -36,10 +142,10 @@ function spellsStack(mod: IrModule): boolean { ); } -function containsStackRead(fn: IrFunction, computedKeys: boolean): boolean { +function containsStackRead(fn: IrFunction, keys: StackKeys): boolean { return !everyStmtList(fn.body, { stmt: () => true, - expr: (expr) => !readsStack(expr, computedKeys), + expr: (expr) => !readsStack(fn, expr, keys), }); } @@ -65,22 +171,14 @@ function visitLiveExprs(body: IrStmt[], visit: (expr: IrExpr) => void): void { * and those reads must not cost every call of the program a frame. * Programs without any stack read answer false without the walk. */ export function observesStacks(mod: IrModule): boolean { - const computedKeys = spellsStack(mod); - if (!mod.functions.some((fn) => containsStackRead(fn, computedKeys))) return false; + const keys = new StackKeys(mod); + if (!mod.functions.some((fn) => containsStackRead(fn, keys))) return false; const byName = new Map(mod.functions.map((fn) => [fn.name, fn])); const classNames = new Set((mod.classes ?? []).map((cls) => cls.name)); const pending: string[] = [...moduleFunctionReferences(mod)]; // Class methods, accessors and constructors are reachable through // dispatch the IR does not name per site: all of them are roots. - for (const fn of mod.functions) { - if (!fn.name.startsWith("%")) continue; - for (let dot = fn.name.indexOf("."); dot !== -1; dot = fn.name.indexOf(".", dot + 1)) { - if (classNames.has(fn.name.slice(1, dot))) { - pending.push(fn.name); - break; - } - } - } + for (const fn of mod.functions) if (isClassMember(fn.name, classNames)) pending.push(fn.name); const reached = new Set(); let observed = false; while (pending.length > 0 && !observed) { @@ -90,7 +188,7 @@ export function observesStacks(mod: IrModule): boolean { const fn = byName.get(name); if (fn === undefined) continue; visitLiveExprs(fn.body, (expr) => { - if (readsStack(expr, computedKeys)) observed = true; + if (readsStack(fn, expr, keys)) observed = true; if (expr.kind === "call" && !reached.has(expr.callee)) pending.push(expr.callee); const referenced = exprFunctionReference(expr); if (referenced !== undefined && !reached.has(referenced)) pending.push(referenced); From 95b2f81716dc644ec5a58309cf6d08ff71c81ca9 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Tue, 20 Oct 2026 03:24:08 -0700 Subject: [PATCH 075/122] Pipe into untyped destinations by unwrapping boxed native streams Co-Authored-By: Claude Opus 5.5 --- .../builtin-apis/streams-and-readline.mdx | 1 + .../src/frontend/lowering/lower-stream.ts | 53 +++++++++++++++++++ .../6467-node-stream-untyped-sources/main.ts | 11 +++- 3 files changed, 63 insertions(+), 2 deletions(-) diff --git a/docs/content/limitations/builtin-apis/streams-and-readline.mdx b/docs/content/limitations/builtin-apis/streams-and-readline.mdx index 860daa5d8..042fd1e50 100644 --- a/docs/content/limitations/builtin-apis/streams-and-readline.mdx +++ b/docs/content/limitations/builtin-apis/streams-and-readline.mdx @@ -1,6 +1,7 @@ - **Streams and readline:** Stream chunks are Buffers or strings. `NodeJS.ReadableStream` and `NodeJS.WritableStream`, and `stream.Readable` and `stream.Writable` under `@types/node`, accept every stream: when a program passes file, zlib, or `node:stream` streams through them, child-process pipes and `process.stdout` and `process.stderr` join those streams as `Readable` and `Writable` instances. `readable.pipe(process.stdout)` and `readable.pipe(process.stderr)` compile and, as in Node, do not end the process stream. + A `pipeline` stage that arrives as an untyped value must be a native stream at run time; other values throw a not-supported error. `Readable.from` accepts arrays, Sets, Map and Set iterators, generators, async generators, and class iterables and async iterables whose values are strings or Buffers, and in JavaScript any iterable or async iterable of them; a stream that mixes strings and Buffers is unsupported. In JavaScript, a sync iterator value with a `then` method that is not a promise, and an object whose `[Symbol.asyncIterator]` or `[Symbol.iterator]` method is a generator method, are unsupported. Object-mode streams of other values (including `Readable.from` over a `Map`, whose values are entry arrays) are unsupported. diff --git a/packages/compiler/src/frontend/lowering/lower-stream.ts b/packages/compiler/src/frontend/lowering/lower-stream.ts index 400fd79ac..51c86fe39 100644 --- a/packages/compiler/src/frontend/lowering/lower-stream.ts +++ b/packages/compiler/src/frontend/lowering/lower-stream.ts @@ -3371,6 +3371,58 @@ function pipeIntoStdio( }; } +/** `readable.pipe(untyped)`: a checked-dynamic destination unwraps a boxed + * native stream at run time (stream.unboxDyn; other values throw the + * not-supported Error), and pipe() answers the destination value itself. */ +function pipeIntoDyn( + lowerer: Lowerer, + call: ts.CallExpression, + access: ts.PropertyAccessExpression, + target: IrExpr, +): IrExpr { + const loc = locOf(call); + const receiverValue = lowerer.lowerExpr(access.expression); + const source = lowerer.declareHiddenLocal("%pipeSource", receiverValue.type); + const destination = lowerer.declareHiddenLocal("%pipeDestination", target.type); + const stmts: IrStmt[] = [ + { kind: "varDecl", localId: source.id, init: receiverValue, loc }, + { kind: "varDecl", localId: destination.id, init: target, loc }, + ]; + const end = pipeEndOption(lowerer, call.arguments.nodes[1], loc); + const duplex: IrType = { kind: "object", className: "%Duplex" }; + stmts.push({ + kind: "exprStmt", + expr: { + kind: "libCall", + fn: "readable.pipe", + args: [ + { kind: "varRef", localId: source.id, type: receiverValue.type, loc }, + { + kind: "libCall", + fn: "stream.unboxDyn", + args: [ + { kind: "varRef", localId: destination.id, type: target.type, loc }, + strLit("pipe destinations other than native streams are not supported yet", loc), + ], + type: duplex, + loc, + }, + end, + ], + type: duplex, + loc, + }, + loc, + }); + return { + kind: "seqExpr", + stmts, + result: { kind: "varRef", localId: destination.id, type: target.type, loc }, + type: target.type, + loc, + }; +} + export function lowerStreamMethodCall( lowerer: Lowerer, call: ts.CallExpression, @@ -3622,6 +3674,7 @@ export function lowerStreamMethodCall( } const target = lowerer.lowerExpr(args[0]!); if (target.type.kind === "procStream") return pipeIntoStdio(lowerer, call, access, target); + if (target.type.kind === "dyn") return pipeIntoDyn(lowerer, call, access, target); const dst = target; const dstInfo = dst.type.kind === "object" ? lowerer.classes.get(dst.type.className) : undefined; diff --git a/tests/corpus/6467-node-stream-untyped-sources/main.ts b/tests/corpus/6467-node-stream-untyped-sources/main.ts index bab7e8cc7..5cb3a628d 100644 --- a/tests/corpus/6467-node-stream-untyped-sources/main.ts +++ b/tests/corpus/6467-node-stream-untyped-sources/main.ts @@ -1,7 +1,7 @@ // Streams that arrive untyped meet NodeJS stream interface slots: an // untyped JavaScript function's stream returned as NodeJS.ReadableStream, -// a checked cast from 'unknown', pipeline() over untyped source and -// destination stages, and 'for await' over a NodeJS.ReadableStream +// a checked cast from 'unknown', pipe() and pipeline() into untyped +// destinations, and 'for await' over a NodeJS.ReadableStream // parameter fed by a file stream and a child's stdout. import { spawn } from "node:child_process"; import * as fs from "node:fs"; @@ -37,6 +37,13 @@ async function main(): Promise { const child = spawn("sh", ["-c", "printf 'child text\\n'"], { stdio: ["ignore", "pipe", "inherit"] }); if (child.stdout) await collect("child", child.stdout); + await new Promise((resolve) => { + const piped = fs.createReadStream(plain); + piped.on("end", () => resolve()); + piped.pipe(collector()); + }); + console.log("pipe", JSON.stringify(joined())); + const sink = collector(); await pipeline(openUntyped(packed), zlib.createGunzip(), sink); console.log("pipeline", JSON.stringify(joined())); From ac6b5ebe9715ff0f86a0da59ca8a1c71928f8fc0 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Tue, 20 Oct 2026 03:08:21 -0700 Subject: [PATCH 076/122] Throw a record extended by Object.assign as a checked-dynamic copy `throw Object.assign(failure, { bail: true })`, where the record's type lacks the source's member, used to fall to the Object.assign fence: a fixed record cannot grow a field. As the operand of a throw, the call now copies the target into a checked-dynamic object and adds the source's members. The copy is unobservable, because control leaves with the throw and nothing on that path reads the target again. Paths that already lowered run first, so no compiled program changes. Co-Authored-By: Claude Opus 5.5 --- .../src/frontend/lowering/lower-calls.ts | 31 +++++++++++++++++++ tests/corpus/6404-error-in-and-assign.ts | 25 +++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/packages/compiler/src/frontend/lowering/lower-calls.ts b/packages/compiler/src/frontend/lowering/lower-calls.ts index 6862d2d52..c51b75b03 100644 --- a/packages/compiler/src/frontend/lowering/lower-calls.ts +++ b/packages/compiler/src/frontend/lowering/lower-calls.ts @@ -7601,6 +7601,37 @@ function lowerObjectStaticCall( } } } + // `throw Object.assign(record, { extra })`: an error-shaped record given + // a member its type lacks, thrown at once. A fixed record cannot grow + // the member, so the throw carries a checked-dynamic copy of the target + // with the source's members added. The copy is unobservable: control + // leaves with the throw, so nothing on that path reads the target again. + { + let parent: ts.Node | undefined = call.parent; + while (parent && ts.isParenthesizedExpression(parent)) parent = parent.parent; + const argNodes = call.arguments.nodes; + if ( + parent && + ts.isThrowStatement(parent) && + argNodes.length === 2 && + !argNodes.some((a) => ts.isSpreadElement(a)) + ) { + const tProbe = tryLowerExpression(lowerer, argNodes[0]!); + const sProbe = tryLowerExpression(lowerer, argNodes[1]!); + if ( + tProbe?.type.kind === "record" && + lowerer.dynConvertible(tProbe.type) && + sProbe && + (sProbe.type.kind === "dyn" || lowerer.dynConvertible(sProbe.type)) + ) { + const loc = locOf(call); + const target = lowerer.coerceToExpected(lowerer.lowerExpr(argNodes[0]!), DYN); + const source = lowerer.coerceToExpected(lowerer.lowerExpr(argNodes[1]!), DYN); + if (target.type.kind === "dyn" && source.type.kind === "dyn") + return { kind: "libCall", fn: "dyn.assign", args: [target, source], type: DYN, loc }; + } + } + } // `Object.assign(target, ...sources)` over a CHECKED-DYNAMIC target // — the n-ary/spread form (`Object.assign({}, ...plugins.map(p => // p.options), coreOptions)`, support.js's option-table merge). The diff --git a/tests/corpus/6404-error-in-and-assign.ts b/tests/corpus/6404-error-in-and-assign.ts index fc6f470e6..a9bb6d086 100644 --- a/tests/corpus/6404-error-in-and-assign.ts +++ b/tests/corpus/6404-error-in-and-assign.ts @@ -117,3 +117,28 @@ function failure(message: string, bail: boolean): BuildFailure { } for (const f of [failure("stop", true), failure("go", false)]) console.log(f.message, f.bail, f.code); + +// A record given a member its type lacks and thrown at once. +interface FailureRecord { + from: string; + isGraceful: boolean; + errorValue: unknown; +} +function classifyFailure(err: unknown, from: string): FailureRecord { + return { from, isGraceful: from === "user", errorValue: err }; +} +function handleFailure(err: unknown, from: string): string { + const failure = classifyFailure(err, from); + if (failure.from === "build") { + throw Object.assign(failure, { bail: true }); + } + return `${failure.from} ${failure.isGraceful}`; +} +for (const from of ["build", "user"]) { + try { + console.log("handled", handleFailure(new Error("x"), from)); + } catch (e) { + const thrownRecord = e as { from: string; bail?: boolean; isGraceful: boolean }; + console.log("thrown", thrownRecord.from, thrownRecord.bail, thrownRecord.isGraceful); + } +} From 1f58ff62568fd3cb1656dd0db1524ba5660d7467 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Tue, 20 Oct 2026 03:41:24 -0700 Subject: [PATCH 077/122] Record the order-parity baseline for the untyped stream corpus program Co-Authored-By: Claude Opus 5.5 --- .../corpus/6467-node-stream-untyped-sources/main.ts.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6467-node-stream-untyped-sources/main.ts.json diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6467-node-stream-untyped-sources/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6467-node-stream-untyped-sources/main.ts.json new file mode 100644 index 000000000..dc843c047 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6467-node-stream-untyped-sources/main.ts.json @@ -0,0 +1,7 @@ +{ + "order": [ + "/tests/corpus/6467-node-stream-untyped-sources/sources.js", + "/tests/corpus/6467-node-stream-untyped-sources/main.ts" + ], + "diags": [] +} From 447e9bf000a87d14757d9b9def438eb2afe8bdf5 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Tue, 20 Oct 2026 04:14:52 -0700 Subject: [PATCH 078/122] Compile Transform option callbacks and loosely typed child-process listeners @types/node types the transform/flush completion callback's data as any and a child stream 'data' chunk as any, and ChildProcess accepts (code: number, signal: string) exit and close listeners through its catch-all overload. None of these compiled: the any-typed completion callback crashed the LLVM emitter, and the child listeners were refused. The completion callback's data parameter is typed Buffer | string | undefined while the option callback lowers. Child stream 'data' listeners with an unknown or any chunk box it as a checked-dynamic value, taking strings when a setEncoding statement on the same stream precedes the listener and Buffers when the function never calls setEncoding on it; other placements are refused. Exit and close listener parameters declared number or string bind as checked-dynamic values, so the body sees the null Node passes. Co-Authored-By: Claude Opus 5.5 --- .../builtin-apis/streams-and-readline.mdx | 1 + .../limitations/child-process-workers.mdx | 4 + packages/compiler/src/backend/llvm/emitter.ts | 5 + .../src/backend/llvm/expr-callbacks.ts | 25 +++ .../compiler/src/backend/llvm/lib-stream.ts | 11 +- .../builtins/child-process-methods.ts | 156 +++++++++++++++++- .../src/frontend/lowering/lower-stream.ts | 64 +++++-- .../compiler/src/frontend/lowering/lowerer.ts | 4 +- .../compiler/src/ir/builtin-signatures.ts | 2 +- ...stream-transform-any-data-callback.ts.json | 6 + .../6471-stream-data-buffer-or-string.ts.json | 6 + .../6472-child-data-unknown-chunk.ts.json | 6 + .../node-types/stream-construction.ts.json | 6 + ...6470-stream-transform-any-data-callback.ts | 60 +++++++ .../6471-stream-data-buffer-or-string.ts | 44 +++++ tests/corpus/6472-child-data-unknown-chunk.ts | 65 ++++++++ tests/diagnostics/child-process.ts | 5 + .../node-types/stream-construction.ts | 100 +++++++++++ .../__snapshots__/child-process.ts.txt | 9 +- tests/harness/project-config.test.ts | 18 ++ 20 files changed, 568 insertions(+), 29 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6470-stream-transform-any-data-callback.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6471-stream-data-buffer-or-string.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6472-child-data-unknown-chunk.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/stream-construction.ts.json create mode 100644 tests/corpus/6470-stream-transform-any-data-callback.ts create mode 100644 tests/corpus/6471-stream-data-buffer-or-string.ts create mode 100644 tests/corpus/6472-child-data-unknown-chunk.ts create mode 100644 tests/fixtures/node-types/stream-construction.ts diff --git a/docs/content/limitations/builtin-apis/streams-and-readline.mdx b/docs/content/limitations/builtin-apis/streams-and-readline.mdx index 042fd1e50..3de3e5628 100644 --- a/docs/content/limitations/builtin-apis/streams-and-readline.mdx +++ b/docs/content/limitations/builtin-apis/streams-and-readline.mdx @@ -14,6 +14,7 @@ `stream.compose` accepts two or more streams, or one `Duplex`; functions, iterables, and web streams as stages are unsupported, and the `ERR_INVALID_ARG_VALUE` message for a stream that is no longer readable or writable names the stream's class instead of printing the stream. `pipeline` from `node:stream/promises` accepts a trailing `{ signal, end }` object, and `finished` accepts `readable`, `writable`, `error`, `cleanup`, and `signal` options, with boolean literals for the flags. Stream constructors accept a `signal` option in an options object literal, and `stream.addAbortSignal` accepts any stream. + A `transform` or `flush` option callback whose completion callback types `data` as `any`, as @types/node's `TransformCallback` does, may pass a Buffer, a string, or nothing; any other value throws a TypeError where the callback is called, instead of Node's `ERR_INVALID_ARG_TYPE` stream error. `node:readline` and `node:readline/promises` (also reached as `readline.promises`) interfaces, from `createInterface(options)` or `createInterface(input, output, completer, terminal)`, read `process.stdin` or a readable stream: `question`, `prompt`, `write` (with a key object literal of `name`, `ctrl`, `meta`, and `shift`), `pause`, `resume`, `'line'`/`'close'`/`'pause'`/`'resume'`/`'error'`/`'history'`/`'SIGINT'`/`'SIGTSTP'`/`'SIGCONT'` listeners, and `for await` compile. The `history`, `historySize`, `removeHistoryDuplicates`, `completer`, `tabSize`, and `escapeCodeTimeout` options are validated as in Node. With `terminal: true`, or with no `terminal` option and `process.stdout` as the output when it is a TTY, the interface runs Node's terminal mode: the editing keys, the kill ring, undo and redo, history recording and recall, Tab completion through the completer, Ctrl+C, Ctrl+D, and Ctrl+Z, `TERM=dumb`, and the same escape sequences written to the output. diff --git a/docs/content/limitations/child-process-workers.mdx b/docs/content/limitations/child-process-workers.mdx index 5b41c5e53..9d6602509 100644 --- a/docs/content/limitations/child-process-workers.mdx +++ b/docs/content/limitations/child-process-workers.mdx @@ -12,3 +12,7 @@ Message listeners may return `void` or `Promise`. Messages use newline-delimited JSON, so their static types must be JSON-stringifiable or `unknown` backed by a checked JSON tree. `send()` returns the channel's backpressure state and invokes its callback later with `null` or an `Error`. `disconnect()` changes `connected` synchronously, before the later disconnect events. + +A `'data'` listener on a child's `stdout` or `stderr` whose chunk is `unknown` or `any` receives strings when a `setEncoding()` statement on the same stream precedes it in its block or an enclosing block, and Buffers when the function never calls `setEncoding()` on that stream. +Any other placement of `setEncoding()` is refused at compile time, and a `setEncoding()` call that runs elsewhere afterwards makes the listener throw a TypeError instead of receiving strings. +An inline `'exit'` or `'close'` listener may declare `(code: number, signal: string)`, which TypeScript accepts through the catch-all `on` overload; it receives the `null` values Node passes, and a typed use of a `null` value throws a TypeError. diff --git a/packages/compiler/src/backend/llvm/emitter.ts b/packages/compiler/src/backend/llvm/emitter.ts index f7b9f2517..427c15aac 100644 --- a/packages/compiler/src/backend/llvm/emitter.ts +++ b/packages/compiler/src/backend/llvm/emitter.ts @@ -181,6 +181,7 @@ import { genResultThunkFor, childExitThunkFor, childExitSignalThunkFor, + childDataDynThunkFor, childDataThunkFor, execFileThunkFor, ipcMessageThunkFor, @@ -5564,6 +5565,10 @@ export class LlEmitter { return childDataThunkFor(this, param); } + childDataDynThunkFor(chunk: "bytes" | "str"): string { + return childDataDynThunkFor(this, chunk); + } + emitterFixedAdapter(cbT: IrType & { kind: "func" }): { fn: string; shim: string } { return emitterFixedAdapter(this, cbT); } diff --git a/packages/compiler/src/backend/llvm/expr-callbacks.ts b/packages/compiler/src/backend/llvm/expr-callbacks.ts index db1dca1af..6b6aae055 100644 --- a/packages/compiler/src/backend/llvm/expr-callbacks.ts +++ b/packages/compiler/src/backend/llvm/expr-callbacks.ts @@ -754,6 +754,31 @@ export function childDataThunkFor(host: LlvmEmitterContext, param: IrType): stri return sym; } +/** The child-stream 'data' thunk for an unknown/any chunk parameter: the + * runtime's Buffer (or, after setEncoding, string) chunk boxes into a + * checked-dynamic value the listener owns. */ +export function childDataDynThunkFor(host: LlvmEmitterContext, chunk: "bytes" | "str"): string { + const key = `cdd:${chunk}`; + let sym = host.resolveThunks.get(key); + if (sym) return sym; + sym = `sc_cdd_${host.resolveThunks.size}`; + host.resolveThunks.set(key, sym); + const box = chunk === "bytes" ? "scr_dyn_new_buffer" : "scr_dyn_new_str"; + host.declare(`declare ptr @${box}(ptr)`); + host.resolveThunkDefs.push( + `define internal void @${sym}(ptr %cb, ptr %chunk) ${FN_ATTRS} { ; child 'data' → dyn (${chunk})`, + `entry:`, + ` %d = call ptr @${box}(ptr %chunk)`, + ` %fnp = getelementptr inbounds %ScrClosure, ptr %cb, i64 0, i32 1`, + ` %fn = load ptr, ptr %fnp`, + ` call void %fn(ptr %cb, ptr %d)`, + ` ret void`, + `}`, + ``, + ); + return sym; +} + export function emitterFixedAdapter( host: LlvmEmitterContext, cbT: IrType & { kind: "func" }, diff --git a/packages/compiler/src/backend/llvm/lib-stream.ts b/packages/compiler/src/backend/llvm/lib-stream.ts index b62b237df..0ded59a1a 100644 --- a/packages/compiler/src/backend/llvm/lib-stream.ts +++ b/packages/compiler/src/backend/llvm/lib-stream.ts @@ -56,6 +56,8 @@ export function emitStreamLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlV host.declare(`declare void @scr_child_stream_thunk0(ptr, ptr)`); } else if (param.kind === "union") { adapter = host.childDataThunkFor(param); + } else if (param.kind === "dyn") { + adapter = host.childDataDynThunkFor("bytes"); } else { adapter = "scr_child_stream_thunk_bytes"; host.declare(`declare void @scr_child_stream_thunk_bytes(ptr, ptr)`); @@ -78,7 +80,8 @@ export function emitStreamLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlV if (e.fn === "stream.onDataStr") { host.usesTimers = true; const cbT = e.args[1]!.type; - if (cbT.kind !== "func" || cbT.params[0]?.kind !== "string") { + const strParam = cbT.kind === "func" ? cbT.params[0]?.kind : undefined; + if (strParam !== "string" && strParam !== "dyn") { throw new InternalCompilerError( "llvm emitter bug: stream.onDataStr callback not a string func", ); @@ -87,10 +90,12 @@ export function emitStreamLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlV const cb = host.emitExpr(e.args[1]!); const once = host.emitExpr(e.args[2]!); host.toHeap(cb); - host.declare(`declare void @scr_child_stream_thunk_str(ptr, ptr)`); + let adapter = "scr_child_stream_thunk_str"; + if (strParam === "dyn") adapter = host.childDataDynThunkFor("str"); + else host.declare(`declare void @scr_child_stream_thunk_str(ptr, ptr)`); host.declare(`declare void @scr_child_stream_on_data_str(ptr, ptr, ptr, i1 zeroext)`); B.line( - `call void @scr_child_stream_on_data_str(ptr ${s0.name}, ptr ${cb.name}, ptr @scr_child_stream_thunk_str, i1 ${once.name})`, + `call void @scr_child_stream_on_data_str(ptr ${s0.name}, ptr ${cb.name}, ptr @${adapter}, i1 ${once.name})`, ); return { name: "", type: e.type }; } diff --git a/packages/compiler/src/frontend/lowering/builtins/child-process-methods.ts b/packages/compiler/src/frontend/lowering/builtins/child-process-methods.ts index 7f0c2cde0..a2b0e1f57 100644 --- a/packages/compiler/src/frontend/lowering/builtins/child-process-methods.ts +++ b/packages/compiler/src/frontend/lowering/builtins/child-process-methods.ts @@ -132,8 +132,16 @@ export function lowerChildMethodCall( ) { for (const param of listenerNode.parameters.nodes) lowerer.checkedCallbackParams.add(param); } - const cb = lowerer.lowerExpr(call.arguments.nodes[1]!); + const narrowed = + event === "exit" || event === "close" + ? narrowedExitListenerParams(lowerer, listenerNode) + : []; + const cb = + narrowed.length > 0 + ? lowerWithCheckedParams(lowerer, listenerNode, narrowed) + : lowerer.lowerExpr(call.arguments.nodes[1]!); if ( + narrowed.length > 0 || (isJsSourceFile(listenerNode.getSourceFile()) && cb.type.kind === "dyn") || (cb.type.kind === "func" && (cb.type.rest || @@ -302,10 +310,12 @@ export function lowerChildMethodCall( /** Method calls on piped child-output stream receivers (child.stdout / * child.stderr — the childStream kind): on/once("data" | "end"). - * 'data' listeners take zero parameters, `(chunk: Buffer)`, or a - * `Buffer | string`-union chunk (the ngrok appendOutput shape — the - * runtime only ever fires Buffers; the compiler-emitted adapter wraps - * the chunk at the union's Buffer arm); 'end' listeners take none. + * 'data' listeners take zero parameters, `(chunk: Buffer)`, `(chunk: + * string)`, an unknown/any chunk (boxed as a checked-dynamic value; see + * childStreamEncodedAt), or a `Buffer | string`-union chunk (the ngrok + * appendOutput shape — the runtime only ever fires Buffers; the + * compiler-emitted adapter wraps the chunk at the union's Buffer arm); + * 'end' listeners take none. * Statement position only (Node returns the stream for chaining; here * the result is void). Chained receivers (`child.stdout?.on(...)`) * ride the optional-chain re-dispatch (chainBlocked). Everything else @@ -379,18 +389,26 @@ export function lowerChildStreamMethodCall( if ( param !== undefined && param.kind !== "string" && + param.kind !== "dyn" && !(param.kind === "bytes" && param.elem === "u8") && !unionOk(param) ) { lowerer.unsupported( "SC1090", call.arguments.nodes[1]!, - `data listeners whose parameter is not 'Buffer', 'string', or a Buffer-armed union (got '${lowerer.fmt(param)}')`, + `data listeners whose parameter is not 'Buffer', 'string', 'unknown', or a Buffer-armed union (got '${lowerer.fmt(param)}')`, ); } + // An unknown/any chunk is a Buffer, or a string once setEncoding ran. + // The registration takes whichever the enclosing code establishes; a + // later setEncoding that contradicts it throws the runtime's + // listener-type TypeError instead of delivering the other kind. + const strings = + param?.kind === "string" || + (param?.kind === "dyn" && childStreamEncodedAt(lowerer, access.expression, call)); return { kind: "libCall", - fn: param?.kind === "string" ? "stream.onDataStr" : "stream.onData", + fn: strings ? "stream.onDataStr" : "stream.onData", args: [receiver, cb, once], type: VOID, loc, @@ -521,3 +539,127 @@ export function lowerChildWriterMethodCall( lowerer.checker.getSymbolAtLocation(access.name), ); } + +/** A receiver spelled as a plain access path (`child.stdout`, `out`): its + * root symbol and member names, or null for other forms. */ +function accessPathOf( + lowerer: Lowerer, + expr: ts.Expression, +): { root: ts.Symbol; path: string } | null { + if (ts.isParenthesizedExpression(expr) || ts.isNonNullExpression(expr)) { + return accessPathOf(lowerer, expr.expression); + } + if (ts.isIdentifier(expr)) { + const root = lowerer.checker.getSymbolAtLocation(expr); + return root ? { root, path: "" } : null; + } + if (ts.isPropertyAccessExpression(expr) && !expr.questionDotToken) { + const base = accessPathOf(lowerer, expr.expression); + return base === null ? null : { root: base.root, path: `${base.path}.${expr.name.text}` }; + } + return null; +} + +/** Whether an unknown-typed 'data' listener registered by `call` receives + * strings: a `setEncoding` statement on the same receiver path precedes + * the registration in its own block or an enclosing block of the same + * function. With no setEncoding on that path in the function, chunks are + * Buffers. Any other placement leaves the encoding state unclear, which + * is reported. */ +function childStreamEncodedAt( + lowerer: Lowerer, + receiver: ts.Expression, + call: ts.CallExpression, +): boolean { + const key = accessPathOf(lowerer, receiver); + const statement: ts.Node = call.parent!; + let scope: ts.Node | undefined = statement; + while (scope && !ts.isFunctionLike(scope) && !ts.isSourceFile(scope)) scope = scope.parent; + const unclear = (): never => + lowerer.unsupported( + "SC1090", + call.arguments.nodes[1]!, + "untyped child-stream 'data' listeners whose setEncoding state is unclear at their registration sites (declare the chunk as 'Buffer' or 'string')", + ); + if (key === null || scope === undefined) return unclear(); + const preceding = new Set(); + for (let node: ts.Node = statement; node !== scope;) { + const parent: ts.Node | undefined = node.parent; + if (parent === undefined) break; + if (ts.isBlock(parent) || ts.isSourceFile(parent)) { + for (const sibling of parent.statements.nodes) { + if (sibling === node) break; + preceding.add(sibling); + } + } + node = parent; + } + const samePath = ( + a: { root: ts.Symbol; path: string } | null, + b: { root: ts.Symbol; path: string }, + ): boolean => a !== null && a.root === b.root && a.path === b.path; + let before = false; + let elsewhere = false; + const visit = (node: ts.Node): void => { + if (node !== scope && ts.isFunctionLike(node)) return; + if ( + ts.isCallExpression(node) && + ts.isPropertyAccessExpression(node.expression) && + node.expression.name.text === "setEncoding" && + samePath(accessPathOf(lowerer, node.expression.expression), key) + ) { + if (ts.isExpressionStatement(node.parent) && preceding.has(node.parent)) before = true; + else elsewhere = true; + } + ts.forEachChild(node, visit); + }; + visit(scope); + if (elsewhere) return unclear(); + return before; +} + +/** An inline 'exit'/'close' listener may declare `(code: number, signal: + * string)`: TypeScript accepts it through ChildProcess's catch-all `on` + * overload, while Node passes `number | null` and `Signals | null`. Those + * parameters bind as checked-dynamic values (the socket 'data' listener + * precedent), so the body observes the null Node passes; a typed use of a + * null value throws the checked conversion's TypeError. Returns the + * narrowed parameter symbols, empty when the declared types already + * admit null. */ +function narrowedExitListenerParams(lowerer: Lowerer, listener: ts.Expression): ts.Symbol[] { + if (!ts.isArrowFunction(listener) && !ts.isFunctionExpression(listener)) return []; + if (isJsSourceFile(listener.getSourceFile())) return []; + const symbols: ts.Symbol[] = []; + const wanted: readonly string[] = ["f64", "string"]; + listener.parameters.nodes.slice(0, 2).forEach((param, i) => { + if (!ts.isIdentifier(param.name) || param.dotDotDotToken || param.initializer) return; + if (param.questionToken) return; + const symbol = lowerer.checker.getSymbolAtLocation(param.name); + if (!symbol || lowerer.runtimeOptionalBindingTypes.has(symbol)) return; + if (lowerer.paramShape(param).type.kind !== wanted[i]) return; + symbols.push(symbol); + }); + return symbols; +} + +/** Lowers an inline listener with the given parameters bound as + * checked-dynamic values typed `unknown` for the body. */ +function lowerWithCheckedParams( + lowerer: Lowerer, + listener: ts.Expression, + symbols: readonly ts.Symbol[], +): IrExpr { + const previousImplicit = lowerer.implicitParamTypes; + const implicit = new Map(previousImplicit ?? []); + for (const symbol of symbols) { + implicit.set(symbol, lowerer.checker.getUnknownType()); + lowerer.runtimeOptionalBindingTypes.set(symbol, DYN); + } + lowerer.implicitParamTypes = implicit; + try { + return lowerer.lowerExpr(listener); + } finally { + for (const symbol of symbols) lowerer.runtimeOptionalBindingTypes.delete(symbol); + lowerer.implicitParamTypes = previousImplicit; + } +} diff --git a/packages/compiler/src/frontend/lowering/lower-stream.ts b/packages/compiler/src/frontend/lowering/lower-stream.ts index 51c86fe39..fc89858b5 100644 --- a/packages/compiler/src/frontend/lowering/lower-stream.ts +++ b/packages/compiler/src/frontend/lowering/lower-stream.ts @@ -587,6 +587,33 @@ function dynOptionsValue(lowerer: Lowerer, arg: ts.Expression): IrExpr | null { return v.type.kind === "dyn" ? v : null; } +/** @types/node declares the transform/flush completion callback as + * `(error?: Error | null, data?: any) => void`. Outside objectMode Node + * accepts only a Buffer, a string or nothing as `data`, so the callback + * parameter is typed with that union while the option callback lowers: a + * checked-dynamic value passed as `data` converts at the call. Returns the + * pinned parameter symbol (the caller unpins it), or null when the + * declared type needs no pin. */ +function pinAnyDataCompletion( + lowerer: Lowerer, + which: string, + fnNode: ts.MethodDeclaration | ts.FunctionExpression | ts.ArrowFunction, +): ts.Symbol | null { + const index = which === "transform" ? 2 : which === "flush" ? 0 : -1; + const param = index < 0 ? undefined : fnNode.parameters.nodes[index]; + if (param === undefined || !ts.isIdentifier(param.name) || param.dotDotDotToken) return null; + const symbol = lowerer.checker.getSymbolAtLocation(param.name); + if (!symbol || lowerer.stageSourceTypes.has(symbol)) return null; + const declared = lowerer.paramShape(param).type; + if (declared.kind !== "func" || declared.rest || declared.params[1]?.kind !== "dyn") return null; + const data = unionOf(lowerer, [BYTES, STRING, { kind: "undefinedT" }]); + lowerer.stageSourceTypes.set( + symbol, + funcOf([declared.params[0]!, data, ...declared.params.slice(2)], declared.ret), + ); + return symbol; +} + /** Parses the options object literal of a stream constructor. `which` * names the callbacks this class accepts, in canonical flag order. */ function parseStreamOptions( @@ -672,22 +699,27 @@ function parseStreamOptions( continue; } const full = tuples[name]!.map((t) => (t === "fn" ? null : t)); - // The completion-callback tail: typed by the checker contextually; - // accept the user's declared func type as the tuple's entry so the - // prefix check compares like with like. - const { funcType } = lowerer.lambdaSignature(fnNode); - const tuple: IrType[] = full.map((t, i) => { - if (t !== null) return t; - const declared = funcType.params[i]; - if (declared !== undefined && declared.kind !== "func" && declared.kind !== "dyn") { - lowerer.noLowering( - `a '${name}' callback whose parameter ${i} is '${lowerer.fmt(declared)}' where Node passes the completion callback`, - fnNode!, - ); - } - return declared ?? funcOf([], VOID); - }); - present.set(name, lowerStreamCallback(lowerer, name, fnNode, thisType, tuple)); + const pinned = pinAnyDataCompletion(lowerer, name, fnNode); + try { + // The completion-callback tail: typed by the checker contextually; + // accept the user's declared func type as the tuple's entry so the + // prefix check compares like with like. + const { funcType } = lowerer.lambdaSignature(fnNode); + const tuple: IrType[] = full.map((t, i) => { + if (t !== null) return t; + const declared = funcType.params[i]; + if (declared !== undefined && declared.kind !== "func" && declared.kind !== "dyn") { + lowerer.noLowering( + `a '${name}' callback whose parameter ${i} is '${lowerer.fmt(declared)}' where Node passes the completion callback`, + fnNode!, + ); + } + return declared ?? funcOf([], VOID); + }); + present.set(name, lowerStreamCallback(lowerer, name, fnNode, thisType, tuple)); + } finally { + if (pinned) lowerer.stageSourceTypes.delete(pinned); + } continue; } // `{ signal }` shorthand: the binding is the value. diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index c9a0c6435..393db65d9 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -2064,7 +2064,9 @@ export class Lowerer { /** A pipeline() function stage's `source` parameter: the IR type of what * the pipeline passes it (the previous stage as an async iterable), set * while the stage lowers. Parameter typing and `for await` heads read - * it before the checker's `AsyncIterable`. */ + * it before the checker's `AsyncIterable`. A stream option callback's + * completion parameter declared with an any-typed `data` is pinned here + * too while the callback lowers (pinAnyDataCompletion). */ readonly stageSourceTypes = new Map(); /** Set when a match result lowers to execGroups/matchGroups, the inputs * of regex span folding. */ diff --git a/packages/compiler/src/ir/builtin-signatures.ts b/packages/compiler/src/ir/builtin-signatures.ts index 597076cb1..57c5af04c 100644 --- a/packages/compiler/src/ir/builtin-signatures.ts +++ b/packages/compiler/src/ir/builtin-signatures.ts @@ -2540,7 +2540,7 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ * throws when the program lacks the stream unit's boxing helpers. */ "child.stdioStream": { argTypes: [CHILD_T, F64], result: VOID }, // Listener registrations: callback func shapes are program-dependent - // (zero-param / Buffer / Buffer-armed union) — the slots pin arity, + // (zero-param / Buffer / string / unknown / Buffer-armed union) — the slots pin arity, // the stream receiver, and the once-flag. "stream.onData": { argTypes: [CHILDSTREAM_T, null, BOOL], result: VOID }, "stream.onDataStr": { argTypes: [CHILDSTREAM_T, null, BOOL], result: VOID }, diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6470-stream-transform-any-data-callback.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6470-stream-transform-any-data-callback.ts.json new file mode 100644 index 000000000..3d4a39486 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6470-stream-transform-any-data-callback.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6470-stream-transform-any-data-callback.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6471-stream-data-buffer-or-string.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6471-stream-data-buffer-or-string.ts.json new file mode 100644 index 000000000..e8ff1e084 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6471-stream-data-buffer-or-string.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6471-stream-data-buffer-or-string.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6472-child-data-unknown-chunk.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6472-child-data-unknown-chunk.ts.json new file mode 100644 index 000000000..3c7183bb6 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6472-child-data-unknown-chunk.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6472-child-data-unknown-chunk.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/stream-construction.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/stream-construction.ts.json new file mode 100644 index 000000000..35dc0be0d --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/stream-construction.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/node-types/stream-construction.ts" + ], + "diags": [] +} diff --git a/tests/corpus/6470-stream-transform-any-data-callback.ts b/tests/corpus/6470-stream-transform-any-data-callback.ts new file mode 100644 index 000000000..cc4935a7e --- /dev/null +++ b/tests/corpus/6470-stream-transform-any-data-callback.ts @@ -0,0 +1,60 @@ +// Transform option callbacks whose completion callback declares an +// any-typed `data` (the @types/node TransformCallback spelling): the data +// passed back may be a Buffer, a string, or omitted. +import { PassThrough, Transform } from "node:stream"; +import { createGunzip, gzipSync } from "node:zlib"; + +type Done = (error?: Error | null, data?: any) => void; + +interface Progress { + bytes: number; + chunks: number; +} + +function counting(progress: Progress): Transform { + return new Transform({ + transform(chunk: Buffer, _encoding: string, callback: Done) { + progress.bytes += chunk.length; + progress.chunks++; + callback(null, chunk); + }, + }); +} + +const upper = new Transform({ + transform(chunk: Buffer, _encoding: string, callback: Done) { + const text = chunk.toString(); + if (text === "skip") { + callback(); + return; + } + callback(null, text.toUpperCase()); + }, + flush(callback: Done) { + callback(null, Buffer.from("!")); + }, +}); +upper.on("data", (d: Buffer) => console.log("upper", JSON.stringify(d.toString()))); +upper.on("end", () => console.log("upper end")); +upper.write("hello"); +upper.write("skip"); +upper.end("world"); + +const failing = new Transform({ + transform(chunk: Buffer, _encoding: string, callback: Done) { + callback(new Error(`refused ${chunk.length} bytes`)); + }, +}); +failing.on("error", (e: Error) => console.log("error", e.message)); +failing.write("abc"); + +const progress: Progress = { bytes: 0, chunks: 0 }; +const counter = counting(progress); +const out = new PassThrough(); +counter.pipe(createGunzip()).pipe(out); +const parts: string[] = []; +out.on("data", (d: Buffer) => parts.push(d.toString())); +out.on("end", () => { + console.log("gunzip", JSON.stringify(parts.join("")), progress.bytes > 0, progress.chunks); +}); +counter.end(gzipSync(Buffer.from("compressed payload"))); diff --git a/tests/corpus/6471-stream-data-buffer-or-string.ts b/tests/corpus/6471-stream-data-buffer-or-string.ts new file mode 100644 index 000000000..69289ced5 --- /dev/null +++ b/tests/corpus/6471-stream-data-buffer-or-string.ts @@ -0,0 +1,44 @@ +// 'data' listeners on runtime streams may declare the chunk as +// `Buffer | string` (the @types/node ReadStream listener type): the +// listener receives a Buffer, or a string once an encoding applies. +import * as fs from "node:fs"; +import * as os from "node:os"; +import * as path from "node:path"; +import { PassThrough, Readable } from "node:stream"; + +function describe(label: string, chunk: Buffer | string): void { + if (typeof chunk === "string") console.log(label, "string", JSON.stringify(chunk)); + else console.log(label, "buffer", chunk.length, JSON.stringify(chunk.toString())); +} + +const raw = new PassThrough(); +raw.on("data", (chunk: Buffer | string) => describe("raw", chunk)); +raw.write("abc"); +raw.end(Buffer.from("de")); + +const text = new PassThrough(); +text.setEncoding("utf8"); +text.on("data", (chunk: Buffer | string) => describe("text", chunk)); +text.end("héllo"); + +const r = new Readable({ read() {} }); +r.on("data", (chunk: Buffer | string) => describe("readable", chunk)); +r.on("end", () => console.log("readable end")); +r.push("one"); +r.push(null); + +const file = path.join(os.tmpdir(), `scriptc-6471-${process.pid}.txt`); +fs.writeFileSync(file, "file contents\n"); +const stream = fs.createReadStream(file); +const release = (): void => console.log("released"); +stream.on("data", (chunk: Buffer | string) => describe("file", chunk)); +stream.on("close", release); +stream.on("error", release); +stream.on("end", () => { + const encoded = fs.createReadStream(file, { encoding: "utf8" }); + encoded.on("data", (chunk: Buffer | string) => describe("file utf8", chunk)); + encoded.on("close", () => { + fs.unlinkSync(file); + console.log("done"); + }); +}); diff --git a/tests/corpus/6472-child-data-unknown-chunk.ts b/tests/corpus/6472-child-data-unknown-chunk.ts new file mode 100644 index 000000000..4a0e97d34 --- /dev/null +++ b/tests/corpus/6472-child-data-unknown-chunk.ts @@ -0,0 +1,65 @@ +// A child stream 'data' listener whose chunk is unknown or any receives a +// Buffer, or a string when setEncoding ran before the listener registered. +import { spawn } from "node:child_process"; + +function report(code: number | null, kinds: string[], stdout: string, stderr: string): void { + console.log("code", code, "kinds", [...new Set(kinds)].join(",")); + console.log("stdout", JSON.stringify(stdout)); + console.log("stderr", JSON.stringify(stderr)); +} + +function runEncoded(script: string): Promise { + return new Promise((resolve) => { + const child = spawn("/bin/sh", ["-c", script], { stdio: "pipe" }); + let stdout = ""; + let stderr = ""; + const kinds: string[] = []; + if (child.stdout) { + child.stdout.setEncoding("utf8"); + child.stdout.on("data", (c: any) => { + kinds.push(typeof c); + stdout += c; + }); + } + const err = child.stderr; + if (err !== null) { + err.setEncoding("utf8"); + err.on("data", (c: unknown) => { + kinds.push(typeof c); + stderr += String(c); + }); + } + child.on("close", (code) => { + report(code, kinds, stdout, stderr); + resolve(); + }); + }); +} + +function runRaw(script: string): Promise { + return new Promise((resolve) => { + const child = spawn("/bin/sh", ["-c", script], { stdio: "pipe" }); + let stdout = ""; + let stderr = ""; + const kinds: string[] = []; + child.stdout?.on("data", (c: unknown) => { + kinds.push(Buffer.isBuffer(c) ? "buffer" : typeof c); + stdout += String(c); + }); + if (child.stderr) { + child.stderr.on("data", (c: any) => { + const chunk = String(c); + kinds.push(Buffer.isBuffer(c) ? "buffer" : typeof c); + stderr += chunk; + }); + } + child.on("close", (code) => { + report(code, kinds, stdout, stderr); + resolve(); + }); + }); +} + +runEncoded("printf 'h\\303\\251llo'; echo warn >&2") + .then(() => runRaw("printf raw; echo oops >&2; exit 2")) + .then(() => console.log("done")); diff --git a/tests/diagnostics/child-process.ts b/tests/diagnostics/child-process.ts index f96bce418..6c5683efc 100644 --- a/tests/diagnostics/child-process.ts +++ b/tests/diagnostics/child-process.ts @@ -42,3 +42,8 @@ const staticWorker = new URL("./child-process.ts", import.meta.url); fork(staticWorker, [], { serialization: "advanced" }); fork(staticWorker, [], { stdio: ["ignore", "ignore", "ignore", "ignore"] }); // Keep each fence on its own statement so diagnostics remain site-specific. +// An unknown 'data' chunk takes the setEncoding state established before +// the listener registers; a setEncoding placed elsewhere leaves it unclear. +const pipedOut = piped.stdout!; +pipedOut.on("data", (chunk: unknown) => console.log(String(chunk))); +pipedOut.setEncoding("utf8"); diff --git a/tests/fixtures/node-types/stream-construction.ts b/tests/fixtures/node-types/stream-construction.ts new file mode 100644 index 000000000..37a1b852e --- /dev/null +++ b/tests/fixtures/node-types/stream-construction.ts @@ -0,0 +1,100 @@ +// Streams constructed under @types/node: option-bag Transform and +// PassThrough streams with @types/node's TransformCallback, a gunzip stage, +// fs.ReadStream listeners, and child-process listeners declared with +// unknown chunks and non-null exit types. +import { spawn } from "node:child_process"; +import * as fs from "node:fs"; +import * as os from "node:os"; +import * as path from "node:path"; +import { PassThrough, Transform } from "node:stream"; +import { createGunzip, gzipSync } from "node:zlib"; + +interface Progress { + bytes: number; + chunks: number; +} + +function createCounter(progress: Progress) { + return new Transform({ + transform(chunk, _encoding, callback) { + const length = typeof chunk === "string" ? Buffer.byteLength(chunk) : chunk.length; + progress.bytes += length; + progress.chunks++; + callback(null, chunk); + }, + flush(callback) { + callback(); + }, + }); +} + +function gunzipThrough(payload: Buffer): Promise { + return new Promise((resolve) => { + const progress: Progress = { bytes: 0, chunks: 0 }; + const counter = createCounter(progress); + const gunzip = createGunzip(); + const out = new PassThrough(); + const parts: string[] = []; + counter.pipe(gunzip).pipe(out); + out.on("data", (d: Buffer) => parts.push(d.toString())); + out.on("end", () => resolve(`${parts.join("")} ${progress.bytes > 0} ${progress.chunks}`)); + counter.end(payload); + }); +} + +function readFileStream(file: string): Promise { + return new Promise((resolve) => { + const stream = fs.createReadStream(file); + let text = ""; + const release = () => console.log("released"); + stream.on("close", release); + stream.on("error", release); + stream.on("data", (chunk) => { + text += chunk.toString(); + }); + stream.on("end", () => resolve(text)); + }); +} + +function run(script: string): Promise { + return new Promise((resolve, reject) => { + const child = spawn("/bin/sh", ["-c", script], { stdio: "pipe" }); + let stdout = ""; + let stderr = ""; + if (child.stdout) { + child.stdout.setEncoding("utf8"); + child.stdout.on("data", (c) => { + stdout += c; + }); + } + if (child.stderr) { + child.stderr.on("data", (c) => { + const chunk = String(c); + stderr += chunk; + }); + } + child.on("error", reject); + child.on("close", (code: number, signal: string) => { + if (code === 0) return resolve(stdout); + reject(new Error(`Exited with ${code || signal}: ${stderr.trim()}`)); + }); + }); +} + +async function main(): Promise { + console.log("gunzip", await gunzipThrough(gzipSync(Buffer.from("payload")))); + const file = path.join(os.tmpdir(), `scriptc-stream-construction-${process.pid}.txt`); + fs.writeFileSync(file, "file text\n"); + console.log("file", JSON.stringify(await readFileStream(file))); + fs.unlinkSync(file); + console.log("run", JSON.stringify(await run("echo hi; echo warn >&2"))); + for (const script of ["echo bad >&2; exit 3", "kill -TERM $$"]) { + try { + await run(script); + } catch (e) { + console.log("error", (e as Error).message); + } + } +} + +void main(); diff --git a/tests/harness/__snapshots__/child-process.ts.txt b/tests/harness/__snapshots__/child-process.ts.txt index 2d83132f5..6fee8a2c2 100644 --- a/tests/harness/__snapshots__/child-process.ts.txt +++ b/tests/harness/__snapshots__/child-process.ts.txt @@ -91,4 +91,11 @@ child-process.ts:43:64 - error SC2020: 'fork without a fourth-slot IPC channel' | ^~~~~~~~ 44 | // Keep each fence on its own statement so diagnostics remain site-specific. - hint: the fourth stdio entry must be "ipc" \ No newline at end of file + hint: the fourth stdio entry must be "ipc" + +child-process.ts:48:21 - error SC1090: untyped child-stream 'data' listeners whose setEncoding state is unclear at their registration sites (declare the chunk as 'Buffer' or 'string') are not supported yet + + 47 | const pipedOut = piped.stdout!; + 48 | pipedOut.on("data", (chunk: unknown) => console.log(String(chunk))); + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 49 | pipedOut.setEncoding("utf8"); \ No newline at end of file diff --git a/tests/harness/project-config.test.ts b/tests/harness/project-config.test.ts index 3635c88ed..31e84fdbb 100644 --- a/tests/harness/project-config.test.ts +++ b/tests/harness/project-config.test.ts @@ -479,6 +479,24 @@ test("node-types: NodeJS stream interfaces and stream.Readable/Writable take eve expect(comparableStderr(native.stderr)).toBe(node.stderr); }); +test("node-types: streams construct and child listeners take @types/node's parameter types", async () => { + const outDir = outDirFor("stream-construction"); + const entry = join(nodeTypesDir, "stream-construction.ts"); + const result = await compile(entry, { + outPath: join(outDir, "stream-construction"), + outDir, + sanitize, + }); + expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true); + if (!result.ok) return; + const [native, node] = await Promise.all([ + execFileAsync(result.binaryPath), + execFileAsync(process.execPath, ["--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", entry]), + ]); + expect(native.stdout).toBe(node.stdout); + expect(comparableStderr(native.stderr)).toBe(node.stderr); +}); + test("node-types: promisified setTimeout and setImmediate lower as timers/promises", async () => { const outDir = outDirFor("promisify-timers"); const entry = join(nodeTypesDir, "promisify-timers.ts"); From b9aaaa9d8c7af2a8fcaf69f96cc6d974aa39f1b8 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Tue, 20 Oct 2026 04:30:50 -0700 Subject: [PATCH 079/122] Answer server.address() with the socket path for Unix-socket servers Node answers a Unix-socket server's address() with its path. Programs that call listen() with a string argument now get a Unix-aware address(): typed `string | AddressInfo | null` (the @types/node declaration), it answers the path; typed as the AddressInfo record alone, which cannot hold the path, it throws for such a server, as does the composed address().port read. Every other program keeps the record-only lowering, so their output is unchanged. Co-Authored-By: Claude Opus 5.5 --- .../limitations/unix-domain-sockets.mdx | 2 +- .../compiler/src/backend/llvm/lib-shared.ts | 2 + .../src/frontend/lowering/lower-server.ts | 126 ++++++++++++++++-- .../compiler/src/frontend/lowering/lowerer.ts | 4 + packages/compiler/src/ir/builtin-effects.ts | 2 + .../compiler/src/ir/builtin-signatures.ts | 5 + packages/runtime/src/scr_net.c | 20 ++- packages/runtime/src/scr_net.h | 2 + tests/fixtures/node-types/http-socket-path.ts | 12 ++ 9 files changed, 161 insertions(+), 14 deletions(-) diff --git a/docs/content/limitations/unix-domain-sockets.mdx b/docs/content/limitations/unix-domain-sockets.mdx index 70a8a47e4..e98bc72e3 100644 --- a/docs/content/limitations/unix-domain-sockets.mdx +++ b/docs/content/limitations/unix-domain-sockets.mdx @@ -1,2 +1,2 @@ -`server.listen(path)` and the `socketPath` option of `http.request()` and `https.request()` use Unix domain sockets on Linux and macOS. On Windows, where Node maps these paths to named pipes, they fail with `ENOTSUP`. `server.address()` on a server listening on a Unix socket returns `{ address: , family: "IPv4", port: 0 }`, where Node returns the socket path as a string: compiled code holds `address()` as the `{ address, family, port }` record. `listen(path, backlog, callback)` is refused at compile time; pass `listen(path, callback)`. +`server.listen(path)` and the `socketPath` option of `http.request()` and `https.request()` use Unix domain sockets on Linux and macOS. On Windows, where Node maps these paths to named pipes, they fail with `ENOTSUP`. `server.address()` on a server listening on a Unix socket returns the socket path, as in Node, when the result is typed `string | AddressInfo | null` (the @types/node declaration). Where it is typed as the `{ address, family, port }` record alone, which cannot hold the path, the call throws an error for such a server. `listen(path, backlog, callback)` is refused at compile time; pass `listen(path, callback)`. When the options of `http.request()` come from a variable, or from an object literal that spreads one, they are read at run time with Node's validation errors. The `auth`, `createConnection`, `family`, `hints`, `insecureHTTPParser`, `joinDuplicateHeaders`, `localAddress`, `localPort`, `lookup` and `uniqueHeaders` options then throw a runtime error when they hold a value, and so do `setHost: false` and `setDefaultHeaders: false`. diff --git a/packages/compiler/src/backend/llvm/lib-shared.ts b/packages/compiler/src/backend/llvm/lib-shared.ts index b4b0db146..fd62aa613 100644 --- a/packages/compiler/src/backend/llvm/lib-shared.ts +++ b/packages/compiler/src/backend/llvm/lib-shared.ts @@ -1153,6 +1153,8 @@ export const LIB_FN_SYMS: Record = { "net.sockOnCloseDyn": "scr_net_sock_on_close_dyn", "net.sockOnLookup": "scr_net_sock_on_lookup", "net.serverCloseDyn": "scr_net_server_close_dyn", + "net.serverUnixPath": "scr_net_server_unix_path", + "net.serverAddressCheck": "scr_net_server_address_check", "net.sockOnWriteFlushDyn": "scr_net_sock_on_write_flush_dyn", "net.sockOnFinishDyn": "scr_net_sock_on_finish_dyn", "net.isIP": "scr_net_is_ip", diff --git a/packages/compiler/src/frontend/lowering/lower-server.ts b/packages/compiler/src/frontend/lowering/lower-server.ts index 9e86c65dd..b4e64c9d0 100644 --- a/packages/compiler/src/frontend/lowering/lower-server.ts +++ b/packages/compiler/src/frontend/lowering/lower-server.ts @@ -2093,6 +2093,32 @@ function lowerNetServerMethodCall( const result = lowerer.mapTypeOf(lowerer.typeOf(call)); if (result && isAddressInfoRecord(lowerer, result)) { const receiver = coerceToHandle(lowerer, access.expression, NETSERVER_T); + if (unixListenReachable(lowerer)) { + // A Unix-socket server's address() is its path, which this record + // cannot hold: the check throws for one before the record reads. + const local = lowerer.declareHiddenLocal("%addressServer", NETSERVER_T); + const server = (): IrExpr => varRef(local.id, NETSERVER_T, loc); + return { + kind: "seqExpr", + stmts: [ + { kind: "varDecl", localId: local.id, init: receiver, loc }, + { + kind: "exprStmt", + expr: { + kind: "libCall", + fn: "net.serverAddressCheck", + args: [server()], + type: VOID, + loc, + }, + loc, + }, + ], + result: { kind: "libCall", fn: "net.serverAddress", args: [server()], type: result, loc }, + type: result, + loc, + }; + } return { kind: "libCall", fn: "net.serverAddress", args: [receiver], type: result, loc }; } // @types/node's `string | AddressInfo | null`: the record while the @@ -2112,20 +2138,50 @@ function lowerNetServerMethodCall( const receiver = coerceToHandle(lowerer, access.expression, NETSERVER_T); const local = lowerer.declareHiddenLocal("%addressServer", NETSERVER_T); const server = varRef(local.id, NETSERVER_T, loc); + const recordArm: IrExpr = { + kind: "unionWrap", + unionId, + tag: lowerer.armTag(unionId, record), + value: { kind: "libCall", fn: "net.serverAddress", args: [server], type: record, loc }, + type: result, + loc, + }; + const stringTag = lowerer.armTag(unionId, STRING); + let listeningArm: IrExpr = recordArm; + const stmts: IrStmt[] = [{ kind: "varDecl", localId: local.id, init: receiver, loc }]; + if (stringTag >= 0 && unixListenReachable(lowerer)) { + // Node answers a Unix-socket server's address() with its path. + const pathLocal = lowerer.declareHiddenLocal("%addressPath", STRING); + const path = (): IrExpr => varRef(pathLocal.id, STRING, loc); + stmts.push({ + kind: "varDecl", + localId: pathLocal.id, + init: { kind: "libCall", fn: "net.serverUnixPath", args: [server], type: STRING, loc }, + loc, + }); + listeningArm = { + kind: "ternary", + cond: { + kind: "strEq", + negated: true, + left: path(), + right: strLit("", loc), + type: BOOL, + loc, + }, + then: { kind: "unionWrap", unionId, tag: stringTag, value: path(), type: result, loc }, + else_: recordArm, + type: result, + loc, + }; + } return { kind: "seqExpr", - stmts: [{ kind: "varDecl", localId: local.id, init: receiver, loc }], + stmts, result: { kind: "ternary", cond: { kind: "libCall", fn: "net.serverListening", args: [server], type: BOOL, loc }, - then: { - kind: "unionWrap", - unionId, - tag: lowerer.armTag(unionId, record), - value: { kind: "libCall", fn: "net.serverAddress", args: [server], type: record, loc }, - type: result, - loc, - }, + then: listeningArm, else_: { kind: "unionWrap", unionId, @@ -2181,6 +2237,34 @@ function socketEncodingReachable(lowerer: Lowerer): boolean { return reachable; } +/** Whether some server in the program may listen on a Unix socket: a + * `.listen(x, ...)` call on a server (or an untyped value) whose first + * argument is string-typed. Only then does address() check for the socket + * path, so other programs keep the record-only lowering. */ +function unixListenReachable(lowerer: Lowerer): boolean { + if (lowerer.unixListenReachable !== null) return lowerer.unixListenReachable; + let reachable = false; + for (const file of lowerer.program.getSourceFiles()) { + if (file.isDeclarationFile || !file.text.includes("listen")) continue; + ts.walkPreorder(file, (node) => { + if (reachable) return "skip"; + if (!ts.isCallExpression(node) || node.arguments.nodes.length === 0) return undefined; + const callee = node.expression; + if (!ts.isPropertyAccessExpression(callee) || callee.name.text !== "listen") return undefined; + const kind = lowerer.mapTypeOf(lowerer.checker.getTypeAtLocation(callee.expression))?.kind; + if (kind !== undefined && kind !== "netServer" && kind !== "dyn") return undefined; + const arg = lowerer.mapTypeOf(lowerer.checker.getTypeAtLocation(node.arguments.nodes[0]!)); + // Only a string-typed argument lowers to listen(path) (an untyped + // one checks into a port). + if (arg?.kind === "string") reachable = true; + return undefined; + }); + if (reachable) break; + } + lowerer.unixListenReachable = reachable; + return reachable; +} + /** An inline 'data' listener whose chunk parameter is typed Buffer, for a * socket that may have an encoding set: the chunk binds as a checked * dynamic value, so the body sees what Node passes (a string after @@ -4020,6 +4104,30 @@ export function lowerServerProperty( if (lowerer.mapTypeOf(lowerer.typeOf(inner.expression))?.kind !== "netServer") return null; if (!lowerer.isStdlibMember(inner)) return null; const receiver = coerceToHandle(lowerer, inner.expression, NETSERVER_T); + if (unixListenReachable(lowerer)) { + const local = lowerer.declareHiddenLocal("%addressServer", NETSERVER_T); + const server = (): IrExpr => varRef(local.id, NETSERVER_T, loc); + return { + kind: "seqExpr", + stmts: [ + { kind: "varDecl", localId: local.id, init: receiver, loc }, + { + kind: "exprStmt", + expr: { + kind: "libCall", + fn: "net.serverAddressCheck", + args: [server()], + type: VOID, + loc, + }, + loc, + }, + ], + result: { kind: "libCall", fn: "net.serverPort", args: [server()], type: F64, loc }, + type: F64, + loc, + }; + } return { kind: "libCall", fn: "net.serverPort", args: [receiver], type: F64, loc }; } diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index 1361b346f..198a9f55b 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -1950,6 +1950,10 @@ export class Lowerer { * setEncoding call whose receiver is a socket or an untyped value. * Computed once on first use (null until then). */ socketEncodingReachable: boolean | null = null; + /** Whether some server in this program may listen on a Unix socket: a + * listen call whose first argument is a string. Computed once on first + * use (null until then); address() answers the path only when true. */ + unixListenReachable: boolean | null = null; /** IMPLICIT-ANY instances lowered EAGERLY at first demand (their return * types are inferred from the body — the call site needs them settled), * collected here for run()'s function list (the liftedFns discipline). */ diff --git a/packages/compiler/src/ir/builtin-effects.ts b/packages/compiler/src/ir/builtin-effects.ts index 754968113..af5b24129 100644 --- a/packages/compiler/src/ir/builtin-effects.ts +++ b/packages/compiler/src/ir/builtin-effects.ts @@ -269,6 +269,8 @@ export const MAY_THROW_LIB_FNS: ReadonlySet = new Set([ "http.requestDynCb", "https.requestDyn", "https.requestDynCb", + // address() of a Unix-socket server read as the AddressInfo record. + "net.serverAddressCheck", // The h2 client's throwing entries: connect on a bad/non-http // authority (ERR_INVALID_URL / ERR_INVALID_PROTOCOL), request on a // closed/destroyed session (ERR_HTTP2_INVALID_SESSION), setEncoding on diff --git a/packages/compiler/src/ir/builtin-signatures.ts b/packages/compiler/src/ir/builtin-signatures.ts index 4bfdf031a..4b56da342 100644 --- a/packages/compiler/src/ir/builtin-signatures.ts +++ b/packages/compiler/src/ir/builtin-signatures.ts @@ -1311,6 +1311,11 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ * undefined once the server closes, or Node's ERR_SERVER_NOT_RUNNING * when it was not listening. */ "net.serverCloseDyn": { argTypes: [NETSERVER_T, DYN], result: VOID }, + /** server.address() in programs that may listen on a Unix socket: the + * path a listening server answers ("" for a TCP server), and the check + * a record-typed address() makes first (throws for a Unix server). */ + "net.serverUnixPath": { argTypes: [NETSERVER_T], result: STRING }, + "net.serverAddressCheck": { argTypes: [NETSERVER_T], result: VOID }, "net.serverCloseCb": { argTypes: [NETSERVER_T, { kind: "func", params: [], ret: VOID }], result: VOID, diff --git a/packages/runtime/src/scr_net.c b/packages/runtime/src/scr_net.c index a92f738bd..978f42e3d 100644 --- a/packages/runtime/src/scr_net.c +++ b/packages/runtime/src/scr_net.c @@ -2706,6 +2706,22 @@ ScrNetSocket *scr_net_connect_path(ScrStr *path, bool deferred) { return s; } +/* server.address() in programs that listen on a path (the compiler links + * these only there): the Unix-socket path a listening server answers in + * place of the AddressInfo record (+1, "" for a TCP server), and the check + * a record-typed address() read makes first. */ +ScrStr *scr_net_server_unix_path(ScrNetServer *s) { + return s->listening && s->unix_path != NULL ? scr_str_retain(s->unix_path) : scr_str_new("", 0); +} + +void scr_net_server_address_check(ScrNetServer *s) { + if (!s->listening || s->unix_path == NULL) return; + static const char msg[] = + "server.address() of a server listening on a Unix socket is the socket path string; " + "type the result as string | AddressInfo | null (Node's declaration) to read it"; + scr_throw_error_msg(SCR_ERR_ERROR, msg, sizeof msg - 1); +} + /* server.listen(path[, cb]): binds a Unix socket now; a failure is the * deferred 'error', success the deferred 'listening', as for a port. */ void scr_net_listen_path(ScrNetServer *s, ScrStr *path /*borrowed*/, @@ -2738,10 +2754,6 @@ void scr_net_listen_path(ScrNetServer *s, ScrStr *path /*borrowed*/, s->emit_listening = true; scr_str_release(s->unix_path); s->unix_path = scr_str_retain(path); - /* address() carries the path in its address field (Node answers - * the path string itself) */ - scr_str_release(s->bound_host); - s->bound_host = scr_str_retain(path); scr_net_watch_read(fd, s, true); scr_net_server_register(s); return; diff --git a/packages/runtime/src/scr_net.h b/packages/runtime/src/scr_net.h index 385704654..d78ae48f0 100644 --- a/packages/runtime/src/scr_net.h +++ b/packages/runtime/src/scr_net.h @@ -167,6 +167,8 @@ void scr_net_server_close(ScrNetServer *s, ScrClosure *cb /*moves, nullable*/); void scr_net_server_close_dyn(ScrNetServer *s, const ScrDyn *listener /*borrowed*/); void scr_net_sock_on_lookup(ScrNetSocket *s, const ScrDyn *listener /*borrowed*/, bool once); void scr_net_lookup_install(void); +ScrStr *scr_net_server_unix_path(ScrNetServer *s); /* +1, "" unless listening on a path */ +void scr_net_server_address_check(ScrNetServer *s); /* throws for a Unix-socket server */ void scr_net_server_close_all_connections(ScrNetServer *s); void scr_net_server_close_idle_connections(ScrNetServer *s); ScrDyn *scr_net_server_max_connections_get(ScrNetServer *s); /* +1 */ diff --git a/tests/fixtures/node-types/http-socket-path.ts b/tests/fixtures/node-types/http-socket-path.ts index 1b7ff96b5..9282ed052 100644 --- a/tests/fixtures/node-types/http-socket-path.ts +++ b/tests/fixtures/node-types/http-socket-path.ts @@ -2,9 +2,11 @@ // endpoint URL is configured, typed by @types/node: the location is an // http.RequestOptions chosen at run time, spread into each request's // options with a path, method, headers, timeout and an optional signal. +// server.address() answers the socket path (a TCP server's the record), and // server.close callbacks take Node's ERR_SERVER_NOT_RUNNING error. import * as http from "node:http"; import { existsSync } from "node:fs"; +import type { AddressInfo } from "node:net"; import { tmpdir } from "node:os"; import { join } from "node:path"; @@ -52,7 +54,17 @@ const server = http.createServer((req, res) => { }); }); +function describeAddress(address: string | AddressInfo | null): string { + if (address === null) return "null"; + return typeof address === "string" ? address.split(socketPath).join("") : `${address.family} port>0=${address.port > 0}`; +} + server.listen(socketPath, async () => { + console.log("address", describeAddress(server.address())); + const tcp = http.createServer(); + await new Promise((resolve) => tcp.listen(0, "127.0.0.1", () => resolve())); + console.log("tcp address", describeAddress(tcp.address())); + tcp.close(); console.log(await call("/v1/start", JSON.stringify({ id: 1 }), 5000)); console.log(await call("/v1/status", JSON.stringify({ id: 2 }), 5000, new AbortController())); try { From 1092f69ea9d4abad2894d95f630b1a55aa89fda4 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Tue, 20 Oct 2026 05:22:55 -0700 Subject: [PATCH 080/122] Split a chained Set call the self-hosted compiler cannot lower `deadDeclarators.add(d).add(memoDecl)` stopped the self-hosting build with "chained Set method calls ... has no scriptc lowering yet". Co-Authored-By: Claude Opus 5.5 --- packages/compiler/src/frontend/npm-static-bundled-esm.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/compiler/src/frontend/npm-static-bundled-esm.ts b/packages/compiler/src/frontend/npm-static-bundled-esm.ts index 9c77128e1..d9fa88fc4 100644 --- a/packages/compiler/src/frontend/npm-static-bundled-esm.ts +++ b/packages/compiler/src/frontend/npm-static-bundled-esm.ts @@ -633,7 +633,8 @@ export function lowerBundledEsmInits( text: `(${init}${getterText(value)})`, }); } - deadDeclarators.add(d).add(memoDecl); + deadDeclarators.add(d); + deadDeclarators.add(memoDecl); loaderSpans.push({ start: d.getStart(sf), end: d.getEnd() }); lowered.add(target); } From 465abda816f9ce827a156e3c7b77abfd7c4253a7 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Tue, 20 Oct 2026 04:30:43 -0700 Subject: [PATCH 081/122] Compile the remaining safe-fetch shapes: Promise race entries, typed Array(n).fill, narrowed header entries, lookup option unions and CommonJS export aliases - Promise.race admits a Promise entry (the deadline idiom) under any result type: it can only reject, so its fulfillment adapter traps. A Promise executor's one-parameter resolve validates against the void inner. - Array(n).fill(v) and new Array(n).fill(v), which the checker types as any[], take v's type. - Object.entries/values over an index-signature record cast to a narrower value type check each value into the element type instead of refusing. - dns.LookupAllOptions and the lookup-function option union that holds it (LookupOneOptions | LookupAllOptions | number | undefined) ride checked-dynamic storage, and dns.lookup passes a cast of a checked-dynamic options value through unchanged. - npm packages: a CommonJS module that names its export object through a variable (`var ip = exports; ip.isPrivate = ...`, the ip package) is respelled through `exports`, and tsc output that adds a default-export object (`exports.default = { metrics: metrics_api_1.metrics }`, the @opentelemetry/api index) keeps its named exports linked. Co-Authored-By: Claude Opus 5.5 --- .../src/backend/llvm/expr-callbacks.ts | 17 ++ .../frontend/lowering/builtins/promises.ts | 13 +- .../lowering/containers/array-construction.ts | 25 +++ .../lowering/containers/indexed-objects.ts | 76 +++++-- .../src/frontend/lowering/lower-dgram.ts | 13 +- .../frontend/npm-static-cjs-exports-var.ts | 198 ++++++++++++++++++ .../src/frontend/npm-static-rewrite-syntax.ts | 22 +- packages/compiler/src/frontend/npm-static.ts | 6 +- packages/compiler/src/frontend/services.ts | 9 + packages/compiler/src/frontend/type-mapper.ts | 29 +++ packages/compiler/src/ir/validate.ts | 5 +- .../6450-promise-race-never-entry.ts.json | 6 + .../6451-array-length-fill-spread.ts.json | 6 + .../lookup-and-header-entries.ts.json | 6 + .../npm/cases/cjs-exports-var/main.ts.json | 6 + .../npm/cases/tsc-default-object/main.ts.json | 6 + tests/corpus/6450-promise-race-never-entry.ts | 22 ++ tests/corpus/6451-array-length-fill-spread.ts | 15 ++ .../node-types/lookup-and-header-entries.ts | 47 +++++ .../npm/cases/cjs-exports-var/main.ts | 12 ++ .../npm/cases/tsc-default-object/main.ts | 7 + .../npm/node_modules/exportsvar/index.d.ts | 6 + .../npm/node_modules/exportsvar/lib/ip.js | 35 ++++ .../npm/node_modules/exportsvar/package.json | 6 + .../node_modules/tscdefault/build/index.d.ts | 8 + .../node_modules/tscdefault/build/index.js | 11 + .../tscdefault/build/registry-api.d.ts | 2 + .../tscdefault/build/registry-api.js | 5 + .../tscdefault/build/registry.d.ts | 5 + .../node_modules/tscdefault/build/registry.js | 20 ++ .../npm/node_modules/tscdefault/package.json | 6 + tests/harness/project-config.test.ts | 19 ++ 32 files changed, 651 insertions(+), 18 deletions(-) create mode 100644 packages/compiler/src/frontend/npm-static-cjs-exports-var.ts create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6450-promise-race-never-entry.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6451-array-length-fill-spread.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/lookup-and-header-entries.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/cjs-exports-var/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/tsc-default-object/main.ts.json create mode 100644 tests/corpus/6450-promise-race-never-entry.ts create mode 100644 tests/corpus/6451-array-length-fill-spread.ts create mode 100644 tests/fixtures/node-types/lookup-and-header-entries.ts create mode 100644 tests/fixtures/npm/cases/cjs-exports-var/main.ts create mode 100644 tests/fixtures/npm/cases/tsc-default-object/main.ts create mode 100644 tests/fixtures/npm/node_modules/exportsvar/index.d.ts create mode 100644 tests/fixtures/npm/node_modules/exportsvar/lib/ip.js create mode 100644 tests/fixtures/npm/node_modules/exportsvar/package.json create mode 100644 tests/fixtures/npm/node_modules/tscdefault/build/index.d.ts create mode 100644 tests/fixtures/npm/node_modules/tscdefault/build/index.js create mode 100644 tests/fixtures/npm/node_modules/tscdefault/build/registry-api.d.ts create mode 100644 tests/fixtures/npm/node_modules/tscdefault/build/registry-api.js create mode 100644 tests/fixtures/npm/node_modules/tscdefault/build/registry.d.ts create mode 100644 tests/fixtures/npm/node_modules/tscdefault/build/registry.js create mode 100644 tests/fixtures/npm/node_modules/tscdefault/package.json diff --git a/packages/compiler/src/backend/llvm/expr-callbacks.ts b/packages/compiler/src/backend/llvm/expr-callbacks.ts index db1dca1af..b7954c174 100644 --- a/packages/compiler/src/backend/llvm/expr-callbacks.ts +++ b/packages/compiler/src/backend/llvm/expr-callbacks.ts @@ -55,6 +55,23 @@ export function raceAdapterFor(host: LlvmEmitterContext, from: IrType, to: IrTyp if (sym) return sym; sym = `sc_race_${host.resolveThunks.size}`; host.resolveThunks.set(key, sym); + const toArms = to.kind === "union" ? (host.unionsById.get(to.unionId)?.arms ?? []) : []; + if (from.kind === "void" && !toArms.some((a) => a.kind === "undefinedT")) { + // A Promise entry (the frontend admits void entries without an + // undefined result arm only for those): it can only reject, so its + // fulfillment adapter never runs. + host.declare(`declare void @scr_trap(ptr)`); + const msg = host.cstr("scriptc: a Promise race entry fulfilled\n"); + host.resolveThunkDefs.push( + `define internal void @${sym}(ptr %dst, ptr %src) ${FN_ATTRS} { ; race ${key}`, + `entry:`, + ` call void @scr_trap(ptr ${msg})`, + ` unreachable`, + `}`, + ``, + ); + return sym; + } if (to.kind !== "union") throw new InternalCompilerError("llvm emitter bug: race adapter to a non-union"); const toDef = host.unionsById.get(to.unionId); diff --git a/packages/compiler/src/frontend/lowering/builtins/promises.ts b/packages/compiler/src/frontend/lowering/builtins/promises.ts index 48106d082..03e9c7497 100644 --- a/packages/compiler/src/frontend/lowering/builtins/promises.ts +++ b/packages/compiler/src/frontend/lowering/builtins/promises.ts @@ -74,6 +74,17 @@ export function lowerPromiseStaticCall( } return resultArms.some((a) => typeEquals(a, inner)); }; + // A Promise entry (the deadline idiom: `new Promise((_, + // reject) => setTimeout(reject, ms))`) can only reject, so it races + // under any result type; its inner type maps to void. + const neverEntry = (el: ts.Expression): boolean => { + const t = lowerer.typeOf(el); + const arg = + t.getSymbol()?.name === "Promise" + ? lowerer.checker.getTypeArguments(t as ts.TypeReference)[0] + : undefined; + return arg !== undefined && (arg.flags & ts.TypeFlags.Never) !== 0; + }; const entries = argNode.elements.nodes.map((el) => { const entry = lowerer.lowerExpr(el); if (entry.type.kind !== "promise") { @@ -83,7 +94,7 @@ export function lowerPromiseStaticCall( "JS would resolve plain values — wrap them: new Promise((r) => r(v))", ); } - if (!compatible(entry.type.inner)) { + if (!compatible(entry.type.inner) && !(entry.type.inner.kind === "void" && neverEntry(el))) { lowerer.unsupported( "SC1090", el, diff --git a/packages/compiler/src/frontend/lowering/containers/array-construction.ts b/packages/compiler/src/frontend/lowering/containers/array-construction.ts index 6072b58b7..08ac43920 100644 --- a/packages/compiler/src/frontend/lowering/containers/array-construction.ts +++ b/packages/compiler/src/frontend/lowering/containers/array-construction.ts @@ -265,6 +265,22 @@ function appendWidenedArrayValues( } /** The Array constructor's element and count forms, shared by calls and new. */ +/** The value of `.fill(v)` when `expr` is that call's receiver. */ +function fillArgumentOfReceiver(expr: ts.Expression): ts.Expression | null { + let receiver: ts.Node = expr; + let parent = expr.parent; + while (ts.isParenthesizedExpression(parent)) { + receiver = parent; + parent = parent.parent; + } + if (!ts.isPropertyAccessExpression(parent) || parent.expression !== receiver) return null; + if (parent.name.text !== "fill" || parent.questionDotToken) return null; + const call = parent.parent; + if (!ts.isCallExpression(call) || call.expression !== parent) return null; + const value = call.arguments.nodes[0]; + return value !== undefined && !ts.isSpreadElement(value) ? value : null; +} + export function lowerArrayConstructor( lowerer: Lowerer, expr: ts.CallExpression | ts.NewExpression, @@ -295,6 +311,15 @@ export function lowerArrayConstructor( const contextual = lowerer.checker.getContextualType(expr); if (contextual) result = lowerer.mapTypeOf(contextual); } + // `Array(n).fill(v)`: the checker types the length-constructed array as + // any[], but fill gives every element v's type. + if (result?.kind !== "array") { + const fillValue = fillArgumentOfReceiver(expr); + const elem = fillValue + ? lowerer.mapTypeOf(lowerer.checker.getBaseTypeOfLiteralType(lowerer.typeOf(fillValue))) + : null; + if (elem !== null && isSupportedArrayElem(elem)) result = arrayOf(elem); + } if (isJsSourceFile(expr.getSourceFile()) && (result === null || result.kind === "dyn")) result = arrayOf(DYN); if (result?.kind !== "array" || !isSupportedArrayElem(result.elem)) { diff --git a/packages/compiler/src/frontend/lowering/containers/indexed-objects.ts b/packages/compiler/src/frontend/lowering/containers/indexed-objects.ts index 450e64142..326419504 100644 --- a/packages/compiler/src/frontend/lowering/containers/indexed-objects.ts +++ b/packages/compiler/src/frontend/lowering/containers/indexed-objects.ts @@ -148,12 +148,29 @@ export function objectIterOverIndexShape( } // The overflow value must surface as the element type (identity, an // arm of a union element, or a dyn element over a dyn signature). + // A cast that narrows the signature's union (`Object.entries(headers as + // Record)` over a signature that + // also holds numbers): each overflow value checks into the element type, + // so a value of a dropped arm throws instead of mistyping. + let ivNarrows = false; if (valueT) { const ivSurfaces = typeEquals(iv, valueT) || (valueT.kind === "union" && lowerer.armTag(valueT.unionId, iv) >= 0) || (valueT.kind === "dyn" && iv.kind === "dyn"); - if (!ivSurfaces) { + if ( + !ivSurfaces && + iv.kind === "union" && + lowerer.dynConvertible(iv) && + lowerer.dynConvertible(valueT) + ) { + const ivUnion = iv; + const narrowArms = + valueT.kind === "union" ? (lowerer.unions.get(valueT.unionId)?.arms ?? []) : [valueT]; + ivNarrows = + narrowArms.length > 0 && narrowArms.every((a) => lowerer.armTag(ivUnion.unionId, a) >= 0); + } + if (!ivSurfaces && !ivNarrows) { lowerer.unsupported( "SC1090", node, @@ -267,6 +284,21 @@ export function objectIterOverIndexShape( } } } + // A cast that narrows the field's type (the element type keeps + // some of its arms): the value checks into the element type, so a + // dropped arm throws instead of mistyping. + if (lowerer.dynConvertible(f.type) && lowerer.dynConvertible(valueT)) { + const armsOf = (t: IrType): IrType[] => + t.kind === "union" ? (lowerer.unions.get(t.unionId)?.arms ?? []) : [t]; + const target = armsOf(valueT); + if (armsOf(f.type).some((a) => target.some((b) => typeEquals(a, b)))) + return { + kind: "dynCheck", + value: { kind: "dynFrom", value: raw, type: DYN, loc }, + type: valueT, + loc, + }; + } return null; }; const pushedOf = (element: IrExpr): IrExpr => @@ -337,17 +369,37 @@ export function objectIterOverIndexShape( const ksT = arrayOf(STRING); const ksRef = varRef("ks.0", ksT, loc); const kRef = varRef("k.0", STRING, loc); - const readValue: IrExpr | null = valueT - ? { - kind: "recordKeyGet", - obj: rRef, - shapeId: argIr.shapeId, - key: kRef, - overflowOnly: true, - type: valueT, - loc, - } - : null; + const readValue: IrExpr | null = !valueT + ? null + : ivNarrows + ? { + kind: "dynCheck", + value: { + kind: "dynFrom", + value: { + kind: "recordKeyGet", + obj: rRef, + shapeId: argIr.shapeId, + key: kRef, + overflowOnly: true, + type: iv, + loc, + }, + type: DYN, + loc, + }, + type: valueT, + loc, + } + : { + kind: "recordKeyGet", + obj: rRef, + shapeId: argIr.shapeId, + key: kRef, + overflowOnly: true, + type: valueT, + loc, + }; const loopPushed: IrExpr = member === "keys" ? kRef diff --git a/packages/compiler/src/frontend/lowering/lower-dgram.ts b/packages/compiler/src/frontend/lowering/lower-dgram.ts index 54f9d158b..d035b21c2 100644 --- a/packages/compiler/src/frontend/lowering/lower-dgram.ts +++ b/packages/compiler/src/frontend/lowering/lower-dgram.ts @@ -290,8 +290,17 @@ function lowerDnsModuleCall( loc: SrcLoc, ): IrExpr { const args = expr.arguments.nodes; - const dyn = (node: ts.Expression): IrExpr => - lowerer.coerceToExpected(lowerer.lowerExpr(node), DYN); + const dyn = (node: ts.Expression): IrExpr => { + // `opts as dns.LookupOneOptions` over a checked-dynamic value (the + // overload-satisfying cast of a pass-through lookup): the cast changes + // no value, so the options reach the runtime as they are. + let inner = node; + while (ts.isAsExpression(inner) || ts.isParenthesizedExpression(inner)) + inner = inner.expression; + if (inner !== node && lowerer.mapTypeOf(lowerer.typeOf(inner))?.kind === "dyn") + return lowerer.lowerExpr(inner); + return lowerer.coerceToExpected(lowerer.lowerExpr(node), DYN); + }; // A callback whose second parameter is typed (string, LookupAddress[]) // cannot take the undefined Node passes after an error. const typedResult = (node: ts.Expression): IrExpr => { diff --git a/packages/compiler/src/frontend/npm-static-cjs-exports-var.ts b/packages/compiler/src/frontend/npm-static-cjs-exports-var.ts new file mode 100644 index 000000000..38972990c --- /dev/null +++ b/packages/compiler/src/frontend/npm-static-cjs-exports-var.ts @@ -0,0 +1,198 @@ +/* CommonJS modules that name their export object through a variable: + * + * var ip = exports; + * ip.isPrivate = function (addr) { … }; + * ip.isPublic = function (addr) { return !ip.isPrivate(addr); }; + * + * `ip` IS the export object, so every member read or write through it is + * an export. The pass respells those accesses as `exports.`, the + * form the static export protocol models, and drops the alias declaration + * (the export object has no storage of its own to bind). + * + * Exact only while the variable is nothing but that alias: one top-level + * declaration initialized with the CommonJS `exports` global, never + * reassigned, never declared again anywhere in the file (so no nested + * binding shadows it), and used only as the object of member accesses. + * `exports` itself must be the global (never declared in the file) and is + * never reassigned. Other shapes answer null and keep their existing + * treatment. */ +import * as ts from "./ts7/syntax.js"; + +function unparen(e: ts.Expression): ts.Expression { + let x = e; + while (ts.isParenthesizedExpression(x)) x = x.expression; + return x; +} + +function isDeclarationName(id: ts.Identifier): boolean { + const p = id.parent; + return ( + ((ts.isVariableDeclaration(p) || + ts.isParameter(p) || + ts.isBindingElement(p) || + ts.isFunctionDeclaration(p) || + ts.isFunctionExpression(p) || + ts.isClassDeclaration(p) || + ts.isClassExpression(p)) && + p.name === id) || + (ts.isPropertyAccessExpression(p) && p.name === id) || + ((ts.isPropertyAssignment(p) || ts.isMethodDeclaration(p) || ts.isPropertyDeclaration(p)) && + p.name === id) + ); +} + +function isFunctionScope(n: ts.Node): boolean { + return ( + ts.isFunctionDeclaration(n) || + ts.isFunctionExpression(n) || + ts.isArrowFunction(n) || + ts.isMethodDeclaration(n) || + ts.isGetAccessorDeclaration(n) || + ts.isSetAccessorDeclaration(n) || + ts.isConstructorDeclaration(n) + ); +} + +function bindsName(name: ts.BindingName | undefined, text: string): boolean { + if (name === undefined) return false; + if (ts.isIdentifier(name)) return name.text === text; + for (const element of name.elements.nodes) { + if (!ts.isOmittedExpression(element) && bindsName(element.name, text)) return true; + } + return false; +} + +/** True when `scope` (a function, or a block or catch clause) declares + * `text` itself, so references inside it name that binding instead of the + * module-level alias: a parameter, a var/let/const or a function or class + * declaration in a function (nested functions excluded), a let/const or + * class in a block, a catch binding. */ +function shadows(scope: ts.Node, text: string): boolean { + if (isFunctionScope(scope)) { + const fn = scope as ts.SignatureDeclaration; + if (fn.parameters.nodes.some((p) => bindsName(p.name, text))) return true; + if ( + (ts.isFunctionExpression(scope) || ts.isClassExpression(scope)) && + scope.name?.text === text + ) + return true; + } else if (ts.isCatchClause(scope)) { + if (bindsName(scope.variableDeclaration?.name, text)) return true; + } else if (!ts.isBlock(scope)) { + return false; + } + const functionScope = isFunctionScope(scope); + let found = false; + ts.walkPreorder(scope, (n) => { + if (found) return "stop"; + if (n === scope) return undefined; + if (isFunctionScope(n)) { + if (ts.isFunctionDeclaration(n) && n.name?.text === text) found = true; + return "skip"; + } + if (!functionScope && ts.isBlock(n)) return "skip"; /* its own scope */ + if (ts.isVariableDeclaration(n) && bindsName(n.name, text)) { + const list = n.parent; + const blockScoped = + ts.isVariableDeclarationList(list) && (list.flags & ts.NodeFlags.BlockScoped) !== 0; + if (functionScope || blockScoped) found = true; + } + if (ts.isClassDeclaration(n) && n.name?.text === text) found = true; + return undefined; + }); + return found; +} + +export function normalizeCjsExportsVar(sf: ts.SourceFile): string | null { + const source = sf.text; + if (!/\b(?:var|let|const)\s+[A-Za-z_$][\w$]*\s*=\s*exports\s*[;,\n]/.test(source)) return null; + if (!sf.fileName.endsWith(".cjs") && ts.isExternalModule(sf)) return null; + let alias: { name: string; stmt: ts.VariableStatement } | null = null; + for (const stmt of sf.statements.nodes) { + if (!ts.isVariableStatement(stmt)) continue; + const decls = stmt.declarationList.declarations.nodes; + if (decls.length !== 1) continue; + const decl = decls[0]!; + const init = decl.initializer !== undefined ? unparen(decl.initializer) : undefined; + if ( + ts.isIdentifier(decl.name) && + init !== undefined && + ts.isIdentifier(init) && + init.text === "exports" + ) { + if (alias !== null) return null; /* two aliases: keep the plain treatment */ + alias = { name: decl.name.text, stmt }; + } + } + if (alias === null) return null; + const name = alias.name; + const aliasDecl = alias.stmt.declarationList.declarations.nodes[0]!; + const edits: { start: number; end: number; text: string }[] = []; + let ok = true; + ts.walkPreorder(sf, (n) => { + if (!ok) return "stop"; + if (n.kind >= ts.SyntaxKind.FirstJSDocNode && n.kind <= ts.SyntaxKind.LastJSDocNode) + return "skip"; + if ( + ts.isBinaryExpression(n) && + n.operatorToken.kind === ts.SyntaxKind.EqualsToken && + ts.isPropertyAccessExpression(unparen(n.left)) && + (unparen(n.left) as ts.PropertyAccessExpression).name.text === "exports" && + ts.isIdentifier((unparen(n.left) as ts.PropertyAccessExpression).expression) && + ((unparen(n.left) as ts.PropertyAccessExpression).expression as ts.Identifier).text === + "module" + ) { + ok = false; /* module.exports replaced: the alias no longer names the exports */ + return "stop"; + } + // A scope that declares the alias's name again: its references name + // that binding, not the export object. + if ( + n !== sf && + (isFunctionScope(n) || ts.isBlock(n) || ts.isCatchClause(n)) && + shadows(n, name) + ) + return "skip"; + if (!ts.isIdentifier(n)) return undefined; + if (n.text === "exports" && n !== aliasDecl.initializer) { + // `exports` declared in the file, or the export object replaced + if (isDeclarationName(n) && !ts.isPropertyAccessExpression(n.parent)) ok = false; + const p = n.parent; + if ( + ts.isBinaryExpression(p) && + p.left === n && + p.operatorToken.kind === ts.SyntaxKind.EqualsToken + ) + ok = false; + return undefined; + } + if (n.text !== name) return undefined; + if (n === aliasDecl.name) return undefined; + const p = n.parent; + if (ts.isPropertyAccessExpression(p) && p.name === n) return undefined; + if ((ts.isPropertyAssignment(p) || ts.isMethodDeclaration(p)) && p.name === n) return undefined; + if (isDeclarationName(n)) { + ok = false; /* declared again (a shadowing binding): not a plain alias */ + return undefined; + } + if ( + (ts.isPropertyAccessExpression(p) || ts.isElementAccessExpression(p)) && + p.expression === n + ) { + edits.push({ start: n.getStart(sf), end: n.getEnd(), text: "exports" }); + return undefined; + } + ok = false; /* used as a value, assigned, or passed along */ + return undefined; + }); + if (!ok || edits.length === 0) return null; + edits.push({ start: alias.stmt.getStart(sf), end: alias.stmt.getEnd(), text: "" }); + edits.sort((a, b) => a.start - b.start); + let out = ""; + let at = 0; + for (const e of edits) { + out += source.slice(at, e.start) + e.text; + at = e.end; + } + return out + source.slice(at); +} diff --git a/packages/compiler/src/frontend/npm-static-rewrite-syntax.ts b/packages/compiler/src/frontend/npm-static-rewrite-syntax.ts index 1730e1153..2fef30224 100644 --- a/packages/compiler/src/frontend/npm-static-rewrite-syntax.ts +++ b/packages/compiler/src/frontend/npm-static-rewrite-syntax.ts @@ -158,6 +158,26 @@ function chaseValue(e: ts.Expression): { kind: "ident" | "hoist"; text: string } return null; } +/** An object literal of side-effect-free member values (`exports.default + * = { context: context_api_1.context, metrics: metrics_api_1.metrics }`, + * tsc's default-export object): the tail const can build it once, like a + * chased value. Null for any other shape (spreads, methods, accessors, + * computed keys, or a value chaseValue rejects). */ +function chaseObjectLiteral(e: ts.Expression): { kind: "hoist"; text: string } | null { + let cur: ts.Expression = e; + while (ts.isParenthesizedExpression(cur)) cur = cur.expression; + if (!ts.isObjectLiteralExpression(cur)) return null; + for (const prop of cur.properties.nodes) { + if ( + !ts.isPropertyAssignment(prop) || + !(ts.isIdentifier(prop.name) || ts.isStringLiteral(prop.name)) || + (chaseValue(prop.initializer) === null && chaseObjectLiteral(prop.initializer) === null) + ) + return null; + } + return { kind: "hoist", text: cur.getText() }; +} + /** The getter body's returned expression for the two descriptor getter * spellings (`get() { return X }` / `get: function () { return X }`) and * the arrow forms the esbuild table uses (`() => X`, `() => { return X }`). */ @@ -792,7 +812,7 @@ export function rewriteBundlerCjsSyntax( // anything else keeps the file untouched (the offender path owns // it: neutralizing would drop an evaluation, keeping it meets the // lowering's discard fence at load). - const chased = chaseValue(cur); + const chased = chaseValue(cur) ?? chaseObjectLiteral(cur); if (chased === null) { return toEsm.pads.length > 0 ? { degrade: TO_ESM_MIXED_DEGRADE } : null; } diff --git a/packages/compiler/src/frontend/npm-static.ts b/packages/compiler/src/frontend/npm-static.ts index 7dffd05f7..7d6e08307 100644 --- a/packages/compiler/src/frontend/npm-static.ts +++ b/packages/compiler/src/frontend/npm-static.ts @@ -407,6 +407,10 @@ export function npmStaticFsShadow(services: FrontendServices): NpmStaticFsShadow for (const chunk of split.chunks) setVirtualSource(chunk.path, chunk.source); original = split.text; } + // A module that names its export object through a variable reaches + // its members through `exports`. + const exportsVar = original === null ? null : services.cjsExportsVar(original, path); + if (exportsVar !== null) original = exportsVar; if (original !== null) { const namepaths = services.jsDocNamepaths(path, original); const source = namepaths ?? original; @@ -441,7 +445,7 @@ export function npmStaticFsShadow(services: FrontendServices): NpmStaticFsShadow findWidened?.text ?? classFields?.text ?? namepaths ?? - (split !== null ? original : null); + (split !== null || exportsVar !== null ? original : null); } } } catch { diff --git a/packages/compiler/src/frontend/services.ts b/packages/compiler/src/frontend/services.ts index c6bb24ae5..c5100d52d 100644 --- a/packages/compiler/src/frontend/services.ts +++ b/packages/compiler/src/frontend/services.ts @@ -8,6 +8,7 @@ import { type BundledChunkSplit, } from "./npm-static-bundled-chunks.js"; import * as syntax from "./npm-static-declaration-syntax.js"; +import { normalizeCjsExportsVar } from "./npm-static-cjs-exports-var.js"; import type { SourceFile } from "./ts7/ast-types.js"; /** Evaluate an isolated, self-contained TypeScript callback under a finite @@ -76,6 +77,14 @@ export class FrontendServices { if (!mayBundleChunks(source)) return null; return splitBundledChunks(this.parse(path, source, "js"), path); } + /** A CommonJS module that names its export object through a variable + * (`var ip = exports`), its member accesses respelled through `exports` + * (null: none). */ + cjsExportsVar(source: string, path: string): string | null { + this.ensureOpen(); + if (!source.includes("exports")) return null; + return normalizeCjsExportsVar(this.parse(path, source, "js")); + } nullableClassFields(path: string, source: string): syntax.NpmStaticOverloadRewrite | null { return syntax.applyNpmStaticNullableClassFields(this.parse(path, source, "js"), source); } diff --git a/packages/compiler/src/frontend/type-mapper.ts b/packages/compiler/src/frontend/type-mapper.ts index 431d9fe3e..abc35e350 100644 --- a/packages/compiler/src/frontend/type-mapper.ts +++ b/packages/compiler/src/frontend/type-mapper.ts @@ -1355,6 +1355,26 @@ function isWorkerHandleSymbol( /** The `{ message: any }` object type (or its union with undefined) that * worker_threads declares as receiveMessageOnPort's result. */ +/** A union of dns lookup option bags with numbers and nullish values that + * includes LookupAllOptions (whose `all: true` arm has no record form). */ +function isLookupOptionsUnion(type: ts.Type, ctx: Pick): boolean { + if (!type.isUnionType()) return false; + let all = false; + for (const member of ts.constituentTypes(type)) { + if (member.flags & (ts.TypeFlags.NumberLike | ts.TypeFlags.Null | ts.TypeFlags.Undefined)) + continue; + const symbol = member.getSymbol(); + if ( + symbol === undefined || + !/^Lookup(One|All)?Options$/.test(symbol.name) || + !symbol.declarations.some((d) => ctx.isStdlibFile(d.getSourceFile())) + ) + return false; + if (symbol.name === "LookupAllOptions") all = true; + } + return all; +} + function isReceiveMessageResult( type: ts.Type, ctx: Pick, @@ -1545,6 +1565,10 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // worker_threads.receiveMessageOnPort's `{ message: any } | undefined`: // the runtime's { message } object, a checked-dynamic value. if (isReceiveMessageResult(type, ctx)) return DYN; + // The lookup-function option union (`LookupOneOptions | LookupAllOptions + // | number | undefined`): with a LookupAllOptions arm it is an option bag + // dns.lookup reads at run time, so it rides checked-dynamic storage. + if (isLookupOptionsUnion(type, ctx)) return DYN; // @types/node spells child_process.ExecFileException as an intersection // type alias over two Error-derived metadata interfaces. The value is // still one ordinary Error; recognize the owned alias before generic @@ -2511,6 +2535,11 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // spawn consumes a presence-sensitive option bag, including optional // stdio and env values. Keep it checked-dynamic for native normalization. if (isStdlibInterface("SpawnOptions")) return DYN; + // dns.LookupAllOptions (`all: true`, the overload whose callback takes + // an address list): a lookup option bag dns.lookup reads at run time. + // It rides checked-dynamic storage, so the lookup function type that + // takes `LookupOneOptions | LookupAllOptions | number` compiles. + if (isStdlibInterface("LookupAllOptions")) return DYN; // http.RequestOptions / https.RequestOptions as a declared type: an // option record whose members include functions, agents and signals. // Values ride checked-dynamic storage, like SpawnOptions. diff --git a/packages/compiler/src/ir/validate.ts b/packages/compiler/src/ir/validate.ts index 50ba236b8..716774b03 100644 --- a/packages/compiler/src/ir/validate.ts +++ b/packages/compiler/src/ir/validate.ts @@ -6200,7 +6200,10 @@ function validateFunction( if (expectsArg && (rp.params.length !== 1 || !typeEquals(rp.params[0]!, inner))) { err(`newPromise resolve param must be (${inner.kind}) => void`, e.loc); } - if (!expectsArg && rp.params.length !== 0) { + // A Promise executor types resolve as `(value: never) => + // void` (one parameter over a void inner): the void resolve never + // reads an argument, and a never value cannot be produced. + if (!expectsArg && rp.params.length > 1) { err("newPromise resolve takes no argument", e.loc); } } diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6450-promise-race-never-entry.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6450-promise-race-never-entry.ts.json new file mode 100644 index 000000000..bb709e7f3 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6450-promise-race-never-entry.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6450-promise-race-never-entry.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6451-array-length-fill-spread.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6451-array-length-fill-spread.ts.json new file mode 100644 index 000000000..0302374fc --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6451-array-length-fill-spread.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6451-array-length-fill-spread.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/lookup-and-header-entries.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/lookup-and-header-entries.ts.json new file mode 100644 index 000000000..13a440abb --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/lookup-and-header-entries.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/node-types/lookup-and-header-entries.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/cjs-exports-var/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/cjs-exports-var/main.ts.json new file mode 100644 index 000000000..9ec4bbdeb --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/cjs-exports-var/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/npm/cases/cjs-exports-var/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/tsc-default-object/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/tsc-default-object/main.ts.json new file mode 100644 index 000000000..7ce71c66f --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/tsc-default-object/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/npm/cases/tsc-default-object/main.ts" + ], + "diags": [] +} diff --git a/tests/corpus/6450-promise-race-never-entry.ts b/tests/corpus/6450-promise-race-never-entry.ts new file mode 100644 index 000000000..412c5f817 --- /dev/null +++ b/tests/corpus/6450-promise-race-never-entry.ts @@ -0,0 +1,22 @@ +// Promise.race with a Promise deadline entry under a typed result: +// the never entry can only reject, so it races with any result type. A +// read that settles first wins; a deadline that fires first rejects. +class Deadline extends Error {} +async function readWithDeadline(read: () => Promise<{ done: boolean; value?: Uint8Array }>, ms: number): Promise<{ done: boolean; value?: Uint8Array }> { + let timer: ReturnType | undefined; + try { + return await Promise.race([ + read(), + new Promise((_, reject) => { + timer = setTimeout(() => reject(new Deadline()), ms); + }), + ]); + } catch (error) { + if (error instanceof Deadline) return { done: true }; + throw error; + } finally { + if (timer !== undefined) clearTimeout(timer); + } +} +readWithDeadline(async () => ({ done: false, value: new Uint8Array([1, 2]) }), 1000).then((r) => console.log(r.done, r.value?.length)); +readWithDeadline(() => new Promise((res) => setTimeout(() => res({ done: false }), 200)), 20).then((r) => console.log("deadline", r.done)); diff --git a/tests/corpus/6451-array-length-fill-spread.ts b/tests/corpus/6451-array-length-fill-spread.ts new file mode 100644 index 000000000..03b80c300 --- /dev/null +++ b/tests/corpus/6451-array-length-fill-spread.ts @@ -0,0 +1,15 @@ +// Array(n).fill(v): the checker types the length-constructed array as +// any[], but fill gives every element v's type, so the array (and an +// array literal spreading it) holds strings. +function expand(head: string, tail: string): number[] | null { + const headParts = head ? head.split(":") : []; + const tailParts = tail ? tail.split(":") : []; + const missing = 8 - headParts.length - tailParts.length; + if (missing < 0) return null; + const all = [...headParts, ...Array(missing).fill("0"), ...tailParts]; + if (all.length !== 8) return null; + return all.map((part) => Number.parseInt(part || "0", 16)); +} +console.log(JSON.stringify(expand("2001:db8", "1")), JSON.stringify(expand("fe80", ""))); +const pad: string[] = Array(3).fill("-"); +console.log(pad.join(""), [..."ab", ...new Array(2).fill("c")].join("")); diff --git a/tests/fixtures/node-types/lookup-and-header-entries.ts b/tests/fixtures/node-types/lookup-and-header-entries.ts new file mode 100644 index 000000000..a87f612d7 --- /dev/null +++ b/tests/fixtures/node-types/lookup-and-header-entries.ts @@ -0,0 +1,47 @@ +// A dns lookup function whose options parameter is Node's lookup-option +// union (`LookupOneOptions | LookupAllOptions | number | undefined`) passes +// the options to dns.lookup as they are, and Object.entries over a header +// record cast to a narrower value type reads every entry. +import * as dns from "node:dns"; +import type { OutgoingHttpHeaders } from "node:http"; + +type LookupCallback = ( + err: NodeJS.ErrnoException | null, + address: string | dns.LookupAddress[], + family?: number, +) => void; + +function createLookup(): ( + hostname: string, + opts: dns.LookupOneOptions | dns.LookupAllOptions | number | undefined, + callback: LookupCallback, +) => void { + return function lookup(hostname, opts, callback) { + dns.lookup(hostname, opts as dns.LookupOneOptions, (err, address, family) => { + if (err) return callback(err, "", 0); + callback(null, address, family); + }); + }; +} + +type HeadersLike = Headers | [string, string][] | OutgoingHttpHeaders; + +function flatten(input: HeadersLike | undefined): string[] { + const out: string[] = []; + if (!input || input instanceof Headers || Array.isArray(input)) return out; + for (const [name, value] of Object.entries(input as Record)) { + if (value === undefined) continue; + if (Array.isArray(value)) for (const v of value) out.push(`${name}=${v}`); + else out.push(`${name}=${value}`); + } + return out; +} + +const lookup = createLookup(); +lookup("localhost", { family: 4 }, (err, address, family) => { + console.log("one", err, address, family); + lookup("127.0.0.1", 4, (err3, address3, family3) => { + console.log("family", err3, address3, family3); + console.log(flatten({ accept: "text/plain", "x-list": ["a", "b"], "x-skip": undefined }).join(",")); + }); +}); diff --git a/tests/fixtures/npm/cases/cjs-exports-var/main.ts b/tests/fixtures/npm/cases/cjs-exports-var/main.ts new file mode 100644 index 000000000..60a13b850 --- /dev/null +++ b/tests/fixtures/npm/cases/cjs-exports-var/main.ts @@ -0,0 +1,12 @@ +// A CommonJS module that names its export object through a variable +// (`var ip = exports; ip.isPrivate = function …`), the ip package's +// shape, imported by default as Node does: its members are the exports, +// including the ones it calls on itself through the alias. +import ip from "exportsvar"; + +for (const addr of ["10.0.0.1", "192.168.1.20", "127.0.0.1", "::1", "8.8.8.8"]) { + console.log(addr, ip.isPrivate(addr), ip.isLoopback(addr), ip.isPublic(addr)); +} +console.log(ip.toLong("192.168.1.1")); +// Members whose parameter shadows the alias name. +console.log(ip.isV4Format("1.2.3.4"), ip.isV4Format("::1"), ip.normalize(" FE80::1 ")); diff --git a/tests/fixtures/npm/cases/tsc-default-object/main.ts b/tests/fixtures/npm/cases/tsc-default-object/main.ts new file mode 100644 index 000000000..bcc650c06 --- /dev/null +++ b/tests/fixtures/npm/cases/tsc-default-object/main.ts @@ -0,0 +1,7 @@ +// tsc's CommonJS output for a module with named re-exports AND a default +// export object (`exports.default = { registry: registry_api_1.registry }`, +// the @opentelemetry/api index shape): the named exports still link, read +// through their defineProperty getters. +import { registry, VERSION } from "tscdefault"; + +console.log(VERSION, registry.count("a", 2), registry.count("a", 3), registry.count("b", 1)); diff --git a/tests/fixtures/npm/node_modules/exportsvar/index.d.ts b/tests/fixtures/npm/node_modules/exportsvar/index.d.ts new file mode 100644 index 000000000..8782f2267 --- /dev/null +++ b/tests/fixtures/npm/node_modules/exportsvar/index.d.ts @@ -0,0 +1,6 @@ +export function isPrivate(addr: string): boolean; +export function isLoopback(addr: string): boolean; +export function isPublic(addr: string): boolean; +export function toLong(addr: string): number; +export function isV4Format(ip: string): boolean; +export function normalize(ip: string): string; diff --git a/tests/fixtures/npm/node_modules/exportsvar/lib/ip.js b/tests/fixtures/npm/node_modules/exportsvar/lib/ip.js new file mode 100644 index 000000000..8e782dad2 --- /dev/null +++ b/tests/fixtures/npm/node_modules/exportsvar/lib/ip.js @@ -0,0 +1,35 @@ +'use strict'; + +var ip = exports; + +ip.isPrivate = function(addr) { + return /^(::f{4}:)?10\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$/i.test(addr) || + /^(::f{4}:)?192\.168\.([0-9]{1,3})\.([0-9]{1,3})$/i.test(addr); +}; + +ip.isLoopback = function(addr) { + return /^(::f{4}:)?127\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})/.test(addr) || + /^::1$/.test(addr); +}; + +ip.isPublic = function(addr) { + return !ip.isPrivate(addr) && !ip.isLoopback(addr); +}; + +ip.toLong = function(addr) { + var n = 0; + addr.split('.').forEach(function(octet) { + n <<= 8; + n += parseInt(octet, 10); + }); + return n >>> 0; +}; + +ip.isV4Format = function(ip) { + return /^(\d{1,3}\.){3,3}\d{1,3}$/.test(ip); +}; + +ip.normalize = function(ip) { + var trimmed = ip.trim(); + return trimmed.toLowerCase(); +}; diff --git a/tests/fixtures/npm/node_modules/exportsvar/package.json b/tests/fixtures/npm/node_modules/exportsvar/package.json new file mode 100644 index 000000000..04881bdbd --- /dev/null +++ b/tests/fixtures/npm/node_modules/exportsvar/package.json @@ -0,0 +1,6 @@ +{ + "name": "exportsvar", + "version": "1.0.0", + "main": "lib/ip", + "types": "index.d.ts" +} diff --git a/tests/fixtures/npm/node_modules/tscdefault/build/index.d.ts b/tests/fixtures/npm/node_modules/tscdefault/build/index.d.ts new file mode 100644 index 000000000..7fedd198f --- /dev/null +++ b/tests/fixtures/npm/node_modules/tscdefault/build/index.d.ts @@ -0,0 +1,8 @@ +import { registry } from './registry-api'; +export { registry }; +export declare const VERSION = "1.0.0"; +declare const _default: { + registry: import("./registry").Registry; + version: string; +}; +export default _default; diff --git a/tests/fixtures/npm/node_modules/tscdefault/build/index.js b/tests/fixtures/npm/node_modules/tscdefault/build/index.js new file mode 100644 index 000000000..0dff83d00 --- /dev/null +++ b/tests/fixtures/npm/node_modules/tscdefault/build/index.js @@ -0,0 +1,11 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.VERSION = exports.registry = void 0; +const registry_api_1 = require("./registry-api"); +Object.defineProperty(exports, "registry", { enumerable: true, get: function () { return registry_api_1.registry; } }); +exports.VERSION = "1.0.0"; +// Default export. +exports.default = { + registry: registry_api_1.registry, + version: "1.0.0", +}; diff --git a/tests/fixtures/npm/node_modules/tscdefault/build/registry-api.d.ts b/tests/fixtures/npm/node_modules/tscdefault/build/registry-api.d.ts new file mode 100644 index 000000000..2010f655e --- /dev/null +++ b/tests/fixtures/npm/node_modules/tscdefault/build/registry-api.d.ts @@ -0,0 +1,2 @@ +import { Registry } from './registry'; +export declare const registry: Registry; diff --git a/tests/fixtures/npm/node_modules/tscdefault/build/registry-api.js b/tests/fixtures/npm/node_modules/tscdefault/build/registry-api.js new file mode 100644 index 000000000..209ad4e9a --- /dev/null +++ b/tests/fixtures/npm/node_modules/tscdefault/build/registry-api.js @@ -0,0 +1,5 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.registry = void 0; +const registry_1 = require("./registry"); +exports.registry = registry_1.Registry.getInstance(); diff --git a/tests/fixtures/npm/node_modules/tscdefault/build/registry.d.ts b/tests/fixtures/npm/node_modules/tscdefault/build/registry.d.ts new file mode 100644 index 000000000..e9957d79d --- /dev/null +++ b/tests/fixtures/npm/node_modules/tscdefault/build/registry.d.ts @@ -0,0 +1,5 @@ +export declare class Registry { + private counts; + static getInstance(): Registry; + count(name: string, n: number): number; +} diff --git a/tests/fixtures/npm/node_modules/tscdefault/build/registry.js b/tests/fixtures/npm/node_modules/tscdefault/build/registry.js new file mode 100644 index 000000000..2797e469a --- /dev/null +++ b/tests/fixtures/npm/node_modules/tscdefault/build/registry.js @@ -0,0 +1,20 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.Registry = void 0; +class Registry { + constructor() { + this.counts = new Map(); + } + static getInstance() { + if (!this._instance) { + this._instance = new Registry(); + } + return this._instance; + } + count(name, n) { + const next = (this.counts.get(name) ?? 0) + n; + this.counts.set(name, next); + return next; + } +} +exports.Registry = Registry; diff --git a/tests/fixtures/npm/node_modules/tscdefault/package.json b/tests/fixtures/npm/node_modules/tscdefault/package.json new file mode 100644 index 000000000..230541aaf --- /dev/null +++ b/tests/fixtures/npm/node_modules/tscdefault/package.json @@ -0,0 +1,6 @@ +{ + "name": "tscdefault", + "version": "1.0.0", + "main": "build/index.js", + "types": "build/index.d.ts" +} diff --git a/tests/harness/project-config.test.ts b/tests/harness/project-config.test.ts index 14e4c27af..b5d47fae1 100644 --- a/tests/harness/project-config.test.ts +++ b/tests/harness/project-config.test.ts @@ -443,6 +443,25 @@ test("node-types: util.promisify over typed callback-last functions settles like expect(comparableStderr(native.stderr)).toBe(node.stderr); }); +test("node-types: lookup-option unions and narrowed header-record entries under @types/node", async () => { + const outDir = outDirFor("lookup-and-header-entries"); + const entry = join(nodeTypesDir, "lookup-and-header-entries.ts"); + const result = await compile(entry, { + outPath: join(outDir, "lookup-and-header-entries"), + outDir, + sanitize, + }); + expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true); + if (!result.ok) return; + const native = await execFileAsync(result.binaryPath); + const node = await execFileAsync(process.execPath, [ + "--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", + entry, + ]); + expect(native.stdout).toBe(node.stdout); + expect(comparableStderr(native.stderr)).toBe(node.stderr); +}); + test("node-types: stream subclasses extend the @types/node stream classes", async () => { const outDir = outDirFor("stream-subclass-hooks"); const entry = join(nodeTypesDir, "stream-subclass-hooks.ts"); From c475aabc2a1c8a8ce75a2738de0088c7218b1ebe Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Tue, 20 Oct 2026 08:41:04 -0500 Subject: [PATCH 082/122] Refuse arguments to inherited stream constructors from untyped code Construction from checked-dynamic code (`new F(options)` with F an untyped value holding a ctor-less subclass of a stream class or EventEmitter) dispatched to the class's constructor and completed the arguments against the inherited signature, which takes none here, so the options were dropped. The checked construction dispatch now reports those arguments at run time with the message typed construction reports at compile time, located at the class: a fixed argument count refuses at the call, and a spread call refuses when its argument list is not empty. --- .../frontend/lowering/class-construction.ts | 48 ++++++++++++++++++- .../src/frontend/lowering/lower-classes.ts | 28 +++++++++++ tests/harness/errors.test.ts | 31 ++++++++++++ 3 files changed, 105 insertions(+), 2 deletions(-) diff --git a/packages/compiler/src/frontend/lowering/class-construction.ts b/packages/compiler/src/frontend/lowering/class-construction.ts index 7f4a003c9..30f504b4a 100644 --- a/packages/compiler/src/frontend/lowering/class-construction.ts +++ b/packages/compiler/src/frontend/lowering/class-construction.ts @@ -18,6 +18,7 @@ import type { Lowerer } from "./lowerer.js"; import type { ParamShape } from "./call-signatures.js"; import { classPrototypeData } from "./class-prototypes.js"; import { classInstanceOf } from "./class-dynamic-dispatch.js"; +import { inheritedBuiltinCtorFence } from "./lower-classes.js"; const PREFIX = "%class.construct:"; const PACKED = `${PREFIX}packed`; @@ -380,6 +381,29 @@ export class ClassConstructionDispatch { changed = true; continue; } + const type: IrType = { kind: "classval", className: name }; + const callee = varRef("p.0", DYN, loc); + // A constructor inherited from a runtime stream or EventEmitter + // takes no arguments here: arguments report at run time, as they do + // at compile time for typed construction. + const fence = inheritedBuiltinCtorFence(lowerer, info, loc); + if (fence !== null && fn.name !== PACKED && fn.params.length > 1) { + fn.body.unshift({ + kind: "if", + cond: { + kind: "libCall", + fn: "dyn.classIs", + args: [callee, { kind: "strLit", value: typeKey(type), type: STRING, loc }], + type: BOOL, + loc, + }, + then: [fence], + else_: null, + loc, + }); + changed = true; + continue; + } const args = constructorArguments( lowerer, info.ctorParams, @@ -388,8 +412,6 @@ export class ClassConstructionDispatch { fn.name === PACKED ? varRef("p.1", DYN, loc) : undefined, ); if (!args) continue; - const type: IrType = { kind: "classval", className: name }; - const callee = varRef("p.0", DYN, loc); const value = constructedValue(lowerer, info, { kind: "newValue", callee: { kind: "dynCheck", value: callee, type, loc }, @@ -400,6 +422,28 @@ export class ClassConstructionDispatch { const body: IrStmt[] = [ { kind: "return", value: lowerer.coerceToExpected(value, DYN), loc }, ]; + // A spread call's argument count is known only at run time. + if (fence !== null && fn.name === PACKED) + body.unshift({ + kind: "if", + cond: { + kind: "bin", + op: ">", + left: { + kind: "libCall", + fn: "dyn.arrLen", + args: [varRef("p.1", DYN, loc)], + type: F64, + loc, + }, + right: { kind: "numLit", value: 0, type: F64, loc }, + type: BOOL, + loc, + }, + then: [fence], + else_: null, + loc, + }); fn.body.unshift({ kind: "if", cond: { diff --git a/packages/compiler/src/frontend/lowering/lower-classes.ts b/packages/compiler/src/frontend/lowering/lower-classes.ts index 714588a57..cd0c6b8bd 100644 --- a/packages/compiler/src/frontend/lowering/lower-classes.ts +++ b/packages/compiler/src/frontend/lowering/lower-classes.ts @@ -8725,6 +8725,34 @@ function inheritsBuiltinStreamCtor(lowerer: Lowerer, info: ClassInfo): boolean { return false; } +/** The run-time form of refuseInheritedBuiltinCtorArgs, for construction + * from checked-dynamic code (`new F(options)` with F an untyped value + * holding C), whose argument count is known only to the caller: a + * runtimeFence located at C, or null when C's constructor takes its + * arguments. */ +export function inheritedBuiltinCtorFence( + lowerer: Lowerer, + info: ClassInfo, + loc: SrcLoc, +): IrStmt | null { + let feature: string | null = null; + if (inheritsBuiltinStreamCtor(lowerer, info)) + feature = `new ${info.def.name.replace(/^%/, "")} with arguments through an inherited stream constructor (declare a constructor that passes an inline options object to super(...))`; + else if (lowerer.inheritsBuiltinEmitterCtor(info)) + feature = "EventEmitter constructor options ('captureRejections')"; + if (feature === null) return null; + // Located at the class, whose constructor takes no arguments. + const at = info.decl !== null ? locOf(info.decl) : loc; + const diag = unsupportedDiag("SC1090", at, feature); + lowerer.runtimeFences.push(diag); + const line = + info.decl !== null + ? ts.getLineAndCharacterOfPosition(info.decl.getSourceFile(), at.start).line + 1 + : null; + const where = line !== null ? ` [${diag.code} at ${at.file}:${line}]` : ""; + return { kind: "runtimeFence", code: diag.code, message: `${diag.message}${where}`, loc }; +} + /** `new C(args)` where C's constructor is the inherited runtime stream or * EventEmitter constructor (a ctor-less chain into the base): that * constructor takes zero arguments here (the options bag fences, like the diff --git a/tests/harness/errors.test.ts b/tests/harness/errors.test.ts index 1f3d9729b..f08ed45d7 100644 --- a/tests/harness/errors.test.ts +++ b/tests/harness/errors.test.ts @@ -359,6 +359,37 @@ const arr = tuple.map(() => new Date(0));`, expect(r).toEqual({ stdout: reference.stdout, stderr: reference.stderr, exitCode: 0 }); }); + test.each([ + ["an argument", "make(Upper, { objectMode: true });"], + ["a spread argument", "spread(Upper, [{ objectMode: true }]);"], + ])( + "%s through an untyped factory to an inherited stream constructor is a named deferred fence (JS lane)", + async (_, call) => { + // The constructor a ctor-less stream subclass inherits takes no + // arguments here; typed construction refuses them at compile time, + // and construction from untyped code refuses them at run time + // instead of constructing without them. + const r = await compileAndRun( + `inherited-stream-ctor-${call.startsWith("make") ? "args" : "spread"}`, + `'use strict'; +const { Transform } = require('node:stream'); +class Upper extends Transform {} +function make(F, options) { return new F(options); } +function bare(F) { return new F(); } +function spread(F, args) { return new F(...args); } +console.log('bare', bare(Upper) instanceof Upper, spread(Upper, []) instanceof Upper); +${call} +`, + "cjs", + ); + expect(r.exitCode).toBe(1); + expect(r.stdout).toBe("bare true true\n"); + expect(r.stderr).toMatch( + /^Uncaught Error: new Upper with arguments through an inherited stream constructor .* is not supported yet \[SC1090 at .*inherited-stream-ctor-\w+\.cjs:3\]\n$/, + ); + }, + ); + test("extending a property-assigned class ABOVE its assignment is a named deferred fence (JS lane)", async () => { // The property-assigned class-expression family (corpus 2032/2033 // pins what lowers): `exports.O = class extends exports.I {}` with From 1ef2004c9beb0049762f4724d51eda6479c5cdd5 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Tue, 20 Oct 2026 08:46:56 -0500 Subject: [PATCH 083/122] Compare only the 48 bytes SHA-384 writes in the cross-kernel check --- packages/runtime/test/test_sha_kernels.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/runtime/test/test_sha_kernels.c b/packages/runtime/test/test_sha_kernels.c index f9f9b9df7..da7d79235 100644 --- a/packages/runtime/test/test_sha_kernels.c +++ b/packages/runtime/test/test_sha_kernels.c @@ -192,7 +192,7 @@ int main(void) { sha512_of(data, n, chunks[c], sha384, a); select512(0); sha512_of(data, n, chunks[c], sha384, b); - check(memcmp(a, b, 64) == 0, kernel512_names[k], n, chunks[c]); + check(memcmp(a, b, sha384 ? 48 : 64) == 0, kernel512_names[k], n, chunks[c]); } } } From dfd59e18ee4d70cb2c5f432342c2629dd4dce728 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Tue, 20 Oct 2026 06:49:37 -0700 Subject: [PATCH 084/122] Let NodeJS stream interface slots hold JavaScript stream objects Under the general stream representation, NodeJS.ReadableStream and NodeJS.WritableStream compile as the runtime Readable and Writable classes, so a JavaScript stream object (a constructor-function stream, a plain object with on/pipe, a stream from bundled npm code) passed through a TypeScript function typed with them took the checked unwrap and threw. A program that moves an untyped value into a runtime stream class slot, or passes an untyped pipe() destination or pipeline stage, now lowers once more with checked-dynamic stream slots: the two interfaces (and intersections over them) map to dyn. A native stream rides its typed capsule and keeps its identity; any other value is the JavaScript object itself. Member calls dispatch per value: stream.dynNative tests for a boxed native stream, which takes the ordinary class-method lowering; other values call their own member through dynInvoke. pipe() into a JavaScript destination follows Node's Readable.prototype.pipe listener protocol (stream.pipeDyn). for await and pipeline stages keep the not-supported error for values that are not native streams. The runtime code lives in scr_stream_dyn.c behind a new streamDyn feature, so programs without these libCalls link the same objects. Co-Authored-By: Claude Opus 5.5 --- .../builtin-apis/streams-and-readline.mdx | 5 +- .../src/backend/executable-features.ts | 1 + .../compiler/src/backend/llvm/lib-shared.ts | 1 + .../compiler/src/backend/llvm/lib-stream.ts | 18 + .../src/backend/native-link-info-core.ts | 3 + .../compiler/src/backend/native/contracts.ts | 3 + .../compiler/src/backend/native/executable.ts | 3 + .../compiler/src/backend/runtime-pack-core.ts | 2 + .../src/executable/executable-cache.ts | 1 + .../frontend/lowering/builtins/properties.ts | 3 + .../src/frontend/lowering/dyn-streams.ts | 314 ++++++++++++++++ .../src/frontend/lowering/general-streams.ts | 15 +- .../lowering/lower-async-iteration.ts | 4 +- .../src/frontend/lowering/lower-calls.ts | 4 + .../src/frontend/lowering/lower-stmts.ts | 6 + .../src/frontend/lowering/lower-stream.ts | 59 ++- .../compiler/src/frontend/lowering/lowerer.ts | 57 ++- packages/compiler/src/frontend/type-mapper.ts | 45 ++- packages/compiler/src/index.ts | 1 + packages/compiler/src/ir/builtin-effects.ts | 1 + .../compiler/src/ir/builtin-signatures.ts | 10 + packages/compiler/src/ir/runtime-features.ts | 8 + packages/compiler/src/ir/validate.ts | 15 + .../compiler/src/native/sanitizer.test.ts | 1 + .../runtime-pack-matrix.mjs | 2 + packages/runtime/src/scr_stream_dyn.c | 349 ++++++++++++++++++ 26 files changed, 916 insertions(+), 15 deletions(-) create mode 100644 packages/compiler/src/frontend/lowering/dyn-streams.ts create mode 100644 packages/runtime/src/scr_stream_dyn.c diff --git a/docs/content/limitations/builtin-apis/streams-and-readline.mdx b/docs/content/limitations/builtin-apis/streams-and-readline.mdx index 042fd1e50..bd49c89e4 100644 --- a/docs/content/limitations/builtin-apis/streams-and-readline.mdx +++ b/docs/content/limitations/builtin-apis/streams-and-readline.mdx @@ -1,7 +1,10 @@ - **Streams and readline:** Stream chunks are Buffers or strings. `NodeJS.ReadableStream` and `NodeJS.WritableStream`, and `stream.Readable` and `stream.Writable` under `@types/node`, accept every stream: when a program passes file, zlib, or `node:stream` streams through them, child-process pipes and `process.stdout` and `process.stderr` join those streams as `Readable` and `Writable` instances. `readable.pipe(process.stdout)` and `readable.pipe(process.stderr)` compile and, as in Node, do not end the process stream. - A `pipeline` stage that arrives as an untyped value must be a native stream at run time; other values throw a not-supported error. + When an untyped value reaches `NodeJS.ReadableStream` or `NodeJS.WritableStream`, those two interfaces hold any value, so JavaScript stream objects (constructor-function streams, plain objects with `on` and `pipe`) pass through TypeScript functions typed with them and come back to JavaScript as the same objects. + Member calls on such a value use the native stream when it is one and call the object's own members otherwise; `readable.pipe()` into a JavaScript destination follows Node's pipe protocol. + A `pipeline` stage and a `for await` source that arrive as untyped values must be native streams at run time; other values throw a not-supported error. + Native streams expose no `emit` to JavaScript, so a JavaScript stream whose own `pipe()` emits on its destination (the legacy `Stream` pipe) cannot pipe into one. `Readable.from` accepts arrays, Sets, Map and Set iterators, generators, async generators, and class iterables and async iterables whose values are strings or Buffers, and in JavaScript any iterable or async iterable of them; a stream that mixes strings and Buffers is unsupported. In JavaScript, a sync iterator value with a `then` method that is not a promise, and an object whose `[Symbol.asyncIterator]` or `[Symbol.iterator]` method is a generator method, are unsupported. Object-mode streams of other values (including `Readable.from` over a `Map`, whose values are entry arrays) are unsupported. diff --git a/packages/compiler/src/backend/executable-features.ts b/packages/compiler/src/backend/executable-features.ts index eeb6c6900..74d5de278 100644 --- a/packages/compiler/src/backend/executable-features.ts +++ b/packages/compiler/src/backend/executable-features.ts @@ -25,6 +25,7 @@ export function executableLinkFeatures(mod: IrModule): NativeLinkFeatures { dc: features.dc, dynAsync: features.dynAsync, ...(features.dynTier ? { dynTier: true } : {}), + ...(features.streamDyn ? { streamDyn: true } : {}), events: features.processEvents, emitter: features.emitter, symbol: features.symbol, diff --git a/packages/compiler/src/backend/llvm/lib-shared.ts b/packages/compiler/src/backend/llvm/lib-shared.ts index 4602a2bdd..6f296205c 100644 --- a/packages/compiler/src/backend/llvm/lib-shared.ts +++ b/packages/compiler/src/backend/llvm/lib-shared.ts @@ -728,6 +728,7 @@ export const LIB_FN_SYMS: Record = { "procStream.write": "scr_proc_stream_write", "stream.stdioWritable": "scr_stream_stdio_writable", "stream.unboxDyn": "scr_stream_unbox_dyn", + "stream.pipeDyn": "scr_stream_pipe_dyn", "process.kill": "scr_process_kill_named", "process.killNum": "scr_process_kill", // fs.promises (scr_bytes_io.c / scr_lib.c): the promise forms settle diff --git a/packages/compiler/src/backend/llvm/lib-stream.ts b/packages/compiler/src/backend/llvm/lib-stream.ts index b62b237df..21e5038cd 100644 --- a/packages/compiler/src/backend/llvm/lib-stream.ts +++ b/packages/compiler/src/backend/llvm/lib-stream.ts @@ -36,6 +36,24 @@ export function emitStreamLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlV host.emitPendingCheck(); return out; } + if (e.fn === "stream.dynNative") { + // Checked-dynamic stream slots: the boxed native stream (+1) or NULL. + if (e.type.kind !== "union") + throw new InternalCompilerError(`llvm emitter bug: ${e.fn} result is not a union`); + const def = host.unionsById.get(e.type.unionId); + const streamTag = def ? def.arms.findIndex((a) => a.kind === "object") : -1; + const nullTag = def ? def.arms.findIndex((a) => a.kind === "nullT") : -1; + if (streamTag < 0 || nullTag < 0) + throw new InternalCompilerError(`llvm emitter bug: ${e.fn} union lacks its arms`); + const value = host.emitExpr(e.args[0]!); + const side = host.emitExpr(e.args[1]!); + host.declare(`declare ptr @scr_stream_dyn_native(ptr, double)`); + const raw = B.tmp(); + B.line( + `${raw} = call ptr @scr_stream_dyn_native(ptr ${value.name}, double ${side.name}) ; +1 or NULL`, + ); + return host.wrapNullable(raw, raw, def!.arms[streamTag]!, streamTag, e.type, nullTag); + } if (e.fn === "stream.onData") { // The callback MOVES into the stream's listener registry; the // adapter is per callback shape — runtime-provided for the diff --git a/packages/compiler/src/backend/native-link-info-core.ts b/packages/compiler/src/backend/native-link-info-core.ts index 86146259d..df03d528a 100644 --- a/packages/compiler/src/backend/native-link-info-core.ts +++ b/packages/compiler/src/backend/native-link-info-core.ts @@ -40,6 +40,9 @@ export interface NativeLinkFeatures { /** Dynamic-tier jsval operations: the scr_js_* units. Absent (not false) * for programs without them, so their link features stay unchanged. */ dynTier?: boolean; + /** Checked-dynamic stream slots: the scr_stream_dyn.c unit. Absent (not + * false) for programs without them. */ + streamDyn?: boolean; events: boolean; emitter: boolean; symbol: boolean; diff --git a/packages/compiler/src/backend/native/contracts.ts b/packages/compiler/src/backend/native/contracts.ts index 33b13960e..3b700ea69 100644 --- a/packages/compiler/src/backend/native/contracts.ts +++ b/packages/compiler/src/backend/native/contracts.ts @@ -113,6 +113,9 @@ export interface CcOptions { * the IR): compiles the scr_js_* units (two-tier design I3). Programs * without them link no scr_js_ symbol. */ dynTier?: boolean; + /** Checked-dynamic stream slots (stream.dynNative / stream.pipeDyn): + * compiles scr_stream_dyn.c. */ + streamDyn?: boolean; /** The program constructs EventTarget/Event/CustomEvent values or * calls events.addAbortListener (the event.* libCalls), or uses the * native fetch AbortSignal built on the same engine: compiles diff --git a/packages/compiler/src/backend/native/executable.ts b/packages/compiler/src/backend/native/executable.ts index 5ebb4796e..d2616151b 100644 --- a/packages/compiler/src/backend/native/executable.ts +++ b/packages/compiler/src/backend/native/executable.ts @@ -240,6 +240,9 @@ const RUNTIME_UNITS: readonly RuntimeUnit[] = [ // stream unit. unit((u) => u.opts.zlib && u.opts.stream, "scr_zlib_stream.c"), unit((u) => u.opts.stream && u.opts.dynAsync, "scr_stream_ops.c"), + // Checked-dynamic stream slots test values and call JavaScript stream + // members through the dyn-invoke unit. + unit((u) => u.opts.streamDyn === true, "scr_stream_dyn.c"), // FileHandle streams reference both the stream unit and FileHandle. unit((u) => u.opts.stream && u.opts.fileHandle, "scr_fs_stream_fh.c"), // The readiness-poller backends (scr_platform.h): kqueue on macOS/BSD, diff --git a/packages/compiler/src/backend/runtime-pack-core.ts b/packages/compiler/src/backend/runtime-pack-core.ts index 6a660e00c..41fa1442a 100644 --- a/packages/compiler/src/backend/runtime-pack-core.ts +++ b/packages/compiler/src/backend/runtime-pack-core.ts @@ -83,6 +83,7 @@ export interface RuntimeFeatureSet extends NativeLinkFeatures { youngNursery: boolean; eventTarget: boolean; dynTier: boolean; + streamDyn: boolean; warningReplace: boolean; stdioExtended: boolean; nativeFetch: boolean; @@ -273,6 +274,7 @@ export function effectiveRuntimeFeatures( youngNursery: features.youngNursery === true, eventTarget: features.eventTarget === true, dynTier: features.dynTier === true, + streamDyn: features.streamDyn === true, warningReplace: features.warningReplace === true, stdioExtended: features.stdioExtended === true, nativeFetch, diff --git a/packages/compiler/src/executable/executable-cache.ts b/packages/compiler/src/executable/executable-cache.ts index 8c67c1006..7e3b427e5 100644 --- a/packages/compiler/src/executable/executable-cache.ts +++ b/packages/compiler/src/executable/executable-cache.ts @@ -65,6 +65,7 @@ export interface EarlyExecutableNativeFeatures { dc: boolean; dynAsync: boolean; dynTier?: boolean; + streamDyn?: boolean; events: boolean; emitter: boolean; symbol: boolean; diff --git a/packages/compiler/src/frontend/lowering/builtins/properties.ts b/packages/compiler/src/frontend/lowering/builtins/properties.ts index e60a14c0c..70512fed9 100644 --- a/packages/compiler/src/frontend/lowering/builtins/properties.ts +++ b/packages/compiler/src/frontend/lowering/builtins/properties.ts @@ -290,6 +290,9 @@ function generalChildStdioType( const className = name === "stdin" ? "%Writable" : "%Readable"; const mapped = lowerer.mapTypeOf(lowerer.typeOf(expr)); if (!mapped) return null; + // Checked-dynamic stream slots: the declared stream type maps to dyn + // (dyn-streams.ts); the read still produces the bridged stream. + if (lowerer.dynNodeStreams && mapped.kind === "dyn") return { kind: "object", className }; const arms = mapped.kind === "union" ? (lowerer.unions.get(mapped.unionId)?.arms ?? []) : [mapped]; return arms.some((arm) => arm.kind === "object" && arm.className === className) diff --git a/packages/compiler/src/frontend/lowering/dyn-streams.ts b/packages/compiler/src/frontend/lowering/dyn-streams.ts new file mode 100644 index 000000000..9694e3820 --- /dev/null +++ b/packages/compiler/src/frontend/lowering/dyn-streams.ts @@ -0,0 +1,314 @@ +/* Checked-dynamic stream slots (TypeMapperCtx.dynNodeStreams). + * + * Under the general stream representation (general-streams.ts), + * NodeJS.ReadableStream and NodeJS.WritableStream map to the runtime stream + * classes. JavaScript code also hands such slots its own stream objects: + * constructor-function streams over EventEmitter or the legacy Stream, plain + * objects implementing on/pipe/write/end, streams of bundled npm code. A + * program that moves an untyped value into a stream slot therefore lowers + * again with checked-dynamic slots: those two interfaces map to dyn, so a + * slot holds either a boxed native stream (its typed capsule, which unboxes + * to the same stream) or the JavaScript object itself, and a value returned + * to JavaScript is the object that came in. + * + * A member call on such a slot dispatches per value at run time. A native + * stream unboxes and takes the ordinary class-method lowering (the same + * stream surface a typed stream has); any other value calls its own member + * with JavaScript's dynamic semantics (dynInvoke). Programs without such a + * flow never take this pass, so their IR is unchanged. */ +import type { IrExpr, IrStmt, IrType } from "../../ir/ir.js"; +import { dynUndefinedExpr, numLit, strLit, varRef } from "../../ir/build.js"; +import { DYN } from "../../ir/ir.js"; +import * as ts from "../ts7/adapter.js"; +import { locOf } from "../program.js"; +import { isDeclaredInAmbientNamespace } from "../type-mapper.js"; +import { resultIsDiscarded } from "./call-position.js"; +import { lowerObjectMethodCall } from "./lower-calls.js"; +import type { Lowerer } from "./lowerer.js"; + +/** The side a NodeJS stream interface names: "r" for NodeJS.ReadableStream, + * "w" for NodeJS.WritableStream (through null/undefined and intersections), + * or null for any other type. */ +export function nodeStreamInterfaceSide(lowerer: Lowerer, type: ts.Type): "r" | "w" | null { + const parts = (t: ts.Type): ts.Type[] => + (t.flags & ts.TypeFlags.Intersection) !== 0 ? ts.constituentTypes(t).flatMap(parts) : [t]; + for (const part of parts(lowerer.checker.getNonNullableType(type))) { + const psym = part.getSymbol() ?? part.getAliasSymbol(); + if (psym?.name !== "ReadableStream" && psym?.name !== "WritableStream") continue; + const declared = psym.declarations.some( + (d) => + ts.isInterfaceDeclaration(d) && + lowerer.isStdlibFile(d.getSourceFile()) && + isDeclaredInAmbientNamespace(d, "NodeJS"), + ); + if (declared) return psym.name === "ReadableStream" ? "r" : "w"; + } + return null; +} + +/** The runtime class a native value of the side unboxes to. */ +export function nativeStreamClass(side: "r" | "w"): IrType { + return { kind: "object", className: side === "r" ? "%Readable" : "%Writable" }; +} + +/** The native stream of a checked-dynamic value (+1, typed as the side's + * class) or null: the stream.dynNative libCall, which tests the value's + * typed capsule without throwing. */ +function dynNativeStream(lowerer: Lowerer, value: IrExpr, side: "r" | "w"): IrExpr { + const native = nativeStreamClass(side); + return { + kind: "libCall", + fn: "stream.dynNative", + args: [value, numLit(side === "r" ? 1 : 2, value.loc)], + type: { kind: "union", unionId: lowerer.unions.intern([native, { kind: "nullT" }]) }, + loc: value.loc, + }; +} + +/** Runs `lower` with `node` lowering to `value` and its member calls taking + * `type` as the receiver representation. */ +function withStreamReceiver( + lowerer: Lowerer, + node: ts.Expression, + value: IrExpr, + type: IrType, + lower: () => T, +): T { + const previous = lowerer.receiverTypeOverrides.get(node); + lowerer.receiverTypeOverrides.set(node, type); + try { + return lowerer.withExpressionOverride(node, value, lower); + } finally { + if (previous) lowerer.receiverTypeOverrides.set(node, previous); + else lowerer.receiverTypeOverrides.delete(node); + } +} + +/** `stream.member(args)` on a NodeJS.ReadableStream / NodeJS.WritableStream + * receiver under checked-dynamic stream slots, or null when the call is not + * one. */ +export function lowerDynStreamMemberCall( + lowerer: Lowerer, + call: ts.CallExpression, + access: ts.PropertyAccessExpression, +): IrExpr | null { + if (!lowerer.dynNodeStreams) return null; + // The native arm re-enters the call lowering with the receiver bound. + if (lowerer.chainRecvByNode.has(access.expression)) return null; + if (access.questionDotToken || call.questionDotToken || !ts.isIdentifier(access.name)) + return null; + if (call.arguments.nodes.some(ts.isSpreadElement)) return null; + const side = nodeStreamInterfaceSide(lowerer, lowerer.typeOf(access.expression)); + if (side === null) return null; + const loc = locOf(call); + const receiverValue = lowerer.lowerExpr(access.expression); + const receiver = lowerer.declareHiddenLocal("%streamReceiver", receiverValue.type); + const receiverRef = varRef(receiver.id, receiver.type, locOf(access.expression)); + const stmts: IrStmt[] = [{ kind: "varDecl", localId: receiver.id, init: receiverValue, loc }]; + const seq = (result: IrExpr): IrExpr => ({ + kind: "seqExpr", + stmts, + result, + type: result.type, + loc, + }); + // A typed stream whose declared type is one of the interfaces (a child + // stdio read): the class-method path on its own representation. + if (receiverValue.type.kind !== "dyn") { + const typed = + receiverValue.type.kind === "object" + ? withStreamReceiver(lowerer, access.expression, receiverRef, receiverValue.type, () => + lowerObjectMethodCall(lowerer, call, access), + ) + : lowerer.withExpressionOverride(access.expression, receiverRef, () => + lowerer.lowerCall(call), + ); + if (typed === null) lowerer.noLowering(`${access.name.text} on this stream`, call); + return seq(typed); + } + const nativeType = nativeStreamClass(side); + const nullable = dynNativeStream(lowerer, receiverRef, side); + const nativeOrNull = lowerer.declareHiddenLocal("%streamNative", nullable.type); + stmts.push({ kind: "varDecl", localId: nativeOrNull.id, init: nullable, loc }); + const unionId = nullable.type.kind === "union" ? nullable.type.unionId : ""; + const tag = lowerer.armTag(unionId, nativeType); + const native = lowerer.declareHiddenLocal("%streamNativeValue", nativeType); + const nativeRef = varRef(native.id, nativeType, locOf(access.expression)); + const nativeDecl: IrStmt = { + kind: "varDecl", + localId: native.id, + init: { + kind: "unionNarrow", + unionId, + tag, + value: varRef(nativeOrNull.id, nullable.type, loc), + type: nativeType, + loc, + }, + loc, + }; + const typed = withStreamReceiver(lowerer, access.expression, nativeRef, nativeType, () => + lowerObjectMethodCall(lowerer, call, access), + ); + if (typed === null) + lowerer.noLowering( + `${side === "r" ? "NodeJS.ReadableStream" : "NodeJS.WritableStream"}.${access.name.text}`, + call, + ); + // Any other value calls its own member, with the arguments as untyped + // values (typed callbacks box behind checked thunks). + const invoked: IrExpr = { + kind: "dynInvoke", + recv: receiverRef, + method: access.name.text, + calleeName: access.getText(), + args: call.arguments.nodes.map((arg) => lowerer.lowerExprExpecting(arg, DYN)), + type: DYN, + loc, + }; + const cond: IrExpr = { + kind: "unionIsTag", + unionId, + tag, + negated: false, + value: varRef(nativeOrNull.id, nullable.type, loc), + type: { kind: "bool" }, + loc, + }; + const resultType = resultIsDiscarded(call) ? null : lowerer.irTypeOf(call); + if (resultType === null || resultType.kind === "void") { + stmts.push({ + kind: "if", + cond, + then: [nativeDecl, { kind: "exprStmt", expr: typed, loc }], + else_: [{ kind: "exprStmt", expr: invoked, loc }], + loc, + }); + return seq(dynUndefinedExpr(loc)); + } + const result = lowerer.declareHiddenLocal("%streamResult", resultType); + result.mutable = true; + stmts.push( + { kind: "varDecl", localId: result.id, init: null, loc }, + { + kind: "if", + cond, + then: [ + nativeDecl, + { + kind: "assign", + localId: result.id, + value: lowerer.coerceInto(call, typed, resultType), + loc, + }, + ], + else_: [ + { + kind: "assign", + localId: result.id, + value: lowerer.coerceInto(call, invoked, resultType), + loc, + }, + ], + loc, + }, + ); + return seq(varRef(result.id, resultType, loc)); +} + +/** `for await (const chunk of stream)` over a checked-dynamic + * NodeJS.ReadableStream: a native stream iterates through the Readable + * lowering; any other value throws the not-supported Error (async + * iteration of JavaScript stream objects has no lowering). */ +export function lowerForAwaitDynStream( + lowerer: Lowerer, + stmt: ts.ForOfStatement, + lowerLoop: () => IrStmt | null, +): IrStmt | null { + if (!lowerer.dynNodeStreams || lowerer.chainRecvByNode.has(stmt.expression)) return null; + if (nodeStreamInterfaceSide(lowerer, lowerer.typeOf(stmt.expression)) !== "r") return null; + const loc = locOf(stmt.expression); + const value = lowerer.lowerExpr(stmt.expression); + const native: IrType = + value.type.kind === "dyn" ? { kind: "object", className: "%Duplex" } : value.type; + const local = lowerer.declareHiddenLocal("%streamIterated", native); + const init: IrExpr = + value.type.kind === "dyn" + ? { + kind: "libCall", + fn: "stream.unboxDyn", + args: [ + value, + strLit( + "for await over stream objects other than native streams is not supported yet", + loc, + ), + ], + type: native, + loc, + } + : value; + const loop = withStreamReceiver( + lowerer, + stmt.expression, + varRef(local.id, native, loc), + native, + lowerLoop, + ); + if (loop === null) return null; + return { + kind: "block", + body: [{ kind: "varDecl", localId: local.id, init, loc }, loop], + loc: locOf(stmt), + }; +} + +/** A statement that runs `onNative` with the native stream a + * checked-dynamic value boxes (typed as the side's class), or `otherwise` + * for any other value. */ +export function dynNativeBranch( + lowerer: Lowerer, + value: IrExpr, + side: "r" | "w", + onNative: (native: IrExpr) => IrStmt, + otherwise: IrStmt, +): IrStmt { + const loc = value.loc; + const nativeType = nativeStreamClass(side); + const nullable = dynNativeStream(lowerer, value, side); + const nativeOrNull = lowerer.declareHiddenLocal("%streamNative", nullable.type); + const unionId = nullable.type.kind === "union" ? nullable.type.unionId : ""; + const tag = lowerer.armTag(unionId, nativeType); + const native = lowerer.declareHiddenLocal("%streamNativeValue", nativeType); + const nullableRef = varRef(nativeOrNull.id, nullable.type, loc); + return { + kind: "block", + body: [ + { kind: "varDecl", localId: nativeOrNull.id, init: nullable, loc }, + { + kind: "if", + cond: { + kind: "unionIsTag", + unionId, + tag, + negated: false, + value: nullableRef, + type: { kind: "bool" }, + loc, + }, + then: [ + { + kind: "varDecl", + localId: native.id, + init: { kind: "unionNarrow", unionId, tag, value: nullableRef, type: nativeType, loc }, + loc, + }, + onNative(varRef(native.id, nativeType, loc)), + ], + else_: [otherwise], + loc, + }, + ], + loc, + }; +} diff --git a/packages/compiler/src/frontend/lowering/general-streams.ts b/packages/compiler/src/frontend/lowering/general-streams.ts index 0113a9958..2cb6a4b6c 100644 --- a/packages/compiler/src/frontend/lowering/general-streams.ts +++ b/packages/compiler/src/frontend/lowering/general-streams.ts @@ -88,9 +88,11 @@ export function stdioIntoGeneralStream( ): IrExpr | null { if (!lowerer.generalNodeStreams || expr.type.kind !== "procStream") return null; const writable: IrType = { kind: "object", className: "%Writable" }; + // Checked-dynamic stream slots (dyn-streams.ts) hold the Writable boxed. const accepts = (expected.kind === "object" && expected.className === "%Writable") || - (expected.kind === "union" && lowerer.armTag(expected.unionId, writable) >= 0); + (expected.kind === "union" && lowerer.armTag(expected.unionId, writable) >= 0) || + (expected.kind === "dyn" && lowerer.dynNodeStreams); if (!accepts) return null; return { kind: "libCall", @@ -112,3 +114,14 @@ export function requestGeneralStreamSurface(lowerer: Lowerer, receiver: ts.Expre if (isGeneralNodeStreamInterface(type, lowerer.typeCtx)) lowerer.requiresGeneralNodeStreams = true; } + +/** Under the general representation, an untyped value reaching a runtime + * stream class slot (the checked unwrap) or an untyped pipe/pipeline + * stage: the program lowers again with checked-dynamic stream slots + * (dyn-streams.ts), where NodeJS.ReadableStream / NodeJS.WritableStream + * hold JavaScript stream objects too. */ +export function noteUntypedStreamFlow(lowerer: Lowerer, expected: IrType | null): void { + if (!lowerer.generalNodeStreams || lowerer.dynNodeStreams) return; + if (expected === null || containsPart(lowerer, expected, (p) => isGeneralStream(lowerer, p))) + lowerer.requiresDynNodeStreams = true; +} diff --git a/packages/compiler/src/frontend/lowering/lower-async-iteration.ts b/packages/compiler/src/frontend/lowering/lower-async-iteration.ts index f095c5b9c..0e48f2040 100644 --- a/packages/compiler/src/frontend/lowering/lower-async-iteration.ts +++ b/packages/compiler/src/frontend/lowering/lower-async-iteration.ts @@ -137,7 +137,9 @@ function literalBooleanOption( function nodeReadablePlan(lowerer: Lowerer, expression: ts.Expression): NodeReadablePlan | null { const direct = - lowerer.stageSourceType(expression) ?? lowerer.mapTypeOf(lowerer.typeOf(expression)); + lowerer.receiverTypeOverrides.get(expression) ?? + lowerer.stageSourceType(expression) ?? + lowerer.mapTypeOf(lowerer.typeOf(expression)); if (direct?.kind === "object") { const sides = streamSidesOf(lowerer, lowerer.classes.get(direct.className)); if (sides === "r" || sides === "rw") { diff --git a/packages/compiler/src/frontend/lowering/lower-calls.ts b/packages/compiler/src/frontend/lowering/lower-calls.ts index ae864c2cb..9be693ef5 100644 --- a/packages/compiler/src/frontend/lowering/lower-calls.ts +++ b/packages/compiler/src/frontend/lowering/lower-calls.ts @@ -1,4 +1,5 @@ import { guardNullProto, nullProtoRecordShape } from "./null-prototype.js"; +import { lowerDynStreamMemberCall } from "./dyn-streams.js"; import { refuseModuleMemberThis } from "./module-member-this.js"; import { lowerUnionEquality, tagEqualityMayMissAlias } from "./strict-equality.js"; import { lowerHashStreamCall } from "./builtins/hash-stream.js"; @@ -1397,6 +1398,8 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { } if (ts.isPropertyAccessExpression(expr.expression)) { const intrinsic = + // Checked-dynamic stream slots dispatch per value (dyn-streams.ts). + lowerDynStreamMemberCall(lowerer, expr, expr.expression) ?? fenceNodeModuleMutationCall(lowerer, expr, expr.expression) ?? // Builtin namespace imports first (`fs.readFileSync(...)` where fs // is `import * as fs from "node:fs"`): the same tables and fences @@ -9289,6 +9292,7 @@ export function lowerObjectMethodCall( ): IrExpr | null { if (lowerer.chainBlocked(access, call)) return null; let mappedReceiver = + lowerer.receiverTypeOverrides.get(access.expression) ?? structuralReadType(lowerer, access.expression) ?? lowerer.mapTypeOf(lowerer.typeOf(access.expression)); // EventEmitter's static side (the class, a value of it, or the events diff --git a/packages/compiler/src/frontend/lowering/lower-stmts.ts b/packages/compiler/src/frontend/lowering/lower-stmts.ts index afc329f40..03150f0a3 100644 --- a/packages/compiler/src/frontend/lowering/lower-stmts.ts +++ b/packages/compiler/src/frontend/lowering/lower-stmts.ts @@ -1,5 +1,6 @@ import { hostsAll } from "./expressions/hosted-narrowing.js"; import { requestGeneralStreamSurface } from "./general-streams.js"; +import { lowerForAwaitDynStream } from "./dyn-streams.js"; import { matchAllRowsReadByElements, storedMatchAllRowsReadByElements, @@ -8915,6 +8916,11 @@ export function lowerForOf(lowerer: Lowerer, stmt: ts.ForOfStatement): IrStmt { ) { return lowerForAwaitStdin(lowerer, stmt); } + // A checked-dynamic NodeJS.ReadableStream iterates its native stream. + const dynStream = lowerForAwaitDynStream(lowerer, stmt, () => + lowerForAwaitBuiltin(lowerer, stmt), + ); + if (dynStream) return dynStream; const builtin = lowerForAwaitBuiltin(lowerer, stmt); if (builtin) return builtin; // A NodeJS.ReadableStream iterates as the general Readable. diff --git a/packages/compiler/src/frontend/lowering/lower-stream.ts b/packages/compiler/src/frontend/lowering/lower-stream.ts index 51c86fe39..53e2927d5 100644 --- a/packages/compiler/src/frontend/lowering/lower-stream.ts +++ b/packages/compiler/src/frontend/lowering/lower-stream.ts @@ -49,7 +49,8 @@ import { sideEffectFreeOptionValue } from "./surfaces.js"; import { lowerDynObjectLiteral } from "./expressions/object-literals.js"; import { dynUndefinedExpr, varRef } from "../../ir/build.js"; import { genResultRecord } from "../type-mapper.js"; -import { requestGeneralStreamSurface } from "./general-streams.js"; +import { noteUntypedStreamFlow, requestGeneralStreamSurface } from "./general-streams.js"; +import { dynNativeBranch } from "./dyn-streams.js"; import { extractIteratorValue, genStep, genValue, stepDrivable } from "./lower-generators.js"; import { BOOL, @@ -2049,13 +2050,16 @@ function lowerStreamArg( // a boxed native stream unwraps at run time; other values (iterables, // functions) throw the not-supported Error there. if (v.type.kind === "dyn" && what === "pipeline") { + noteUntypedStreamFlow(lowerer, null); return { kind: "libCall", fn: "stream.unboxDyn", args: [ v, strLit( - "pipeline stages other than native streams (iterables, generators, plain functions, and { readable, writable } pairs) are not supported yet", + lowerer.dynNodeStreams + ? "pipeline stages other than native streams (JavaScript stream objects, iterables, generators, plain functions, and { readable, writable } pairs) are not supported yet" + : "pipeline stages other than native streams (iterables, generators, plain functions, and { readable, writable } pairs) are not supported yet", locOf(node), ), ], @@ -3381,6 +3385,7 @@ function pipeIntoDyn( target: IrExpr, ): IrExpr { const loc = locOf(call); + noteUntypedStreamFlow(lowerer, null); const receiverValue = lowerer.lowerExpr(access.expression); const source = lowerer.declareHiddenLocal("%pipeSource", receiverValue.type); const destination = lowerer.declareHiddenLocal("%pipeDestination", target.type); @@ -3390,6 +3395,56 @@ function pipeIntoDyn( ]; const end = pipeEndOption(lowerer, call.arguments.nodes[1], loc); const duplex: IrType = { kind: "object", className: "%Duplex" }; + // Checked-dynamic stream slots (dyn-streams.ts): a JavaScript stream + // object is a destination too, piped with Node's listener protocol. + if (lowerer.dynNodeStreams) { + const sourceRef: IrExpr = { kind: "varRef", localId: source.id, type: receiverValue.type, loc }; + const destinationRef: IrExpr = { + kind: "varRef", + localId: destination.id, + type: target.type, + loc, + }; + const ending = lowerer.declareHiddenLocal("%pipeEnd", BOOL); + stmts.push({ kind: "varDecl", localId: ending.id, init: end, loc }); + const endRef: IrExpr = { kind: "varRef", localId: ending.id, type: BOOL, loc }; + stmts.push( + dynNativeBranch( + lowerer, + destinationRef, + "w", + (native) => ({ + kind: "exprStmt", + expr: { + kind: "libCall", + fn: "readable.pipe", + args: [sourceRef, native, endRef], + type: native.type, + loc, + }, + loc, + }), + { + kind: "exprStmt", + expr: { + kind: "libCall", + fn: "stream.pipeDyn", + args: [sourceRef, lowerer.coerceToExpected(sourceRef, DYN), destinationRef, endRef], + type: VOID, + loc, + }, + loc, + }, + ), + ); + return { + kind: "seqExpr", + stmts, + result: destinationRef, + type: target.type, + loc, + }; + } stmts.push({ kind: "exprStmt", expr: { diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index c9a0c6435..f31ed99c8 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -1,4 +1,8 @@ -import { needsGeneralNodeStreams, stdioIntoGeneralStream } from "./general-streams.js"; +import { + needsGeneralNodeStreams, + noteUntypedStreamFlow, + stdioIntoGeneralStream, +} from "./general-streams.js"; import { extendAssertedRecordMembers } from "./assertion-members.js"; import { jsDynamicDeclarations } from "./js-dynamic-objects.js"; import { @@ -882,6 +886,11 @@ export interface LowererMode { * generalNodeStreams). lowerToIr turns it on only after a pass without * it saw a stream flow that needs it (requiresGeneralNodeStreams). */ generalNodeStreams?: boolean; + /** With generalNodeStreams, the checked-dynamic stream slots + * (TypeMapperCtx.dynNodeStreams). lowerToIr turns it on only after a + * pass saw an untyped value flow into a stream slot + * (requiresDynNodeStreams). */ + dynNodeStreams?: boolean; /** LoadResult.inlineRequireCycleCalls. */ inlineRequireCycleCalls?: ReadonlySet | undefined; /** LowerOptions.libraryCallbacks (see there). */ @@ -1047,7 +1056,11 @@ export function lowerToIr( // a general stream into a stdio-represented stream slot lowers again // with it. Every other program keeps the single pass above unchanged. const generalNodeStreams = reachableEmit.requiresGeneralNodeStreams; - if (generalNodeStreams) { + // Checked-dynamic stream slots (dyn-streams.ts) are a further per-program + // decision under the general representation: only a program that moves an + // untyped value into a stream slot lowers again with them. + let dynNodeStreams = reachableEmit.requiresDynNodeStreams; + const streamPass = (dyn: boolean): Lowerer => { const general = new Lowerer(program, entry, moduleOrder, { frontendServices: options.frontendServices, projectCheckJs: options.projectCheckJs ?? false, @@ -1063,6 +1076,7 @@ export function lowerToIr( tierMode, ffiBindingSymbols: ffiValidation.symbolsByName, generalNodeStreams: true, + ...(dyn ? { dynNodeStreams: true } : {}), }); for (const d of extraModuleCycleDiags) general.pushDiag(d); for (const d of ffiValidation.diagnostics) general.pushDiag(d); @@ -1070,7 +1084,15 @@ export function lowerToIr( reachable = generalEmitted.reachable; result = generalEmitted.result; resultLowerer = general; - timing("general-streams-relower", { reachable: reachable.size }); + timing(dyn ? "dyn-streams-relower" : "general-streams-relower", { reachable: reachable.size }); + return general; + }; + if (generalNodeStreams) { + const general = streamPass(dynNodeStreams); + if (!dynNodeStreams && general.requiresDynNodeStreams) { + dynNodeStreams = true; + streamPass(true); + } } // A generic class-rest support decision can depend on record metadata // whose historical owner is discovered only while retained bodies lower. @@ -1084,6 +1106,7 @@ export function lowerToIr( frontendServices: options.frontendServices, projectCheckJs: options.projectCheckJs ?? false, ...(generalNodeStreams ? { generalNodeStreams: true } : {}), + ...(dynNodeStreams ? { dynNodeStreams: true } : {}), reachable, targetPlatform, startupCrash, @@ -1140,11 +1163,12 @@ export function lowerToIr( }; else if (contracts.count > 0) result = { ...result, contracts }; if (options.coverage !== true) return result; - const remainderPass = (general: boolean): Lowerer => + const remainderPass = (general: boolean, dyn = dynNodeStreams): Lowerer => new Lowerer(program, entry, moduleOrder, { frontendServices: options.frontendServices, projectCheckJs: options.projectCheckJs ?? false, ...(general ? { generalNodeStreams: true } : {}), + ...(general && dyn ? { dynNodeStreams: true } : {}), reachable, remainder: true, alreadyFlushed: resultLowerer.flushedSymbols, @@ -1165,7 +1189,11 @@ export function lowerToIr( // stream flow there that needs the general representation lowers the // remainder again with it, as reachable emit would. if (!generalNodeStreams && remainder.requiresGeneralNodeStreams) { - remainder = remainderPass(true); + remainder = remainderPass(true, remainder.requiresDynNodeStreams); + rem = remainder.run(); + } + if (!dynNodeStreams && remainder.generalNodeStreams && remainder.requiresDynNodeStreams) { + remainder = remainderPass(true, true); rem = remainder.run(); } timing("coverage-remainder"); @@ -2548,6 +2576,15 @@ export class Lowerer { * a stdio handle reached an 'unknown' slot. lowerToIr then lowers the * program again under the general stream representation. */ requiresGeneralNodeStreams = false; + /** An untyped value reached a stream slot (a checked unwrap into a + * runtime stream class, or an untyped pipe/pipeline stage). lowerToIr + * then lowers the program again with checked-dynamic stream slots + * (dyn-streams.ts). */ + requiresDynNodeStreams = false; + /** Receiver representations forced by the per-value stream dispatch + * (dyn-streams.ts): the native arm lowers a member call on the unboxed + * stream through the ordinary class-method path. */ + readonly receiverTypeOverrides = new Map(); /** Helpers that snapshot shape declaration order into their bodies. * Reachability lowers inits before the declarations they discover, so * these rebuild after the worklist restores historical shape metadata. */ @@ -2996,6 +3033,7 @@ export class Lowerer { * committed refusals in "auto" and keeps them refusals in "static-only". */ readonly tierMode: TierMode; readonly generalNodeStreams: boolean; + readonly dynNodeStreams: boolean; /** Functions finishModule placed in the dynamic tier (ir/tiers.ts). */ dynamicFunctions: DynamicFunction[] = []; /** Their IR, for coverage's dynamic statement count. */ @@ -3125,6 +3163,7 @@ export class Lowerer { this.targetPlatform = mode.targetPlatform ?? process.platform; this.tierMode = mode.tierMode ?? "auto"; this.generalNodeStreams = mode.generalNodeStreams ?? false; + this.dynNodeStreams = this.generalNodeStreams && (mode.dynNodeStreams ?? false); this.workerTargets = mode.workerTargets ?? []; this.workerTargets.forEach((sf, id) => { this.workerTargetIdByPath.set(tsgoPath(resolve(sf.fileName)), id); @@ -3222,6 +3261,7 @@ export class Lowerer { this.externalTypeSpecifiersByFile.has(tsgoPath(resolve(sf.fileName))), typeMemo: this.typeMemo, ...(this.generalNodeStreams ? { generalNodeStreams: true } : {}), + ...(this.dynNodeStreams ? { dynNodeStreams: true } : {}), canMemoizeType: () => this.typeParamBindings === null && this.typeParamTsBindings === null && @@ -8506,8 +8546,12 @@ export class Lowerer { * validator ICEs on them as the usual backstop. */ requireExactShape(node: ts.Node, actual: IrType, expected: IrType): void { if (typeEquals(actual, expected)) return; - if (!this.generalNodeStreams && needsGeneralNodeStreams(this, actual, expected)) + if (!this.generalNodeStreams && needsGeneralNodeStreams(this, actual, expected)) { this.requiresGeneralNodeStreams = true; + // An untyped value into a stream slot also needs the checked-dynamic + // slots (dyn-streams.ts). + if (actual.kind === "dyn") this.requiresDynNodeStreams = true; + } // dyn mismatches first. A dyn ('unknown') value flowing into a typed // slot needs a CHECKED cast — the hint points at `as `; tsc // usually rejects this before we do, so the fence is mostly defensive. @@ -8888,6 +8932,7 @@ export class Lowerer { (id) => this.unions.get(id), ); const handleOk = DYN_HANDLE_KINDS.has(expected.kind); + noteUntypedStreamFlow(this, expected); if ( canDynCheckTo( expected, diff --git a/packages/compiler/src/frontend/type-mapper.ts b/packages/compiler/src/frontend/type-mapper.ts index 7acb539d1..3e6fb3be2 100644 --- a/packages/compiler/src/frontend/type-mapper.ts +++ b/packages/compiler/src/frontend/type-mapper.ts @@ -1178,6 +1178,12 @@ export interface TypeMapperCtx { * from every stream producer share their slots. Off (the default), the * stdio handles keep those spellings. */ generalNodeStreams?: boolean; + /** With generalNodeStreams, the program's checked-dynamic stream slots + * (lowerToIr decides it per program, dyn-streams.ts): NodeJS.ReadableStream + * and NodeJS.WritableStream map to the checked-dynamic representation, so + * a slot holds a native stream (a boxed runtime stream, identity kept) or + * a JavaScript stream object. Members dispatch per value at run time. */ + dynNodeStreams?: boolean; /** Successful context-free mappings, keyed by the * TypeScript server's stable type id. A generated facade can reference the * same large record/function type thousands of times; remapping its full @@ -3033,6 +3039,27 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // Under the program's general stream representation (generalNodeStreams) // NodeJS.WritableStream is the runtime Writable instead: the process // streams convert into it where they flow (process.stdioWritable). + // Checked-dynamic stream slots: an intersection over a NodeJS stream + // interface (`NodeJS.ReadableStream & { destroy?(): void }`) holds the + // same values as the interface. + if ( + ctx.dynNodeStreams === true && + (type.flags & ts.TypeFlags.Intersection) !== 0 && + ts.constituentTypes(type).some((part) => { + const sym = part.getSymbol(); + return ( + (sym?.name === "ReadableStream" || sym?.name === "WritableStream") && + sym.declarations.some( + (d) => + ts.isInterfaceDeclaration(d) && + ctx.isStdlibFile(d.getSourceFile()) && + isDeclaredInAmbientNamespace(d, "NodeJS"), + ) + ); + }) + ) { + return DYN; + } const nodeWritableInterface = psym?.name === "WritableStream" && psym.declarations.some( @@ -3042,7 +3069,7 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { isDeclaredInAmbientNamespace(d, "NodeJS"), ); if (nodeWritableInterface && ctx.generalNodeStreams === true) { - return { kind: "object", className: "%Writable" }; + return ctx.dynNodeStreams === true ? DYN : { kind: "object", className: "%Writable" }; } if ( (psym?.name === "WriteStream" && @@ -3168,8 +3195,13 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { )) ) { // The general representation: the runtime Readable, which child - // stdio reads produce through the child stream bridge. - if (ctx.generalNodeStreams === true) return { kind: "object", className: "%Readable" }; + // stdio reads produce through the child stream bridge. With + // checked-dynamic stream slots, NodeJS.ReadableStream holds any + // stream value (dyn-streams.ts); stream.Readable keeps the class. + if (ctx.generalNodeStreams === true) { + if (ctx.dynNodeStreams === true && psym.name === "ReadableStream") return DYN; + return { kind: "object", className: "%Readable" }; + } return { kind: "childStream" }; } // dgram.Socket: the same ambiguous "Socket" name disambiguated by its @@ -4195,7 +4227,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { (ctx.generalNodeStreams === true && arm.kind === "object" && ((expected === "childStream" && arm.className === "%Readable") || - (expected === "childWriter" && arm.className === "%Writable"))); + (expected === "childWriter" && arm.className === "%Writable"))) || + // Checked-dynamic stream slots: `NodeJS.ReadableStream | null` + // collapses to dyn (the reads still produce the bridged stream). + (ctx.dynNodeStreams === true && + arm.kind === "dyn" && + (expected === "childStream" || expected === "childWriter")); return ( arms !== undefined && arms.some(isExpected) && diff --git a/packages/compiler/src/index.ts b/packages/compiler/src/index.ts index 7d8d88889..12b84654b 100644 --- a/packages/compiler/src/index.ts +++ b/packages/compiler/src/index.ts @@ -537,6 +537,7 @@ async function compileExecutableNative( dc: features.dc, dynAsync: features.dynAsync, ...(features.dynTier ? { dynTier: true } : {}), + ...(features.streamDyn ? { streamDyn: true } : {}), events: features.events, emitter: features.emitter, symbol: features.symbol, diff --git a/packages/compiler/src/ir/builtin-effects.ts b/packages/compiler/src/ir/builtin-effects.ts index 1c390bbe4..fa171b8a1 100644 --- a/packages/compiler/src/ir/builtin-effects.ts +++ b/packages/compiler/src/ir/builtin-effects.ts @@ -77,6 +77,7 @@ export const MAY_THROW_LIB_FNS: ReadonlySet = new Set([ "procStream.write", "child.stdioStream", "stream.unboxDyn", + "stream.pipeDyn", "weakMap.new", "weakSet.new", "dyn.fromEntries", diff --git a/packages/compiler/src/ir/builtin-signatures.ts b/packages/compiler/src/ir/builtin-signatures.ts index 597076cb1..2d70a7254 100644 --- a/packages/compiler/src/ir/builtin-signatures.ts +++ b/packages/compiler/src/ir/builtin-signatures.ts @@ -2571,6 +2571,16 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ argTypes: [DYN, STRING], result: { kind: "object", className: "%Duplex" }, }, + /** Checked-dynamic stream slots (dyn-streams.ts): the native stream a + * checked-dynamic value boxes (+1) — a runtime stream with the readable + * (1) or writable (2) side — or null for any other value. Never throws; + * the result union is `%Readable | null` or `%Writable | null`. */ + "stream.dynNative": { argTypes: [DYN, F64], result: VOID }, + /** readable.pipe(destination, end) into a checked-dynamic destination + * that is not a native stream: [source, the source boxed, destination, + * end]. Node's pipe listener protocol over the destination's members + * (scr_stream_dyn.c); throws what those members throw. */ + "stream.pipeDyn": { argTypes: [null, DYN, DYN, BOOL], result: VOID }, "stream.stdioWritable": { argTypes: [PROCSTREAM_T], result: { kind: "object", className: "%Writable" }, diff --git a/packages/compiler/src/ir/runtime-features.ts b/packages/compiler/src/ir/runtime-features.ts index 99ba76f79..9b45fad1a 100644 --- a/packages/compiler/src/ir/runtime-features.ts +++ b/packages/compiler/src/ir/runtime-features.ts @@ -163,6 +163,8 @@ const STREAM_LIB_FNS: ReadonlySet = new Set([ "stream.destroyErr", "stream.errored", "stream.prop", + "stream.dynNative", + "stream.pipeDyn", "stream.stdioWritable", "stream.unboxDyn", "stream.workerStdio", @@ -199,6 +201,8 @@ const READABLE_DYN_ASYNC_PREFIXES: readonly string[] = ["readable.fromGen", "rea /** Entry points that link the dyn-invoke unit. */ const DYN_INVOKE_LIB_FNS: ReadonlySet = new Set([ + "stream.dynNative", + "stream.pipeDyn", "dyn.arrayProtoCall", "dyn.arrayPrototype", "dyn.builtinMethod", @@ -502,6 +506,9 @@ const RUNTIME_FEATURE_RULES = { /** The program has dynamic-tier jsval operations (js.* builtins, the * jsval pass): links the scr_js_* units (two-tier design I3). */ dynTier: rule({ prefixes: ["js."] }), + /** Checked-dynamic stream slots test values and pipe into JavaScript + * stream objects (scr_stream_dyn.c). */ + streamDyn: rule({ fns: ["stream.dynNative", "stream.pipeDyn"] }), /** The stream unit emits through the emitter unit, also when the * program constructs no stream (a pipeline of functions). */ emitter: rule({ @@ -662,6 +669,7 @@ function noRuntimeFeatures(): RuntimeFeatures { searchParams: false, stdioExtended: false, stream: false, + streamDyn: false, symbol: false, tls: false, tlsCa: false, diff --git a/packages/compiler/src/ir/validate.ts b/packages/compiler/src/ir/validate.ts index 63078ae4a..68997c295 100644 --- a/packages/compiler/src/ir/validate.ts +++ b/packages/compiler/src/ir/validate.ts @@ -5224,6 +5224,21 @@ function validateFunction( } return true; } + if (e.fn === "stream.dynNative") { + const def = e.type.kind === "union" ? unions.get(e.type.unionId) : undefined; + const side = e.args[1]; + const className = + side?.kind === "numLit" ? (side.value === 1 ? "%Readable" : "%Writable") : null; + const ok = + def && + className !== null && + e.args[0]?.type.kind === "dyn" && + def.arms.length === 2 && + def.arms.some((a) => a.kind === "object" && a.className === className) && + def.arms.some((a) => a.kind === "nullT"); + if (!ok) err(`libCall ${e.fn} must return its side's stream class or null`, e.loc); + return true; + } if (e.fn === "child.stdioStream") { const def = e.type.kind === "union" ? unions.get(e.type.unionId) : undefined; const slot = e.args[1]; diff --git a/packages/compiler/src/native/sanitizer.test.ts b/packages/compiler/src/native/sanitizer.test.ts index 6ca84af6f..d9efb7cd8 100644 --- a/packages/compiler/src/native/sanitizer.test.ts +++ b/packages/compiler/src/native/sanitizer.test.ts @@ -76,6 +76,7 @@ test.skipIf(process.platform !== "linux" && process.platform !== "darwin")( youngNursery: false, eventTarget: false, dynTier: false, + streamDyn: false, warningReplace: false, stdioExtended: false, regex: true, diff --git a/packages/runtime-pack-common/runtime-pack-matrix.mjs b/packages/runtime-pack-common/runtime-pack-matrix.mjs index 1e959eccc..790864677 100644 --- a/packages/runtime-pack-common/runtime-pack-matrix.mjs +++ b/packages/runtime-pack-common/runtime-pack-matrix.mjs @@ -93,6 +93,8 @@ export const OPTIONAL_RUNTIME_SOURCES = [ ["scr_zlib_stream.c", all("zlib", "stream")], // The Readable operators await through the dyn-async unit. ["scr_stream_ops.c", all("stream", "dynAsync")], + // Checked-dynamic stream slots call JavaScript members through the dyn-invoke unit. + ["scr_stream_dyn.c", "streamDyn"], // fs.ReadStream / fs.WriteStream reference only the stream unit and base units. ["scr_fs_stream.c", "stream"], // FileHandle streams reference both the stream unit and FileHandle. diff --git a/packages/runtime/src/scr_stream_dyn.c b/packages/runtime/src/scr_stream_dyn.c new file mode 100644 index 000000000..f938f122d --- /dev/null +++ b/packages/runtime/src/scr_stream_dyn.c @@ -0,0 +1,349 @@ +/* Checked-dynamic stream slots (the compiler's dyn-streams.ts). + * + * A program whose NodeJS.ReadableStream / NodeJS.WritableStream slots also + * hold JavaScript stream objects compiles them as checked-dynamic values: a + * native stream rides its typed capsule, any other value is the JavaScript + * object itself. Member calls test the value here and take the native + * stream path or the value's own members. Linked only by such programs + * (the stream and dyn-invoke units together). */ +#include "scr_runtime.h" + +#include +#include + +static bool scr_stream_dyn_within(const ScrVt *vt, const ScrVt *cls) { + return vt == cls || (cls->pre <= vt->pre && vt->pre <= cls->post); +} + +/* The native stream a checked-dynamic value boxes (+1), when it has the + * requested side (1 readable, 2 writable; Duplex, Transform and PassThrough + * have both), or NULL for any other value. Never throws. */ +ScrStream *scr_stream_dyn_native(const ScrDyn *value, double side) { + if (value->kind != SCR_DYN_TYPED_REF || value->v.typed_ref.type_key_len <= 7 || + memcmp(value->v.typed_ref.type_key, "object:", 7) != 0) + return NULL; + ScrStream *stream = scr_dyn_typed_ref_unbox(value); + const ScrVt *vt = stream->vt; + bool duplex = scr_stream_dyn_within(vt, &scr_duplex_vt) || + scr_stream_dyn_within(vt, &scr_transform_vt) || + scr_stream_dyn_within(vt, &scr_passthrough_vt); + bool ok = duplex || (side == 1 ? scr_stream_dyn_within(vt, &scr_readable_vt) + : scr_stream_dyn_within(vt, &scr_writable_vt)); + if (ok) return stream; + value->v.typed_ref.release(stream); + return NULL; +} + +/* ── readable.pipe(destination) into a JavaScript stream object ───────── + * Node's Readable.prototype.pipe (lib/internal/streams/readable.js) over a + * native source and a destination that is not a native stream: the same + * listeners in the same order, with the destination's members called + * through the dyn-invoke unit. The context holds the source, its boxed + * value (what 'pipe' and 'unpipe' pass), the destination and the boxed + * listeners until cleanup; the live list breaks the listener cycles at + * exit without calling JavaScript. */ + +enum { + SCR_DYN_PIPE_DATA, + SCR_DYN_PIPE_END, + SCR_DYN_PIPE_UNPIPE, + SCR_DYN_PIPE_DRAIN, + SCR_DYN_PIPE_ERROR, + SCR_DYN_PIPE_CLOSE, + SCR_DYN_PIPE_FINISH, + SCR_DYN_PIPE_ON_UNPIPE, + SCR_DYN_PIPE_COUNT +}; + +typedef struct ScrDynPipe { + size_t rc; + ScrStream *src; /* +1 until dropped */ + ScrDyn *src_box; /* +1 until dropped */ + ScrDyn *dest; /* +1 until dropped */ + ScrDyn *l[SCR_DYN_PIPE_COUNT]; /* +1 boxed listeners, or NULL */ + bool end, cleaned, awaiting_drain, unpiped; + struct ScrDynPipe *live_prev, *live_next; +} ScrDynPipe; + +static SCR_TL ScrDynPipe *scr_dyn_pipe_live = NULL; + +/* A string value for an event name (+1). */ +static ScrDyn *scr_dyn_pipe_str(const char *text, size_t len) { + ScrStr *s = scr_str_new(text, len); + ScrDyn *value = scr_dyn_new_str(s); + scr_str_release(s); + return value; +} +static SCR_TL bool scr_dyn_pipe_atexit_set = false; + +static void *scr_dyn_pipe_retain_v(void *p) { + ((ScrDynPipe *)p)->rc++; + return p; +} + +static void scr_dyn_pipe_drop(ScrDynPipe *p) { + if (p->live_prev) p->live_prev->live_next = p->live_next; + else if (scr_dyn_pipe_live == p) scr_dyn_pipe_live = p->live_next; + if (p->live_next) p->live_next->live_prev = p->live_prev; + p->live_prev = p->live_next = NULL; + for (size_t i = 0; i < SCR_DYN_PIPE_COUNT; i++) { + ScrDyn *l = p->l[i]; + p->l[i] = NULL; + if (l) scr_dyn_release(l); + } + if (p->src) { + ScrStream *src = p->src; + p->src = NULL; + scr_stream_release(src); + } + if (p->src_box) { + ScrDyn *box = p->src_box; + p->src_box = NULL; + scr_dyn_release(box); + } + if (p->dest) { + ScrDyn *dest = p->dest; + p->dest = NULL; + scr_dyn_release(dest); + } +} + +static void scr_dyn_pipe_release_v(void *v) { + ScrDynPipe *p = v; + if (--p->rc > 0) return; + scr_dyn_pipe_drop(p); + free(p); +} + +static void scr_dyn_pipe_atexit(void) { + while (scr_dyn_pipe_live) { + ScrDynPipe *p = scr_dyn_pipe_retain_v(scr_dyn_pipe_live); + scr_dyn_pipe_drop(p); + scr_dyn_pipe_release_v(p); + } +} + +/* destination.method(args...), the result discarded; false on a throw. */ +static bool scr_dyn_pipe_call(ScrDynPipe *p, const char *method, ScrDyn *const *args, size_t argc) { + ScrDyn *result = scr_dyn_invoke(p->dest, method, args, argc, method); + if (result) scr_dyn_release(result); + return !scr_exc_pending(); +} + +static bool scr_dyn_pipe_dest_off(ScrDynPipe *p, const char *event, size_t which) { + if (!p->l[which]) return true; + ScrDyn *args[2] = {scr_dyn_pipe_str(event, strlen(event)), p->l[which]}; + bool ok = scr_dyn_pipe_call(p, "removeListener", args, 2); + scr_dyn_release(args[0]); + return ok; +} + +static void scr_dyn_pipe_src_off(ScrDynPipe *p, const char *event, size_t which) { + if (!p->l[which] || !p->src) return; + ScrStr *name = scr_str_new(event, strlen(event)); + scr_emitter_release(scr_emitter_off_dyn((ScrEmitter *)p->src, name, p->l[which])); + scr_str_release(name); +} + +static void scr_dyn_pipe_ondrain(ScrDynPipe *p) { + p->awaiting_drain = false; + if (!p->src) return; + ScrStr *data = scr_str_new("data", 4); + double listeners = scr_emitter_listener_count((ScrEmitter *)p->src, data); + scr_str_release(data); + if (listeners > 0) scr_stream_release(scr_stream_resume(p->src)); +} + +/* pipe's cleanup(): every listener off, then a pending drain resumes. */ +static void scr_dyn_pipe_cleanup(ScrDynPipe *p) { + if (p->cleaned) return; + if (!scr_dyn_pipe_dest_off(p, "close", SCR_DYN_PIPE_CLOSE) || + !scr_dyn_pipe_dest_off(p, "finish", SCR_DYN_PIPE_FINISH) || + !scr_dyn_pipe_dest_off(p, "drain", SCR_DYN_PIPE_DRAIN) || + !scr_dyn_pipe_dest_off(p, "error", SCR_DYN_PIPE_ERROR) || + !scr_dyn_pipe_dest_off(p, "unpipe", SCR_DYN_PIPE_ON_UNPIPE)) + return; + scr_dyn_pipe_src_off(p, "end", SCR_DYN_PIPE_END); + scr_dyn_pipe_src_off(p, "end", SCR_DYN_PIPE_UNPIPE); + scr_dyn_pipe_src_off(p, "data", SCR_DYN_PIPE_DATA); + p->cleaned = true; + if (p->l[SCR_DYN_PIPE_DRAIN] && p->awaiting_drain) scr_dyn_pipe_ondrain(p); + ScrDynPipe *held = scr_dyn_pipe_retain_v(p); + scr_dyn_pipe_drop(held); + scr_dyn_pipe_release_v(held); +} + +/* src.unpipe(dest): the source pauses (its only pipe goes) and the + * destination hears 'unpipe' with { hasUnpiped: false }. */ +static void scr_dyn_pipe_unpipe(ScrDynPipe *p) { + if (p->unpiped || !p->src) return; + p->unpiped = true; + scr_stream_release(scr_stream_pause(p->src)); + ScrDyn *info = scr_dyn_new_obj(); + scr_dyn_obj_set(info, "hasUnpiped", 10, scr_dyn_new_bool(false)); + ScrDyn *args[3] = {scr_dyn_pipe_str("unpipe", 6), scr_dyn_retain(p->src_box), info}; + (void)scr_dyn_pipe_call(p, "emit", args, 3); + for (size_t i = 0; i < 3; i++) scr_dyn_release(args[i]); +} + +static ScrDyn *scr_dyn_pipe_listener(ScrDynPipe *p, int kind); + +/* pipe's pause(): the source stops until the destination drains. */ +static void scr_dyn_pipe_pause(ScrDynPipe *p) { + if (!p->cleaned) { + p->awaiting_drain = true; + scr_stream_release(scr_stream_pause(p->src)); + } + if (!p->l[SCR_DYN_PIPE_DRAIN] && p->dest) { + p->l[SCR_DYN_PIPE_DRAIN] = scr_dyn_pipe_listener(p, SCR_DYN_PIPE_DRAIN); + ScrDyn *args[2] = {scr_dyn_pipe_str("drain", 5), p->l[SCR_DYN_PIPE_DRAIN]}; + (void)scr_dyn_pipe_call(p, "on", args, 2); + scr_dyn_release(args[0]); + } +} + +static ScrDyn *scr_dyn_pipe_thunk(ScrClosure *clo, ScrDyn *const *args, size_t argc) { + ScrDynPipe *p = scr_box_get_ref(clo->caps[0]); /* +1 */ + int kind = (int)scr_box_get_f64(clo->caps[1]); + ScrDyn *first = argc ? args[0] : scr_dyn_undefined(); + switch (kind) { + case SCR_DYN_PIPE_DATA: { + if (!p->dest) break; + ScrDyn *ret = scr_dyn_invoke(p->dest, "write", &first, 1, "write"); + if (ret) { + if (ret->kind == SCR_DYN_BOOL && !scr_dyn_truthy(ret)) scr_dyn_pipe_pause(p); + scr_dyn_release(ret); + } + break; + } + case SCR_DYN_PIPE_END: + if (p->dest) (void)scr_dyn_pipe_call(p, "end", NULL, 0); + break; + case SCR_DYN_PIPE_UNPIPE: + scr_dyn_pipe_unpipe(p); + break; + case SCR_DYN_PIPE_DRAIN: + scr_dyn_pipe_ondrain(p); + break; + case SCR_DYN_PIPE_ERROR: { + if (!p->dest) break; + ScrDyn *dest = scr_dyn_retain(p->dest); + ScrDyn *self = scr_dyn_retain(p->l[SCR_DYN_PIPE_ERROR]); + scr_dyn_pipe_unpipe(p); + if (!scr_exc_pending()) { + ScrDyn *off[2] = {scr_dyn_pipe_str("error", 5), self}; + ScrDyn *r = scr_dyn_invoke(dest, "removeListener", off, 2, "removeListener"); + if (r) scr_dyn_release(r); + ScrDyn *count = scr_exc_pending() ? NULL : scr_dyn_invoke(dest, "listenerCount", off, 1, "listenerCount"); + if (count && count->kind == SCR_DYN_NUM && !scr_dyn_truthy(count)) { + ScrDyn *emit[2] = {off[0], first}; + r = scr_dyn_invoke(dest, "emit", emit, 2, "emit"); + if (r) scr_dyn_release(r); + } + if (count) scr_dyn_release(count); + scr_dyn_release(off[0]); + } + scr_dyn_release(self); + scr_dyn_release(dest); + break; + } + case SCR_DYN_PIPE_CLOSE: + if (scr_dyn_pipe_dest_off(p, "finish", SCR_DYN_PIPE_FINISH)) scr_dyn_pipe_unpipe(p); + break; + case SCR_DYN_PIPE_FINISH: + if (scr_dyn_pipe_dest_off(p, "close", SCR_DYN_PIPE_CLOSE)) scr_dyn_pipe_unpipe(p); + break; + case SCR_DYN_PIPE_ON_UNPIPE: { + ScrDyn *info = argc > 1 ? args[1] : NULL; + if (p->src_box && scr_dyn_strict_eq(first, p->src_box) && info && info->kind == SCR_DYN_OBJ) { + ScrDyn *has = scr_dyn_obj_read(info, "hasUnpiped", 10); + bool unpiped = has->kind == SCR_DYN_BOOL && !scr_dyn_truthy(has); + scr_dyn_release(has); + if (unpiped) { + scr_dyn_obj_set(info, "hasUnpiped", 10, scr_dyn_new_bool(true)); + scr_dyn_pipe_cleanup(p); + } + } + break; + } + } + scr_dyn_pipe_release_v(p); + return scr_exc_pending() ? NULL : scr_dyn_retain(scr_dyn_undefined()); +} + +static ScrDyn *scr_dyn_pipe_listener(ScrDynPipe *p, int kind) { + ScrClosure *clo = scr_closure_new((void *)scr_dyn_pipe_thunk, 2); + clo->caps[0] = scr_box_new_obj(scr_dyn_pipe_retain_v, scr_dyn_pipe_release_v, NULL); + scr_box_set_ref(clo->caps[0], scr_dyn_pipe_retain_v(p)); + clo->caps[1] = scr_box_new(SCR_BOX_F64); + scr_box_set_f64(clo->caps[1], (double)kind); + return scr_dyn_new_func(clo, scr_dyn_pipe_thunk, 1, "native pipe listener", NULL); +} + +/* destination.on / once / prependListener(event, listener) */ +static bool scr_dyn_pipe_listen(ScrDynPipe *p, const char *method, const char *event, int kind) { + p->l[kind] = scr_dyn_pipe_listener(p, kind); + ScrDyn *args[2] = {scr_dyn_pipe_str(event, strlen(event)), p->l[kind]}; + bool ok = scr_dyn_pipe_call(p, method, args, 2); + scr_dyn_release(args[0]); + return ok; +} + +static void scr_dyn_pipe_src_on(ScrDynPipe *p, const char *event, int kind, bool once) { + p->l[kind] = scr_dyn_pipe_listener(p, kind); + ScrStr *name = scr_str_new(event, strlen(event)); + scr_stream_release(scr_stream_on_dyn(p->src, name, p->l[kind], once, false)); + scr_str_release(name); +} + +/* readable.pipe(destination, { end }) for a destination that is not a + * native stream. src and src_box (the source's boxed value) and dest are + * borrowed. Throws what the destination's members throw. */ +void scr_stream_pipe_dyn(ScrStream *src, ScrDyn *src_box, ScrDyn *dest, bool end) { + ScrDynPipe *p = calloc(1, sizeof *p); + if (!p) abort(); + p->rc = 1; + p->src = scr_stream_retain(src); + p->src_box = scr_dyn_retain(src_box); + p->dest = scr_dyn_retain(dest); + p->end = end; + p->live_next = scr_dyn_pipe_live; + if (scr_dyn_pipe_live) scr_dyn_pipe_live->live_prev = p; + scr_dyn_pipe_live = p; + if (!scr_dyn_pipe_atexit_set) { + scr_dyn_pipe_atexit_set = true; + scr_atexit(scr_dyn_pipe_atexit); + } + /* The source's end ends the destination (end: true) or unpipes it. */ + scr_dyn_pipe_src_on(p, "end", end ? SCR_DYN_PIPE_END : SCR_DYN_PIPE_UNPIPE, true); + if (scr_exc_pending() || !scr_dyn_pipe_listen(p, "on", "unpipe", SCR_DYN_PIPE_ON_UNPIPE)) goto out; + scr_dyn_pipe_src_on(p, "data", SCR_DYN_PIPE_DATA, false); + if (scr_exc_pending()) goto out; + { + ScrDyn *prepend = scr_dyn_obj_read(dest, "prependListener", 15); + bool has_prepend = prepend->kind == SCR_DYN_FUNC; + scr_dyn_release(prepend); + if (!scr_dyn_pipe_listen(p, has_prepend ? "prependListener" : "on", "error", SCR_DYN_PIPE_ERROR)) + goto out; + } + if (!scr_dyn_pipe_listen(p, "once", "close", SCR_DYN_PIPE_CLOSE) || + !scr_dyn_pipe_listen(p, "once", "finish", SCR_DYN_PIPE_FINISH)) + goto out; + { + ScrDyn *args[2] = {scr_dyn_pipe_str("pipe", 4), scr_dyn_retain(src_box)}; + bool ok = scr_dyn_pipe_call(p, "emit", args, 2); + scr_dyn_release(args[0]); + scr_dyn_release(args[1]); + if (!ok) goto out; + } + { + ScrDyn *need = scr_dyn_obj_read(dest, "writableNeedDrain", 17); + bool pause = need->kind == SCR_DYN_BOOL && scr_dyn_truthy(need); + scr_dyn_release(need); + if (pause) scr_dyn_pipe_pause(p); + else if (scr_stream_is_paused(src)) + scr_stream_release(scr_stream_resume(src)); + } +out: + scr_dyn_pipe_release_v(p); +} From 8284e61f1ea3a63b7733cde868201d210f056880 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Tue, 20 Oct 2026 06:49:37 -0700 Subject: [PATCH 085/122] Add corpus programs for JavaScript stream objects in NodeJS stream slots 6480 passes a plain object, a constructor-function stream and a native Readable through a NodeJS.ReadableStream function and consumes them in TypeScript (on("data"), pipe()) and JavaScript, including identity and a native pipe into JavaScript sinks. 6481 covers NodeJS.WritableStream slots and a copy helper across native and JavaScript sources and destinations. 6482 covers the helper shapes real code writes: an async producer, pipe with { end: false } and unpipe, pause/resume/setEncoding, an optional destroy through an intersection type, for await and pipeline over native values. Co-Authored-By: Claude Opus 5.5 --- .../main.ts.json | 7 + .../main.ts.json | 7 + .../6482-node-stream-dyn-helpers/main.ts.json | 7 + .../6480-node-stream-dyn-readable/main.ts | 72 +++++++++ .../6480-node-stream-dyn-readable/sources.js | 138 ++++++++++++++++++ .../6481-node-stream-dyn-writable/main.ts | 67 +++++++++ .../6481-node-stream-dyn-writable/sources.js | 49 +++++++ .../6482-node-stream-dyn-helpers/main.ts | 98 +++++++++++++ .../6482-node-stream-dyn-helpers/sources.js | 71 +++++++++ 9 files changed, 516 insertions(+) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6480-node-stream-dyn-readable/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6481-node-stream-dyn-writable/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6482-node-stream-dyn-helpers/main.ts.json create mode 100644 tests/corpus/6480-node-stream-dyn-readable/main.ts create mode 100644 tests/corpus/6480-node-stream-dyn-readable/sources.js create mode 100644 tests/corpus/6481-node-stream-dyn-writable/main.ts create mode 100644 tests/corpus/6481-node-stream-dyn-writable/sources.js create mode 100644 tests/corpus/6482-node-stream-dyn-helpers/main.ts create mode 100644 tests/corpus/6482-node-stream-dyn-helpers/sources.js diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6480-node-stream-dyn-readable/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6480-node-stream-dyn-readable/main.ts.json new file mode 100644 index 000000000..b3698b91c --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6480-node-stream-dyn-readable/main.ts.json @@ -0,0 +1,7 @@ +{ + "order": [ + "/tests/corpus/6480-node-stream-dyn-readable/sources.js", + "/tests/corpus/6480-node-stream-dyn-readable/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6481-node-stream-dyn-writable/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6481-node-stream-dyn-writable/main.ts.json new file mode 100644 index 000000000..45b8ebb8a --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6481-node-stream-dyn-writable/main.ts.json @@ -0,0 +1,7 @@ +{ + "order": [ + "/tests/corpus/6481-node-stream-dyn-writable/sources.js", + "/tests/corpus/6481-node-stream-dyn-writable/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6482-node-stream-dyn-helpers/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6482-node-stream-dyn-helpers/main.ts.json new file mode 100644 index 000000000..b45958c33 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6482-node-stream-dyn-helpers/main.ts.json @@ -0,0 +1,7 @@ +{ + "order": [ + "/tests/corpus/6482-node-stream-dyn-helpers/sources.js", + "/tests/corpus/6482-node-stream-dyn-helpers/main.ts" + ], + "diags": [] +} diff --git a/tests/corpus/6480-node-stream-dyn-readable/main.ts b/tests/corpus/6480-node-stream-dyn-readable/main.ts new file mode 100644 index 000000000..21d7bf2b1 --- /dev/null +++ b/tests/corpus/6480-node-stream-dyn-readable/main.ts @@ -0,0 +1,72 @@ +// NodeJS.ReadableStream slots hold native streams and JavaScript stream +// objects alike: a plain object implementing on/pipe, a constructor-function +// stream, and a native Readable pass through a TypeScript function typed +// NodeJS.ReadableStream. TypeScript consumes them with on("data") and +// pipe() into a native Writable, JavaScript consumes the returned values +// (the same objects), and a native Readable typed NodeJS.ReadableStream +// pipes into JavaScript sinks through Node's pipe protocol. +import { Readable, Writable } from "node:stream"; +import { jsCollect, plainSink, plainSource, same, sink, source } from "./sources.js"; + +function pass(stream: NodeJS.ReadableStream): NodeJS.ReadableStream { + return stream; +} + +async function collect(label: string, stream: NodeJS.ReadableStream): Promise { + const parts: string[] = []; + await new Promise((resolve) => { + stream.on("data", (chunk: Buffer | string) => parts.push(chunk.toString())); + stream.on("end", () => resolve()); + }); + console.log(label, "on data", parts.join("")); +} + +async function pipeInto(label: string, stream: NodeJS.ReadableStream): Promise { + const parts: string[] = []; + await new Promise((resolve) => { + const native = new Writable({ + write(chunk: Buffer, _encoding, done) { + parts.push(chunk.toString()); + done(); + }, + }); + native.on("finish", () => resolve()); + stream.pipe(native); + }); + console.log(label, "pipe", parts.join("")); +} + +async function main(): Promise { + await collect("plain", pass(plainSource(["p1", "p2"]))); + await collect("constructor", pass(source(["s1", "s2"]))); + await collect("native", pass(Readable.from(["n1", "n2"]))); + + await pipeInto("plain", pass(plainSource(["p3", "p4"]))); + await pipeInto("constructor", pass(source(["s3", "s4"]))); + await pipeInto("native", pass(Readable.from(["n3", "n4"]))); + + console.log("plain js", await jsCollect(pass(plainSource(["p5", "p6"])))); + console.log("constructor js", await jsCollect(pass(source(["s5", "s6"])))); + console.log("native js", await jsCollect(pass(Readable.from(["n5", "n6"])))); + + const plain = plainSource(["p7"]); + const made = source(["s7"]); + const native = Readable.from(["n7"]); + console.log("identity", same(plain, pass(plain)), same(made, pass(made)), same(native, pass(native))); + + await new Promise((resolve) => { + pass(Readable.from(["n8", "n9"])).pipe(plainSink("native into plain")); + pass(plainSource(["p8"])).pipe(plainSink("plain into plain")); + setTimeout(resolve, 20); + }); + + await new Promise((resolve) => { + const target = sink("native into constructor"); + target.on("pipe", () => console.log("sink heard pipe")); + target.on("unpipe", () => console.log("sink heard unpipe")); + target.on("finish", () => setImmediate(resolve)); + pass(Readable.from(["n10", "n11"])).pipe(target); + }); +} + +void main(); diff --git a/tests/corpus/6480-node-stream-dyn-readable/sources.js b/tests/corpus/6480-node-stream-dyn-readable/sources.js new file mode 100644 index 000000000..3b520c29f --- /dev/null +++ b/tests/corpus/6480-node-stream-dyn-readable/sources.js @@ -0,0 +1,138 @@ +// JavaScript stream objects that are not native streams: a plain object +// implementing on/pipe, and a constructor function with its own listener +// table. Sinks: a plain object with write/end, and a constructor function +// that keeps listeners, so a native pipe() into it runs Node's protocol. + +/** @returns {any} */ +export function plainSource(chunks) { + const listeners = {}; + const source = { + on(event, listener) { + (listeners[event] ??= []).push(listener); + return source; + }, + emit(event, value) { + for (const listener of listeners[event] ?? []) listener(value); + }, + pipe(dest) { + source.on("data", (chunk) => dest.write(chunk)); + source.on("end", () => dest.end()); + return dest; + }, + }; + setImmediate(() => { + for (const chunk of chunks) source.emit("data", chunk); + source.emit("end"); + }); + return source; +} + +function Source(chunks) { + this.chunks = chunks; + this.listeners = {}; + this.readable = true; + setImmediate(() => { + for (const chunk of this.chunks) this.emit("data", chunk); + this.emit("end"); + }); +} +Source.prototype.on = function (event, listener) { + (this.listeners[event] = this.listeners[event] || []).push(listener); + return this; +}; +Source.prototype.emit = function (event, value) { + for (const listener of this.listeners[event] || []) listener(value); + return true; +}; +Source.prototype.pipe = function (dest) { + this.on("data", (chunk) => dest.write(chunk)); + this.on("end", () => dest.end()); + return dest; +}; + +/** @returns {any} */ +export function source(chunks) { + return new Source(chunks); +} + +/** @returns {any} */ +export function plainSink(label) { + const parts = []; + return { + write(chunk) { + parts.push(String(chunk)); + return true; + }, + end() { + console.log(label, "sink end", parts.join("")); + }, + on() { + return this; + }, + once() { + return this; + }, + emit() { + return false; + }, + }; +} + +function Sink(label) { + this.label = label; + this.parts = []; + this.listeners = []; + this.writable = true; +} +Sink.prototype.on = function (event, listener) { + this.listeners.push({ event, listener, once: false }); + return this; +}; +Sink.prototype.once = function (event, listener) { + this.listeners.push({ event, listener, once: true }); + return this; +}; +Sink.prototype.prependListener = function (event, listener) { + this.listeners.unshift({ event, listener, once: false }); + return this; +}; +Sink.prototype.removeListener = function (event, listener) { + this.listeners = this.listeners.filter((l) => l.event !== event || l.listener !== listener); + return this; +}; +Sink.prototype.listenerCount = function (event) { + return this.listeners.filter((l) => l.event === event).length; +}; +Sink.prototype.emit = function (event, ...args) { + const matching = this.listeners.filter((l) => l.event === event); + this.listeners = this.listeners.filter((l) => l.event !== event || !l.once); + for (const l of matching) l.listener(...args); + return matching.length > 0; +}; +Sink.prototype.write = function (chunk) { + this.parts.push(String(chunk)); + return true; +}; +Sink.prototype.end = function () { + console.log(this.label, "ended with", this.parts.join("|"), "listening to", this.listeners.map((l) => l.event).join(",")); + this.emit("finish"); + console.log(this.label, "after finish listening to", this.listeners.map((l) => l.event).join(",")); +}; + +/** @returns {any} */ +export function sink(label) { + return new Sink(label); +} + +/** @returns {Promise} */ +export function jsCollect(stream) { + return new Promise((resolve) => { + const parts = []; + stream.on("data", (chunk) => parts.push(String(chunk))); + stream.on("end", () => resolve(parts.join(""))); + }); +} + +export function same(a, b) { + return a === b; +} diff --git a/tests/corpus/6481-node-stream-dyn-writable/main.ts b/tests/corpus/6481-node-stream-dyn-writable/main.ts new file mode 100644 index 000000000..9ac429b8f --- /dev/null +++ b/tests/corpus/6481-node-stream-dyn-writable/main.ts @@ -0,0 +1,67 @@ +// NodeJS.WritableStream slots hold a native Writable, a plain JavaScript +// sink and process.stdout: TypeScript writes and ends them, and a copy +// helper typed over both interfaces pipes native and JavaScript sources +// into native and JavaScript destinations. +import { Readable, Writable } from "node:stream"; +import { plainSink, plainSource } from "./sources.js"; + +function emit(label: string, out: NodeJS.WritableStream): void { + const ok = out.write(`${label} one\n`); + out.write(`${label} two\n`); + console.log(label, "write answered", typeof ok); +} + +function finish(out: NodeJS.WritableStream): void { + out.end(); +} + +function copy(src: NodeJS.ReadableStream, dst: NodeJS.WritableStream): NodeJS.WritableStream { + return src.pipe(dst); +} + +function collector(parts: string[]): Writable { + return new Writable({ + write(chunk: Buffer, _encoding, done) { + parts.push(chunk.toString()); + done(); + }, + }); +} + +async function main(): Promise { + const parts: string[] = []; + const native = collector(parts); + const finished = new Promise((resolve) => native.on("finish", () => resolve())); + emit("native", native); + finish(native); + await finished; + console.log("native got", JSON.stringify(parts.join(""))); + + const sink = plainSink("plain"); + emit("plain", sink); + finish(sink); + + emit("stdout", process.stdout); + + const target = plainSink("copy native into plain"); + console.log("copy returns target", copy(Readable.from(["c1", "c2"]), target) === target); + await new Promise((resolve) => setTimeout(resolve, 10)); + copy(plainSource(["c3", "c4"]), plainSink("copy plain into plain")); + await new Promise((resolve) => setTimeout(resolve, 10)); + const copied: string[] = []; + const native2 = collector(copied); + await new Promise((resolve) => { + native2.on("finish", () => resolve()); + copy(plainSource(["c5", "c6"]), native2); + }); + console.log("copy plain into native", copied.join("")); + const copiedNative: string[] = []; + const native3 = collector(copiedNative); + await new Promise((resolve) => { + native3.on("finish", () => resolve()); + copy(Readable.from(["c7", "c8"]), native3); + }); + console.log("copy native into native", copiedNative.join("")); +} + +void main(); diff --git a/tests/corpus/6481-node-stream-dyn-writable/sources.js b/tests/corpus/6481-node-stream-dyn-writable/sources.js new file mode 100644 index 000000000..9c8491455 --- /dev/null +++ b/tests/corpus/6481-node-stream-dyn-writable/sources.js @@ -0,0 +1,49 @@ +// A plain JavaScript source implementing on/pipe and a plain sink with +// write/end. + +/** @returns {any} */ +export function plainSource(chunks) { + const listeners = {}; + const source = { + on(event, listener) { + (listeners[event] ??= []).push(listener); + return source; + }, + emit(event, value) { + for (const listener of listeners[event] ?? []) listener(value); + }, + pipe(dest) { + source.on("data", (chunk) => dest.write(chunk)); + source.on("end", () => dest.end()); + return dest; + }, + }; + setImmediate(() => { + for (const chunk of chunks) source.emit("data", chunk); + source.emit("end"); + }); + return source; +} + +/** @returns {any} */ +export function plainSink(label) { + const parts = []; + return { + write(chunk) { + parts.push(String(chunk)); + return true; + }, + end() { + console.log(label, "sink end", parts.join("")); + }, + on() { + return this; + }, + once() { + return this; + }, + emit() { + return false; + }, + }; +} diff --git a/tests/corpus/6482-node-stream-dyn-helpers/main.ts b/tests/corpus/6482-node-stream-dyn-helpers/main.ts new file mode 100644 index 000000000..db1c686a9 --- /dev/null +++ b/tests/corpus/6482-node-stream-dyn-helpers/main.ts @@ -0,0 +1,98 @@ +// The helper shapes real code writes around NodeJS.ReadableStream, fed by +// native and JavaScript streams: an async function returning one of +// several producers, a JavaScript consumer of its result, a part piped +// into a PassThrough with { end: false } and unpiped, pause/resume/ +// setEncoding/readable, an optional destroy through an intersection type, +// for await and pipeline over native values in the slot. +import * as fs from "node:fs"; +import * as os from "node:os"; +import * as path from "node:path"; +import { PassThrough, Readable, Writable } from "node:stream"; +import { pipeline } from "node:stream/promises"; +import * as zlib from "node:zlib"; +import { openPart, part, streamToText } from "./sources.js"; + +async function open(kind: string, file: string): Promise { + if (kind === "file") return fs.createReadStream(file); + return openPart(["x", "y"]); +} + +async function pipePart(item: NodeJS.ReadableStream, out: PassThrough): Promise { + item.pipe(out, { end: false }); + await new Promise((resolve) => item.on("end", () => resolve())); + item.unpipe(out); +} + +function pass(stream: NodeJS.ReadableStream): NodeJS.ReadableStream { + return stream; +} + +function stop(stream: NodeJS.ReadableStream): void { + (stream as NodeJS.ReadableStream & { destroy?: () => void }).destroy?.(); +} + +async function iterate(label: string, stream: NodeJS.ReadableStream): Promise { + let text = ""; + for await (const chunk of stream) text += chunk.toString(); + console.log(label, "iterated", text); +} + +async function main(): Promise { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "scriptc-stream-dyn-")); + const file = path.join(dir, "a.txt"); + fs.writeFileSync(file, "file text"); + console.log("file", await streamToText(await open("file", file))); + console.log("part", await streamToText(await open("part", file))); + + const out = new PassThrough(); + const parts: string[] = []; + out.on("data", (chunk: Buffer) => parts.push(chunk.toString())); + await pipePart(await open("part", file), out); + await pipePart(await open("file", file), out); + const ended = new Promise((resolve) => out.on("end", () => resolve())); + out.end(); + await ended; + console.log("out", parts.join("|")); + + const js = pass(part(["j1"])); + js.pause(); + js.resume(); + js.setEncoding("utf8"); + console.log("js readable", js.readable); + await new Promise((resolve) => js.on("end", () => resolve())); + const native = pass(Readable.from(["n1"])); + native.setEncoding("utf8"); + console.log("native readable", native.readable); + const chunks: string[] = []; + await new Promise((resolve) => { + native.on("data", (chunk: Buffer | string) => chunks.push(chunk.toString())); + native.on("end", () => resolve()); + }); + console.log("native chunks", chunks.join("")); + + const closing = pass(Readable.from(["c1"])); + closing.on("close", () => console.log("native closed")); + stop(pass(part(["d1"]))); + stop(closing); + + await iterate("native", pass(Readable.from(["i1", "i2"]))); + await iterate("file", pass(fs.createReadStream(file))); + + const packed = path.join(dir, "a.gz"); + fs.writeFileSync(packed, zlib.gzipSync(Buffer.from("packed text"))); + const unpacked: string[] = []; + await pipeline( + pass(fs.createReadStream(packed)), + zlib.createGunzip(), + new Writable({ + write(chunk: Buffer, _encoding, done) { + unpacked.push(chunk.toString()); + done(); + }, + }), + ); + console.log("pipeline", unpacked.join("")); + fs.rmSync(dir, { recursive: true }); +} + +void main(); diff --git a/tests/corpus/6482-node-stream-dyn-helpers/sources.js b/tests/corpus/6482-node-stream-dyn-helpers/sources.js new file mode 100644 index 000000000..c395b4379 --- /dev/null +++ b/tests/corpus/6482-node-stream-dyn-helpers/sources.js @@ -0,0 +1,71 @@ +// A constructor-function stream part with its own listener table, whose +// pipe() honors { end: false } and whose unpipe() detaches it, plus a +// JavaScript consumer that reads a stream to text. + +function Part(chunks) { + this.chunks = chunks; + this.listeners = {}; + this.dest = null; + this.ending = true; + this.readable = true; + setImmediate(() => { + for (const chunk of this.chunks) { + if (this.dest) this.dest.write(chunk); + else this.emit("data", chunk); + } + if (this.dest && this.ending) this.dest.end(); + this.emit("end"); + }); +} +Part.prototype.on = function (event, listener) { + (this.listeners[event] = this.listeners[event] || []).push(listener); + return this; +}; +Part.prototype.emit = function (event, value) { + for (const listener of this.listeners[event] || []) listener(value); + return true; +}; +Part.prototype.pipe = function (dest, options) { + this.dest = dest; + this.ending = !options || options.end !== false; + return dest; +}; +Part.prototype.unpipe = function () { + console.log("part unpipe"); + this.dest = null; + return this; +}; +Part.prototype.pause = function () { + console.log("part pause"); + return this; +}; +Part.prototype.resume = function () { + console.log("part resume"); + return this; +}; +Part.prototype.setEncoding = function (encoding) { + console.log("part setEncoding", encoding); + return this; +}; +Part.prototype.destroy = function () { + console.log("part destroy"); +}; + +/** @returns {Promise} */ +export async function openPart(chunks) { + return new Part(chunks); +} + +/** @returns {any} */ +export function part(chunks) { + return new Part(chunks); +} + +/** @returns {any} */ +export function streamToText(stream) { + return new Promise((resolve) => { + const parts = []; + stream.on("data", (chunk) => parts.push(String(chunk))); + stream.on("end", () => resolve(parts.join(""))); + }); +} From 8e87f6f9ef5a5b90444f7cd624902bf87f163238 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Tue, 20 Oct 2026 08:49:48 -0500 Subject: [PATCH 086/122] Resolve const aliases when deciding a child stream's setEncoding state An untyped child-stream 'data' listener takes strings when a setEncoding call on the same stream precedes it. A const alias of an access path (`const out = child.stdout`) now resolves to that path, so a setEncoding through one alias and a listener through another, or through the stream itself, agree. A setEncoding on another receiver that can hold the same stream (a let copy, a parameter, a call result) leaves the state unclear and reports the existing SC1090. Receivers that name different members of one root, or two const roots each initialized by a call or new, are treated as different streams. --- .../builtins/child-process-methods.ts | 70 +++++++++++++++---- tests/corpus/6472-child-data-unknown-chunk.ts | 22 ++++++ tests/diagnostics/child-process.ts | 5 ++ .../__snapshots__/child-process.ts.txt | 9 ++- 4 files changed, 93 insertions(+), 13 deletions(-) diff --git a/packages/compiler/src/frontend/lowering/builtins/child-process-methods.ts b/packages/compiler/src/frontend/lowering/builtins/child-process-methods.ts index a2b0e1f57..aae51a8be 100644 --- a/packages/compiler/src/frontend/lowering/builtins/child-process-methods.ts +++ b/packages/compiler/src/frontend/lowering/builtins/child-process-methods.ts @@ -541,25 +541,64 @@ export function lowerChildWriterMethodCall( } /** A receiver spelled as a plain access path (`child.stdout`, `out`): its - * root symbol and member names, or null for other forms. */ + * root symbol and member names, or null for other forms. A `const` alias + * of an access path (`const out = child.stdout`) resolves to that path, + * so every spelling of one stream gets the same key. */ function accessPathOf( lowerer: Lowerer, expr: ts.Expression, + depth = 0, ): { root: ts.Symbol; path: string } | null { + if (depth > 16) return null; if (ts.isParenthesizedExpression(expr) || ts.isNonNullExpression(expr)) { - return accessPathOf(lowerer, expr.expression); + return accessPathOf(lowerer, expr.expression, depth + 1); } if (ts.isIdentifier(expr)) { const root = lowerer.checker.getSymbolAtLocation(expr); - return root ? { root, path: "" } : null; + if (!root) return null; + const init = constInitializerOf(root); + const aliased = init === null ? null : accessPathOf(lowerer, init, depth + 1); + return aliased ?? { root, path: "" }; } if (ts.isPropertyAccessExpression(expr) && !expr.questionDotToken) { - const base = accessPathOf(lowerer, expr.expression); + const base = accessPathOf(lowerer, expr.expression, depth + 1); return base === null ? null : { root: base.root, path: `${base.path}.${expr.name.text}` }; } return null; } +/** The initializer of a symbol declared once as a `const` variable, else null. */ +function constInitializerOf(symbol: ts.Symbol): ts.Expression | null { + const decls = symbol.declarations; + if (decls.length !== 1) return null; + const decl = decls[0]!; + if ( + !ts.isVariableDeclaration(decl) || + !ts.isIdentifier(decl.name) || + decl.initializer === undefined || + (ts.getCombinedNodeFlags(decl) & ts.NodeFlags.Const) === 0 + ) + return null; + return decl.initializer; +} + +/** Whether two resolved access paths name different objects: the same root + * with different members, or two `const` roots each initialized by a call + * or `new` (such as two `spawn()` results). */ +function distinctPaths( + a: { root: ts.Symbol; path: string }, + b: { root: ts.Symbol; path: string }, +): boolean { + if (a.root === b.root) return a.path !== b.path; + const fresh = (root: ts.Symbol): boolean => { + let init = constInitializerOf(root); + while (init !== null && (ts.isParenthesizedExpression(init) || ts.isAwaitExpression(init))) + init = init.expression; + return init !== null && (ts.isCallExpression(init) || ts.isNewExpression(init)); + }; + return fresh(a.root) && fresh(b.root); +} + /** Whether an unknown-typed 'data' listener registered by `call` receives * strings: a `setEncoding` statement on the same receiver path precedes * the registration in its own block or an enclosing block of the same @@ -594,10 +633,7 @@ function childStreamEncodedAt( } node = parent; } - const samePath = ( - a: { root: ts.Symbol; path: string } | null, - b: { root: ts.Symbol; path: string }, - ): boolean => a !== null && a.root === b.root && a.path === b.path; + const receiverType = lowerer.checker.getNonNullableType(lowerer.typeOf(receiver)); let before = false; let elsewhere = false; const visit = (node: ts.Node): void => { @@ -605,11 +641,21 @@ function childStreamEncodedAt( if ( ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && - node.expression.name.text === "setEncoding" && - samePath(accessPathOf(lowerer, node.expression.expression), key) + node.expression.name.text === "setEncoding" ) { - if (ts.isExpressionStatement(node.parent) && preceding.has(node.parent)) before = true; - else elsewhere = true; + const target = node.expression.expression; + const path = accessPathOf(lowerer, target); + if (path !== null && path.root === key.root && path.path === key.path) { + if (ts.isExpressionStatement(node.parent) && preceding.has(node.parent)) before = true; + else elsewhere = true; + } else if ( + (path === null || !distinctPaths(path, key)) && + lowerer.checker.isTypeAssignableTo(receiverType, lowerer.typeOf(target)) + ) { + // Another spelling that can hold this stream (a parameter, a `let` + // copy, a call result): its setEncoding may be this stream's. + elsewhere = true; + } } ts.forEachChild(node, visit); }; diff --git a/tests/corpus/6472-child-data-unknown-chunk.ts b/tests/corpus/6472-child-data-unknown-chunk.ts index 4a0e97d34..8308aa8cf 100644 --- a/tests/corpus/6472-child-data-unknown-chunk.ts +++ b/tests/corpus/6472-child-data-unknown-chunk.ts @@ -60,6 +60,28 @@ function runRaw(script: string): Promise { }); } +// setEncoding through one const alias of the stream and the listener +// through another: both name child.stdout, so chunks are strings. +function runAliased(script: string): Promise { + return new Promise((resolve) => { + const child = spawn("/bin/sh", ["-c", script], { stdio: "pipe" }); + const encoded = child.stdout!; + const listened = child.stdout!; + let stdout = ""; + const kinds: string[] = []; + encoded.setEncoding("utf8"); + listened.on("data", (c: unknown) => { + kinds.push(typeof c); + stdout += String(c); + }); + child.on("close", (code) => { + report(code, kinds, stdout, ""); + resolve(); + }); + }); +} + runEncoded("printf 'h\\303\\251llo'; echo warn >&2") .then(() => runRaw("printf raw; echo oops >&2; exit 2")) + .then(() => runAliased("printf alias")) .then(() => console.log("done")); diff --git a/tests/diagnostics/child-process.ts b/tests/diagnostics/child-process.ts index 6c5683efc..02b4349e8 100644 --- a/tests/diagnostics/child-process.ts +++ b/tests/diagnostics/child-process.ts @@ -47,3 +47,8 @@ fork(staticWorker, [], { stdio: ["ignore", "ignore", "ignore", "ignore"] }); const pipedOut = piped.stdout!; pipedOut.on("data", (chunk: unknown) => console.log(String(chunk))); pipedOut.setEncoding("utf8"); +// A setEncoding through a copy that can hold the same stream (a `let` +// binding here) leaves the state unclear as well. +let pipedErr = piped.stderr!; +pipedErr.setEncoding("utf8"); +piped.stderr!.on("data", (chunk: unknown) => console.log(String(chunk))); diff --git a/tests/harness/__snapshots__/child-process.ts.txt b/tests/harness/__snapshots__/child-process.ts.txt index 6fee8a2c2..f5f9f8f86 100644 --- a/tests/harness/__snapshots__/child-process.ts.txt +++ b/tests/harness/__snapshots__/child-process.ts.txt @@ -98,4 +98,11 @@ child-process.ts:48:21 - error SC1090: untyped child-stream 'data' listeners who 47 | const pipedOut = piped.stdout!; 48 | pipedOut.on("data", (chunk: unknown) => console.log(String(chunk))); | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - 49 | pipedOut.setEncoding("utf8"); \ No newline at end of file + 49 | pipedOut.setEncoding("utf8"); + +child-process.ts:54:26 - error SC1090: untyped child-stream 'data' listeners whose setEncoding state is unclear at their registration sites (declare the chunk as 'Buffer' or 'string') are not supported yet + + 53 | pipedErr.setEncoding("utf8"); + 54 | piped.stderr!.on("data", (chunk: unknown) => console.log(String(chunk))); + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 55 | \ No newline at end of file From 5e0ad5bbaa005dd3431394b6e33f92289d146a10 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Tue, 20 Oct 2026 08:51:47 -0500 Subject: [PATCH 087/122] Compare checker panics without their text in the checker identity check A statement whose lowering meets a checker panic keeps the panic's text in its SC0004 fence. tsgo and tsc-ts reach the same panic for `s.match(re) || []` (6187-js-match-or-empty-array.js) and describe it in their own words, so the check now replaces the text after `panic:` before it compares the IR and the diagnostics. --- scripts/checker-identity.mjs | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/scripts/checker-identity.mjs b/scripts/checker-identity.mjs index 35d01cb93..a710d5946 100644 --- a/scripts/checker-identity.mjs +++ b/scripts/checker-identity.mjs @@ -1,6 +1,7 @@ #!/usr/bin/env node // Compiles every corpus program to LLVM IR with each TypeScript 7 API server (tsc-ts, the default, and tsgo with -// SCRIPTC_CHECKER=tsgo) and fails unless both give the same IR and the same diagnostics. +// SCRIPTC_CHECKER=tsgo) and fails unless both give the same IR and the same diagnostics. The text of a checker panic +// is the checker's own (Go's runtime error for tsgo, tsc-ts's for tsc-ts), so it is compared as `panic: …`. // // Usage: node scripts/checker-identity.mjs [--jobs ] [--optimization release|dev] [--filter ] // @@ -73,11 +74,11 @@ async function main() { const [a, b] = checkers.map((checker) => results.get(checker).get(file)); const name = file.slice(corpusDir.length + 1); if (a === undefined || b === undefined) differences.push(`${name}: not compiled`); - else if (a.ok !== b.ok || a.diagnostics !== b.diagnostics) + else if (a.ok !== b.ok || withoutPanicText(a.diagnostics) !== withoutPanicText(b.diagnostics)) differences.push( `${name}: tsgo ${a.ok ? "compiles" : a.diagnostics}; tsc-ts ${b.ok ? "compiles" : b.diagnostics}`, ); - else if (a.ok && !readFileSync(a.llvm).equals(readFileSync(b.llvm))) + else if (a.ok && irWithoutPanicText(a.llvm) !== irWithoutPanicText(b.llvm)) differences.push(`${name}: LLVM IR differs`); } if (differences.length > 0) { @@ -96,6 +97,31 @@ async function main() { } } +/** A diagnostic list with each SC0004 checker panic's text replaced by `…`. */ +function withoutPanicText(diagnostics) { + return diagnostics?.replace(/(SC0004: [^;]*?: panic: )[^;]*/g, "$1…"); +} + +/** A module's LLVM IR with each SC0004 checker panic's text replaced by `…`. A fence statement keeps the + * diagnostic's message in a string constant named for a hash of its contents, and passes its length. */ +function irWithoutPanicText(llvm) { + let ir = readFileSync(llvm, "utf8"); + const constant = + /^(@sc_cs_\d+) = internal constant \[\d+ x i8\] c"(the TypeScript checker crashed answering a query here [^"]*?: panic: )[^"]*?( \[SC0004 at [^"]*)"$/gm; + const names = []; + ir = ir.replace(constant, (_, name, head, tail) => { + names.push(name); + return `@checker_panic_${names.length} = internal constant c"${head}…${tail}"`; + }); + names.forEach((name, index) => { + ir = ir.replace( + new RegExp(`${name}\\b(, i64 \\d+)?`, "g"), + (_, length) => `@checker_panic_${index + 1}${length === undefined ? "" : ", i64 …"}`, + ); + }); + return ir; +} + async function worker([outDir, optimization, ...files]) { const { compile } = await import( pathToFileURL(join(repoRoot, "packages/compiler/dist/index.js")).href From 02fc8ab13c512df7224be4a1ce6f73f9f604dab2 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Tue, 20 Oct 2026 08:54:18 -0500 Subject: [PATCH 088/122] Keep one process-stream Writable per descriptor and never end it from a pipe Under the general stream representation, every conversion of process.stdout or process.stderr into a Writable slot made a new stream, so two NodeJS.WritableStream slots holding process.stdout compared unequal and kept separate listeners and writable state. A pipe into such a slot also ended the converted Writable at the source's end, so later writes through the slot failed; Node never ends process.stdout or process.stderr from a pipe. scr_stream_stdio_writable now keeps one Writable per descriptor and thread, released at exit. Under the general representation, a pipe into a Writable slot lowers to readable.pipeGeneral (scr_stream_pipe_general, last in the stream unit), which keeps end off when the destination is one of those Writables; programs without the general representation lower pipes as before. The fallback stream.Writable's on() and once() overloads for drain, finish, close and error return the stream again, so listener chains such as out.on("finish", listener).end(data) type-check. Adds corpus 6468. --- .../ambient/scriptc-node-fallback.d.ts | 8 +-- .../compiler/src/backend/llvm/lib-shared.ts | 2 + .../src/frontend/lowering/lower-stream.ts | 14 +++++- packages/compiler/src/ir/builtin-effects.ts | 1 + .../compiler/src/ir/builtin-signatures.ts | 5 +- packages/compiler/src/ir/validate.ts | 4 +- .../6468-node-stdio-writable-slot.ts.json | 6 +++ packages/runtime/src/scr_stream.c | 36 +++++++++++--- packages/runtime/src/scr_stream.h | 3 ++ tests/corpus/6468-node-stdio-writable-slot.ts | 49 +++++++++++++++++++ .../6468-node-stdio-writable-slot.ts.json | 1 + 11 files changed, 114 insertions(+), 15 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6468-node-stdio-writable-slot.ts.json create mode 100644 tests/corpus/6468-node-stdio-writable-slot.ts create mode 100644 tests/harness/corpus-costs/6468-node-stdio-writable-slot.ts.json diff --git a/packages/compiler/ambient/scriptc-node-fallback.d.ts b/packages/compiler/ambient/scriptc-node-fallback.d.ts index 3885e0fef..4e39d9e42 100644 --- a/packages/compiler/ambient/scriptc-node-fallback.d.ts +++ b/packages/compiler/ambient/scriptc-node-fallback.d.ts @@ -6736,12 +6736,12 @@ declare module "stream" { readonly closed: boolean; readonly destroyed: boolean; readonly errored: Error | null; - on(event: "drain" | "finish" | "close", listener: () => void): void; - on(event: "error", listener: (err: Error) => void): void; + on(event: "drain" | "finish" | "close", listener: () => void): this; + on(event: "error", listener: (err: Error) => void): this; on(event: "pipe" | "unpipe", listener: (src: Readable) => void): this; on(event: string, listener: (...args: any[]) => void): this; - once(event: "drain" | "finish" | "close", listener: () => void): void; - once(event: "error", listener: (err: Error) => void): void; + once(event: "drain" | "finish" | "close", listener: () => void): this; + once(event: "error", listener: (err: Error) => void): this; once(event: "pipe" | "unpipe", listener: (src: Readable) => void): this; once(event: string, listener: (...args: any[]) => void): this; addListener(event: "drain" | "finish" | "close", listener: () => void): this; diff --git a/packages/compiler/src/backend/llvm/lib-shared.ts b/packages/compiler/src/backend/llvm/lib-shared.ts index 4602a2bdd..d38379bf0 100644 --- a/packages/compiler/src/backend/llvm/lib-shared.ts +++ b/packages/compiler/src/backend/llvm/lib-shared.ts @@ -1106,6 +1106,7 @@ export const LIB_FN_SYMS: Record = { "readable.opFind": "scr_stream_op_find", "readable.opReduce": "scr_stream_op_reduce", "readable.pipe": "scr_stream_pipe", + "readable.pipeGeneral": "scr_stream_pipe_general", "writable.cork": "scr_stream_cork", "writable.uncork": "scr_stream_uncork", "stream.destroyErr": "scr_stream_destroy", @@ -1588,6 +1589,7 @@ export const USES_TIMERS_LIB_FNS = new Set([ "readable.opReduce", "readable.resume", "readable.pipe", + "readable.pipeGeneral", "readable.unpipe", "writable.write", "writable.writeStr", diff --git a/packages/compiler/src/frontend/lowering/lower-stream.ts b/packages/compiler/src/frontend/lowering/lower-stream.ts index 51c86fe39..88ba1f5b8 100644 --- a/packages/compiler/src/frontend/lowering/lower-stream.ts +++ b/packages/compiler/src/frontend/lowering/lower-stream.ts @@ -3371,6 +3371,16 @@ function pipeIntoStdio( }; } +/** The pipe() libCall for a destination of type `dst`. Under the general + * stream representation a Writable slot can hold the converted + * process.stdout / process.stderr, which a pipe never ends (as in Node), so + * the pipe takes readable.pipeGeneral, which checks for them. */ +function generalPipe(lowerer: Lowerer, dst: IrType): "readable.pipe" | "readable.pipeGeneral" { + return lowerer.generalNodeStreams && dst.kind === "object" && dst.className === "%Writable" + ? "readable.pipeGeneral" + : "readable.pipe"; +} + /** `readable.pipe(untyped)`: a checked-dynamic destination unwraps a boxed * native stream at run time (stream.unboxDyn; other values throw the * not-supported Error), and pipe() answers the destination value itself. */ @@ -3394,7 +3404,7 @@ function pipeIntoDyn( kind: "exprStmt", expr: { kind: "libCall", - fn: "readable.pipe", + fn: lowerer.generalNodeStreams ? "readable.pipeGeneral" : "readable.pipe", args: [ { kind: "varRef", localId: source.id, type: receiverValue.type, loc }, { @@ -3691,7 +3701,7 @@ export function lowerStreamMethodCall( const receiver = lowerer.lowerExpr(access.expression); return { kind: "libCall", - fn: "readable.pipe", + fn: generalPipe(lowerer, dst.type), args: [receiver, dst, end], type: dst.type, loc, diff --git a/packages/compiler/src/ir/builtin-effects.ts b/packages/compiler/src/ir/builtin-effects.ts index 1c390bbe4..11a292a2e 100644 --- a/packages/compiler/src/ir/builtin-effects.ts +++ b/packages/compiler/src/ir/builtin-effects.ts @@ -326,6 +326,7 @@ export const MAY_THROW_LIB_FNS: ReadonlySet = new Set([ "stream.onDyn", "readable.resume", "readable.pipe", + "readable.pipeGeneral", "readable.unpipe", "writable.write", "writable.writeStr", diff --git a/packages/compiler/src/ir/builtin-signatures.ts b/packages/compiler/src/ir/builtin-signatures.ts index 597076cb1..82ecf461d 100644 --- a/packages/compiler/src/ir/builtin-signatures.ts +++ b/packages/compiler/src/ir/builtin-signatures.ts @@ -3612,7 +3612,9 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ * returns the below-hwm answer. readable.read: (recv, size — -1 for * absent) → Buffer|null union. pause/resume return recv +1 (`this`); * isPaused answers the flag. readable.pipe: (recv, dst, end) → dst +1, - * fires 'pipe' on dst; readable.unpipe (recv[, dst]) → recv +1. + * fires 'pipe' on dst; readable.pipeGeneral: the same, with `end` off + * when dst is the converted process.stdout / process.stderr; + * readable.unpipe (recv[, dst]) → recv +1. * writable.write/writeStr: (recv, chunk[, cb]) → below-hwm bool (cb * MOVES when present, called after the user write completes). * writable.end: (recv, flags[, chunk][, cb]) → recv +1. cork/uncork are @@ -3810,6 +3812,7 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ "readable.resume": { argTypes: [null], result: VOID }, "readable.isPaused": { argTypes: [null], result: BOOL }, "readable.pipe": { argTypes: [null, null, BOOL], result: VOID }, + "readable.pipeGeneral": { argTypes: [null, null, BOOL], result: VOID }, "readable.unpipe": { argTypes: [null], result: VOID }, "readable.flowing": { argTypes: [null], result: VOID }, "writable.write": { argTypes: [null, BYTES_U8], result: BOOL }, diff --git a/packages/compiler/src/ir/validate.ts b/packages/compiler/src/ir/validate.ts index 63078ae4a..742787d98 100644 --- a/packages/compiler/src/ir/validate.ts +++ b/packages/compiler/src/ir/validate.ts @@ -5729,9 +5729,9 @@ function validateFunction( } return true; } - if (e.fn === "readable.pipe") { + if (e.fn === "readable.pipe" || e.fn === "readable.pipeGeneral") { if (!isStreamObject(e.args[1]?.type) || !typeEquals(e.type, e.args[1]!.type)) { - err(`libCall readable.pipe must return its destination's type`, e.loc); + err(`libCall ${e.fn} must return its destination's type`, e.loc); } return true; } diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6468-node-stdio-writable-slot.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6468-node-stdio-writable-slot.ts.json new file mode 100644 index 000000000..baf428dee --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6468-node-stdio-writable-slot.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6468-node-stdio-writable-slot.ts" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_stream.c b/packages/runtime/src/scr_stream.c index 1c5e339a0..7c7b2279c 100644 --- a/packages/runtime/src/scr_stream.c +++ b/packages/runtime/src/scr_stream.c @@ -6665,18 +6665,42 @@ ScrStream *scr_stream_child_stdio(void *child, const char *key, size_t key_len) * stream.stdioWritable libCall): a Writable whose _write hands each chunk * to the descriptor through process.stdout.write's synchronous path and * completes at once, so ordering against console output and direct writes - * is unchanged. The closure keeps the fd as given (1 or 2). */ + * is unchanged. The closure keeps the fd as given (1 or 2). Each thread + * keeps one per descriptor, so every conversion of process.stdout answers + * the same stream, as Node's process.stdout is one object: listeners, + * writable state and identity are shared. The cache is released at exit. */ +static SCR_TL ScrStream *scr_stream_stdio_writables[2]; static void scr_stream_stdio_write(ScrClosure *cb, ScrStream *s, ScrBytes *chunk) { int fd = (int)scr_box_get_f64(cb->caps[0]); (void)scr_stdio_write(fd == 2 ? 2 : 1, chunk->data, chunk->len * scr_bytes_elem_size(chunk->elem)); scr_stream_write_done(s, NULL); } +static void scr_stream_stdio_writables_free(void) { + for (int i = 0; i < 2; i++) { + ScrStream *s = scr_stream_stdio_writables[i]; + scr_stream_stdio_writables[i] = NULL; + if (s) scr_stream_release(s); + } +} ScrStream *scr_stream_stdio_writable(double fd) { - ScrClosure *write = scr_closure_new((void *)scr_stream_stdio_write, 1); - write->caps[0] = scr_box_new(SCR_BOX_F64); - scr_box_set_f64(write->caps[0], fd); - return scr_stream_new_writable(-1, true, true, write, scr_stream_stdio_write, - NULL, NULL, NULL, NULL); + int i = fd == 2 ? 1 : 0; + if (scr_stream_stdio_writables[i] == NULL) { + if (scr_stream_stdio_writables[1 - i] == NULL) scr_atexit(scr_stream_stdio_writables_free); + ScrClosure *write = scr_closure_new((void *)scr_stream_stdio_write, 1); + write->caps[0] = scr_box_new(SCR_BOX_F64); + scr_box_set_f64(write->caps[0], i ? 2 : 1); + scr_stream_stdio_writables[i] = scr_stream_new_writable(-1, true, true, write, + scr_stream_stdio_write, NULL, NULL, NULL, NULL); + } + return scr_stream_retain(scr_stream_stdio_writables[i]); +} + +/* readable.pipe() into a Writable slot under the general representation + * (the readable.pipeGeneral libCall): Node never ends process.stdout or + * process.stderr from a pipe, so their converted Writable keeps `end` off. */ +ScrStream *scr_stream_pipe_general(ScrStream *src, ScrStream *dst, bool end) { + if (dst == scr_stream_stdio_writables[0] || dst == scr_stream_stdio_writables[1]) end = false; + return scr_stream_pipe(src, dst, end); } /* A pipeline() stage that arrives as a checked-dynamic value (the diff --git a/packages/runtime/src/scr_stream.h b/packages/runtime/src/scr_stream.h index 63ed0c28a..69da3dc0e 100644 --- a/packages/runtime/src/scr_stream.h +++ b/packages/runtime/src/scr_stream.h @@ -130,6 +130,9 @@ ScrStream *scr_stream_new_writable_dyn(struct ScrDyn *opts); * Writable (fd 1 or 2). */ ScrStream *scr_stream_child_stdio(void *child, const char *key, size_t key_len); ScrStream *scr_stream_stdio_writable(double fd); +/* readable.pipe() under the general representation: a converted + * process.stdout / process.stderr destination is never ended (dst +1). */ +ScrStream *scr_stream_pipe_general(ScrStream *src, ScrStream *dst, bool end); /* A boxed native stream out of a checked-dynamic pipeline stage (+1), or * NULL with `message` thrown as the not-supported Error. */ ScrStream *scr_stream_unbox_dyn(const struct ScrDyn *value, const ScrStr *message); diff --git a/tests/corpus/6468-node-stdio-writable-slot.ts b/tests/corpus/6468-node-stdio-writable-slot.ts new file mode 100644 index 000000000..a326344d1 --- /dev/null +++ b/tests/corpus/6468-node-stdio-writable-slot.ts @@ -0,0 +1,49 @@ +// process.stdout and process.stderr in NodeJS.WritableStream slots of a +// program that also passes other streams there: every slot holds the same +// stream object, a pipe through such a slot leaves the process stream +// open, and Writable's listener methods return the stream for chaining. +import { Readable, Writable } from "node:stream"; + +class Sink extends Writable { + _write(_chunk: Buffer, _encoding: string, callback: (error?: Error | null) => void): void { + callback(); + } +} + +function same(a: NodeJS.WritableStream, b: NodeJS.WritableStream): boolean { + return a === b; +} + +function pipeInto(dst: NodeJS.WritableStream, parts: string[]): Promise { + return new Promise((resolve) => { + const src = Readable.from(parts); + src.on("end", () => resolve()); + src.pipe(dst); + }); +} + +function write(dst: NodeJS.WritableStream, text: string): boolean { + return dst.write(text); +} + +async function main(): Promise { + console.log("stdout same", same(process.stdout, process.stdout)); + console.log("stderr same", same(process.stderr, process.stderr)); + console.log("stdout and stderr", same(process.stdout, process.stderr)); + console.log("sink and stdout", same(new Sink(), process.stdout)); + + await pipeInto(process.stdout, ["piped ", "through ", "a slot\n"]); + // The pipe did not end process.stdout: writes through a slot still work. + console.log("write after pipe", write(process.stdout, "still open\n")); + await pipeInto(process.stderr, ["piped to stderr\n"]); + write(process.stderr, "stderr still open\n"); + + // Writable's listener methods return the stream. + const sink = new Sink(); + await new Promise((resolve) => { + sink.on("finish", () => resolve()).end("done"); + }); + console.log("sink finished", sink.writableFinished); +} + +void main(); diff --git a/tests/harness/corpus-costs/6468-node-stdio-writable-slot.ts.json b/tests/harness/corpus-costs/6468-node-stdio-writable-slot.ts.json new file mode 100644 index 000000000..3fafdfc4b --- /dev/null +++ b/tests/harness/corpus-costs/6468-node-stdio-writable-slot.ts.json @@ -0,0 +1 @@ +{ "plain": 3000, "san": 3500 } From d850e14d505dd8c17ef584014564b5b112e89642 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Tue, 20 Oct 2026 08:58:50 -0500 Subject: [PATCH 089/122] Refuse dropped members through conditional and satisfies returns The refusal of callback returns that the inferred result type would cut down found the return by walking up through parentheses and await only, so a return inside a conditional's branch or a `satisfies` expression converted silently and lost the extended members. It now walks through every parent whose value can be the expression's own: parentheses, await, satisfies, non-null assertions, conditional branches, the operands of ??, || and &&, and the right operand of a comma. --- .../compiler/src/frontend/lowering/lowerer.ts | 31 +++++++++++++++++-- ...nferred-callback-drops-conditional.ts.json | 6 ++++ .../inferred-callback-drops-satisfies.ts.json | 6 ++++ .../inferred-callback-drops-conditional.ts | 12 +++++++ .../inferred-callback-drops-satisfies.ts | 13 ++++++++ ...inferred-callback-drops-conditional.ts.txt | 6 ++++ .../inferred-callback-drops-satisfies.ts.txt | 6 ++++ 7 files changed, 78 insertions(+), 2 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/inferred-callback-drops-conditional.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/inferred-callback-drops-satisfies.ts.json create mode 100644 tests/diagnostics/inferred-callback-drops-conditional.ts create mode 100644 tests/diagnostics/inferred-callback-drops-satisfies.ts create mode 100644 tests/harness/__snapshots__/inferred-callback-drops-conditional.ts.txt create mode 100644 tests/harness/__snapshots__/inferred-callback-drops-satisfies.ts.txt diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index 3edf33d22..4798f49c5 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -13007,8 +13007,7 @@ function refuseInferredCallbackDrops( t.kind === "record" || t.kind === "array" || t.kind === "promise"; if (!structured(actual) || !structured(expected)) return; let site: ts.Node = node; - while (ts.isParenthesizedExpression(site.parent) || ts.isAwaitExpression(site.parent)) - site = site.parent; + while (yieldsOperand(site)) site = site.parent!; let fn: ts.Node | undefined; if (ts.isReturnStatement(site.parent) && site.parent.expression === site) { fn = site.parent.parent; @@ -13031,6 +13030,34 @@ function refuseInferredCallbackDrops( ); } +/** True when the value of `node`'s parent can be `node`'s own value: + * parentheses, `await`, `satisfies`, a non-null assertion, a conditional's + * branch, either operand of `??`, `||` and `&&`, and the right operand of a + * comma. */ +function yieldsOperand(node: ts.Node): boolean { + const p = node.parent; + if (p === undefined) return false; + if ( + ts.isParenthesizedExpression(p) || + ts.isAwaitExpression(p) || + ts.isSatisfiesExpression(p) || + ts.isNonNullExpression(p) + ) + return true; + if (ts.isConditionalExpression(p)) return p.whenTrue === node || p.whenFalse === node; + if (ts.isBinaryExpression(p)) { + const op = p.operatorToken.kind; + if ( + op === ts.SyntaxKind.QuestionQuestionToken || + op === ts.SyntaxKind.BarBarToken || + op === ts.SyntaxKind.AmpersandAmpersandToken + ) + return true; + return op === ts.SyntaxKind.CommaToken && p.right === node; + } + return false; +} + /** Member names a structural conversion from `from` into `to` drops: * declared fields of a source record the target record lacks, through * array elements and promise payloads. */ diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/inferred-callback-drops-conditional.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/inferred-callback-drops-conditional.ts.json new file mode 100644 index 000000000..2014c8620 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/inferred-callback-drops-conditional.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/diagnostics/inferred-callback-drops-conditional.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/inferred-callback-drops-satisfies.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/inferred-callback-drops-satisfies.ts.json new file mode 100644 index 000000000..44c5e8bbc --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/inferred-callback-drops-satisfies.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/diagnostics/inferred-callback-drops-satisfies.ts" + ], + "diags": [] +} diff --git a/tests/diagnostics/inferred-callback-drops-conditional.ts b/tests/diagnostics/inferred-callback-drops-conditional.ts new file mode 100644 index 000000000..f124beed9 --- /dev/null +++ b/tests/diagnostics/inferred-callback-drops-conditional.ts @@ -0,0 +1,12 @@ +type Plain = { name: string; n: number }; +type Item = Plain & { extra?: boolean }; +const plain: Plain[] = [{ name: "a", n: 1 }]; +function run(fn: () => T): T { + return fn(); +} +// The checker unifies both branches into Plain[]; the extended objects keep +// `extra` in Node, so converting them into Plain would drop it silently. +const items: Item[] = run(() => + plain.length === 0 ? plain : plain.map((x) => ({ ...x, extra: true })), +); +console.log(items.length); diff --git a/tests/diagnostics/inferred-callback-drops-satisfies.ts b/tests/diagnostics/inferred-callback-drops-satisfies.ts new file mode 100644 index 000000000..ad58237b8 --- /dev/null +++ b/tests/diagnostics/inferred-callback-drops-satisfies.ts @@ -0,0 +1,13 @@ +type Plain = { name: string; n: number }; +type Item = Plain & { extra?: boolean }; +const plain: Plain[] = [{ name: "a", n: 1 }]; +function run(fn: () => T): T { + return fn(); +} +// `satisfies` checks the return against Plain[] without changing it; the +// extended objects keep `extra` in Node. +const items: Item[] = run(() => { + if (plain.length === 0) return plain; + return plain.map((x) => ({ ...x, extra: true })) satisfies Plain[]; +}); +console.log(items.length); diff --git a/tests/harness/__snapshots__/inferred-callback-drops-conditional.ts.txt b/tests/harness/__snapshots__/inferred-callback-drops-conditional.ts.txt new file mode 100644 index 000000000..a14f19317 --- /dev/null +++ b/tests/harness/__snapshots__/inferred-callback-drops-conditional.ts.txt @@ -0,0 +1,6 @@ +inferred-callback-drops-conditional.ts:10:32 - error SC1090: returning '{ extra: boolean; n: number; name: string }[]' from a callback whose result the checker infers as the narrower '{ n: number; name: string }[]' (the conversion would drop 'extra', which the object keeps at run time — annotate the callback's return type or the call's type argument with a type that declares them) is not supported yet + + 9 | const items: Item[] = run(() => + 10 | plain.length === 0 ? plain : plain.map((x) => ({ ...x, extra: true })), + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 11 | ); \ No newline at end of file diff --git a/tests/harness/__snapshots__/inferred-callback-drops-satisfies.ts.txt b/tests/harness/__snapshots__/inferred-callback-drops-satisfies.ts.txt new file mode 100644 index 000000000..ea1e37549 --- /dev/null +++ b/tests/harness/__snapshots__/inferred-callback-drops-satisfies.ts.txt @@ -0,0 +1,6 @@ +inferred-callback-drops-satisfies.ts:11:10 - error SC1090: returning '{ extra: boolean; n: number; name: string }[]' from a callback whose result the checker infers as the narrower '{ n: number; name: string }[]' (the conversion would drop 'extra', which the object keeps at run time — annotate the callback's return type or the call's type argument with a type that declares them) is not supported yet + + 10 | if (plain.length === 0) return plain; + 11 | return plain.map((x) => ({ ...x, extra: true })) satisfies Plain[]; + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 12 | }); \ No newline at end of file From 39e3b4a5dd6947b9b26f2104fec566e33d60ab9a Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Tue, 20 Oct 2026 09:34:19 -0500 Subject: [PATCH 090/122] Adopt thenable entries and place tuple slots by position in mixed Promise.all - A checked-dynamic entry of a tuple Promise.all goes through PromiseResolve (`dyn.promiseResolve`): a native promise is itself, a thenable adopts its settlement through its resolve job, and any other value fulfills. Before, a thenable fulfilled the slot with the object. - Tuple fields read their entry by position. Shape fields are sorted by name, so tuples of more than ten entries paired fields with the wrong entries. --- .../compiler/src/backend/llvm/lib-shared.ts | 1 + .../frontend/lowering/builtins/promises.ts | 29 +++++++++---------- .../frontend/lowering/checked-promise-all.ts | 2 +- .../compiler/src/ir/builtin-signatures.ts | 4 +++ packages/compiler/src/ir/runtime-features.ts | 1 + packages/runtime/src/scr_async_dyn.c | 15 ++++++---- packages/runtime/src/scr_dyn.h | 4 +++ tests/corpus/6482-promise-all-tuples.ts | 23 ++++++++++++++- 8 files changed, 56 insertions(+), 23 deletions(-) diff --git a/packages/compiler/src/backend/llvm/lib-shared.ts b/packages/compiler/src/backend/llvm/lib-shared.ts index 44741edc8..9ddcb6240 100644 --- a/packages/compiler/src/backend/llvm/lib-shared.ts +++ b/packages/compiler/src/backend/llvm/lib-shared.ts @@ -922,6 +922,7 @@ export const LIB_FN_SYMS: Record = { "dyn.promiseAll": "scr_dyn_promise_all", "dyn.promiseAllSettled": "scr_dyn_promise_all_settled", "dyn.promiseAny": "scr_dyn_promise_any", + "dyn.promiseResolve": "scr_dyn_promise_resolve", "dyn.typeof": "scr_dyn_typeof", "dyn.objectTag": "scr_dyn_object_tag", "dyn.freeze": "scr_dyn_freeze", diff --git a/packages/compiler/src/frontend/lowering/builtins/promises.ts b/packages/compiler/src/frontend/lowering/builtins/promises.ts index 5d4a2e3f2..6ac1232b9 100644 --- a/packages/compiler/src/frontend/lowering/builtins/promises.ts +++ b/packages/compiler/src/frontend/lowering/builtins/promises.ts @@ -1,4 +1,4 @@ -import { checkedPromiseAll } from "../checked-promise-all.js"; +import { checkedPromiseAll, thenReader } from "../checked-promise-all.js"; import { InternalCompilerError } from "../../../errors.js"; import * as ts from "../../ts7/adapter.js"; import { newFnCtx, type Lowerer } from "../lowerer.js"; @@ -6,7 +6,6 @@ import { varRef } from "../../../ir/build.js"; import { maybePromiseResolve, maybePromiseUnionOf } from "../maybe-promise.js"; import { isJsSourceFile, locOf } from "../../program.js"; import { - BOOL, DYN, type IrExpr, type IrFunction, @@ -31,7 +30,8 @@ import { * checked-dynamic tree. Non-promise entries (numbers, strings, booleans, * unit values — never thenable) pass straight into their slot; a * checked-dynamic entry (a JavaScript function's result) joins through - * the promise it holds, or a promise fulfilled with its plain value. The tuple + * PromiseResolve: the promise it holds, a promise that adopts a thenable, + * or a promise fulfilled with its plain value. The tuple * fulfills one microtask turn after the combinator does. Null when the * result type or an entry has no typed home here. */ function lowerTuplePromiseAll( @@ -68,8 +68,9 @@ function lowerTuplePromiseAll( const ref = (i: number): IrExpr => varRef(`entry.${i}`, elems[i]!.type, loc); const dynPromise: IrType = { kind: "promise", inner: DYN }; const body: IrStmt[] = []; - // Each dyn entry's promise view: the promise it holds, or a promise - // fulfilled with the plain value (Promise.resolve's rule). + // Each dyn entry's promise view, in entry order: the promise it + // holds, a promise that adopts a thenable's settlement, or a promise + // fulfilled with the plain value (PromiseResolve's rule). const views = new Map(); elems.forEach((e, i) => { if (e.type.kind !== "dyn") return; @@ -78,16 +79,9 @@ function lowerTuplePromiseAll( kind: "varDecl", localId: view.id, init: { - kind: "ternary", - cond: { kind: "dynTest", test: "promise", value: ref(i), type: BOOL, loc }, - then: { kind: "dynCheck", value: ref(i), type: dynPromise, loc }, - else_: { - kind: "intrinsic", - name: "promise.resolve", - args: [ref(i)], - type: dynPromise, - loc, - }, + kind: "libCall", + fn: "dyn.promiseResolve", + args: [ref(i), thenReader(lowerer, loc)], type: dynPromise, loc, }, @@ -138,7 +132,10 @@ function lowerTuplePromiseAll( type: VOID, loc, }; - const fields = shape.fields.map((field, i) => { + // Shape fields are sorted by name ("10" before "2"); a tuple field's + // name is its position. + const fields = shape.fields.map((field) => { + const i = Number(field.name); const entry = elems[i]!; let value: IrExpr; const view = views.get(i); diff --git a/packages/compiler/src/frontend/lowering/checked-promise-all.ts b/packages/compiler/src/frontend/lowering/checked-promise-all.ts index c3be91833..6c24a8b2c 100644 --- a/packages/compiler/src/frontend/lowering/checked-promise-all.ts +++ b/packages/compiler/src/frontend/lowering/checked-promise-all.ts @@ -11,7 +11,7 @@ const COMBINATOR_LIB_CALLS = { /** `value.then` through the program's own property dispatch, so a class * instance's `then` method is visible to the runtime's thenable check. */ -function thenReader(lowerer: Lowerer, loc: SrcLoc): IrExpr { +export function thenReader(lowerer: Lowerer, loc: SrcLoc): IrExpr { const name = "%promise.thenOf"; const type = funcOf([DYN], DYN); if (!lowerer.liftedFns.some((fn) => fn.name === name)) { diff --git a/packages/compiler/src/ir/builtin-signatures.ts b/packages/compiler/src/ir/builtin-signatures.ts index c55444e60..55e66f667 100644 --- a/packages/compiler/src/ir/builtin-signatures.ts +++ b/packages/compiler/src/ir/builtin-signatures.ts @@ -239,6 +239,10 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ "dyn.promiseAll": { argTypes: [DYN, DYN], result: { kind: "promise", inner: DYN } }, "dyn.promiseAllSettled": { argTypes: [DYN, DYN], result: { kind: "promise", inner: DYN } }, "dyn.promiseAny": { argTypes: [DYN, DYN], result: { kind: "promise", inner: DYN } }, + /** PromiseResolve over one checked-dynamic value: the promise it holds, + * a promise that adopts a thenable's settlement, or a promise fulfilled + * with the value. The second argument reads `then` as above. */ + "dyn.promiseResolve": { argTypes: [DYN, DYN], result: { kind: "promise", inner: DYN } }, /** Bare `typeof v` on a dyn value AS A STRING (arg: the dyn value, * borrowed; result: an owned string) — the dyn kind's JS answer: * undefined→"undefined", null/object/array/bytes→"object" (JS's oldest diff --git a/packages/compiler/src/ir/runtime-features.ts b/packages/compiler/src/ir/runtime-features.ts index 9abe15345..46e44e0e1 100644 --- a/packages/compiler/src/ir/runtime-features.ts +++ b/packages/compiler/src/ir/runtime-features.ts @@ -21,6 +21,7 @@ const DYN_ASYNC_LIB_FNS: ReadonlySet = new Set([ "dyn.promiseAll", "dyn.promiseAllSettled", "dyn.promiseAny", + "dyn.promiseResolve", "process.onUncaughtException", "process.offUncaughtException", "process.onUnhandledRejection", diff --git a/packages/runtime/src/scr_async_dyn.c b/packages/runtime/src/scr_async_dyn.c index 1cdd466c3..ec4dad794 100644 --- a/packages/runtime/src/scr_async_dyn.c +++ b/packages/runtime/src/scr_async_dyn.c @@ -1437,24 +1437,29 @@ static void scr_dyn_thenable_schedule(ScrPromise *target, const ScrDyn *thenable * a thenable gets a fresh promise and its resolve job, anything else a * fulfilled promise (a throwing `then` read rejects it). The element * joins the combinator right after, so a thenable's job sits between - * the jobs of the entries around it, exactly Node's iteration. */ -static ScrPromise *scr_dyn_promise_entry(ScrCombine *c, ScrDyn *value, const ScrDyn *reader) { + * the jobs of the entries around it, exactly Node's iteration. Without a + * combinator (`c` NULL) this is PromiseResolve for one value. */ +static ScrPromise *scr_dyn_promise_entry(ScrCombine *c, const ScrDyn *value, const ScrDyn *reader) { if (value->kind == SCR_DYN_PROMISE) return scr_promise_retain(value->v.promise); ScrPromise *source = scr_promise_new(); ScrDyn *then = scr_dyn_then_of(value, reader); if (scr_exc_pending()) { scr_promise_reject_pending(source); } else if (then) { - scr_promise_combine_flush(c); + if (c) scr_promise_combine_flush(c); scr_dyn_thenable_schedule(source, value, then, reader); } else { - scr_promise_fulfill_ref(source, scr_dyn_retain(value), scr_dyn_retain_v, scr_dyn_release_v, - scr_dyn_trace_v); + scr_promise_fulfill_ref(source, scr_dyn_retain((ScrDyn *)value), scr_dyn_retain_v, + scr_dyn_release_v, scr_dyn_trace_v); } scr_dyn_release(then); return source; } +ScrPromise *scr_dyn_promise_resolve(const ScrDyn *value, const ScrDyn *reader) { + return scr_dyn_promise_entry(NULL, value, reader); +} + static void scr_dyn_settled_store(ScrArr *a, double i, ScrPromise *src) { ScrDyn *record = scr_dyn_new_obj(); bool fulfilled = !scr_promise_is_rejected(src); diff --git a/packages/runtime/src/scr_dyn.h b/packages/runtime/src/scr_dyn.h index a3d506a10..93170a1e3 100644 --- a/packages/runtime/src/scr_dyn.h +++ b/packages/runtime/src/scr_dyn.h @@ -1309,6 +1309,10 @@ extern SCR_TL ScrDyn *(*scr_dyn_promise_member_fn)(const char *key, size_t len); ScrPromise *scr_dyn_promise_all(const ScrDyn *entries, const ScrDyn *reader); ScrPromise *scr_dyn_promise_all_settled(const ScrDyn *entries, const ScrDyn *reader); ScrPromise *scr_dyn_promise_any(const ScrDyn *entries, const ScrDyn *reader); +/* PromiseResolve over one dyn value (borrowed): a native promise is + * itself, a thenable adopts through its resolve job, a throwing `then` + * read rejects, and any other value fulfills. +1. */ +ScrPromise *scr_dyn_promise_resolve(const ScrDyn *value, const ScrDyn *reader); /* Native predicates for typed reference capsules and boxed errors. */ bool scr_dyn_ref_typeof_is(const ScrDyn *d, const char *name); diff --git a/tests/corpus/6482-promise-all-tuples.ts b/tests/corpus/6482-promise-all-tuples.ts index ff1615604..3c5e38e29 100644 --- a/tests/corpus/6482-promise-all-tuples.ts +++ b/tests/corpus/6482-promise-all-tuples.ts @@ -1,7 +1,8 @@ // Promise.all over an array literal of DIFFERENT promise types (the // checker's tuple overload): destructuring with holes and nested // patterns, void entries, plain values, `any` entries that may or may not -// hold a promise, entry identity, and rejection — +// hold a promise or a thenable, tuples of more than ten entries, entry +// identity, and rejection — // the first rejection in settlement order wins, later ones count as // handled. interface Version { @@ -48,6 +49,18 @@ function untypedRecord(): any { return Promise.resolve({ label: "from a promise" }); } +// A thenable that is not a native promise: Promise.all adopts its +// settlement, calling `then` one microtask after the call. +class Deferred { + then(resolve: (value: string) => void): void { + console.log("then called"); + resolve("adopted"); + } +} +function untypedThenable(): any { + return new Deferred(); +} + async function main(): Promise { const [runtime, { tool: kind, schema: level }] = await Promise.all([version(), scan()]); console.log(runtime.major, kind, level, runtime === shared); @@ -64,6 +77,14 @@ async function main(): Promise { const [count, { label }, plain] = await Promise.all([untypedCount(), untypedRecord(), "p"]); console.log(count + 1, label, plain); + const pending = Promise.all([untypedThenable(), version()]); + console.log("after the call"); + const [adopted, latest] = await pending; + console.log(adopted, latest.major); + + const many = await Promise.all([version(), 1, 2, 3, 4, 5, 6, 7, 8, 9, "ten", scan()]); + console.log(many[0].major, many[2], many[9], many[10], many[11].tool); + try { await Promise.all([delay(20, 1, "slow failure"), delay(5, "s", "fast failure"), delay(1, true)]); } catch (e) { From cb99a06fe59bb77f94405f002d9af86c338006d7 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Tue, 20 Oct 2026 09:35:14 -0500 Subject: [PATCH 091/122] Shadow the export alias only in the scope that declares it again The export-alias rewrite skipped a whole function when a let or const anywhere inside it declared the alias's name, so the function's other references to the alias kept the alias name after its declaration was removed. A function now shadows the alias only through its parameters, its own name, its vars and the declarations directly in its body; a block, case block, catch clause or for loop shadows it through its own declarations, and the walk skips just that scope. --- .../frontend/npm-static-cjs-exports-var.ts | 94 +++++++++++++------ .../npm/cases/cjs-exports-var/main.ts | 4 + .../npm/node_modules/exportsvar/index.d.ts | 2 + .../npm/node_modules/exportsvar/lib/ip.js | 14 +++ 4 files changed, 87 insertions(+), 27 deletions(-) diff --git a/packages/compiler/src/frontend/npm-static-cjs-exports-var.ts b/packages/compiler/src/frontend/npm-static-cjs-exports-var.ts index 38972990c..1af4ea1ed 100644 --- a/packages/compiler/src/frontend/npm-static-cjs-exports-var.ts +++ b/packages/compiler/src/frontend/npm-static-cjs-exports-var.ts @@ -62,47 +62,80 @@ function bindsName(name: ts.BindingName | undefined, text: string): boolean { return false; } -/** True when `scope` (a function, or a block or catch clause) declares - * `text` itself, so references inside it name that binding instead of the - * module-level alias: a parameter, a var/let/const or a function or class - * declaration in a function (nested functions excluded), a let/const or - * class in a block, a catch binding. */ -function shadows(scope: ts.Node, text: string): boolean { - if (isFunctionScope(scope)) { - const fn = scope as ts.SignatureDeclaration; - if (fn.parameters.nodes.some((p) => bindsName(p.name, text))) return true; +/** True when one of `statements` declares `text` lexically: a let/const, + * or a class or function declaration. */ +function declaresLexically(statements: readonly ts.Statement[], text: string): boolean { + for (const stmt of statements) { if ( - (ts.isFunctionExpression(scope) || ts.isClassExpression(scope)) && - scope.name?.text === text + ts.isVariableStatement(stmt) && + (stmt.declarationList.flags & ts.NodeFlags.BlockScoped) !== 0 && + stmt.declarationList.declarations.nodes.some((d) => bindsName(d.name, text)) ) return true; - } else if (ts.isCatchClause(scope)) { - if (bindsName(scope.variableDeclaration?.name, text)) return true; - } else if (!ts.isBlock(scope)) { - return false; + if ((ts.isClassDeclaration(stmt) || ts.isFunctionDeclaration(stmt)) && stmt.name?.text === text) + return true; } - const functionScope = isFunctionScope(scope); + return false; +} + +/** True when a function body declares `text` with `var` (or a function + * declaration in a nested block) anywhere outside nested functions. */ +function declaresVar(body: ts.Node, text: string): boolean { let found = false; - ts.walkPreorder(scope, (n) => { + ts.walkPreorder(body, (n) => { if (found) return "stop"; - if (n === scope) return undefined; + if (n === body) return undefined; if (isFunctionScope(n)) { if (ts.isFunctionDeclaration(n) && n.name?.text === text) found = true; return "skip"; } - if (!functionScope && ts.isBlock(n)) return "skip"; /* its own scope */ - if (ts.isVariableDeclaration(n) && bindsName(n.name, text)) { - const list = n.parent; - const blockScoped = - ts.isVariableDeclarationList(list) && (list.flags & ts.NodeFlags.BlockScoped) !== 0; - if (functionScope || blockScoped) found = true; - } - if (ts.isClassDeclaration(n) && n.name?.text === text) found = true; + if ( + ts.isVariableDeclarationList(n) && + (n.flags & ts.NodeFlags.BlockScoped) === 0 && + n.declarations.nodes.some((d) => bindsName(d.name, text)) + ) + found = true; return undefined; }); return found; } +/** True when `scope` declares `text` itself, so references inside it name + * that binding instead of the module-level alias: a function's parameters, + * own name, `var`s and the let/const, class and function declarations of + * its body; a block's or case block's own let/const, class and function + * declarations; a catch binding; a for loop's let/const. A let/const in a + * nested block shadows only that block, which the walk reaches as its own + * scope. */ +function shadows(scope: ts.Node, text: string): boolean { + if (isFunctionScope(scope)) { + const fn = scope as ts.SignatureDeclaration; + if (fn.parameters.nodes.some((p) => bindsName(p.name, text))) return true; + if (ts.isFunctionExpression(scope) && scope.name?.text === text) return true; + const body = (scope as { body?: ts.Node }).body; + if (body === undefined || !ts.isBlock(body)) return false; + return declaresVar(body, text) || declaresLexically(body.statements.nodes, text); + } + if (ts.isBlock(scope)) return declaresLexically(scope.statements.nodes, text); + if (ts.isCaseBlock(scope)) + return declaresLexically( + scope.clauses.nodes.flatMap((clause) => clause.statements.nodes), + text, + ); + if (ts.isCatchClause(scope)) return bindsName(scope.variableDeclaration?.name, text); + if (ts.isForStatement(scope) || ts.isForInStatement(scope) || ts.isForOfStatement(scope)) { + const init = scope.initializer; + return ( + init !== undefined && + ts.isVariableDeclarationList(init) && + (init.flags & ts.NodeFlags.BlockScoped) !== 0 && + init.declarations.nodes.some((d) => bindsName(d.name, text)) + ); + } + if (ts.isClassExpression(scope)) return scope.name?.text === text; + return false; +} + export function normalizeCjsExportsVar(sf: ts.SourceFile): string | null { const source = sf.text; if (!/\b(?:var|let|const)\s+[A-Za-z_$][\w$]*\s*=\s*exports\s*[;,\n]/.test(source)) return null; @@ -149,7 +182,14 @@ export function normalizeCjsExportsVar(sf: ts.SourceFile): string | null { // that binding, not the export object. if ( n !== sf && - (isFunctionScope(n) || ts.isBlock(n) || ts.isCatchClause(n)) && + (isFunctionScope(n) || + ts.isBlock(n) || + ts.isCaseBlock(n) || + ts.isCatchClause(n) || + ts.isForStatement(n) || + ts.isForInStatement(n) || + ts.isForOfStatement(n) || + ts.isClassExpression(n)) && shadows(n, name) ) return "skip"; diff --git a/tests/fixtures/npm/cases/cjs-exports-var/main.ts b/tests/fixtures/npm/cases/cjs-exports-var/main.ts index 60a13b850..ec72ba751 100644 --- a/tests/fixtures/npm/cases/cjs-exports-var/main.ts +++ b/tests/fixtures/npm/cases/cjs-exports-var/main.ts @@ -10,3 +10,7 @@ for (const addr of ["10.0.0.1", "192.168.1.20", "127.0.0.1", "::1", "8.8.8.8"]) console.log(ip.toLong("192.168.1.1")); // Members whose parameter shadows the alias name. console.log(ip.isV4Format("1.2.3.4"), ip.isV4Format("::1"), ip.normalize(" FE80::1 ")); +// Members with a block-local `let` or a loop `const` of the alias name: +// only that block or loop names the local binding. +console.log(ip.classify("FE80::1"), ip.classify("10.1.2.3"), ip.classify("8.8.4.4")); +console.log(ip.countPrivate(["127.0.0.1", "10.0.0.2", "10.9.9.9"]), ip.countPrivate(["10.0.0.1"])); diff --git a/tests/fixtures/npm/node_modules/exportsvar/index.d.ts b/tests/fixtures/npm/node_modules/exportsvar/index.d.ts index 8782f2267..fa65c9064 100644 --- a/tests/fixtures/npm/node_modules/exportsvar/index.d.ts +++ b/tests/fixtures/npm/node_modules/exportsvar/index.d.ts @@ -4,3 +4,5 @@ export function isPublic(addr: string): boolean; export function toLong(addr: string): number; export function isV4Format(ip: string): boolean; export function normalize(ip: string): string; +export function classify(addr: string): string; +export function countPrivate(addrs: string[]): number; diff --git a/tests/fixtures/npm/node_modules/exportsvar/lib/ip.js b/tests/fixtures/npm/node_modules/exportsvar/lib/ip.js index 8e782dad2..074ed8318 100644 --- a/tests/fixtures/npm/node_modules/exportsvar/lib/ip.js +++ b/tests/fixtures/npm/node_modules/exportsvar/lib/ip.js @@ -33,3 +33,17 @@ ip.normalize = function(ip) { var trimmed = ip.trim(); return trimmed.toLowerCase(); }; + +ip.classify = function(addr) { + if (addr.indexOf(':') !== -1) { + let ip = addr.toLowerCase(); + return 'v6 ' + ip; + } + return ip.isPrivate(addr) ? 'private' : 'public'; +}; + +ip.countPrivate = function(addrs) { + var n = 0; + for (const ip of addrs) if (/^10\./.test(ip)) n++; + return n + (ip.isLoopback(addrs[0]) ? 100 : 0); +}; From 2330953ebe7918f43165813c17d9991b16c7124a Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Tue, 20 Oct 2026 09:49:10 -0500 Subject: [PATCH 092/122] Write the byte order mark in a corpus test as an escape --- tests/corpus/6230-date-parse-legacy.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/corpus/6230-date-parse-legacy.ts b/tests/corpus/6230-date-parse-legacy.ts index 970050cd0..cc71961c0 100644 --- a/tests/corpus/6230-date-parse-legacy.ts +++ b/tests/corpus/6230-date-parse-legacy.ts @@ -68,7 +68,7 @@ const fixed = [ "Jan 1 9999999 GMT", "2024-01-01T10:00:00\u0000junk", "Jan 1
2024", - "Jan 1 2024", + "\uFEFFJan 1 2024", "Jan 1 2024 été", "0", "12", From da0f6b642ba11624a37d4366589141454d47a3da Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Tue, 20 Oct 2026 09:52:32 -0500 Subject: [PATCH 093/122] Merge main --- docs/content/docs/limitations.mdx | 4 + .../limitations/builtin-apis/exit-status.mdx | 3 +- .../builtin-apis/object-freezing.mdx | 5 + .../limitations/builtin-apis/test-mocks.mdx | 3 +- .../language-features/function-values.mdx | 2 +- .../limitations/module-path-globals.mdx | 4 + .../null-and-optional-values.mdx | 3 +- .../generated/node-v24-backlog.json | 28 +- .../generated/node-v24-internal.json | 36 +- internal/compatibility/static-support.json | 8 +- .../ambient/scriptc-node-fallback.d.ts | 7 + .../compiler/src/backend/llvm/byte-numbers.ts | 7 +- .../compiler/src/backend/llvm/bytes-alias.ts | 23 + .../backend/llvm/bytes-loop-versions.test.ts | 278 ++++++ .../src/backend/llvm/bytes-loop-versions.ts | 578 ++++++++++++ .../src/backend/llvm/counted-loops.ts | 51 ++ .../src/backend/llvm/emit-function.ts | 5 + .../src/backend/llvm/emit-statements.ts | 89 +- packages/compiler/src/backend/llvm/emitter.ts | 45 + .../compiler/src/backend/llvm/expr-bytes.ts | 64 +- .../src/backend/llvm/expr-callbacks.ts | 17 +- .../src/backend/llvm/expr-containers.ts | 2 +- .../src/backend/llvm/expr-primitives.ts | 2 +- .../src/backend/llvm/expr-stream-bridges.ts | 34 +- .../compiler/src/backend/llvm/lib-shared.ts | 10 + .../compiler/src/backend/llvm/receivers.ts | 2 +- .../src/backend/stack-observations.test.ts | 35 + .../src/backend/stack-observations.ts | 35 +- .../src/frontend/helper-tokens.test.ts | 8 +- .../compiler/src/frontend/helper-tokens.ts | 15 +- .../compiler/src/frontend/input-tracker.ts | 34 +- .../frontend/lowering/assertion-members.ts | 168 ++++ .../src/frontend/lowering/builtin-calls.ts | 5 + .../lowering/builtins/callable-aliases.ts | 8 +- .../src/frontend/lowering/builtins/ipc.ts | 58 +- .../src/frontend/lowering/builtins/process.ts | 213 ++++- .../frontend/lowering/builtins/promises.ts | 3 + .../lowering/builtins/promisify-callback.ts | 377 ++++++++ .../src/frontend/lowering/call-signatures.ts | 26 + .../src/frontend/lowering/class-attributes.ts | 440 +++++++++ .../frontend/lowering/class-descriptors.ts | 2 +- .../lowering/class-dynamic-dispatch.ts | 105 +++ .../src/frontend/lowering/class-protocols.ts | 6 - .../lowering/coercions/function-adapters.ts | 137 ++- .../lowering/expressions/hosted-narrowing.ts | 157 ++++ .../lowering/expressions/identifiers.ts | 22 +- .../lowering/expressions/narrowing.ts | 35 + .../lowering/expressions/relational.ts | 89 +- .../frontend/lowering/expressions/typeof.ts | 36 +- .../frontend/lowering/expressions/updates.ts | 2 + .../frontend/lowering/key-order-witness.ts | 20 +- .../src/frontend/lowering/lower-calls.ts | 18 + .../src/frontend/lowering/lower-classes.ts | 7 +- .../src/frontend/lowering/lower-inspect.ts | 11 +- .../src/frontend/lowering/lower-mock.ts | 75 +- .../src/frontend/lowering/lower-stmts.ts | 64 +- .../src/frontend/lowering/lower-stream.ts | 24 +- .../compiler/src/frontend/lowering/lowerer.ts | 13 + .../src/frontend/lowering/strict-mode.ts | 34 + .../src/frontend/lowering/surfaces.ts | 6 +- packages/compiler/src/frontend/mock-shapes.ts | 19 +- .../npm-static-bundled-chunks.test.ts | 300 ++++++ .../src/frontend/npm-static-bundled-chunks.ts | 858 ++++++++++++++++++ .../src/frontend/npm-static-rewrite-host.ts | 12 +- .../src/frontend/npm-static-rewrite-syntax.ts | 236 +++-- packages/compiler/src/frontend/npm-static.ts | 65 +- packages/compiler/src/frontend/pipeline.ts | 11 + packages/compiler/src/frontend/program.ts | 225 ++++- packages/compiler/src/frontend/services.ts | 20 +- packages/compiler/src/frontend/type-mapper.ts | 148 ++- packages/compiler/src/ir/builtin-effects.ts | 4 + .../compiler/src/ir/builtin-signatures.ts | 26 + packages/compiler/src/ir/ir.ts | 40 +- packages/compiler/src/ir/runtime-features.ts | 1 + packages/compiler/src/ir/validate.ts | 1 + packages/compiler/surface-manifest.json | 4 +- .../compiler/test/class-attributes.test.ts | 84 ++ .../test/native-class-descriptors.test.ts | 38 +- .../5467-typed-array-loop-versions.ts.json | 6 + .../5468-typed-array-loop-aliasing.ts.json | 6 + ...d-array-loop-initializer-rebinding.ts.json | 6 + .../5816-fixed-into-unknown-rest.ts.json | 6 + .../5916-dyn-error-stack-property.ts.json | 6 + .../6004-class-instance-integrity.ts.json | 6 + ...5-class-instance-integrity-sloppy.cjs.json | 6 + ...4-protocol-subinterface-assertions.ts.json | 6 - .../6398-conditional-fixed-arity.ts.json | 6 + .../6400-dyn-error-stack-property-js.js.json | 6 + .../6401-dyn-error-own-property-names.ts.json | 6 + ...02-dyn-error-own-property-names-js.js.json | 6 + .../corpus/6420-process-send-presence.ts.json | 6 + .../main.ts.json | 6 + .../corpus/6422-process-exitcode-read.ts.json | 6 + .../6423-process-exitcode-default.ts.json | 6 + .../6424-process-exit-keeps-exitcode.ts.json | 6 + .../6425-stream-hook-wider-chunk.ts.json | 6 + .../6426-promise-race-void-entry.ts.json | 6 + .../6427-http-request-options-type.ts.json | 6 + .../6428-promisify-callback-functions.ts.json | 6 + .../tests/corpus/6429-esm-js-dirname.mjs.json | 6 + .../6445-rest-function-fixed-slot.ts.json | 6 + ...6-rest-function-nested-declaration.ts.json | 6 + .../6460-in-union-optional-slots.ts.json | 6 + ...1-predicate-intersection-narrowing.ts.json | 6 + .../6462-assertion-attached-members.ts.json | 6 + .../6480-catch-typeof-untyped-throws.ts.json | 6 + .../boot-path-node-surface-fences.ts.json | 6 + .../class-field-accessor-descriptor.ts.json | 6 + .../promisify-callback-functions.ts.json | 6 + .../node-types/stream-subclass-hooks.ts.json | 6 + .../cases/2558-bundled-chunks/main.ts.json | 6 + .../npm/cases/2559-cjs-cycle/main.ts.json | 6 + .../2560-cjs-cycle-partner-first/main.ts.json | 6 + .../2561-cjs-cycle-early-read/main.ts.json | 6 + .../npm/cases/2562-subpath-leaf/main.ts.json | 6 + .../2564-chunk-helper-shadowing/main.ts.json | 6 + .../2565-chunk-shared-function/main.ts.json | 6 + .../2566-cjs-cycle-indirect-call/main.ts.json | 6 + .../main.ts.json | 6 + packages/runtime/src/scr_alloc.c | 102 ++- packages/runtime/src/scr_alloc.test.c | 87 +- packages/runtime/src/scr_async.c | 6 + packages/runtime/src/scr_child.c | 1 + packages/runtime/src/scr_child.h | 3 + packages/runtime/src/scr_dyn.h | 10 + packages/runtime/src/scr_error.c | 6 + packages/runtime/src/scr_error.h | 3 + packages/runtime/src/scr_json.c | 353 ++++++- packages/runtime/src/scr_lib.c | 13 + packages/runtime/src/scr_lib.h | 5 + packages/runtime/src/scr_message.c | 6 + packages/runtime/src/scr_rc.h | 4 + .../corpus/5467-typed-array-loop-versions.ts | 298 ++++++ .../corpus/5468-typed-array-loop-aliasing.ts | 107 +++ ...-typed-array-loop-initializer-rebinding.ts | 46 + tests/corpus/5816-fixed-into-unknown-rest.ts | 39 + tests/corpus/5916-dyn-error-stack-property.ts | 147 +++ tests/corpus/6004-class-instance-integrity.ts | 169 ++++ .../6005-class-instance-integrity-sloppy.cjs | 40 + .../6064-protocol-subinterface-assertions.ts | 80 -- tests/corpus/6398-conditional-fixed-arity.ts | 40 + .../6400-dyn-error-stack-property-js.js | 148 +++ .../6401-dyn-error-own-property-names.ts | 44 + .../6402-dyn-error-own-property-names-js.js | 44 + tests/corpus/6420-process-send-presence.ts | 24 + .../6421-process-send-presence-fork/main.ts | 13 + .../6421-process-send-presence-fork/worker.ts | 6 + tests/corpus/6422-process-exitcode-read.ts | 22 + tests/corpus/6423-process-exitcode-default.ts | 10 + .../6424-process-exit-keeps-exitcode.ts | 5 + tests/corpus/6425-stream-hook-wider-chunk.ts | 46 + tests/corpus/6426-promise-race-void-entry.ts | 31 + .../corpus/6427-http-request-options-type.ts | 10 + .../6428-promisify-callback-functions.ts | 80 ++ tests/corpus/6429-esm-js-dirname.mjs | 15 + tests/corpus/6445-rest-function-fixed-slot.ts | 49 + .../6446-rest-function-nested-declaration.ts | 26 + tests/corpus/6460-in-union-optional-slots.ts | 53 ++ .../6461-predicate-intersection-narrowing.ts | 70 ++ .../corpus/6462-assertion-attached-members.ts | 58 ++ .../6480-catch-typeof-untyped-throws.ts | 40 + .../boot-path-node-surface-fences.ts | 23 + .../class-field-accessor-descriptor.ts | 14 + tests/diagnostics/maybe-promise-fences.ts | 5 - tests/diagnostics/mock-fences.ts | 12 +- tests/fixtures/esm-path-globals/lib/helper.ts | 2 + tests/fixtures/esm-path-globals/main.ts | 12 + .../node-test/cases/mock-fn-default.ts | 72 ++ .../promisify-callback-functions.ts | 67 ++ .../node-types/stream-subclass-hooks.ts | 46 + .../npm/cases/2558-bundled-chunks/main.ts | 29 + .../fixtures/npm/cases/2559-cjs-cycle/main.ts | 11 + .../2560-cjs-cycle-partner-first/main.ts | 11 + .../cases/2561-cjs-cycle-early-read/main.ts | 6 + .../npm/cases/2562-subpath-leaf/main.ts | 10 + .../cases/2564-chunk-helper-shadowing/main.ts | 7 + .../cases/2565-chunk-shared-function/main.ts | 7 + .../2566-cjs-cycle-indirect-call/main.ts | 7 + .../2571-cjs-cycle-stored-function/main.ts | 8 + .../npm/node_modules/gtchunk/dist/index.d.ts | 4 + .../npm/node_modules/gtchunk/dist/index.js | 132 +++ .../npm/node_modules/gtchunk/package.json | 10 + .../node_modules/gtchunk17/dist/index.d.ts | 4 + .../npm/node_modules/gtchunk17/dist/index.js | 132 +++ .../npm/node_modules/gtchunk17/package.json | 10 + .../gtchunkshadow/dist/index.d.ts | 2 + .../node_modules/gtchunkshadow/dist/index.js | 52 ++ .../node_modules/gtchunkshadow/package.json | 7 + .../gtchunkshared/dist/index.d.ts | 2 + .../node_modules/gtchunkshared/dist/index.js | 56 ++ .../node_modules/gtchunkshared/package.json | 7 + .../node_modules/gtchunkws/dist/index.d.ts | 4 + .../npm/node_modules/gtchunkws/dist/index.js | 1 + .../npm/node_modules/gtchunkws/package.json | 10 + .../npm/node_modules/gtcjsdist/dist/index.cjs | 43 + .../node_modules/gtcjsdist/dist/index.d.cts | 1 + .../npm/node_modules/gtcjsdist/package.json | 11 + .../npm/node_modules/gtcycle/dist/append.d.ts | 1 + .../npm/node_modules/gtcycle/dist/append.js | 30 + .../npm/node_modules/gtcycle/dist/index.d.ts | 4 + .../npm/node_modules/gtcycle/dist/index.js | 44 + .../npm/node_modules/gtcycle/dist/merge.d.ts | 1 + .../npm/node_modules/gtcycle/dist/merge.js | 30 + .../npm/node_modules/gtcycle/package.json | 7 + .../node_modules/gtcyclealias/dist/index.d.ts | 3 + .../node_modules/gtcyclealias/dist/index.js | 37 + .../node_modules/gtcyclealias/dist/reader.js | 34 + .../node_modules/gtcyclealias/package.json | 7 + .../node_modules/gtcyclebad/dist/index.d.ts | 2 + .../npm/node_modules/gtcyclebad/dist/index.js | 33 + .../node_modules/gtcyclebad/dist/reader.js | 33 + .../npm/node_modules/gtcyclebad/package.json | 7 + .../node_modules/gtcyclebox/dist/index.d.ts | 3 + .../npm/node_modules/gtcyclebox/dist/index.js | 37 + .../node_modules/gtcyclebox/dist/reader.js | 34 + .../npm/node_modules/gtcyclebox/package.json | 7 + .../node_modules/gtcyclecall/dist/index.d.ts | 3 + .../node_modules/gtcyclecall/dist/index.js | 37 + .../node_modules/gtcyclecall/dist/reader.js | 33 + .../npm/node_modules/gtcyclecall/package.json | 7 + .../npm/node_modules/gtsubpath/dist/data.d.ts | 2 + .../npm/node_modules/gtsubpath/dist/data.js | 6 + .../npm/node_modules/gtsubpath/dist/detect.js | 11 + .../node_modules/gtsubpath/dist/index.d.ts | 6 + .../npm/node_modules/gtsubpath/dist/index.js | 5 + .../npm/node_modules/gtsubpath/package.json | 10 + .../boot-path-node-surface-fences.ts.txt | 35 + .../class-field-accessor-descriptor.ts.txt | 6 + .../__snapshots__/maybe-promise-fences.ts.txt | 9 +- .../harness/__snapshots__/mock-fences.ts.txt | 60 +- tests/harness/corpus-costs.json | 12 +- .../5816-fixed-into-unknown-rest.ts.json | 1 + .../5916-dyn-error-stack-property.ts.json | 1 + ...4-protocol-subinterface-assertions.ts.json | 1 - ...0-variadic-wrapper-arguments-apply.ts.json | 1 + .../6381-variadic-wrapper-async.ts.json | 1 + ...6382-variadic-wrapper-methods-this.ts.json | 1 + .../6383-variadic-wrapper-rest-tuple.ts.json | 1 + .../6384-variadic-wrapper-nested.ts.json | 1 + .../6390-catch-binding-predicate.ts.json | 1 + .../6391-nullish-function-default.ts.json | 1 + ...6392-optional-chain-protocol-calls.ts.json | 1 + .../6393-function-types-any-params.ts.json | 1 + .../6394-in-key-error-subclasses.ts.json | 1 + .../6395-generic-object-entries.ts.json | 1 + .../6396-generic-any-instantiation.ts.json | 1 + .../6397-event-loop-metrics.ts.json | 1 + .../6398-conditional-fixed-arity.ts.json | 1 + .../6400-dyn-error-stack-property-js.js.json | 1 + .../6401-dyn-error-own-property-names.ts.json | 1 + ...02-dyn-error-own-property-names-js.js.json | 1 + .../6414-catch-values-roundtrip.ts.json | 1 + .../6445-rest-function-fixed-slot.ts.json | 1 + ...6-rest-function-nested-declaration.ts.json | 1 + ...468-generic-unknown-record-entries.ts.json | 1 + tests/harness/esm-path-globals.test.ts | 38 + tests/harness/npm-cases.ts | 4 +- tests/harness/npm-static.test.ts | 136 ++- tests/harness/project-config.test.ts | 36 + 259 files changed, 9524 insertions(+), 442 deletions(-) create mode 100644 docs/content/limitations/module-path-globals.mdx create mode 100644 packages/compiler/src/backend/llvm/bytes-alias.ts create mode 100644 packages/compiler/src/backend/llvm/bytes-loop-versions.test.ts create mode 100644 packages/compiler/src/backend/llvm/bytes-loop-versions.ts create mode 100644 packages/compiler/src/frontend/lowering/assertion-members.ts create mode 100644 packages/compiler/src/frontend/lowering/builtins/promisify-callback.ts create mode 100644 packages/compiler/src/frontend/lowering/class-attributes.ts create mode 100644 packages/compiler/src/frontend/lowering/expressions/hosted-narrowing.ts create mode 100644 packages/compiler/src/frontend/lowering/strict-mode.ts create mode 100644 packages/compiler/src/frontend/npm-static-bundled-chunks.test.ts create mode 100644 packages/compiler/src/frontend/npm-static-bundled-chunks.ts create mode 100644 packages/compiler/test/class-attributes.test.ts create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5467-typed-array-loop-versions.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5468-typed-array-loop-aliasing.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5469-typed-array-loop-initializer-rebinding.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5816-fixed-into-unknown-rest.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5916-dyn-error-stack-property.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6004-class-instance-integrity.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6005-class-instance-integrity-sloppy.cjs.json delete mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6064-protocol-subinterface-assertions.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6398-conditional-fixed-arity.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6400-dyn-error-stack-property-js.js.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6401-dyn-error-own-property-names.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6402-dyn-error-own-property-names-js.js.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6420-process-send-presence.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6421-process-send-presence-fork/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6422-process-exitcode-read.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6423-process-exitcode-default.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6424-process-exit-keeps-exitcode.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6425-stream-hook-wider-chunk.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6426-promise-race-void-entry.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6427-http-request-options-type.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6428-promisify-callback-functions.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6429-esm-js-dirname.mjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6445-rest-function-fixed-slot.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6446-rest-function-nested-declaration.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6460-in-union-optional-slots.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6461-predicate-intersection-narrowing.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6462-assertion-attached-members.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6480-catch-typeof-untyped-throws.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/boot-path-node-surface-fences.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/class-field-accessor-descriptor.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/promisify-callback-functions.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/stream-subclass-hooks.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2558-bundled-chunks/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2559-cjs-cycle/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2560-cjs-cycle-partner-first/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2561-cjs-cycle-early-read/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2562-subpath-leaf/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2564-chunk-helper-shadowing/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2565-chunk-shared-function/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2566-cjs-cycle-indirect-call/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2571-cjs-cycle-stored-function/main.ts.json create mode 100644 tests/corpus/5467-typed-array-loop-versions.ts create mode 100644 tests/corpus/5468-typed-array-loop-aliasing.ts create mode 100644 tests/corpus/5469-typed-array-loop-initializer-rebinding.ts create mode 100644 tests/corpus/5816-fixed-into-unknown-rest.ts create mode 100644 tests/corpus/5916-dyn-error-stack-property.ts create mode 100644 tests/corpus/6004-class-instance-integrity.ts create mode 100644 tests/corpus/6005-class-instance-integrity-sloppy.cjs delete mode 100644 tests/corpus/6064-protocol-subinterface-assertions.ts create mode 100644 tests/corpus/6398-conditional-fixed-arity.ts create mode 100644 tests/corpus/6400-dyn-error-stack-property-js.js create mode 100644 tests/corpus/6401-dyn-error-own-property-names.ts create mode 100644 tests/corpus/6402-dyn-error-own-property-names-js.js create mode 100644 tests/corpus/6420-process-send-presence.ts create mode 100644 tests/corpus/6421-process-send-presence-fork/main.ts create mode 100644 tests/corpus/6421-process-send-presence-fork/worker.ts create mode 100644 tests/corpus/6422-process-exitcode-read.ts create mode 100644 tests/corpus/6423-process-exitcode-default.ts create mode 100644 tests/corpus/6424-process-exit-keeps-exitcode.ts create mode 100644 tests/corpus/6425-stream-hook-wider-chunk.ts create mode 100644 tests/corpus/6426-promise-race-void-entry.ts create mode 100644 tests/corpus/6427-http-request-options-type.ts create mode 100644 tests/corpus/6428-promisify-callback-functions.ts create mode 100644 tests/corpus/6429-esm-js-dirname.mjs create mode 100644 tests/corpus/6445-rest-function-fixed-slot.ts create mode 100644 tests/corpus/6446-rest-function-nested-declaration.ts create mode 100644 tests/corpus/6460-in-union-optional-slots.ts create mode 100644 tests/corpus/6461-predicate-intersection-narrowing.ts create mode 100644 tests/corpus/6462-assertion-attached-members.ts create mode 100644 tests/corpus/6480-catch-typeof-untyped-throws.ts create mode 100644 tests/diagnostics/boot-path-node-surface-fences.ts create mode 100644 tests/diagnostics/class-field-accessor-descriptor.ts create mode 100644 tests/fixtures/esm-path-globals/lib/helper.ts create mode 100644 tests/fixtures/esm-path-globals/main.ts create mode 100644 tests/fixtures/node-test/cases/mock-fn-default.ts create mode 100644 tests/fixtures/node-types/promisify-callback-functions.ts create mode 100644 tests/fixtures/node-types/stream-subclass-hooks.ts create mode 100644 tests/fixtures/npm/cases/2558-bundled-chunks/main.ts create mode 100644 tests/fixtures/npm/cases/2559-cjs-cycle/main.ts create mode 100644 tests/fixtures/npm/cases/2560-cjs-cycle-partner-first/main.ts create mode 100644 tests/fixtures/npm/cases/2561-cjs-cycle-early-read/main.ts create mode 100644 tests/fixtures/npm/cases/2562-subpath-leaf/main.ts create mode 100644 tests/fixtures/npm/cases/2564-chunk-helper-shadowing/main.ts create mode 100644 tests/fixtures/npm/cases/2565-chunk-shared-function/main.ts create mode 100644 tests/fixtures/npm/cases/2566-cjs-cycle-indirect-call/main.ts create mode 100644 tests/fixtures/npm/cases/2571-cjs-cycle-stored-function/main.ts create mode 100644 tests/fixtures/npm/node_modules/gtchunk/dist/index.d.ts create mode 100644 tests/fixtures/npm/node_modules/gtchunk/dist/index.js create mode 100644 tests/fixtures/npm/node_modules/gtchunk/package.json create mode 100644 tests/fixtures/npm/node_modules/gtchunk17/dist/index.d.ts create mode 100644 tests/fixtures/npm/node_modules/gtchunk17/dist/index.js create mode 100644 tests/fixtures/npm/node_modules/gtchunk17/package.json create mode 100644 tests/fixtures/npm/node_modules/gtchunkshadow/dist/index.d.ts create mode 100644 tests/fixtures/npm/node_modules/gtchunkshadow/dist/index.js create mode 100644 tests/fixtures/npm/node_modules/gtchunkshadow/package.json create mode 100644 tests/fixtures/npm/node_modules/gtchunkshared/dist/index.d.ts create mode 100644 tests/fixtures/npm/node_modules/gtchunkshared/dist/index.js create mode 100644 tests/fixtures/npm/node_modules/gtchunkshared/package.json create mode 100644 tests/fixtures/npm/node_modules/gtchunkws/dist/index.d.ts create mode 100644 tests/fixtures/npm/node_modules/gtchunkws/dist/index.js create mode 100644 tests/fixtures/npm/node_modules/gtchunkws/package.json create mode 100644 tests/fixtures/npm/node_modules/gtcjsdist/dist/index.cjs create mode 100644 tests/fixtures/npm/node_modules/gtcjsdist/dist/index.d.cts create mode 100644 tests/fixtures/npm/node_modules/gtcjsdist/package.json create mode 100644 tests/fixtures/npm/node_modules/gtcycle/dist/append.d.ts create mode 100644 tests/fixtures/npm/node_modules/gtcycle/dist/append.js create mode 100644 tests/fixtures/npm/node_modules/gtcycle/dist/index.d.ts create mode 100644 tests/fixtures/npm/node_modules/gtcycle/dist/index.js create mode 100644 tests/fixtures/npm/node_modules/gtcycle/dist/merge.d.ts create mode 100644 tests/fixtures/npm/node_modules/gtcycle/dist/merge.js create mode 100644 tests/fixtures/npm/node_modules/gtcycle/package.json create mode 100644 tests/fixtures/npm/node_modules/gtcyclealias/dist/index.d.ts create mode 100644 tests/fixtures/npm/node_modules/gtcyclealias/dist/index.js create mode 100644 tests/fixtures/npm/node_modules/gtcyclealias/dist/reader.js create mode 100644 tests/fixtures/npm/node_modules/gtcyclealias/package.json create mode 100644 tests/fixtures/npm/node_modules/gtcyclebad/dist/index.d.ts create mode 100644 tests/fixtures/npm/node_modules/gtcyclebad/dist/index.js create mode 100644 tests/fixtures/npm/node_modules/gtcyclebad/dist/reader.js create mode 100644 tests/fixtures/npm/node_modules/gtcyclebad/package.json create mode 100644 tests/fixtures/npm/node_modules/gtcyclebox/dist/index.d.ts create mode 100644 tests/fixtures/npm/node_modules/gtcyclebox/dist/index.js create mode 100644 tests/fixtures/npm/node_modules/gtcyclebox/dist/reader.js create mode 100644 tests/fixtures/npm/node_modules/gtcyclebox/package.json create mode 100644 tests/fixtures/npm/node_modules/gtcyclecall/dist/index.d.ts create mode 100644 tests/fixtures/npm/node_modules/gtcyclecall/dist/index.js create mode 100644 tests/fixtures/npm/node_modules/gtcyclecall/dist/reader.js create mode 100644 tests/fixtures/npm/node_modules/gtcyclecall/package.json create mode 100644 tests/fixtures/npm/node_modules/gtsubpath/dist/data.d.ts create mode 100644 tests/fixtures/npm/node_modules/gtsubpath/dist/data.js create mode 100644 tests/fixtures/npm/node_modules/gtsubpath/dist/detect.js create mode 100644 tests/fixtures/npm/node_modules/gtsubpath/dist/index.d.ts create mode 100644 tests/fixtures/npm/node_modules/gtsubpath/dist/index.js create mode 100644 tests/fixtures/npm/node_modules/gtsubpath/package.json create mode 100644 tests/harness/__snapshots__/boot-path-node-surface-fences.ts.txt create mode 100644 tests/harness/__snapshots__/class-field-accessor-descriptor.ts.txt create mode 100644 tests/harness/corpus-costs/5816-fixed-into-unknown-rest.ts.json create mode 100644 tests/harness/corpus-costs/5916-dyn-error-stack-property.ts.json delete mode 100644 tests/harness/corpus-costs/6064-protocol-subinterface-assertions.ts.json create mode 100644 tests/harness/corpus-costs/6380-variadic-wrapper-arguments-apply.ts.json create mode 100644 tests/harness/corpus-costs/6381-variadic-wrapper-async.ts.json create mode 100644 tests/harness/corpus-costs/6382-variadic-wrapper-methods-this.ts.json create mode 100644 tests/harness/corpus-costs/6383-variadic-wrapper-rest-tuple.ts.json create mode 100644 tests/harness/corpus-costs/6384-variadic-wrapper-nested.ts.json create mode 100644 tests/harness/corpus-costs/6390-catch-binding-predicate.ts.json create mode 100644 tests/harness/corpus-costs/6391-nullish-function-default.ts.json create mode 100644 tests/harness/corpus-costs/6392-optional-chain-protocol-calls.ts.json create mode 100644 tests/harness/corpus-costs/6393-function-types-any-params.ts.json create mode 100644 tests/harness/corpus-costs/6394-in-key-error-subclasses.ts.json create mode 100644 tests/harness/corpus-costs/6395-generic-object-entries.ts.json create mode 100644 tests/harness/corpus-costs/6396-generic-any-instantiation.ts.json create mode 100644 tests/harness/corpus-costs/6397-event-loop-metrics.ts.json create mode 100644 tests/harness/corpus-costs/6398-conditional-fixed-arity.ts.json create mode 100644 tests/harness/corpus-costs/6400-dyn-error-stack-property-js.js.json create mode 100644 tests/harness/corpus-costs/6401-dyn-error-own-property-names.ts.json create mode 100644 tests/harness/corpus-costs/6402-dyn-error-own-property-names-js.js.json create mode 100644 tests/harness/corpus-costs/6414-catch-values-roundtrip.ts.json create mode 100644 tests/harness/corpus-costs/6445-rest-function-fixed-slot.ts.json create mode 100644 tests/harness/corpus-costs/6446-rest-function-nested-declaration.ts.json create mode 100644 tests/harness/corpus-costs/6468-generic-unknown-record-entries.ts.json create mode 100644 tests/harness/esm-path-globals.test.ts diff --git a/docs/content/docs/limitations.mdx b/docs/content/docs/limitations.mdx index d261b6385..c64c5cebd 100644 --- a/docs/content/docs/limitations.mdx +++ b/docs/content/docs/limitations.mdx @@ -228,6 +228,10 @@ title: "Limitations" ../limitations/process-arguments-and-errors.mdx +### Module path globals + +../limitations/module-path-globals.mdx + ## Runtime code evaluation diff --git a/docs/content/limitations/builtin-apis/exit-status.mdx b/docs/content/limitations/builtin-apis/exit-status.mdx index d9bb779db..3cdf10496 100644 --- a/docs/content/limitations/builtin-apis/exit-status.mdx +++ b/docs/content/limitations/builtin-apis/exit-status.mdx @@ -1,3 +1,4 @@ - **Exit status:** Numeric `process.exitCode = value` writes in statement position set the implicit exit status, which `process.exit()` uses when called without an argument. `process.exit(code)` and `process.exitCode` throw a `RangeError` (`ERR_OUT_OF_RANGE`) for a code that is not a safe integer, and convert other codes to int32, as Node does. - Reads, resets, and assignments used as values are unsupported. + Reading `process.exitCode` answers the assigned code, or `undefined` before any assignment; `process.exit(code)` assigns it before the `'exit'` listeners run. + Resets (`process.exitCode = undefined`) and assignments used as values are unsupported. diff --git a/docs/content/limitations/builtin-apis/object-freezing.mdx b/docs/content/limitations/builtin-apis/object-freezing.mdx index 9784cd715..38f0155c0 100644 --- a/docs/content/limitations/builtin-apis/object-freezing.mdx +++ b/docs/content/limitations/builtin-apis/object-freezing.mdx @@ -1,3 +1,8 @@ - **Object freezing:** `Object.freeze` and `Object.isFrozen` support plain checked dictionaries and primitives. Freezing is shallow and preserves accessors. Aliased typed records, arrays, proxies, and other native objects cannot be frozen. + Class instances support `Object.freeze`, `Object.seal`, `Object.preventExtensions`, `Object.isFrozen`, `Object.isSealed`, and `Object.isExtensible` when the static type of the argument names the class. + A write to a read-only field throws Node's `TypeError` in strict mode code and leaves the field unchanged in sloppy mode code. + `Object.defineProperty` with a string-literal key changes the value, `writable`, `enumerable`, and `configurable` attributes of a declared field; an object-literal accessor descriptor (`get` or `set`) for a declared field is refused at compile time with `SC2020`, and another accessor descriptor for a declared field throws `SC2020` when the call runs. + Freezing, sealing, or preventing extensions of a class instance held in an untyped value throws `SC2020`. + A JavaScript class field that is first created after its instance became non-extensible is added instead of throwing. diff --git a/docs/content/limitations/builtin-apis/test-mocks.mdx b/docs/content/limitations/builtin-apis/test-mocks.mdx index abfdb3657..903a873b9 100644 --- a/docs/content/limitations/builtin-apis/test-mocks.mdx +++ b/docs/content/limitations/builtin-apis/test-mocks.mdx @@ -3,7 +3,8 @@ Class instances, builtin objects, and object literals whose methods use `this` throw `SC2020`. Calls through the object record it as `this`, including calls of a method read off the object and called on its own, where Node records `undefined`. `object.method === mock` is `false`, because the field holds a wrapper of the mock. - `mock.fn()` without an original, `mock.getter`, `mock.setter`, `mock.property`, `mock.module` and mock options throw `SC2020`. + `mock.fn()` without an original returns `undefined` and records the arguments of each call, as in Node; with a function type whose return type does not include `undefined`, such as `mock.fn<() => number>()`, it throws `SC2020`. + `mock.getter`, `mock.setter`, `mock.property`, `mock.module` and mock options throw `SC2020`. - **Mock timers:** `mock.timers.enable`, `tick`, `runAll`, `reset`, `setTime` and `[Symbol.dispose]()` compile, on the module's `mock` and on `t.mock`, for the `setTimeout`, `setInterval`, `setImmediate`, `Date`, `scheduler.wait` and `AbortSignal.timeout` APIs. `enable()` takes no options or an object literal whose `apis`, when given, is an array literal. Options and `apis` lists that are not literals, and the `MockTimers` object used as a value, throw `SC2020`. diff --git a/docs/content/limitations/language-features/function-values.mdx b/docs/content/limitations/language-features/function-values.mdx index 88422c406..75229ff95 100644 --- a/docs/content/limitations/language-features/function-values.mdx +++ b/docs/content/limitations/language-features/function-values.mdx @@ -1,4 +1,4 @@ - **Function values:** Immutable aliases of overloaded program functions retain per-call overload resolution and identity. A function stored in a slot with a different signature remains the same function for equality, array searches, `length`, and own properties. - Typed rest-parameter functions can be stored and called indirectly. + Typed rest-parameter functions can be stored and called indirectly, including through a slot with fixed parameters, where the slot's arguments arrive in the rest array. Builtin function values have API-specific restrictions; see [Builtin function values](#builtin-function-values). diff --git a/docs/content/limitations/module-path-globals.mdx b/docs/content/limitations/module-path-globals.mdx new file mode 100644 index 000000000..358d96f95 --- /dev/null +++ b/docs/content/limitations/module-path-globals.mdx @@ -0,0 +1,4 @@ +`__dirname` and `__filename` are compile-time constants: the absolute path of the module's source directory and file at build time, the same strings as `import.meta.dirname` and `import.meta.filename`. +A `.ts` or `.tsx` file keeps them even when it uses `import` syntax, which Node.js would load as an ES module without these globals, because TypeScript projects are commonly shipped as CommonJS bundles where they exist. +In such a file `typeof __dirname` is `"string"`, where Node.js running the source directly reports `"undefined"`. +JavaScript ES modules (`.mjs`, or `.js` in a `"type": "module"` package) do not have them, as in Node.js. diff --git a/docs/content/limitations/types-and-class-definitions/null-and-optional-values.mdx b/docs/content/limitations/types-and-class-definitions/null-and-optional-values.mdx index e8b351679..be8b07571 100644 --- a/docs/content/limitations/types-and-class-definitions/null-and-optional-values.mdx +++ b/docs/content/limitations/types-and-class-definitions/null-and-optional-values.mdx @@ -1,3 +1,4 @@ - **Null and optional values:** `undefined` and `null` compile as standalone values and union members. Optional record and class fields and optional, default, and rest parameters also compile, including rest parameters typed `unknown[]` or `any[]`, whose functions take any number of arguments. - A function value with fixed parameters cannot be stored where such a variadic function type is expected. + A function value whose fixed parameters are all `unknown` or `any`, or that has none, can be stored where such a variadic function type without fixed parameters is expected: each call passes the arguments in order, `undefined` for the ones the caller left out. + Other function values with fixed parameters cannot be stored there. diff --git a/internal/compatibility/generated/node-v24-backlog.json b/internal/compatibility/generated/node-v24-backlog.json index 0bb3a119d..55483f3b3 100644 --- a/internal/compatibility/generated/node-v24-backlog.json +++ b/internal/compatibility/generated/node-v24-backlog.json @@ -55958,7 +55958,9 @@ "confidence": "high", "source": "compiler-feature:process.'disconnect'", "tests": [ - "tests/corpus/2946-child-fork-ipc/main.ts" + "tests/corpus/2946-child-fork-ipc/main.ts", + "tests/corpus/6420-process-send-presence.ts", + "tests/corpus/6421-process-send-presence-fork/main.ts" ] } } @@ -56000,7 +56002,9 @@ "confidence": "high", "source": "compiler-feature:process.'message'", "tests": [ - "tests/corpus/2946-child-fork-ipc/main.ts" + "tests/corpus/2946-child-fork-ipc/main.ts", + "tests/corpus/6420-process-send-presence.ts", + "tests/corpus/6421-process-send-presence-fork/main.ts" ] } } @@ -56383,7 +56387,9 @@ "confidence": "high", "source": "compiler-feature:process.process.disconnect", "tests": [ - "tests/corpus/2946-child-fork-ipc/main.ts" + "tests/corpus/2946-child-fork-ipc/main.ts", + "tests/corpus/6420-process-send-presence.ts", + "tests/corpus/6421-process-send-presence-fork/main.ts" ] } } @@ -57031,7 +57037,9 @@ "confidence": "high", "source": "compiler-feature:process.process.send", "tests": [ - "tests/corpus/2946-child-fork-ipc/main.ts" + "tests/corpus/2946-child-fork-ipc/main.ts", + "tests/corpus/6420-process-send-presence.ts", + "tests/corpus/6421-process-send-presence-fork/main.ts" ] } } @@ -57485,7 +57493,9 @@ "confidence": "high", "source": "compiler-feature:process.connected", "tests": [ - "tests/corpus/2946-child-fork-ipc/main.ts" + "tests/corpus/2946-child-fork-ipc/main.ts", + "tests/corpus/6420-process-send-presence.ts", + "tests/corpus/6421-process-send-presence-fork/main.ts" ] } } @@ -57600,7 +57610,10 @@ "tests/corpus/2941-process-exit-code.ts", "tests/corpus/2942-process-exit-code-override.ts", "tests/corpus/2943-process-exit-code-invalid.ts", - "tests/corpus/2944-process-exit-code-wrap.ts" + "tests/corpus/2944-process-exit-code-wrap.ts", + "tests/corpus/6422-process-exitcode-read.ts", + "tests/corpus/6423-process-exitcode-default.ts", + "tests/corpus/6424-process-exit-keeps-exitcode.ts" ] } } @@ -66549,6 +66562,7 @@ "source": "compiler-feature:test.mock.fn", "tests": [ "tests/fixtures/node-test/cases/mock-fn.ts", + "tests/fixtures/node-test/cases/mock-fn-default.ts", "tests/harness/node-test.test.ts" ] } @@ -76221,6 +76235,8 @@ "tests/fixtures/node-types/promisify-crypto.ts", "tests/corpus/5632-promisify-dns.ts", "tests/fixtures/node-types/promisify-dns.ts", + "tests/corpus/6428-promisify-callback-functions.ts", + "tests/fixtures/node-types/promisify-callback-functions.ts", "tests/harness/project-config.test.ts" ] } diff --git a/internal/compatibility/generated/node-v24-internal.json b/internal/compatibility/generated/node-v24-internal.json index 0ccdfdce2..903547576 100644 --- a/internal/compatibility/generated/node-v24-internal.json +++ b/internal/compatibility/generated/node-v24-internal.json @@ -79435,6 +79435,11 @@ "tests/corpus/5210-process-memory-usage.ts", "tests/corpus/5211-process-identity.ts", "tests/corpus/5809-process-exit-and-rejections.cjs", + "tests/corpus/6420-process-send-presence.ts", + "tests/corpus/6421-process-send-presence-fork/main.ts", + "tests/corpus/6422-process-exitcode-read.ts", + "tests/corpus/6423-process-exitcode-default.ts", + "tests/corpus/6424-process-exit-keeps-exitcode.ts", "tests/corpus/990-process-basics.ts", "tests/corpus/node24-env-files-errors.ts", "tests/corpus/node24-env-files.ts", @@ -79512,7 +79517,9 @@ "status": "partial", "evidence": "compiler-feature:process.'disconnect'", "tests": [ - "tests/corpus/2946-child-fork-ipc/main.ts" + "tests/corpus/2946-child-fork-ipc/main.ts", + "tests/corpus/6420-process-send-presence.ts", + "tests/corpus/6421-process-send-presence-fork/main.ts" ] }, "anchorSource": "exact" @@ -79551,7 +79558,9 @@ "status": "partial", "evidence": "compiler-feature:process.'message'", "tests": [ - "tests/corpus/2946-child-fork-ipc/main.ts" + "tests/corpus/2946-child-fork-ipc/main.ts", + "tests/corpus/6420-process-send-presence.ts", + "tests/corpus/6421-process-send-presence-fork/main.ts" ] }, "anchorSource": "exact" @@ -79964,7 +79973,9 @@ "status": "partial", "evidence": "compiler-feature:process.process.disconnect", "tests": [ - "tests/corpus/2946-child-fork-ipc/main.ts" + "tests/corpus/2946-child-fork-ipc/main.ts", + "tests/corpus/6420-process-send-presence.ts", + "tests/corpus/6421-process-send-presence-fork/main.ts" ] }, "anchorSource": "exact" @@ -80609,7 +80620,9 @@ "status": "partial", "evidence": "compiler-feature:process.process.send", "tests": [ - "tests/corpus/2946-child-fork-ipc/main.ts" + "tests/corpus/2946-child-fork-ipc/main.ts", + "tests/corpus/6420-process-send-presence.ts", + "tests/corpus/6421-process-send-presence-fork/main.ts" ] }, "anchorSource": "exact" @@ -81028,7 +81041,9 @@ "status": "partial", "evidence": "compiler-feature:process.connected", "tests": [ - "tests/corpus/2946-child-fork-ipc/main.ts" + "tests/corpus/2946-child-fork-ipc/main.ts", + "tests/corpus/6420-process-send-presence.ts", + "tests/corpus/6421-process-send-presence-fork/main.ts" ] }, "anchorSource": "chapter" @@ -81137,7 +81152,10 @@ "tests/corpus/2941-process-exit-code.ts", "tests/corpus/2942-process-exit-code-override.ts", "tests/corpus/2943-process-exit-code-invalid.ts", - "tests/corpus/2944-process-exit-code-wrap.ts" + "tests/corpus/2944-process-exit-code-wrap.ts", + "tests/corpus/6422-process-exitcode-read.ts", + "tests/corpus/6423-process-exitcode-default.ts", + "tests/corpus/6424-process-exit-keeps-exitcode.ts" ] }, "anchorSource": "chapter" @@ -91171,6 +91189,7 @@ "tests/fixtures/node-test/cases/ctx-plan.ts", "tests/fixtures/node-test/cases/get-test-context-cancelled.ts", "tests/fixtures/node-test/cases/get-test-context.ts", + "tests/fixtures/node-test/cases/mock-fn-default.ts", "tests/fixtures/node-test/cases/mock-fn.ts", "tests/fixtures/node-test/cases/mock-method.ts", "tests/fixtures/node-test/cases/mock-timers-date.ts", @@ -92582,6 +92601,7 @@ "evidence": "compiler-feature:test.mock.fn", "tests": [ "tests/fixtures/node-test/cases/mock-fn.ts", + "tests/fixtures/node-test/cases/mock-fn-default.ts", "tests/harness/node-test.test.ts" ] }, @@ -101263,6 +101283,7 @@ "tests/corpus/5832-util-types-brands-dynamic.js", "tests/corpus/5835-util-format-integer-specifiers.ts", "tests/corpus/5884-inspect-layout-options.ts", + "tests/corpus/6428-promisify-callback-functions.ts", "tests/corpus/node24-deep-equality-properties.js", "tests/corpus/node24-deep-equality.cjs", "tests/corpus/node24-deep-equality.ts", @@ -101289,6 +101310,7 @@ "tests/corpus/text-codec-values/main.ts", "tests/fixtures/node-types/deep-equality.ts", "tests/fixtures/node-types/env-files.ts", + "tests/fixtures/node-types/promisify-callback-functions.ts", "tests/fixtures/node-types/promisify-crypto.ts", "tests/fixtures/node-types/promisify-dns.ts", "tests/fixtures/node-types/promisify-fs.ts", @@ -102947,6 +102969,8 @@ "tests/fixtures/node-types/promisify-crypto.ts", "tests/corpus/5632-promisify-dns.ts", "tests/fixtures/node-types/promisify-dns.ts", + "tests/corpus/6428-promisify-callback-functions.ts", + "tests/fixtures/node-types/promisify-callback-functions.ts", "tests/harness/project-config.test.ts" ] }, diff --git a/internal/compatibility/static-support.json b/internal/compatibility/static-support.json index 927870590..3fb7803b1 100644 --- a/internal/compatibility/static-support.json +++ b/internal/compatibility/static-support.json @@ -150,7 +150,7 @@ "node-builtin.process.versions": ["tests/corpus/process-versions-storage.ts"], "node-builtin.process.rows": ["tests/corpus/2992-stream-rows.ts", "tests/harness/terminal-geometry.test.ts"], "node-builtin.process.columns": ["tests/corpus/1448-stream-columns.ts", "tests/harness/terminal-geometry.test.ts"], - "node-builtin.process.exitCode": ["tests/corpus/5809-process-exit-and-rejections.cjs", "tests/corpus/2941-process-exit-code.ts", "tests/corpus/2942-process-exit-code-override.ts", "tests/corpus/2943-process-exit-code-invalid.ts", "tests/corpus/2944-process-exit-code-wrap.ts"], + "node-builtin.process.exitCode": ["tests/corpus/5809-process-exit-and-rejections.cjs", "tests/corpus/2941-process-exit-code.ts", "tests/corpus/2942-process-exit-code-override.ts", "tests/corpus/2943-process-exit-code-invalid.ts", "tests/corpus/2944-process-exit-code-wrap.ts", "tests/corpus/6422-process-exitcode-read.ts", "tests/corpus/6423-process-exitcode-default.ts", "tests/corpus/6424-process-exit-keeps-exitcode.ts"], "node-builtin.punycode": ["tests/corpus/6122-punycode-conversions.ts", "tests/corpus/6124-punycode-require-warnings.cjs", "tests/corpus/6126-punycode-dependency/main.cjs"], "node-builtin.querystring": ["tests/corpus/957-builtins-namespace.ts", "tests/corpus/2917-builtin-callable-widths.ts"], "node-builtin.readline": ["tests/corpus/1475-readline-closed-stdin.ts"], @@ -345,7 +345,7 @@ { "chapter": "fs", "signatures": ["`fsPromises.lstat(path[, options])`"], "status": "partial", "evidence": ["tests/corpus/2941-fsp-lstat-identity.ts"] }, { "chapter": "fs", "signatures": ["`fs.globSync(pattern[, options])`", "`fs.glob(pattern[, options], callback)`", "`fsPromises.glob(pattern[, options])`"], "status": "partial", "evidence": ["tests/corpus/5448-fs-glob-patterns.ts", "tests/corpus/5449-fs-glob-options.ts", "tests/corpus/5450-fs-glob-validation.js", "tests/corpus/5451-fs-promises-glob.ts", "tests/corpus/5452-fs-glob-callback.ts", "tests/fixtures/node-types/fs-glob.ts"] }, { "chapter": "fs", "signatures": ["`fs.cpSync(src, dest[, options])`", "`fs.cp(src, dest[, options], callback)`", "`fsPromises.cp(src, dest[, options])`"], "status": "partial", "evidence": ["tests/corpus/5567-fs-cpsync.ts", "tests/corpus/5568-fs-cp-errors.js", "tests/corpus/5569-fs-cp-symlinks.ts", "tests/corpus/5570-fs-cp-async.ts"] }, - { "chapter": "process", "signatures": ["`process.send(message[, sendHandle[, options]][, callback])`", "`process.disconnect()`", "`connected` Type: {boolean}", "Event: `'message'`", "Event: `'disconnect'`"], "status": "partial", "evidence": ["tests/corpus/2946-child-fork-ipc/main.ts"] }, + { "chapter": "process", "signatures": ["`process.send(message[, sendHandle[, options]][, callback])`", "`process.disconnect()`", "`connected` Type: {boolean}", "Event: `'message'`", "Event: `'disconnect'`"], "status": "partial", "evidence": ["tests/corpus/2946-child-fork-ipc/main.ts", "tests/corpus/6420-process-send-presence.ts", "tests/corpus/6421-process-send-presence-fork/main.ts"] }, { "chapter": "fs", "symbols": ["fs.Stats", "stats.isBlockDevice", "stats.isCharacterDevice", "stats.isDirectory", "stats.isFIFO", "stats.isFile", "stats.isSocket", "stats.isSymbolicLink"], "status": "partial", "evidence": ["tests/corpus/5541-fs-stats-fields.cjs"] }, { "chapter": "fs", "parent": "Class: `fs.Stats`", "signatures": ["`mode` Type: {number|bigint}", "`nlink` Type: {number|bigint}", "`uid` Type: {number|bigint}", "`gid` Type: {number|bigint}", "`rdev` Type: {number|bigint}", "`size` Type: {number|bigint}", "`blksize` Type: {number|bigint}", "`blocks` Type: {number|bigint}", "`atimeMs` Type: {number|bigint}", "`mtimeMs` Type: {number|bigint}", "`birthtimeMs` Type: {number|bigint}", "`atimeNs` Type: {bigint}", "`mtimeNs` Type: {bigint}", "`ctimeNs` Type: {bigint}", "`birthtimeNs` Type: {bigint}", "`atime` Type: {Date}", "`mtime` Type: {Date}", "`ctime` Type: {Date}", "`birthtime` Type: {Date}"], "status": "partial", "evidence": ["tests/corpus/5541-fs-stats-fields.cjs"] }, { "chapter": "fs", "signatures": ["`dev` Type: {number|bigint}", "`ino` Type: {number|bigint}", "`ctimeMs` Type: {number|bigint}"], "status": "partial", "evidence": ["tests/corpus/2941-fsp-lstat-identity.ts"] }, @@ -374,7 +374,7 @@ { "chapter": "test", "parent": "Class: `TestContext`", "symbols": ["context.filePath", "passed", "attempt", "tags", "workerId"], "status": "partial", "evidence": ["tests/fixtures/node-test/cases/ctx-members.ts", "tests/harness/node-test.test.ts"] }, { "chapter": "test", "parent": "Class: `SuiteContext`", "symbols": ["context.name", "context.fullName", "context.filePath", "context.diagnostic", "passed", "attempt"], "status": "partial", "evidence": ["tests/fixtures/node-test/cases/suite-context.ts", "tests/harness/node-test.test.ts"] }, { "chapter": "test", "parent": "Class: `TestContext`", "symbols": ["context.diagnostic", "context.name"], "status": "partial", "evidence": ["tests/harness/node-test.test.ts"] }, - { "chapter": "test", "parent": "Class: `MockTracker`", "symbols": ["mock.fn"], "status": "partial", "evidence": ["tests/fixtures/node-test/cases/mock-fn.ts", "tests/harness/node-test.test.ts"] }, + { "chapter": "test", "parent": "Class: `MockTracker`", "symbols": ["mock.fn"], "status": "partial", "evidence": ["tests/fixtures/node-test/cases/mock-fn.ts", "tests/fixtures/node-test/cases/mock-fn-default.ts", "tests/harness/node-test.test.ts"] }, { "chapter": "test", "parent": "Class: `MockTracker`", "symbols": ["mock.method", "mock.reset", "mock.restoreAll"], "status": "partial", "evidence": ["tests/fixtures/node-test/cases/mock-method.ts", "tests/corpus/5810-mock-method-records.ts", "tests/corpus/5811-mock-tracker-restore.ts", "tests/harness/node-test.test.ts"] }, { "chapter": "test", "parent": "Class: `MockTimers`", "symbols": ["timers.enable", "timers.reset", "timers.tick", "timers.runAll", "timers.setTime"], "status": "partial", "evidence": ["tests/fixtures/node-test/cases/mock-timers.ts", "tests/fixtures/node-test/cases/mock-timers-date.ts", "tests/corpus/5813-mock-timers-module.ts", "tests/corpus/5814-mock-timers-date.ts", "tests/harness/node-test.test.ts"] }, { "chapter": "test", "parent": "Class: `MockTimers`", "signatures": ["`timers[Symbol.dispose]()`"], "status": "partial", "evidence": ["tests/fixtures/node-test/cases/mock-timers-date.ts", "tests/harness/node-test.test.ts"] }, @@ -382,7 +382,7 @@ { "chapter": "test", "parent": "Class: `TestContext`", "symbols": ["context.before", "context.after", "context.beforeEach", "context.afterEach", "context.plan", "context.fullName"], "status": "partial", "evidence": ["tests/fixtures/node-test/cases/ctx-hooks.ts", "tests/fixtures/node-test/cases/ctx-plan.ts", "tests/harness/node-test.test.ts"] }, { "chapter": "test", "symbols": ["getTestContext"], "status": "partial", "evidence": ["tests/fixtures/node-test/cases/get-test-context.ts", "tests/fixtures/node-test/cases/get-test-context-cancelled.ts", "tests/harness/node-test.test.ts"] }, { "chapter": "test", "symbols": ["describe.skip", "describe.todo", "describe.only", "suite.skip", "suite.todo", "suite.only", "it.skip", "it.todo", "it.only"], "status": "partial", "evidence": ["tests/fixtures/node-test/cases/suite-variants.ts", "tests/fixtures/node-test/cases/only-nested.ts", "tests/fixtures/node-test/cases/todo-suites.ts", "tests/harness/node-test.test.ts"] }, - { "chapter": "util", "signatures": ["`util.promisify(original)`"], "status": "partial", "evidence": ["tests/corpus/1473-promisify-execfile.ts", "tests/corpus/1573-promisify-execfile-env-spread.ts", "tests/corpus/2912-promisify-fs-readfile.ts", "tests/corpus/5625-promisify-exec.ts", "tests/corpus/5626-promisify-forms.ts", "tests/fixtures/node-types/promisify-fs.ts", "tests/corpus/5628-promisify-timers.ts", "tests/fixtures/node-types/promisify-timers.ts", "tests/corpus/5629-promisify-stream.ts", "tests/corpus/5630-promisify-zlib.ts", "tests/fixtures/node-types/promisify-zlib.ts", "tests/corpus/5631-promisify-crypto.ts", "tests/fixtures/node-types/promisify-crypto.ts", "tests/corpus/5632-promisify-dns.ts", "tests/fixtures/node-types/promisify-dns.ts", "tests/harness/project-config.test.ts"] }, + { "chapter": "util", "signatures": ["`util.promisify(original)`"], "status": "partial", "evidence": ["tests/corpus/1473-promisify-execfile.ts", "tests/corpus/1573-promisify-execfile-env-spread.ts", "tests/corpus/2912-promisify-fs-readfile.ts", "tests/corpus/5625-promisify-exec.ts", "tests/corpus/5626-promisify-forms.ts", "tests/fixtures/node-types/promisify-fs.ts", "tests/corpus/5628-promisify-timers.ts", "tests/fixtures/node-types/promisify-timers.ts", "tests/corpus/5629-promisify-stream.ts", "tests/corpus/5630-promisify-zlib.ts", "tests/fixtures/node-types/promisify-zlib.ts", "tests/corpus/5631-promisify-crypto.ts", "tests/fixtures/node-types/promisify-crypto.ts", "tests/corpus/5632-promisify-dns.ts", "tests/fixtures/node-types/promisify-dns.ts", "tests/corpus/6428-promisify-callback-functions.ts", "tests/fixtures/node-types/promisify-callback-functions.ts", "tests/harness/project-config.test.ts"] }, { "chapter": "typescript", "symbols": ["type"], "status": "supported", "evidence": ["tests/corpus/2702-type-import-link-static/main.ts", "tests/corpus/2703-type-import-link-static-reexport/main.ts", "tests/corpus/2704-type-import-link-static-success/main.ts", "tests/corpus/2707-type-import-link-static-default-reexport/main.ts", "tests/harness/errors.test.ts"] }, { "chapter": "zlib", "signatures": ["Class: `zlib.Deflate`", "Class: `zlib.Inflate`", "Class: `zlib.Gzip`", "Class: `zlib.Gunzip`", "Class: `zlib.DeflateRaw`", "Class: `zlib.InflateRaw`", "Class: `zlib.Unzip`", "Class: `zlib.ZlibBase`", "`zlib.close([callback])`", "`zlib.flush([kind, ]callback)`", "`zlib.params(level, strategy, callback)`", "`zlib.reset()`", "`bytesWritten` Type: {number}", "Class: `Options`"], "status": "partial", "evidence": ["tests/corpus/5370-zlib-stream-roundtrip.ts", "tests/corpus/5371-zlib-stream-chunking.cjs", "tests/corpus/5372-zlib-stream-errors.cjs", "tests/corpus/5373-zlib-stream-options.cjs", "tests/corpus/5374-zlib-stream-flush-params.ts", "tests/corpus/5375-zlib-stream-promises.ts", "tests/corpus/5377-zlib-stream-backpressure.cjs"] }, { "chapter": "zlib", "signatures": ["`zlib.constants`"], "status": "partial", "evidence": ["tests/corpus/5376-zlib-constants.cjs", "tests/corpus/5374-zlib-stream-flush-params.ts"] } diff --git a/packages/compiler/ambient/scriptc-node-fallback.d.ts b/packages/compiler/ambient/scriptc-node-fallback.d.ts index 2e3691cd6..d733aef19 100644 --- a/packages/compiler/ambient/scriptc-node-fallback.d.ts +++ b/packages/compiler/ambient/scriptc-node-fallback.d.ts @@ -3401,6 +3401,13 @@ declare module "util" { info: import("node:crypto").BinaryLike, keylen: number, ) => Promise; + /* A callback-only function (`obj.close.bind(obj)`): before the targets + * whose leading parameters would otherwise capture it. Builtins that take + * arguments before their callback never match these. */ + export function promisify( + fn: (callback: (err: any, result: TResult) => void) => void, + ): () => Promise; + export function promisify(fn: (callback: (err?: any) => void) => void): () => Promise; export function promisify( fn: typeof import("node:crypto").randomFill, ): ( diff --git a/packages/compiler/src/backend/llvm/byte-numbers.ts b/packages/compiler/src/backend/llvm/byte-numbers.ts index c51d03744..9001e015d 100644 --- a/packages/compiler/src/backend/llvm/byte-numbers.ts +++ b/packages/compiler/src/backend/llvm/byte-numbers.ts @@ -4,6 +4,7 @@ import type { LlValue, LlvmEmitterContext } from "./expr-context.js"; import { f64Lit } from "./common.js"; import { exactInteger, widenInteger } from "./integer-values.js"; import { emitTruncF64 } from "./trunc.js"; +import { bytesElementAlias, bytesHeaderAlias } from "./bytes-alias.js"; /** Supported LLVM targets are little-endian. All memory accesses use align * 1: Buffer fields and DataView windows can start at arbitrary byte offsets. */ @@ -79,7 +80,7 @@ export function emitByteNumber( capacity = B.tmp(); room = B.tmp(); B.line(`${lengthPtr} = getelementptr inbounds %ScrBytes, ptr ${receiver.name}, i64 0, i32 1`); - B.line(`${length} = load ${size}, ptr ${lengthPtr}`); + B.line(`${length} = load ${size}, ptr ${lengthPtr}${bytesHeaderAlias(host)}`); B.line(`${room} = icmp uge ${size} ${length}, ${spec.width}`); B.line(`${capacity} = sub ${size} ${length}, ${spec.width}`); if (integer) { @@ -221,14 +222,14 @@ export function emitByteNumber( } } const stored = endianBits(host, raw, spec.width, little); - B.line(`store i${bits} ${stored}, ptr ${pointer}, align 1`); + B.line(`store i${bits} ${stored}, ptr ${pointer}, align 1${bytesElementAlias(host)}`); if (!spec.dataView) { fastResult = B.tmp(); B.line(`${fastResult} = fadd double ${offset.name}, ${f64Lit(spec.width)}`); } } else { const loaded = B.tmp(); - B.line(`${loaded} = load i${bits}, ptr ${pointer}, align 1`); + B.line(`${loaded} = load i${bits}, ptr ${pointer}, align 1${bytesElementAlias(host)}`); const raw = endianBits(host, loaded, spec.width, little); fastResult = B.tmp(); if (spec.floating) { diff --git a/packages/compiler/src/backend/llvm/bytes-alias.ts b/packages/compiler/src/backend/llvm/bytes-alias.ts new file mode 100644 index 000000000..9aaeaa6c7 --- /dev/null +++ b/packages/compiler/src/backend/llvm/bytes-alias.ts @@ -0,0 +1,23 @@ +import type { LlvmEmitterContext } from "./expr-context.js"; + +/** Alias categories of typed-array storage. A ScrBytes header (length, + * data pointer, shared backing) is fixed at construction and only ever + * written by runtime code, never by emitted code. Element storage is one + * category for every element type: views of different types may share a + * buffer. Neither overlaps the other, a native field, array storage or a + * module global's slot, so element stores leave header loads and global + * receiver slots loop-invariant. Untagged accesses and calls still alias + * every category. */ +export function bytesHeaderAlias(host: LlvmEmitterContext): string { + return host.memoryAliasAttachment("bytes:header"); +} + +export function bytesElementAlias(host: LlvmEmitterContext): string { + return host.memoryAliasAttachment("bytes:elements"); +} + +/** A module global's slot is its own category for whole-slot loads of a + * scalar or typed-array binding. Stores stay untagged (and so alias it). */ +export function globalSlotAlias(host: LlvmEmitterContext, id: string, type: string): string { + return type === "bytes" || type === "f64" ? host.memoryAliasAttachment(`global:${id}`) : ""; +} diff --git a/packages/compiler/src/backend/llvm/bytes-loop-versions.test.ts b/packages/compiler/src/backend/llvm/bytes-loop-versions.test.ts new file mode 100644 index 000000000..ddcfd667d --- /dev/null +++ b/packages/compiler/src/backend/llvm/bytes-loop-versions.test.ts @@ -0,0 +1,278 @@ +import { expect, test } from "vitest"; +import { + BOOL, + F64, + VOID, + type IrExpr, + type IrFunction, + type IrLocal, + type IrModule, + type IrStmt, + type IrType, +} from "../../ir/ir.js"; +import { analyzeIntegerRanges } from "../../ir/integer-ranges.js"; +import { matchIntegerCountedForLoop } from "../../ir/integer-loops.js"; +import { findInitializedByteLoopBindings } from "./byte-windows.js"; +import { matchBytesLoopVersion, type BytesLoopVersion } from "./bytes-loop-versions.js"; +import { emitLlvmModule } from "./emitter.js"; + +const loc = { file: "versions.ts", start: 0, end: 0 }; +const f64s: IrType = { kind: "bytes", elem: "f64" }; +const i32s: IrType = { kind: "bytes", elem: "i32" }; +const num = (value: number): IrExpr => ({ kind: "numLit", value, type: F64, loc }); +const ref = (localId: string, type: IrType = F64): IrExpr => ({ + kind: "varRef", + localId, + type, + loc, +}); +const bin = (op: string, left: IrExpr, right: IrExpr): IrExpr => + ({ kind: "bin", op, left, right, type: F64, loc }) as IrExpr; +const get = (receiver: IrExpr, index: IrExpr): IrExpr => ({ + kind: "bytesIntrinsic", + method: "get", + receiver, + args: [index], + invalidNaN: true, + type: F64, + loc, +}); +const set = (arr: IrExpr, index: IrExpr, value: IrExpr): IrStmt => ({ + kind: "bytesSet", + arr, + index, + value, + loc, +}); +const counted = ( + body: IrStmt[], + limit: IrExpr = ref("n"), + counter = "i", +): IrStmt & { kind: "for" } => ({ + kind: "for", + init: { kind: "varDecl", localId: counter, init: num(0), loc }, + cond: { kind: "bin", op: "<", left: ref(counter), right: limit, type: BOOL, loc }, + update: { kind: "assign", localId: counter, value: bin("+", ref(counter), num(1)), loc }, + body, + loc, +}); +const local = ( + id: string, + type: IrType, + mutable = false, + extra: Partial = {}, +): IrLocal => ({ + id, + name: id, + type, + mutable, + ...extra, +}); + +function fn(body: IrStmt[], extra: IrLocal[] = []): IrFunction { + return { + name: "kernel", + params: [ + { localId: "out", name: "out", type: f64s }, + { localId: "src", name: "src", type: f64s }, + { localId: "table", name: "table", type: i32s }, + { localId: "n", name: "n", type: F64 }, + { localId: "k", name: "k", type: F64 }, + ], + returnType: VOID, + locals: [ + local("out", f64s), + local("src", f64s), + local("table", i32s), + local("n", F64), + local("k", F64), + local("i", F64, true), + local("j", F64, true), + ...extra, + ], + body, + loc, + }; +} + +function match( + f: IrFunction, + loop: IrStmt & { kind: "for" }, + captured: string[] = [], +): BytesLoopVersion | null { + const ranges = analyzeIntegerRanges(f); + const locals = new Map(f.locals.map((l) => [l.id, l])); + return matchBytesLoopVersion(loop, matchIntegerCountedForLoop(loop, locals, ranges), { + locals, + invariantGlobals: new Set(), + captured: new Set(captured), + ranges, + initialized: findInitializedByteLoopBindings(f).get(loop) ?? new Set(), + proven: new Set(), + }); +} + +test("a counted loop's affine accesses get one guard and exact operand ranges", () => { + const shifted = bin("+", ref("i"), num(1)); + const loop = counted([set(ref("out", f64s), ref("i"), get(ref("src", f64s), shifted))]); + const version = match(fn([loop]), loop)!; + expect(version).not.toBeNull(); + expect(version.bounds.size).toBe(2); + expect(version.receivers.map((r) => r.ref.kind === "varRef" && r.ref.localId)).toEqual([ + "out", + "src", + ]); + expect(version.affine.map((c) => [c.scale, c.minOffset, c.maxEnd])).toEqual([ + [1, 0, 0], + [1, 1, 1], + ]); + // `n` is unproven: the guard bounds the counter so `i + 1` stays exact. + expect(version.counted?.limitGuarded).toBe(true); + expect(version.counterBound).toBe(Number.MAX_SAFE_INTEGER - 1); + expect(version.ranges.get(shifted)).toEqual({ min: 1, max: Number.MAX_SAFE_INTEGER }); +}); + +test("statically ranged indices compare one constant with the length", () => { + const masked = bin("&", bin("^", ref("i"), ref("k")), num(255)); + const loop = counted([set(ref("out", f64s), num(0), get(ref("table", i32s), masked))]); + const version = match(fn([loop]), loop)!; + expect(version.fixed.map((c) => [version.receivers[c.receiver]!.ref, c.end])).toEqual([ + [ref("out", f64s), 0], + [ref("table", i32s), 255], + ]); + expect(version.affine).toEqual([]); + expect(version.counted).toBeNull(); +}); + +test("scaled counters and body temps resolve to their receiver and index", () => { + const temps = [local("view", f64s), local("at", F64)]; + const at = bin("+", bin("*", ref("i"), num(4)), num(3)); + const loop = counted([ + { kind: "varDecl", localId: "view", init: ref("src", f64s), loc }, + { kind: "varDecl", localId: "at", init: at, loc }, + set(ref("view", f64s), ref("at"), num(1)), + // (The integer counted loop needs one direct counter index.) + set(ref("out", f64s), ref("i"), num(0)), + ]); + const version = match(fn([loop], temps), loop)!; + expect(version.receivers.map((r) => r.ref)).toEqual([ref("src", f64s), ref("out", f64s)]); + expect(version.affine.map((c) => [c.scale, c.minOffset, c.maxEnd])).toEqual([ + [4, 3, 3], + [1, 0, 0], + ]); + expect(version.ranges.has(at)).toBe(true); +}); + +test("negative factors, rebinding, captures and outer loops are not versioned", () => { + // `i * -1` is -0 at zero, so it is not treated as an affine operand. + const negative = counted([set(ref("out", f64s), bin("*", ref("i"), num(-1)), num(1))]); + expect(match(fn([negative]), negative)).toBeNull(); + const rebound = counted([ + set(ref("out", f64s), ref("i"), num(1)), + { kind: "assign", localId: "out", value: ref("src", f64s), loc }, + ]); + expect(match(fn([rebound]), rebound)).toBeNull(); + const plain = counted([set(ref("out", f64s), ref("i"), num(1))]); + expect(match(fn([plain]), plain, ["out"])).toBeNull(); + const inner = counted([set(ref("out", f64s), ref("j"), num(1))], ref("n"), "j"); + const outer = counted([inner]); + expect(match(fn([outer]), outer)).toBeNull(); + expect(match(fn([outer]), inner)).not.toBeNull(); +}); + +test("emitted loops version the body and tag typed-array memory", () => { + const loop = counted([ + set(ref("out", f64s), ref("i"), bin("*", get(ref("src", f64s), ref("i")), num(2))), + ]); + const kernel = fn([loop]); + const mod: IrModule = { + irVersion: 17, + sourceFile: loc.file, + entry: "__main", + functions: [ + { name: "__main", params: [], returnType: VOID, locals: [], body: [], loc }, + kernel, + ], + }; + const llvm = emitLlvmModule(mod); + const body = /^define internal [^\n]*@sc_bf_kernel[^]*?^}/m.exec(llvm)![0]; + expect(body).toContain("bytes.version.fast"); + expect(body).toContain("bytes.version.slow"); + // The checked copy keeps its per-element tests; the guard reads lengths. + expect(body).toContain("bytes.index.invalid"); + expect(body).toMatch(/store double [^\n]*, align 1, !tbaa !\d+/); + expect(body).toMatch(/load double, ptr [^\n]*, align 1, !tbaa !\d+/); + expect(body).toMatch(/load i64, ptr [^\n]*, !tbaa !\d+/); + expect(body).toMatch(/load ptr, ptr [^\n]*, !tbaa !\d+/); + const elements = /store double [^\n]*!tbaa !(\d+)/.exec(body)![1]; + const header = /load ptr, ptr [^\n]*!tbaa !(\d+)/.exec(body)![1]; + expect(elements).not.toBe(header); +}); + +test("Float16Array accesses version in elements and share the element tag", () => { + const f16s: IrType = { kind: "bytes", elem: "f16" }; + const withHalves = (body: IrStmt[]): IrFunction => { + const f = fn(body, [local("half", f16s)]); + return { ...f, params: [...f.params, { localId: "half", name: "half", type: f16s }] }; + }; + // half[i] = src[i + 1]; out[i] = half[i] * 2 + const loop = counted([ + set(ref("half", f16s), ref("i"), get(ref("src", f64s), bin("+", ref("i"), num(1)))), + set(ref("out", f64s), ref("i"), bin("*", get(ref("half", f16s), ref("i")), num(2))), + ]); + const kernel = withHalves([loop]); + const version = match(kernel, loop)!; + expect(version.bounds.size).toBe(4); + expect(version.receivers.map((r) => r.ref)).toEqual([ + ref("half", f16s), + ref("src", f64s), + ref("out", f64s), + ]); + // Element offsets against the element count, whatever the element size. + expect(version.affine.map((c) => [c.scale, c.minOffset, c.maxEnd])).toEqual([ + [1, 0, 0], + [1, 1, 1], + [1, 0, 0], + ]); + + const mod: IrModule = { + irVersion: 17, + sourceFile: loc.file, + entry: "__main", + functions: [ + { name: "__main", params: [], returnType: VOID, locals: [], body: [], loc }, + kernel, + ], + }; + const body = /^define internal [^\n]*@sc_bf_kernel[^]*?^}/m.exec(emitLlvmModule(mod))![0]; + const fast = /^bytes\.version\.fast\d+:$[^]*?^bytes\.version\.done\d+:$/m.exec(body)![0]; + expect(fast).toContain("call double @scr_f16_to_f64"); + expect(fast).toContain("call zeroext i16 @scr_f16_from_f64"); + expect(fast).not.toContain("bytes.index"); + const halfStore = /store i16 [^\n]*, align 1, !tbaa !(\d+)/.exec(fast)![1]; + const halfLoad = /load i16, ptr [^\n]*, align 1, !tbaa !(\d+)/.exec(fast)![1]; + const doubleStore = /store double [^\n]*, align 1, !tbaa !(\d+)/.exec(fast)![1]; + // Views of any element type may share a buffer: one element category. + expect(halfStore).toBe(doubleStore); + expect(halfLoad).toBe(doubleStore); + // The checked copy keeps its per-element tests. + expect(body.slice(0, body.indexOf(fast))).toContain("bytes.index.invalid"); +}); + +test("bodies compiled without optimization keep one checked loop", () => { + const loop = counted([set(ref("out", f64s), ref("i"), get(ref("src", f64s), ref("i")))]); + const boxes = Array.from({ length: 512 }, (_, k) => local(`box${k}`, F64, true, { boxed: true })); + const mod: IrModule = { + irVersion: 17, + sourceFile: loc.file, + entry: "__main", + functions: [ + { name: "__main", params: [], returnType: VOID, locals: [], body: [], loc }, + fn([loop], boxes), + ], + }; + const body = /^define internal [^\n]*@sc_bf_kernel[^]*?^}/m.exec(emitLlvmModule(mod))![0]; + expect(body.split("\n", 1)[0]).toContain("optnone"); + expect(body).not.toContain("bytes.version"); + expect(body).toContain("bytes.index.invalid"); +}); diff --git a/packages/compiler/src/backend/llvm/bytes-loop-versions.ts b/packages/compiler/src/backend/llvm/bytes-loop-versions.ts new file mode 100644 index 000000000..df8977426 --- /dev/null +++ b/packages/compiler/src/backend/llvm/bytes-loop-versions.ts @@ -0,0 +1,578 @@ +import type { IrExpr, IrLocal, IrStmt } from "../../ir/ir.js"; +import type { IntegerRange, IntegerRanges } from "../../ir/integer-ranges.js"; +import type { IntegerCountedForLoop } from "../../ir/integer-loops.js"; +import type { LlvmEmitterContext } from "./expr-context.js"; +import { byteNumberAccess } from "../../ir/byte-numbers.js"; +import { everyExprChild, everyStmtChild } from "../../ir/traverse.js"; +import { exactInteger, widenInteger } from "./integer-values.js"; +import { emitCountedLoopLimit, emitCountedLoopSafety } from "./counted-loops.js"; +import { emitBytesHeaderLength } from "./expr-bytes.js"; + +type Node = IrExpr | IrStmt; +type VarRef = IrExpr & { kind: "varRef" }; + +/** One loop-invariant integer operand of an affine index. */ +interface Term { + expr: IrExpr; + /** 1 or -1. */ + sign: number; +} + +interface KeyedTerm extends Term { + key: string; +} + +/** Every access of `receiver` at a static index range ends below `end + 1`. */ +interface FixedCheck { + receiver: number; + end: number; +} + +/** Every access of `receiver` at `scale * counter + Σ terms + offset` has an + * offset in [minOffset, maxEnd - width + 1]; `maxEnd` includes the width. */ +interface AffineCheck { + receiver: number; + scale: number; + terms: KeyedTerm[]; + minOffset: number; + maxEnd: number; +} + +export interface BytesLoopVersion { + /** Typed-array accesses that are in bounds whenever the guard holds. */ + bounds: ReadonlySet; + /** Root receiver bindings, read once before the loop. */ + receivers: { ref: VarRef; global: boolean }[]; + fixed: FixedCheck[]; + affine: AffineCheck[]; + /** The counted loop whose iteration space the affine checks cover. */ + counted: IntegerCountedForLoop | null; + /** The guard also requires every counter value within [-bound, bound]. */ + counterBound: number | null; + /** Integer ranges of affine index operands that hold in the unchecked + * loop (where the guard bounds the counter). */ + ranges: ReadonlyMap; +} + +export interface BytesLoopFacts { + /** The function's own locals (not module globals). */ + locals: ReadonlyMap; + /** Module globals whose slot no call can replace: stable constants and + * the private bindings of a guarded initializer. */ + invariantGlobals: ReadonlySet; + captured: ReadonlySet; + ranges: IntegerRanges; + /** Locals definitely initialized when the loop is entered. */ + initialized: ReadonlySet; + /** Accesses already proved in bounds without a guard. */ + proven: ReadonlySet; +} + +const MAX_COST = 400; +const MAX_FIXED_END = 2 ** 32; +const MAX_CHECKS = 8; +const MAX_OFFSET = 2 ** 31; +const SAFE = Number.MAX_SAFE_INTEGER; +const INVARIANT_OPS = new Set(["+", "-", "*", "&", "|", "^", "<<", ">>", ">>>"]); + +/** Loop versioning for typed-array bounds. A typed array's length and data + * pointer are fixed at construction (ScrBytes has no resize, transfer or + * detach), so an access is in bounds for the whole loop when its receiver + * binding and index operands cannot change while it runs. One test before + * the loop then covers every iteration: + * + * - a statically ranged integer index `[lo, hi]` (lo >= 0) needs + * `hi < length`, such as `table[x & 0xff]` or a constant-trip counter; + * - an index `k * counter + invariant + c` of a counted loop needs its first + * and last values in bounds, such as `out[base + i]` for `i < n` or + * `view.getUint32(i * 4)`. + * + * Only small synchronous loops are versioned (nested loops included), and + * the emitter bounds how deeply versions nest. The checked loop runs + * whenever the guard fails, and keeps every out-of-bounds read, ignored + * write and NaN-index behavior. */ +export function matchBytesLoopVersion( + loop: IrStmt & { kind: "for" }, + counted: IntegerCountedForLoop | null, + facts: BytesLoopFacts, +): BytesLoopVersion | null { + const written = new Set(); + const temps = new Map(); + let cost = 0; + const scanExpr = (e: IrExpr): boolean => { + if (++cost > MAX_COST) return false; + if (e.kind === "assignExpr" || e.kind === "incDec") written.add(e.localId); + return everyExprChild(e, scanExpr, scanStmt); + }; + const scanStmt = (s: IrStmt): boolean => { + if (++cost > MAX_COST) return false; + switch (s.kind) { + case "forOf": + written.add(s.localId); + break; + case "varDecl": { + if (temps.has(s.localId) || written.has(s.localId)) temps.delete(s.localId); + else if (s.init) temps.set(s.localId, s.init); + written.add(s.localId); + break; + } + case "assign": + written.add(s.localId); + break; + case "tryCatch": + if (s.catchLocalId) written.add(s.catchLocalId); + break; + default: + break; + } + return everyStmtChild(s, scanExpr, scanStmt); + }; + // The guard runs before the initializer, so a binding the initializer + // writes is not invariant either: `for (let i = (a = b, 0); ...)` must + // not keep the bounds the guard proved for the old `a`. + if ( + (loop.init !== null && !scanStmt(loop.init)) || + !loop.body.every(scanStmt) || + (loop.cond !== null && !scanExpr(loop.cond)) || + (loop.update !== null && !scanStmt(loop.update)) + ) + return null; + // A body temp stands for its initializer only when it is a constant + // binding assigned exactly once, by that declaration. + const declared = new Map(); + const countDecls = (s: IrStmt): boolean => { + if (s.kind === "varDecl") declared.set(s.localId, (declared.get(s.localId) ?? 0) + 1); + return everyStmtChild(s, countExprDecls, countDecls); + }; + const countExprDecls = (e: IrExpr): boolean => everyExprChild(e, countExprDecls, countDecls); + loop.body.every(countDecls); + const temp = (id: string): IrExpr | null => { + const local = facts.locals.get(id); + const init = temps.get(id); + if ( + !init || + !local || + local.mutable || + local.boxed || + local.tdz || + facts.captured.has(id) || + declared.get(id) !== 1 + ) + return null; + return init; + }; + const counter = counted?.localId ?? null; + // A binding keeps its loop-entry value for the whole loop. + const invariant = (id: string): boolean => { + if (written.has(id) || id === counter) return false; + const local = facts.locals.get(id); + if (local) + return !local.boxed && !local.tdz && !facts.captured.has(id) && facts.initialized.has(id); + return facts.invariantGlobals.has(id); + }; + const resolving = new Set(); + const root = (e: IrExpr): VarRef | null => { + if (e.kind !== "varRef" || e.type.kind !== "bytes") return null; + const init = temp(e.localId); + if (init) { + if (resolving.has(e.localId)) return null; + resolving.add(e.localId); + const result = root(init); + resolving.delete(e.localId); + return result; + } + return invariant(e.localId) ? e : null; + }; + // A pure integer operand evaluated again before the loop. + const invariantKey = (e: IrExpr): string | null => { + if (!facts.ranges.get(e)) return null; + if (e.kind === "numLit") return Number.isSafeInteger(e.value) ? `n${e.value}` : null; + if (e.kind === "varRef") + return invariant(e.localId) ? `v${e.localId.length}:${e.localId}` : null; + if ( + e.kind === "bin" && + INVARIANT_OPS.has(e.op) && + e.left.type.kind === "f64" && + e.right.type.kind === "f64" + ) { + const left = invariantKey(e.left), + right = invariantKey(e.right); + return left !== null && right !== null ? `(${e.op} ${left} ${right})` : null; + } + return null; + }; + interface Affine { + /** The counter's coefficient; 0 when the value is loop-invariant. */ + counter: number; + terms: KeyedTerm[]; + offset: number; + } + // Every operand of an affine index is `counter * k + Σ terms + offset`. + // Invariant terms need proven ranges. Operands that involve the counter + // need none: the guard bounds the counter so that each of them is a safe + // integer, which makes the index's double arithmetic exact. + const shapes = new Map(); + const affine = (e: IrExpr): Affine | null => { + if (shapes.has(e)) return shapes.get(e)!; + const result = affineShape(e); + shapes.set(e, result); + return result; + }; + const affineShape = (e: IrExpr): Affine | null => { + if (e.kind === "numLit") + return Number.isSafeInteger(e.value) && Math.abs(e.value) <= MAX_OFFSET + ? { counter: 0, terms: [], offset: e.value } + : null; + if (e.kind === "varRef") { + if (e.localId === counter) return { counter: 1, terms: [], offset: 0 }; + const init = temp(e.localId); + if (init) { + if (resolving.has(e.localId)) return null; + resolving.add(e.localId); + const result = affine(init); + resolving.delete(e.localId); + return result; + } + } + if ( + e.kind === "bin" && + (e.op === "+" || e.op === "-") && + e.left.type.kind === "f64" && + e.right.type.kind === "f64" + ) { + const left = affine(e.left), + right = affine(e.right); + if (left && right && (left.counter === 0 || right.counter === 0)) { + const sign = e.op === "+" ? 1 : -1; + const offset = left.offset + sign * right.offset; + if (Math.abs(offset) > MAX_OFFSET) return null; + const terms = [ + ...left.terms, + ...right.terms.map((t) => ({ expr: t.expr, sign: sign * t.sign, key: t.key })), + ]; + const result = { counter: left.counter + sign * right.counter, terms, offset }; + if (result.counter !== 0 || facts.ranges.get(e)) return result; + } + } + // A positive constant multiple of the counter: `i * 4`, `4 * i`, + // `(i + 1) * 8`. (A negative factor turns a zero into -0.) + if (e.kind === "bin" && e.op === "*") { + const rightLiteral = e.right.kind === "numLit"; + const scaled = rightLiteral ? e.left : e.right; + const literal = rightLiteral ? e.right : e.left; + const factor = literal.kind === "numLit" ? literal.value : 0; + if ( + literal.kind === "numLit" && + scaled.type.kind === "f64" && + Number.isSafeInteger(factor) && + factor > 0 && + factor <= MAX_OFFSET + ) { + const inner = affine(scaled); + if (inner && inner.counter !== 0 && inner.terms.length === 0) { + const counterScale = inner.counter * factor, + offset = inner.offset * factor; + if (Math.abs(counterScale) <= MAX_OFFSET && Math.abs(offset) <= MAX_OFFSET) + return { counter: counterScale, terms: [], offset }; + } + } + } + const key = invariantKey(e); + return key !== null ? { counter: 0, terms: [{ expr: e, sign: 1, key }], offset: 0 } : null; + }; + // The operands of an index that involve the counter. + const chain = (e: IrExpr, out: Set): void => { + if (!shapes.get(e)?.counter) return; + out.add(e); + if (e.kind === "varRef") { + const init = temp(e.localId); + if (init) chain(init, out); + } else if (e.kind === "bin") { + chain(e.left, out); + chain(e.right, out); + } + }; + const termMagnitude = (terms: Term[]): number => + terms.reduce((sum, t) => { + const r = facts.ranges.get(t.expr)!; + return sum + Math.max(Math.abs(r.min), Math.abs(r.max)); + }, 0); + + const receivers: { ref: VarRef; global: boolean }[] = []; + const receiverIndex = new Map(); + const fixed = new Map(); + const affineChecks = new Map< + string, + { check: AffineCheck; nodes: Node[]; operands: Set } + >(); + const receiverOf = (ref: VarRef): number => { + let index = receiverIndex.get(ref.localId); + if (index === undefined) { + index = receivers.length; + receiverIndex.set(ref.localId, index); + receivers.push({ ref, global: !facts.locals.has(ref.localId) }); + } + return index; + }; + const access = (node: Node, array: IrExpr, index: IrExpr, width: number): void => { + if (facts.proven.has(node)) return; + const ref = root(array); + if (!ref) return; + const shape = counter !== null ? affine(index) : null; + if (shape && shape.counter !== 0) { + const operands = new Set(); + chain(index, operands); + const terms = [...shape.terms].sort((a, b) => + a.key < b.key ? -1 : a.key > b.key ? 1 : a.sign - b.sign, + ); + const receiver = receiverOf(ref); + const key = `${receiver}|${shape.counter}|${terms.map((t) => `${t.sign}${t.key}`).join(",")}`; + const end = shape.offset + width - 1; + const group = affineChecks.get(key); + if (group) { + group.check.minOffset = Math.min(group.check.minOffset, shape.offset); + group.check.maxEnd = Math.max(group.check.maxEnd, end); + group.nodes.push(node); + for (const operand of operands) group.operands.add(operand); + } else + affineChecks.set(key, { + check: { receiver, scale: shape.counter, terms, minOffset: shape.offset, maxEnd: end }, + nodes: [node], + operands, + }); + return; + } + const range = facts.ranges.get(index); + if (!range) return; + const end = range.max + width - 1; + // Only a length a buffer can have: an outer loop must not pay for a + // guard that bounds an inner loop's unbounded counter. + if (range.min < 0 || end >= MAX_FIXED_END) return; + const receiver = receiverOf(ref); + const group = fixed.get(receiver); + if (group) { + group.check.end = Math.max(group.check.end, end); + group.nodes.push(node); + } else fixed.set(receiver, { check: { receiver, end }, nodes: [node] }); + }; + const visitExpr = (e: IrExpr): boolean => { + if (e.kind === "bytesIntrinsic") { + const numeric = byteNumberAccess(e); + if (numeric) access(e, e.receiver, e.args[numeric.offsetArg]!, numeric.width); + else if (e.method === "get" && e.args[0]) access(e, e.receiver, e.args[0], 1); + } + return everyExprChild(e, visitExpr, visitStmt); + }; + const visitStmt = (s: IrStmt): boolean => { + if (s.kind === "bytesSet") access(s, s.arr, s.index, 1); + return everyStmtChild(s, visitExpr, visitStmt); + }; + loop.body.every(visitStmt); + // Bound the counter so every counter operand stays a safe integer. + let bound = SAFE; + const affineGroups = [...affineChecks.values()].filter((group) => { + let limit = SAFE; + for (const operand of group.operands) { + const shape = shapes.get(operand)!; + const room = SAFE - termMagnitude(shape.terms) - Math.abs(shape.offset); + limit = Math.min(limit, Math.floor(room / Math.abs(shape.counter))); + } + if (limit < 0 || !counted) return false; + bound = Math.min(bound, limit); + return true; + }); + const fixedKept = [...fixed.values()].slice(0, MAX_CHECKS); + const affineKept = affineGroups.slice(0, MAX_CHECKS - fixedKept.length); + if (fixedKept.length === 0 && affineKept.length === 0) return null; + const bounds = new Set(); + const used = new Set(); + const ranges = new Map(); + const counterLow = Math.max(counted?.range.min ?? 0, -bound), + counterHigh = Math.min(counted?.range.max ?? 0, bound); + for (const group of fixedKept) { + used.add(group.check.receiver); + for (const node of group.nodes) bounds.add(node); + } + for (const group of affineKept) { + used.add(group.check.receiver); + for (const node of group.nodes) bounds.add(node); + for (const operand of group.operands) { + if (facts.ranges.get(operand)) continue; + const shape = shapes.get(operand)!; + const a = shape.counter * counterLow, + b = shape.counter * counterHigh; + let min = Math.min(a, b) + shape.offset, + max = Math.max(a, b) + shape.offset; + for (const term of shape.terms) { + const r = facts.ranges.get(term.expr)!; + min += term.sign > 0 ? r.min : -r.max; + max += term.sign > 0 ? r.max : -r.min; + } + if (counterLow <= counterHigh) ranges.set(operand, { min, max }); + } + } + // Renumber the receivers the kept checks use. + const order = [...used].sort((a, b) => a - b); + const renumber = new Map(order.map((old, index) => [old, index])); + const needsBound = + affineKept.length > 0 && + counted !== null && + (counted.range.min < -bound || counted.range.max > bound); + return { + bounds, + receivers: order.map((index) => receivers[index]!), + fixed: fixedKept.map((g) => ({ + receiver: renumber.get(g.check.receiver)!, + end: g.check.end, + })), + affine: affineKept.map((g) => ({ + receiver: renumber.get(g.check.receiver)!, + scale: g.check.scale, + terms: g.check.terms, + minOffset: g.check.minOffset, + maxEnd: g.check.maxEnd, + })), + counted: affineKept.length > 0 ? counted : null, + counterBound: needsBound ? bound : null, + ranges, + }; +} + +/** Branches to `fast` when every access the version proves is in bounds for + * every iteration, and to `slow` otherwise (including a counted loop whose + * integer form is not established, or a module receiver not yet set). */ +export function emitBytesLoopVersionGuard( + host: LlvmEmitterContext, + version: BytesLoopVersion, + fast: string, + slow: string, +): void { + const B = host.B; + const next = (cond: string): void => { + if (cond === "true") return; + const ok = B.newLabel("bytes.version.check"); + B.condBr(cond, ok, slow); + B.startBlock(ok); + }; + const counted = version.counted; + if (counted?.guarded) next(emitCountedLoopSafety(host, counted)); + const receivers = version.receivers.map(({ ref, global }) => { + const value = host.emitReadReceiver(ref).name; + if (global) { + const set = B.tmp(); + B.line(`${set} = icmp ne ptr ${value}, null`); + next(set); + } + return value; + }); + const lengths = receivers.map((receiver) => { + const len = emitBytesHeaderLength(host, receiver); + if (host.sizeType === "i64") return len; + const wide = B.tmp(); + B.line(`${wide} = zext ${host.sizeType} ${len} to i64`); + return wide; + }); + const conditions: string[] = []; + for (const check of version.fixed) { + const ok = B.tmp(); + B.line(`${ok} = icmp ugt i64 ${lengths[check.receiver]}, ${check.end}`); + conditions.push(ok); + } + if (counted && version.affine.length > 0) { + // The first and last counter values, in ascending order. The last value + // of a non-unit step is exact: `start + ⌊(limit ∓ 1 - start) / step⌋ * + // step`. A loop that runs no iteration may compute anything here. + const startValue = host.emitExpr(counted.start); + const startInteger = exactInteger(host, startValue, counted.start); + let start: string; + if (startInteger) start = widenInteger(host, startInteger); + else { + // The counted-loop safety test above proved an exact integer. + start = B.tmp(); + B.line(`${start} = fptosi double ${startValue.name} to i64`); + } + const limit = emitCountedLoopLimit(host, counted); + const step = Math.abs(counted.step); + const edge = B.tmp(); + B.line(`${edge} = add i64 ${limit}, ${counted.step > 0 ? -1 : 1}`); + let last = edge; + if (step !== 1) { + const span = B.tmp(), + whole = B.tmp(), + covered = B.tmp(); + last = B.tmp(); + B.line( + counted.step > 0 + ? `${span} = sub i64 ${edge}, ${start}` + : `${span} = sub i64 ${start}, ${edge}`, + ); + B.line(`${whole} = sdiv i64 ${span}, ${step}`); + B.line(`${covered} = mul i64 ${whole}, ${step}`); + B.line(`${last} = ${counted.step > 0 ? "add" : "sub"} i64 ${start}, ${covered}`); + } + const low = counted.step > 0 ? start : last; + const high = counted.step > 0 ? last : start; + if (version.counterBound !== null) { + const above = B.tmp(), + below = B.tmp(); + B.line(`${above} = icmp sge i64 ${low}, ${-version.counterBound}`); + B.line(`${below} = icmp sle i64 ${high}, ${version.counterBound}`); + conditions.push(above, below); + } + for (const check of version.affine) { + let base = "0"; + for (const term of check.terms) { + const value = host.emitExpr(term.expr); + const integer = exactInteger(host, value, term.expr); + if (!integer) { + // Unreachable: every term has a proven range. Stay checked. + B.br(slow); + B.startBlock(B.newLabel("bytes.version.dead")); + return; + } + const wide = widenInteger(host, integer); + const sum = B.tmp(); + B.line(`${sum} = ${term.sign > 0 ? "add" : "sub"} i64 ${base}, ${wide}`); + base = sum; + } + // Products wrap only for a loop with no iteration: within the + // iteration space every scaled counter is a proven safe integer. + const scaledLow = scaleCounter(host, low, check.scale), + scaledHigh = scaleCounter(host, high, check.scale); + const first = B.tmp(), + final = B.tmp(), + nonNegative = B.tmp(), + below = B.tmp(), + both = B.tmp(); + B.line(`${first} = add i64 ${check.scale > 0 ? scaledLow : scaledHigh}, ${base}`); + B.line(`${final} = add i64 ${check.scale > 0 ? scaledHigh : scaledLow}, ${base}`); + const firstOffset = B.tmp(), + lastEnd = B.tmp(); + B.line(`${firstOffset} = add i64 ${first}, ${check.minOffset}`); + B.line(`${lastEnd} = add i64 ${final}, ${check.maxEnd}`); + B.line(`${nonNegative} = icmp sge i64 ${firstOffset}, 0`); + // Lengths are at most 2^53 elements: a signed comparison is exact. + B.line(`${below} = icmp slt i64 ${lastEnd}, ${lengths[check.receiver]}`); + B.line(`${both} = and i1 ${nonNegative}, ${below}`); + conditions.push(both); + } + } + let valid = "true"; + for (const cond of conditions) { + if (valid === "true") valid = cond; + else { + const all = B.tmp(); + B.line(`${all} = and i1 ${valid}, ${cond}`); + valid = all; + } + } + if (valid === "true") B.br(fast); + else B.condBr(valid, fast, slow); +} + +function scaleCounter(host: LlvmEmitterContext, value: string, scale: number): string { + if (scale === 1) return value; + const scaled = host.B.tmp(); + host.B.line(`${scaled} = mul i64 ${value}, ${scale}`); + return scaled; +} diff --git a/packages/compiler/src/backend/llvm/counted-loops.ts b/packages/compiler/src/backend/llvm/counted-loops.ts index 03e0ed4f3..36fc63e1f 100644 --- a/packages/compiler/src/backend/llvm/counted-loops.ts +++ b/packages/compiler/src/backend/llvm/counted-loops.ts @@ -42,3 +42,54 @@ export function emitCountedLoopLimit( B.line(`${limit} = add nsw i64 ${integer}, ${ascending ? 1 : -1}`); return limit; } + +/** The run-time test of a guarded counted loop: an unproven limit is within + * the integer form's finite range and an unproven start is an exact safe + * integer. Returns `true` when nothing needs testing. */ +export function emitCountedLoopSafety( + host: LlvmEmitterContext, + loop: IntegerCountedForLoop, +): string { + const B = host.B; + let safe = "true"; + if (loop.limitGuarded) { + const bound = host.emitExpr(loop.limit); + safe = B.tmp(); + B.line( + `${safe} = fcmp ${loop.step > 0 ? "ole" : "oge"} double ${bound.name}, ${f64Lit(loop.guardLimit)}`, + ); + } + if (loop.startGuarded) { + // An exact integer of at most 2^53 - 1 in magnitude, and not -0 + // (the bit patterns of the value and its round trip agree). + // NaN and infinities fail the ordered range tests. + const start = host.emitExpr(loop.start); + const low = B.tmp(), + high = B.tmp(), + raw = B.tmp(), + wide = B.tmp(), + back = B.tmp(), + bits = B.tmp(), + backBits = B.tmp(), + exact = B.tmp(), + inRange = B.tmp(), + ok = B.tmp(); + B.line(`${low} = fcmp oge double ${start.name}, ${f64Lit(-Number.MAX_SAFE_INTEGER)}`); + B.line(`${high} = fcmp ole double ${start.name}, ${f64Lit(Number.MAX_SAFE_INTEGER)}`); + B.line(`${inRange} = and i1 ${low}, ${high}`); + B.line(`${raw} = fptosi double ${start.name} to i64`); + B.line(`${wide} = freeze i64 ${raw}`); + B.line(`${back} = sitofp i64 ${wide} to double`); + B.line(`${bits} = bitcast double ${start.name} to i64`); + B.line(`${backBits} = bitcast double ${back} to i64`); + B.line(`${exact} = icmp eq i64 ${bits}, ${backBits}`); + B.line(`${ok} = and i1 ${inRange}, ${exact}`); + if (safe === "true") safe = ok; + else { + const both = B.tmp(); + B.line(`${both} = and i1 ${safe}, ${ok}`); + safe = both; + } + } + return safe; +} diff --git a/packages/compiler/src/backend/llvm/emit-function.ts b/packages/compiler/src/backend/llvm/emit-function.ts index 78f208ff1..9e50bea6d 100644 --- a/packages/compiler/src/backend/llvm/emit-function.ts +++ b/packages/compiler/src/backend/llvm/emit-function.ts @@ -137,6 +137,9 @@ export function emitFunction(emitter: LlEmitter, fn: IrFunction): string { constructorClass(fn.name) === null && emitter.finalFields.size > 0; emitter.byteWindowsEnabled = emitter.countedLoopsEnabled; + emitter.bytesLoopVersionsEnabled = emitter.countedLoopsEnabled; + emitter.bytesLoopEntries = null; + emitter.bytesLoopFunction = fn; emitter.denseLoopsEnabled = emitter.countedLoopsEnabled; emitter.denseLoop = null; emitter.denseLoopEntries = @@ -399,6 +402,8 @@ export function emitFunction(emitter: LlEmitter, fn: IrFunction): string { for (const local of emitter.currentLocals.values()) if (local.boxed || emitter.captureIds.has(local.id)) slots.add(`%${mangleLocal(local.id)}`); B.volatileSlots = slots; + // Without optimization a versioned copy only adds code. + emitter.bytesLoopVersionsEnabled = false; } // Captured bindings come in through the environment — borrowed for the // whole call (the closure owns them): bound here, never released here. diff --git a/packages/compiler/src/backend/llvm/emit-statements.ts b/packages/compiler/src/backend/llvm/emit-statements.ts index 01fa11035..a5da56f55 100644 --- a/packages/compiler/src/backend/llvm/emit-statements.ts +++ b/packages/compiler/src/backend/llvm/emit-statements.ts @@ -1,5 +1,10 @@ /* LlEmitter's statement emission: the statement switch, try/catch, switch, loop copies, speculative writes and string self-concatenation. */ -import { type LlEmitter, type LlScopeEntry, adoptingAwaitOf } from "./emitter.js"; +import { + type LlEmitter, + type LlScopeEntry, + adoptingAwaitOf, + MAX_BYTES_LOOP_VERSION_DEPTH, +} from "./emitter.js"; import { CAUGHT, F64, type IrExpr, type IrStmt, STRING, isRefCounted } from "../../ir/ir.js"; import { emitStringSliceSnapshot } from "./string-slices.js"; import { @@ -40,7 +45,8 @@ import { } from "../../ir/integer-loops.js"; import { emitByteWindowGuard, matchByteWindow } from "./byte-windows.js"; import { countedDenseShape, emitDenseLoopGuard, matchDenseArrayLoop } from "./dense-array-loops.js"; -import { emitCountedLoopLimit } from "./counted-loops.js"; +import { emitCountedLoopLimit, emitCountedLoopSafety } from "./counted-loops.js"; +import { emitBytesLoopVersionGuard } from "./bytes-loop-versions.js"; import { mangleLocal } from "../mangle.js"; import { LlvmUnsupportedError } from "./unsupported.js"; import type { LlValue } from "./expr-context.js"; @@ -939,48 +945,43 @@ export function emitStmtBody(emitter: LlEmitter, s: IrStmt): void { B.startBlock(done); break; } + const version = + emitter.bytesLoopVersionsEnabled && + emitter.bytesLoopVersioning.size < MAX_BYTES_LOOP_VERSION_DEPTH && + !emitter.bytesLoopVersioning.has(s) + ? emitter.matchBytesLoopVersion(s, countedLoop) + : null; + if (version) { + const fast = B.newLabel("bytes.version.fast"), + slow = B.newLabel("bytes.version.slow"), + done = B.newLabel("bytes.version.done"); + emitBytesLoopVersionGuard(emitter, version, fast, slow); + const originalBounds = emitter.bytesBounds, + originalRanges = emitter.integerRanges; + emitter.bytesLoopVersioning.add(s); + try { + B.startBlock(slow); + emitter.emitStmt(s); + B.br(done); + B.startBlock(fast); + emitter.bytesBounds = new Set([...originalBounds, ...version.bounds]); + if (version.ranges.size > 0) + emitter.integerRanges = new Map([...originalRanges, ...version.ranges]); + if (version.counted?.guarded) emitter.provenCountedGuards.add(s); + emitter.emitStmt(s); + B.br(done); + } finally { + emitter.bytesBounds = originalBounds; + emitter.integerRanges = originalRanges; + emitter.provenCountedGuards.delete(s); + emitter.bytesLoopVersioning.delete(s); + } + B.startBlock(done); + break; + } let countedJoin: string | null = null; - if (countedLoop?.guarded) { - let safe = "true"; - if (countedLoop.limitGuarded) { - const bound = emitter.emitExpr(countedLoop.limit); - safe = B.tmp(); - B.line( - `${safe} = fcmp ${countedLoop.step > 0 ? "ole" : "oge"} double ${bound.name}, ${f64Lit(countedLoop.guardLimit)}`, - ); - } - if (countedLoop.startGuarded) { - // An exact integer of at most 2^53 - 1 in magnitude, and not -0 - // (the bit patterns of the value and its round trip agree). - // NaN and infinities fail the ordered range tests. - const start = emitter.emitExpr(countedLoop.start); - const low = B.tmp(), - high = B.tmp(), - raw = B.tmp(), - wide = B.tmp(), - back = B.tmp(), - bits = B.tmp(), - backBits = B.tmp(), - exact = B.tmp(), - inRange = B.tmp(), - ok = B.tmp(); - B.line(`${low} = fcmp oge double ${start.name}, ${f64Lit(-Number.MAX_SAFE_INTEGER)}`); - B.line(`${high} = fcmp ole double ${start.name}, ${f64Lit(Number.MAX_SAFE_INTEGER)}`); - B.line(`${inRange} = and i1 ${low}, ${high}`); - B.line(`${raw} = fptosi double ${start.name} to i64`); - B.line(`${wide} = freeze i64 ${raw}`); - B.line(`${back} = sitofp i64 ${wide} to double`); - B.line(`${bits} = bitcast double ${start.name} to i64`); - B.line(`${backBits} = bitcast double ${back} to i64`); - B.line(`${exact} = icmp eq i64 ${bits}, ${backBits}`); - B.line(`${ok} = and i1 ${inRange}, ${exact}`); - if (safe === "true") safe = ok; - else { - const both = B.tmp(); - B.line(`${both} = and i1 ${safe}, ${ok}`); - safe = both; - } - } + if (countedLoop?.guarded && !emitter.provenCountedGuards.has(s)) { + const safe = emitCountedLoopSafety(emitter, countedLoop); const fast = B.newLabel("counted.fast"); const slow = B.newLabel("counted.slow"); countedJoin = B.newLabel("counted.done"); @@ -1091,7 +1092,7 @@ export function emitStmtBody(emitter: LlEmitter, s: IrStmt): void { B.line( `${lenPtr} = getelementptr inbounds %${integerArrayLoop ? "ScrArr" : "ScrBytes"}, ptr ${receiver.name}, i64 0, i32 1`, ); - if (integerArrayLoop) emitter.markMemoryPointer(lenPtr, "array:header"); + emitter.markMemoryPointer(lenPtr, integerArrayLoop ? "array:header" : "bytes:header"); B.line( `${len} = load ${emitter.sizeType}, ptr ${lenPtr}${emitter.fieldAliasAttachment(lenPtr)}`, ); diff --git a/packages/compiler/src/backend/llvm/emitter.ts b/packages/compiler/src/backend/llvm/emitter.ts index a6b67b33f..9d8bd1f9e 100644 --- a/packages/compiler/src/backend/llvm/emitter.ts +++ b/packages/compiler/src/backend/llvm/emitter.ts @@ -56,6 +56,9 @@ import { moduleRuntimeFeatures, moduleUsesMockTimers } from "../../ir/runtime-fe import { captureFreeFunctions, observesStacks } from "../stack-observations.js"; import { type DenseArrayLoop } from "./dense-array-loops.js"; import { findInitializedByteLoopBindings } from "./byte-windows.js"; +import { matchBytesLoopVersion } from "./bytes-loop-versions.js"; +import { globalSlotAlias } from "./bytes-alias.js"; +import type { IntegerCountedForLoop } from "../../ir/integer-loops.js"; import { materializeSplitPiece, type SplitSpan, @@ -522,6 +525,9 @@ export function boxSlotCount(fn: IrFunction): number { /** Whether any node of `e` names the local (reads, writes, declarations, * captures). */ +/** Typed-array loop versions nest at most this deep (each doubles a body). */ +export const MAX_BYTES_LOOP_VERSION_DEPTH = 3; + function mentionsLocal(e: IrExpr, localId: string): boolean { const exprNames = (node: IrExpr): boolean => { if (node.kind === "varRef" || node.kind === "incDec" || node.kind === "assignExpr") @@ -1029,6 +1035,17 @@ export class LlEmitter { wrappingSums = new Set(); byteWindowsEnabled = true; byteWindowEntries: ReturnType = new Map(); + /** Typed-array loop versioning (bytes-loop-versions.ts) for the current + * function: off in debug and suspending functions and inside a copy. */ + bytesLoopVersionsEnabled = false; + /** Loops being versioned around the statement being emitted: a loop is + * never versioned inside its own copies, and versions nest boundedly. */ + readonly bytesLoopVersioning = new Set(); + bytesLoopEntries: ReturnType | null = null; + bytesLoopFunction: IrFunction | null = null; + /** Counted loops whose integer-form safety test an enclosing version + * guard already passed. */ + readonly provenCountedGuards = new Set(); bytesBounds: ReadonlySet = new Set(); integerViews = new Map(); /** Module globals the current function owns outright (initializer @@ -3347,6 +3364,34 @@ export class LlEmitter { /** A binding's storage: a module global, a plain local slot, or a boxed * local (the slot holds the capture BOX; access goes through it). */ + matchBytesLoopVersion( + s: IrStmt & { kind: "for" }, + counted: IntegerCountedForLoop | null, + ): ReturnType { + const fn = this.bytesLoopFunction; + if (!fn) return null; + this.bytesLoopEntries ??= findInitializedByteLoopBindings(fn); + const initialized = this.bytesLoopEntries.get(s); + if (!initialized) return null; + const invariantGlobals = new Set(this.stableGlobals); + for (const global of this.initializerBindings.get(fn.name) ?? []) + invariantGlobals.add(global.id); + return matchBytesLoopVersion(s, counted, { + locals: this.currentLocals, + invariantGlobals, + captured: this.captureIds, + ranges: this.integerRanges, + initialized, + proven: this.bytesBounds, + }); + } + + /** The alias tag of a whole-slot load of a binding (module globals only). */ + slotAlias(id: string): string { + const binding = this.binding(id); + return binding.kind === "global" ? globalSlotAlias(this, id, binding.type.kind) : ""; + } + binding(id: string): { kind: "global" | "local" | "boxed"; slot: string; diff --git a/packages/compiler/src/backend/llvm/expr-bytes.ts b/packages/compiler/src/backend/llvm/expr-bytes.ts index ac025df09..485b7a6b3 100644 --- a/packages/compiler/src/backend/llvm/expr-bytes.ts +++ b/packages/compiler/src/backend/llvm/expr-bytes.ts @@ -8,6 +8,7 @@ import { exactInteger, widenInteger } from "./integer-values.js"; import { byteNumberAccess } from "../../ir/byte-numbers.js"; import { emitByteNumber } from "./byte-numbers.js"; import { integerOrNaNRange } from "./wrapping-sums.js"; +import { bytesElementAlias, bytesHeaderAlias } from "./bytes-alias.js"; const BYTES_NUM_KIND: Record = { u8: { kind: 0, le: false }, @@ -93,10 +94,7 @@ export function emitBytesIndex( B.line(`${narrow} = trunc i64 ${wide} to ${host.sizeType}`); return narrow; } - const lenPtr = B.tmp(); - const len = B.tmp(); - B.line(`${lenPtr} = getelementptr inbounds %ScrBytes, ptr ${receiver}, i64 0, i32 1`); - B.line(`${len} = load ${host.sizeType}, ptr ${lenPtr}`); + const len = emitBytesHeaderLength(host, receiver); if (integer) { const wideIndex = widenInteger(host, integer); const wideLength = host.sizeType === "i64" ? len : B.tmp(); @@ -145,11 +143,20 @@ export function emitBytesIndex( return idx; } +/** The element count of a typed array, in the target's size type. */ +export function emitBytesHeaderLength(host: LlvmEmitterContext, receiver: string): string { + const p = host.B.tmp(); + const len = host.B.tmp(); + host.B.line(`${p} = getelementptr inbounds %ScrBytes, ptr ${receiver}, i64 0, i32 1`); + host.B.line(`${len} = load ${host.sizeType}, ptr ${p}${bytesHeaderAlias(host)}`); + return len; +} + export function emitBytesData(host: LlvmEmitterContext, receiver: string): string { const p = host.B.tmp(); const data = host.B.tmp(); host.B.line(`${p} = getelementptr inbounds %ScrBytes, ptr ${receiver}, i64 0, i32 3`); - host.B.line(`${data} = load ptr, ptr ${p}`); + host.B.line(`${data} = load ptr, ptr ${p}${bytesHeaderAlias(host)}`); return data; } @@ -160,10 +167,7 @@ export function emitBytesLength( bytes: boolean, ): LlValue { const B = host.B; - const p = B.tmp(); - const len = B.tmp(); - B.line(`${p} = getelementptr inbounds %ScrBytes, ptr ${receiver}, i64 0, i32 1`); - B.line(`${len} = load ${host.sizeType}, ptr ${p}`); + const len = emitBytesHeaderLength(host, receiver); const size = BYTES_ELEMENT_SIZE[elem]; const count = bytes && size !== 1 ? B.tmp() : len; if (count !== len) B.line(`${count} = shl ${host.sizeType} ${len}, ${Math.log2(size)}`); @@ -187,7 +191,7 @@ function emitSharedBytesSplit(host: LlvmEmitterContext, receiver: string): strin isShared = B.tmp(), expected = B.tmp(); B.line(`${slot} = getelementptr inbounds %ScrBytes, ptr ${receiver}, i64 0, i32 9`); - B.line(`${storage} = load ptr, ptr ${slot}`); + B.line(`${storage} = load ptr, ptr ${slot}${bytesHeaderAlias(host)}`); B.line(`${isShared} = icmp ne ptr ${storage}, null`); B.line(`${expected} = call i1 @llvm.expect.i1(i1 ${isShared}, i1 false)`); const shared = B.newLabel("bytes.shared"), @@ -257,9 +261,15 @@ function emitBytesLoad( inBounds: boolean, invalid: string | undefined, ): LlValue { + if (host.mod.workers) { + const idx = emitBytesIndex(host, receiver, index, expr, invalid, inBounds); + return emitSharedOrLocalLoad(host, elem, receiver, idx); + } + // The header is immutable, so the data pointer loads beside the length, + // ahead of the bounds branch: an unconditional load LICM can hoist. + const data = host.emitBytesData(receiver); const idx = emitBytesIndex(host, receiver, index, expr, invalid, inBounds); - if (host.mod.workers) return emitSharedOrLocalLoad(host, elem, receiver, idx); - return emitBytesLoadAt(host, elem, receiver, idx); + return emitBytesLoadAt(host, elem, data, idx); } /** Worker programs: the element at a checked index of a view that may be @@ -285,7 +295,7 @@ function emitSharedOrLocalLoad( const sharedDone = B.newLabel("bytes.shared.done"); B.br(sharedDone); B.startBlock(local); - const own = emitBytesLoadAt(host, elem, receiver, idx); + const own = emitBytesLoadAt(host, elem, host.emitBytesData(receiver), idx); const localDone = B.newLabel("bytes.local.done"); B.br(localDone); B.startBlock(localDone); @@ -306,15 +316,15 @@ function emitSharedOrLocalLoad( return { name: out, type: F64, uint32: u32 }; } -/** The element at a checked index of a thread-owned view. */ +/** The element at a checked index of a thread-owned view, given its data + * pointer. */ function emitBytesLoadAt( host: LlvmEmitterContext, elem: IrBytesElem, - receiver: string, + data: string, idx: string, ): LlValue { const B = host.B; - const data = host.emitBytesData(receiver); const p = B.tmp(); if (elem === "f16") { // Half-precision bits widen exactly; the runtime converts them. @@ -322,7 +332,7 @@ function emitBytesLoadAt( const raw = B.tmp(); const out = B.tmp(); B.line(`${p} = getelementptr inbounds i16, ptr ${data}, ${host.sizeType} ${idx}`); - B.line(`${raw} = load i16, ptr ${p}, align 1`); + B.line(`${raw} = load i16, ptr ${p}, align 1${bytesElementAlias(host)}`); B.line(`${out} = call double @scr_f16_to_f64(i16 zeroext ${raw})`); return { name: out, type: F64 }; } @@ -333,7 +343,7 @@ function emitBytesLoadAt( const wide = B.tmp(); const out = B.tmp(); B.line(`${p} = getelementptr inbounds i${bits}, ptr ${data}, ${host.sizeType} ${idx}`); - B.line(`${raw} = load i${bits}, ptr ${p}, align 1`); + B.line(`${raw} = load i${bits}, ptr ${p}, align 1${bytesElementAlias(host)}`); B.line(`${wide} = ${signed ? "sext" : "zext"} i${bits} ${raw} to i32`); B.line(`${out} = ${signed ? "sitofp" : "uitofp"} i32 ${wide} to double`); return { name: out, type: F64, uint32: wide }; @@ -342,20 +352,20 @@ function emitBytesLoadAt( const raw = B.tmp(); const out = B.tmp(); B.line(`${p} = getelementptr inbounds float, ptr ${data}, ${host.sizeType} ${idx}`); - B.line(`${raw} = load float, ptr ${p}, align 1`); + B.line(`${raw} = load float, ptr ${p}, align 1${bytesElementAlias(host)}`); B.line(`${out} = fpext float ${raw} to double`); return { name: out, type: F64 }; } if (elem === "f64") { const out = B.tmp(); B.line(`${p} = getelementptr inbounds double, ptr ${data}, ${host.sizeType} ${idx}`); - B.line(`${out} = load double, ptr ${p}, align 1`); + B.line(`${out} = load double, ptr ${p}, align 1${bytesElementAlias(host)}`); return { name: out, type: F64 }; } const raw = B.tmp(); const out = B.tmp(); B.line(`${p} = getelementptr inbounds i32, ptr ${data}, ${host.sizeType} ${idx}`); - B.line(`${raw} = load i32, ptr ${p}, align 1`); + B.line(`${raw} = load i32, ptr ${p}, align 1${bytesElementAlias(host)}`); B.line(`${out} = ${elem === "i32" ? "sitofp" : "uitofp"} i32 ${raw} to double`); return { name: out, type: F64, uint32: raw }; } @@ -465,6 +475,7 @@ export function emitBytesSet( const B = host.B; // Invalid integer-indexed writes are ignored after evaluating the RHS. const done = B.newLabel("bytes.store.done"); + const data = host.emitBytesData(receiver); const idx = emitBytesIndex(host, receiver, index, indexExpr, done, inBounds); if (host.mod.workers) { host.declare(`declare void @scr_bytes_shared_set(ptr, ${host.sizeType}, double)`); @@ -487,7 +498,6 @@ export function emitBytesSet( elem === "f16" || elem === "f32" || elem === "f64" ? null : host.emitToUint32(value.name, expr, value.uint32); - const data = host.emitBytesData(receiver); const p = B.tmp(); if (elem === "f16") { // One round-to-nearest-even step from the double, in the runtime. @@ -495,24 +505,24 @@ export function emitBytesSet( const narrowed = B.tmp(); B.line(`${narrowed} = call zeroext i16 @scr_f16_from_f64(double ${value.name})`); B.line(`${p} = getelementptr inbounds i16, ptr ${data}, ${host.sizeType} ${idx}`); - B.line(`store i16 ${narrowed}, ptr ${p}, align 1`); + B.line(`store i16 ${narrowed}, ptr ${p}, align 1${bytesElementAlias(host)}`); } else if (BYTES_ELEMENT_SIZE[elem] < 4) { const bits = BYTES_ELEMENT_SIZE[elem] * 8; const byte = B.tmp(); B.line(`${byte} = trunc i32 ${stored!} to i${bits}`); B.line(`${p} = getelementptr inbounds i${bits}, ptr ${data}, ${host.sizeType} ${idx}`); - B.line(`store i${bits} ${byte}, ptr ${p}, align 1`); + B.line(`store i${bits} ${byte}, ptr ${p}, align 1${bytesElementAlias(host)}`); } else if (elem === "f32") { const narrowed = B.tmp(); B.line(`${narrowed} = fptrunc double ${value.name} to float`); B.line(`${p} = getelementptr inbounds float, ptr ${data}, ${host.sizeType} ${idx}`); - B.line(`store float ${narrowed}, ptr ${p}, align 1`); + B.line(`store float ${narrowed}, ptr ${p}, align 1${bytesElementAlias(host)}`); } else if (elem === "f64") { B.line(`${p} = getelementptr inbounds double, ptr ${data}, ${host.sizeType} ${idx}`); - B.line(`store double ${value.name}, ptr ${p}, align 1`); + B.line(`store double ${value.name}, ptr ${p}, align 1${bytesElementAlias(host)}`); } else { B.line(`${p} = getelementptr inbounds i32, ptr ${data}, ${host.sizeType} ${idx}`); - B.line(`store i32 ${stored!}, ptr ${p}, align 1`); + B.line(`store i32 ${stored!}, ptr ${p}, align 1${bytesElementAlias(host)}`); } B.br(done); B.startBlock(done); diff --git a/packages/compiler/src/backend/llvm/expr-callbacks.ts b/packages/compiler/src/backend/llvm/expr-callbacks.ts index d71df333a..db1dca1af 100644 --- a/packages/compiler/src/backend/llvm/expr-callbacks.ts +++ b/packages/compiler/src/backend/llvm/expr-callbacks.ts @@ -1,6 +1,13 @@ /* Focused LLVM expression emission extracted from emitter.ts. */ import { InternalCompilerError } from "../../errors.js"; -import { type IrType, isRefCounted, isUnitType, typeEquals, typeKey } from "../../ir/ir.js"; +import { + type IrType, + UNDEFINED_T, + isRefCounted, + isUnitType, + typeEquals, + typeKey, +} from "../../ir/ir.js"; import { mangleGenResThunk, mangleRecordNew, mangleRecordStruct } from "../mangle.js"; import { FN_ATTRS, llFieldType, releaseSym, retainSym, traceArg, vAdapters } from "./shapes.js"; import { LlvmUnsupportedError } from "./unsupported.js"; @@ -68,6 +75,14 @@ export function raceAdapterFor(host: LlvmEmitterContext, from: IrType, to: IrTyp `define internal void @${sym}(ptr %dst, ptr %src) ${FN_ATTRS} { ; race ${key}`, `entry:`, ]; + if (from.kind === "void") { + // A Promise entry fulfills with undefined: the result's + // undefined arm, whatever the entry's payload slot holds. + const tag = tagOf(UNDEFINED_T); + d.push(` ${fulfill(host.unitInstanceRef(to.unionId, tag))}`, ` ret void`, `}`, ``); + host.resolveThunkDefs.push(...d); + return sym; + } if (from.kind !== "union") { // One arm wrap, straight off the payload accessors. const tag = tagOf(from); diff --git a/packages/compiler/src/backend/llvm/expr-containers.ts b/packages/compiler/src/backend/llvm/expr-containers.ts index a04f46f7b..0ca76e2d5 100644 --- a/packages/compiler/src/backend/llvm/expr-containers.ts +++ b/packages/compiler/src/backend/llvm/expr-containers.ts @@ -528,7 +528,7 @@ export function emitStableReceiver( if (b.kind !== "boxed") { if (b.kind === "global") host.checkGlobalTdz(receiver.localId); const value = host.B.tmp(); - host.B.line(`${value} = load ptr, ptr ${b.slot}`); + host.B.line(`${value} = load ptr, ptr ${b.slot}${host.slotAlias(receiver.localId)}`); return { name: value, type: receiver.type }; } } diff --git a/packages/compiler/src/backend/llvm/expr-primitives.ts b/packages/compiler/src/backend/llvm/expr-primitives.ts index 9cbd8de74..0d2cd90e8 100644 --- a/packages/compiler/src/backend/llvm/expr-primitives.ts +++ b/packages/compiler/src/backend/llvm/expr-primitives.ts @@ -116,7 +116,7 @@ export function emitLiteralExpr( return host.own({ name: host.adoptHeap(v, e.type), type: e.type }); } const t = B.tmp(); - B.line(`${t} = load ${host.llType(b.type)}, ptr ${b.slot}`); + B.line(`${t} = load ${host.llType(b.type)}, ptr ${b.slot}${host.slotAlias(e.localId)}`); const integerView = host.integerViews.get(e.localId); if (integerView !== undefined) { // Snapshot both representations before later operands can write diff --git a/packages/compiler/src/backend/llvm/expr-stream-bridges.ts b/packages/compiler/src/backend/llvm/expr-stream-bridges.ts index 8a53b5fa8..134c74b92 100644 --- a/packages/compiler/src/backend/llvm/expr-stream-bridges.ts +++ b/packages/compiler/src/backend/llvm/expr-stream-bridges.ts @@ -5,10 +5,12 @@ import { streamTypedRefEligible, undefinedArmTag } from "../../ir/analysis.js"; import { type IrRecordShape, type IrType, + CLASS_ATTRIBUTES, DYN_CLASS_PROPERTIES, RUNTIME_STREAM_CLASSES, SYMBOL_T, classDynViewSupported, + classFieldAttributesName, isClassOwnEnumerableFieldName, isDirentShape, isDynTypedRefType, @@ -766,6 +768,7 @@ export function streamTypedRefMaterializeAdapter( host.declare(`declare ptr @scr_stream_dyn_view(ptr)`); B.line(`${out} = call ptr @scr_stream_dyn_view(ptr %p)`); } else B.line(`${out} = call ptr @scr_dyn_new_obj()`); + const withAttributes = meta.def.fields.some((field) => field.name === CLASS_ATTRIBUTES); if (meta.def.tracksOwnFields) { host.declare(`declare ptr @scr_dyn_copy_property_descriptors(ptr, ptr)`); host.declare(`declare void @scr_dyn_release_v(ptr)`); @@ -835,9 +838,27 @@ export function streamTypedRefMaterializeAdapter( const nullable = host.nullableFields.get(t.className, field.name); if (nullable) fieldValue = nullableFieldUnion(host, B, fieldValue, nullable); const boxed = host.streamTypedRefBoxValue(B, field.type, fieldValue); + // Attributes copied from the bag do not stop the view taking the value. + const setter = withAttributes ? "scr_dyn_obj_set_field" : "scr_dyn_obj_set"; + if (withAttributes) + host.declare(`declare void @scr_dyn_obj_set_field(ptr, ptr, ${host.sizeType}, ptr)`); B.line( - `call void @scr_dyn_obj_set(ptr ${out}, ptr ${host.cstr(field.name)}, ${host.sizeType} ${Buffer.byteLength(field.name, "utf8")}, ptr ${boxed})`, + `call void @${setter}(ptr ${out}, ptr ${host.cstr(field.name)}, ${host.sizeType} ${Buffer.byteLength(field.name, "utf8")}, ptr ${boxed})`, ); + // A field redefined through Object.defineProperty keeps its attributes. + const attributesField = classFieldAttributesName(field.name); + if (meta.def.fields.some((f) => f.name === attributesField)) { + host.declare(`declare void @scr_dyn_class_field_attributes(ptr, ptr, double)`); + const slot = B.tmp(), + bits = B.tmp(); + B.line( + `${slot} = getelementptr inbounds %${classStructSym(t.className)}, ptr %p, i64 0, i32 ${classFieldIndex(meta, attributesField).index}`, + ); + B.line(`${bits} = load double, ptr ${slot}`); + B.line( + `call void @scr_dyn_class_field_attributes(ptr ${out}, ptr ${host.internLiteral(field.name)}, double ${bits})`, + ); + } if (nullable) { host.declare(`declare void @scr_union_release(ptr)`); B.line(`call void @scr_union_release(ptr ${fieldValue})`); @@ -920,6 +941,17 @@ export function streamTypedRefMaterializeAdapter( B.line(`${copied} = call ptr @scr_dyn_copy_property_descriptors(ptr ${out}, ptr ${bag})`); B.line(`call void @scr_dyn_release_v(ptr ${copied})`); } + // A frozen, sealed or non-extensible instance's view says so. + if (withAttributes) { + host.declare(`declare void @scr_dyn_class_view_attributes(ptr, double)`); + const slot = B.tmp(), + bits = B.tmp(); + B.line( + `${slot} = getelementptr inbounds %${classStructSym(t.className)}, ptr %p, i64 0, i32 ${classFieldIndex(meta, CLASS_ATTRIBUTES).index}`, + ); + B.line(`${bits} = load double, ptr ${slot}`); + B.line(`call void @scr_dyn_class_view_attributes(ptr ${out}, double ${bits})`); + } B.terminate(`ret ptr ${out}`); } else if (t.kind === "record") { const shape = host.recordsById.get(t.shapeId); diff --git a/packages/compiler/src/backend/llvm/lib-shared.ts b/packages/compiler/src/backend/llvm/lib-shared.ts index e537d3d74..3c277cc8b 100644 --- a/packages/compiler/src/backend/llvm/lib-shared.ts +++ b/packages/compiler/src/backend/llvm/lib-shared.ts @@ -307,9 +307,12 @@ export const LIB_FN_SYMS: Record = { "process.rusage": "scr_process_rusage", "process.activeResources": "scr_active_resources", "process.exiting": "scr_process_exiting", + "process.ipcOpen": "scr_process_ipc_open", "process.exit": "scr_process_exit", "process.setExitCode": "scr_process_set_exit_code", "process.currentExitCode": "scr_process_exit_code_or_zero", + "process.setExitCodeTracked": "scr_process_set_exit_code_tracked", + "process.hasExitCode": "scr_process_has_exit_code", "process.envSet": "scr_env_set", "process.envUnset": "scr_env_unset", "process.envPairs": "scr_env_pairs", @@ -840,6 +843,7 @@ export const LIB_FN_SYMS: Record = { "dyn.sameValue": "scr_dyn_same_value", "error.toString": "scr_error_to_string", "error.stack": "scr_error_stack", + "error.setStack": "scr_error_set_stack", "error.stackLimitGet": "scr_stack_limit_get", "error.stackLimitSet": "scr_stack_limit_set", "class.name": "scr_classobj_name", @@ -939,6 +943,12 @@ export const LIB_FN_SYMS: Record = { "dyn.objectTag": "scr_dyn_object_tag", "dyn.freeze": "scr_dyn_freeze", "dyn.isFrozen": "scr_dyn_is_frozen", + "class.defineField": "scr_class_define_field", + "class.definedBits": "scr_class_defined_bits", + "class.definedValue": "scr_class_defined_value", + "class.descriptorHasValue": "scr_class_descriptor_has_value", + "class.throwReadOnly": "scr_class_throw_read_only", + "class.throwNotExtensible": "scr_class_throw_not_extensible", "dyn.nativeSetNew": "scr_dyn_native_set_new", "dyn.nativeSetIs": "scr_dyn_native_set_is", "dyn.nativeMapIs": "scr_dyn_native_map_is", diff --git a/packages/compiler/src/backend/llvm/receivers.ts b/packages/compiler/src/backend/llvm/receivers.ts index a1c771797..50b9485a1 100644 --- a/packages/compiler/src/backend/llvm/receivers.ts +++ b/packages/compiler/src/backend/llvm/receivers.ts @@ -50,7 +50,7 @@ export function emitReadReceiver(emitter: LlEmitter, e: IrExpr): LlValue { } if (binding.kind === "global") emitter.checkGlobalTdz(e.localId); const value = emitter.B.tmp(); - emitter.B.line(`${value} = load ptr, ptr ${binding.slot}`); + emitter.B.line(`${value} = load ptr, ptr ${binding.slot}${emitter.slotAlias(e.localId)}`); return { name: value, type: e.type }; } if (e.kind === "dynCheck" && e.exact) return emitter.emitExactCapsuleObject(e.value, e.type); diff --git a/packages/compiler/src/backend/stack-observations.test.ts b/packages/compiler/src/backend/stack-observations.test.ts index a95f0140f..0e1178ce9 100644 --- a/packages/compiler/src/backend/stack-observations.test.ts +++ b/packages/compiler/src/backend/stack-observations.test.ts @@ -1,5 +1,6 @@ import { expect, test } from "vitest"; import { + DYN, F64, STRING, VOID, @@ -49,6 +50,40 @@ test("a reachable stack read is observed", () => { expect(observesStacks(mod([fn("%main", [call("parse")]), fn("parse", [stackRead])]))).toBe(true); }); +test("a stack read on a checked-dynamic value is observed", () => { + const value: IrExpr = { loc, kind: "varRef", localId: "d", type: DYN }; + const read = (key: string): IrStmt => ({ + loc, + kind: "exprStmt", + expr: { + loc, + kind: "dynKeyGet", + value, + key: { loc, kind: "strLit", value: key, type: STRING }, + type: DYN, + }, + }); + expect(observesStacks(mod([fn("%main", [read("stack")])]))).toBe(true); + expect(observesStacks(mod([fn("%main", [read("message")])]))).toBe(false); +}); + +test("a computed key reads the stack when the program spells it", () => { + const value: IrExpr = { loc, kind: "varRef", localId: "d", type: DYN }; + const key: IrExpr = { loc, kind: "varRef", localId: "k", type: STRING }; + const computed: IrStmt = { + loc, + kind: "exprStmt", + expr: { loc, kind: "dynKeyGet", value, key, type: DYN }, + }; + const spelled: IrStmt = { + loc, + kind: "exprStmt", + expr: { loc, kind: "strLit", value: "stack", type: STRING }, + }; + expect(observesStacks(mod([fn("%main", [computed, spelled])]))).toBe(true); + expect(observesStacks(mod([fn("%main", [computed])]))).toBe(false); +}); + test("programs without stack reads are not", () => { expect(observesStacks(mod([fn("%main", [call("helper")]), fn("helper", [ret])]))).toBe(false); }); diff --git a/packages/compiler/src/backend/stack-observations.ts b/packages/compiler/src/backend/stack-observations.ts index 7debf0751..cc6d109e4 100644 --- a/packages/compiler/src/backend/stack-observations.ts +++ b/packages/compiler/src/backend/stack-observations.ts @@ -13,12 +13,34 @@ import { writesPublished } from "./publication.js"; /** Lib calls that read a captured stack trace. */ const STACK_READS: ReadonlySet = new Set(["error.stack"]); -function readsStack(expr: IrExpr): boolean { - return expr.kind === "libCall" && STACK_READS.has(expr.fn); +/** A typed `error.stack` read, or a read on a checked-dynamic value that + * can reach an error's own `stack` accessor: the constant key `stack`, or + * a computed key when `computedKeys` (the program spells "stack", so a + * computed key can name it). A key the program assembles from other + * strings or from data it reads keeps the static answer. */ +function readsStack(expr: IrExpr, computedKeys: boolean): boolean { + if (expr.kind === "libCall") return STACK_READS.has(expr.fn); + if (expr.kind !== "dynKeyGet") return false; + if (expr.key.kind === "strLit") return expr.key.value === "stack"; + return computedKeys; } -function containsStackRead(fn: IrFunction): boolean { - return !everyStmtList(fn.body, { stmt: () => true, expr: (expr) => !readsStack(expr) }); +/** Whether the module contains the string literal "stack". */ +function spellsStack(mod: IrModule): boolean { + return mod.functions.some( + (fn) => + !everyStmtList(fn.body, { + stmt: () => true, + expr: (expr) => !(expr.kind === "strLit" && expr.value === "stack"), + }), + ); +} + +function containsStackRead(fn: IrFunction, computedKeys: boolean): boolean { + return !everyStmtList(fn.body, { + stmt: () => true, + expr: (expr) => !readsStack(expr, computedKeys), + }); } /** Visit every expression of `body` that execution can reach: statements @@ -43,7 +65,8 @@ function visitLiveExprs(body: IrStmt[], visit: (expr: IrExpr) => void): void { * and those reads must not cost every call of the program a frame. * Programs without any stack read answer false without the walk. */ export function observesStacks(mod: IrModule): boolean { - if (!mod.functions.some(containsStackRead)) return false; + const computedKeys = spellsStack(mod); + if (!mod.functions.some((fn) => containsStackRead(fn, computedKeys))) return false; const byName = new Map(mod.functions.map((fn) => [fn.name, fn])); const classNames = new Set((mod.classes ?? []).map((cls) => cls.name)); const pending: string[] = [...moduleFunctionReferences(mod)]; @@ -67,7 +90,7 @@ export function observesStacks(mod: IrModule): boolean { const fn = byName.get(name); if (fn === undefined) continue; visitLiveExprs(fn.body, (expr) => { - if (readsStack(expr)) observed = true; + if (readsStack(expr, computedKeys)) observed = true; if (expr.kind === "call" && !reached.has(expr.callee)) pending.push(expr.callee); const referenced = exprFunctionReference(expr); if (referenced !== undefined && !reached.has(referenced)) pending.push(referenced); diff --git a/packages/compiler/src/frontend/helper-tokens.test.ts b/packages/compiler/src/frontend/helper-tokens.test.ts index c6355cef9..522b39545 100644 --- a/packages/compiler/src/frontend/helper-tokens.test.ts +++ b/packages/compiler/src/frontend/helper-tokens.test.ts @@ -27,6 +27,8 @@ test.each([ ['"default"', '"default;s3:foo;"'], ['"default"', '"default\\0"'], ["mod", "mod other"], + ["cb[0]", "cb[1]"], + ["cb[1]", "cb[10]"], ])("retains a semantic difference between %s and %s", (a, b) => { expect(helperTokens(a)).not.toBe(helperTokens(b)); }); @@ -44,8 +46,12 @@ test.each([ "'\\8'", "`template`", "/regexp/", - "0", "0x10", + "07", + "1.5", + "1e3", + "1n", + "1_000", "a / b", "\\u0061", "café", diff --git a/packages/compiler/src/frontend/helper-tokens.ts b/packages/compiler/src/frontend/helper-tokens.ts index d7a968755..908cc0d22 100644 --- a/packages/compiler/src/frontend/helper-tokens.ts +++ b/packages/compiler/src/frontend/helper-tokens.ts @@ -1,8 +1,8 @@ /** Token fingerprints for the fixed bundler helpers we recognize. This is * deliberately not a JavaScript parser: regexes, templates, escaped names, - * numbers, and unknown punctuation refuse the helper. None occur in the - * canonical helpers. Every accepted token is represented in the result; - * comments and whitespace alone may disappear. */ + * numbers other than plain decimal integers, and unknown punctuation refuse + * the helper. None occur in the canonical helpers. Every accepted token is + * represented in the result; comments and whitespace alone may disappear. */ const operators = [ ">>>=", "===", @@ -157,6 +157,15 @@ export function helperTokens(text: string): string | null { result += fingerprint("s", token.value); i = token.end; previous = "string"; + } else if (/[0-9]/.test(ch)) { + // Plain decimal integers only (esbuild's `cb[keys(cb)[0]]`): a + // legacy octal, fraction, exponent, radix prefix, BigInt suffix or + // numeric separator refuses rather than aliasing another value. + const start = i++; + while (i < text.length && /[0-9]/.test(text.charAt(i))) i++; + if (/[A-Za-z0-9_$.\\]/.test(text.charAt(i)) || (ch === "0" && i - start > 1)) return null; + previous = text.slice(start, i); + result += fingerprint("n", previous); } else if (/[A-Za-z_$]/.test(ch)) { const start = i++; while (i < text.length && /[A-Za-z0-9_$]/.test(text.charAt(i))) i++; diff --git a/packages/compiler/src/frontend/input-tracker.ts b/packages/compiler/src/frontend/input-tracker.ts index 304a21cfa..fa2e92568 100644 --- a/packages/compiler/src/frontend/input-tracker.ts +++ b/packages/compiler/src/frontend/input-tracker.ts @@ -164,8 +164,28 @@ export function markFrontendInputsUnstable(): void { (synchronousTracker ?? activeTracker.getStore())?.markUnstable(); } +/** Compiler-synthesized module sources (a bundle's CommonJS chunks split + * into sibling modules, npm-static-bundled-chunks.ts). They answer like + * regular files but are never recorded: each derives from the content of + * a recorded bundle read, which already keys every cache that sees it. */ +const virtualSources = new Map(); + +export function setVirtualSource(path: string, text: string): void { + virtualSources.set(resolve(path), text); +} + +export function isVirtualSource(path: string): boolean { + return virtualSources.has(resolve(path)); +} + +export function clearVirtualSources(): void { + virtualSources.clear(); +} + export function trackedReadFile(path: string): string | null { path = resolve(path); + const virtual = virtualSources.get(path); + if (virtual !== undefined) return virtual; try { const text = readFileSync(path, "utf8"); record({ op: "file", path, digest: frontendSourceDigest(text) }); @@ -178,6 +198,7 @@ export function trackedReadFile(path: string): string | null { export function trackedFileExists(path: string): boolean { path = resolve(path); + if (virtualSources.has(path)) return true; const kind = pathKind(path); record({ op: "kind", path, kind }); return kind === "file"; @@ -185,6 +206,7 @@ export function trackedFileExists(path: string): boolean { export function trackedDirectoryExists(path: string): boolean { path = resolve(path); + if (virtualSources.has(path)) return false; const kind = pathKind(path); record({ op: "kind", path, kind }); return kind === "directory"; @@ -192,6 +214,7 @@ export function trackedDirectoryExists(path: string): boolean { export function trackedExists(path: string): boolean { path = resolve(path); + if (virtualSources.has(path)) return true; const kind = pathKind(path); record({ op: "kind", path, kind }); return kind !== "missing"; @@ -199,6 +222,7 @@ export function trackedExists(path: string): boolean { export function trackedRealpath(path: string): string | null { path = resolve(path); + if (virtualSources.has(path)) return path; try { const target = systemRealpath(path); record({ op: "realpath", path, target }); @@ -223,9 +247,13 @@ export function trackedAccessibleEntries( } files.sort(); directories.sort(); - const answer = { files, directories }; - record({ op: "entries", path, files, directories }); - return answer; + record({ op: "entries", path, files: [...files], directories }); + for (const virtual of virtualSources.keys()) { + if (dirname(virtual) === path && !files.includes(basename(virtual))) + files.push(basename(virtual)); + } + files.sort(); + return { files, directories }; } catch { // Enumeration can fail even while the path remains a directory. Preserve // the failed operation itself so a permission/ACL repair invalidates the diff --git a/packages/compiler/src/frontend/lowering/assertion-members.ts b/packages/compiler/src/frontend/lowering/assertion-members.ts new file mode 100644 index 000000000..d61c379d6 --- /dev/null +++ b/packages/compiler/src/frontend/lowering/assertion-members.ts @@ -0,0 +1,168 @@ +/* Members a type assertion attaches to record values. */ +import * as ts from "../ts7/adapter.js"; +import type { Lowerer } from "./lowerer.js"; +import { type IrType, typeEquals } from "../../ir/ir.js"; +import { hostsAll } from "./expressions/hosted-narrowing.js"; + +/** `route as Route & { order: number; internal: boolean }` followed by + * `view.internal = true` adds a property to the very object `route` + * names: Node has no layouts, so every later read of `route` (its JSON, + * its keys, a spread) sees the member. Native records are closed structs, + * and the asserted intersection maps to a different struct, so neither a + * conversion (a copy that severs the write from `route`) nor an erasure + * (a struct without the slot) is faithful. + * + * This pass finds such assertions before any body lowers and gives each + * record arm of the operand's type the asserted members it does not + * declare, as optional fields. Values the program builds hold them absent, + * which is invisible to every key surface (`in`, Object.keys, JSON, + * inspect, spread). The asserted type then refines the stored arms + * instead of naming new ones (hosted-narrowing.ts), so the assertion keeps + * the value and its writes land in the stored object. + * + * Only the plain form qualifies: one intersection whose constituents are + * the operand's own type and type literals of required property + * signatures, over plain data types (object types, aliases and interfaces; + * not classes or the standard library's types). Arms that are tuples or + * carry an index signature, members whose type does not map, and members + * two assertions type differently are left alone: those assertions keep + * their exact-shape diagnostics. Assertions whose operand already declares + * every member are skipped before any type maps, so programs without the + * form intern exactly the types they did before. */ +export function extendAssertedRecordMembers( + lowerer: Lowerer, + files: readonly ts.SourceFile[], +): void { + const additions = new Map>(); + const conflicts = new Set(); + const consider = (node: ts.AsExpression | ts.TypeAssertion): void => { + let typeNode: ts.TypeNode = node.type; + while (ts.isParenthesizedTypeNode(typeNode)) typeNode = typeNode.type; + if (!ts.isIntersectionTypeNode(typeNode)) return; + const parts = typeNode.types.nodes; + const literals = parts.filter((p) => ts.isTypeLiteralNode(p)); + const bases = parts.filter((p) => !ts.isTypeLiteralNode(p)); + if (literals.length === 0 || bases.length !== 1) return; + const memberNodes: { name: string; type: ts.TypeNode }[] = []; + for (const literal of literals) { + if (!ts.isTypeLiteralNode(literal)) return; + for (const member of literal.members.nodes) { + // A required member: the form that writes through the view. An + // optional one only reads, and keeps its exact-shape fence. + if (!ts.isPropertySignatureDeclaration(member) || !member.type || member.questionToken) + return; + const name = ts.isIdentifier(member.name) + ? member.name.text + : ts.isStringLiteral(member.name) + ? member.name.text + : null; + if (name === null || name.startsWith("%")) return; + memberNodes.push({ name, type: member.type }); + } + } + // Checker-only gate first: an assertion every constituent of whose + // operand type already declares the members (`e as Expr & { kind: + // "mul" }`, the refinement idiom) attaches nothing, and mapping its + // types here would intern them out of the usual order. + const operandTs = lowerer.typeOf(node.expression); + const constituents = operandTs.isUnionType() ? ts.constituentTypes(operandTs) : [operandTs]; + const declares = (t: ts.Type, name: string): boolean => + lowerer.checker.getPropertyOfType(t, name) !== undefined; + if (constituents.every((t) => memberNodes.every((m) => declares(t, m.name)))) return; + // Plain data types only: object literal types, aliases and interfaces + // (not classes, and not the standard library's types). + const dataType = (t: ts.Type): boolean => { + const symbol = t.getAliasSymbol() ?? t.getSymbol(); + return ( + !!symbol && + (symbol.flags & ts.SymbolFlags.Class) === 0 && + symbol.declarations.length > 0 && + symbol.declarations.every( + (d) => + !lowerer.isStdlibFile(d.getSourceFile()) && + (ts.isTypeLiteralNode(d) || + ts.isInterfaceDeclaration(d) || + ts.isTypeAliasDeclaration(d)), + ) + ); + }; + if (!constituents.every(dataType)) return; + const operand = lowerer.mapTypeOf(operandTs); + const base = lowerer.mapTypeOf(lowerer.checker.getTypeFromTypeNode(bases[0]!)); + if (!operand || !base || !typeEquals(operand, base)) return; + const arms = + operand.kind === "union" ? (lowerer.unions.get(operand.unionId)?.arms ?? []) : [operand]; + if (arms.length === 0) return; + const shapeIds: string[] = []; + for (const arm of arms) { + if (arm.kind !== "record") return; + const shape = lowerer.shapes.get(arm.shapeId); + if (!shape || shape.tuple || shape.indexValue) return; + shapeIds.push(arm.shapeId); + } + const members: { name: string; type: IrType }[] = []; + for (const { name, type } of memberNodes) { + if (shapeIds.every((id) => lowerer.shapes.get(id)!.fields.some((f) => f.name === name))) + continue; + const mapped = lowerer.mapTypeOf(lowerer.checker.getTypeFromTypeNode(type)); + if (!mapped || mapped.kind === "void") return; + const optional = lowerer.withUndefinedArmOf(mapped); + if (!optional) return; + members.push({ name, type: optional }); + } + for (const shapeId of shapeIds) { + const shape = lowerer.shapes.get(shapeId)!; + for (const member of members) { + if (shape.fields.some((f) => f.name === member.name)) continue; + let added = additions.get(shapeId); + if (!added) additions.set(shapeId, (added = new Map())); + const prior = added.get(member.name); + if (prior && !typeEquals(prior, member.type)) conflicts.add(`${shapeId}:${member.name}`); + else added.set(member.name, member.type); + } + } + }; + const visit = (node: ts.Node): void => { + if (ts.isAsExpression(node) || ts.isTypeAssertion(node)) consider(node); + ts.forEachChild(node, visit); + }; + for (const sf of files) { + if (sf.isDeclarationFile) continue; + // Cheap text gate: the form needs an intersection with a type literal. + if (!/&\s*\{/.test(sf.text)) continue; + visit(sf); + } + for (const [shapeId, added] of additions) { + const extra = [...added] + .filter(([name]) => !conflicts.has(`${shapeId}:${name}`)) + .map(([name, type]) => ({ name, type })); + lowerer.shapes.extendShape(shapeId, extra); + } + if (additions.size > 0) keepStoredTypeOfAssertedGlobals(lowerer, files); +} + +/** A module-level `const view = value as T & { member: X }` binding keeps + * the stored type of `value` once the members are attached, exactly as a + * local binding does (lowerVarDecl): the assertion keeps the object, and + * writes through the binding must reach it. Globals took their checker + * (or annotated) type during collection, before the members existed; an + * annotation the stored type hosts gives way to it the same way. */ +function keepStoredTypeOfAssertedGlobals(lowerer: Lowerer, files: readonly ts.SourceFile[]): void { + for (const sf of files) { + if (sf.isDeclarationFile) continue; + for (const statement of sf.statements.nodes) { + if (!ts.isVariableStatement(statement)) continue; + if ((statement.declarationList.flags & ts.NodeFlags.Const) === 0) continue; + for (const decl of statement.declarationList.declarations.nodes) { + if (!ts.isIdentifier(decl.name) || !decl.initializer) continue; + let init: ts.Expression = decl.initializer; + while (ts.isParenthesizedExpression(init)) init = init.expression; + if (!ts.isAsExpression(init) && !ts.isTypeAssertion(init)) continue; + const global = lowerer.globalOf(decl.name); + const stored = lowerer.mapTypeOf(lowerer.typeOf(init.expression)); + if (!global || !stored || (stored.kind !== "union" && stored.kind !== "record")) continue; + if (hostsAll(lowerer, stored, global.type)) global.type = stored; + } + } + } +} diff --git a/packages/compiler/src/frontend/lowering/builtin-calls.ts b/packages/compiler/src/frontend/lowering/builtin-calls.ts index 7d3a5d41a..f83958b32 100644 --- a/packages/compiler/src/frontend/lowering/builtin-calls.ts +++ b/packages/compiler/src/frontend/lowering/builtin-calls.ts @@ -13,6 +13,7 @@ import { lowerEventsEmitterHelper, lowerEventsOnce } from "./lower-event-emitter import type { Lowerer } from "./lowerer.js"; import { lowerTimersMemberCall } from "./lower-timers.js"; import { lowerStreamModuleCall } from "./lower-stream.js"; +import { lowerCallbackPromisify } from "./builtins/promisify-callback.js"; import { lowerNodeModuleCall } from "./builtins/module-resolution.js"; import { builtinModuleFnOf, builtinFenceHintOf } from "./surfaces.js"; @@ -91,6 +92,10 @@ export function lowerBuiltinCall( if (testServed) return testServed; const utilServed = lowerer.lowerUtilModuleCall(expr, bi, loc); if (utilServed) return utilServed; + if (bi.module === "util" && bi.member === "promisify") { + const promisified = lowerCallbackPromisify(lowerer, expr, loc); + if (promisified) return promisified; + } const streamServed = lowerStreamModuleCall(lowerer, expr, bi, loc); if (streamServed) return streamServed; const fsTs = lowerer.lowerFsToUnixTimestampCall(expr, bi, loc); diff --git a/packages/compiler/src/frontend/lowering/builtins/callable-aliases.ts b/packages/compiler/src/frontend/lowering/builtins/callable-aliases.ts index 1669978af..afb221d04 100644 --- a/packages/compiler/src/frontend/lowering/builtins/callable-aliases.ts +++ b/packages/compiler/src/frontend/lowering/builtins/callable-aliases.ts @@ -1,11 +1,12 @@ import * as ts from "../../ts7/adapter.js"; import { type Lowerer } from "../lowerer.js"; -import { isJsSourceFile } from "../../program.js"; +import { isJsSourceFile, locOf } from "../../program.js"; import { builtinModuleFnOf } from "../surfaces.js"; import { registerHttpClientFnBinding } from "../lower-server.js"; import { isPromisifiedZlibMember } from "./compression.js"; import { isPromisifiedCryptoMember } from "./crypto.js"; import { isPromisifiedDnsMember } from "../lower-dgram.js"; +import { callbackPromisifyShape } from "./promisify-callback.js"; /** Compile-time util.promisify projections. The immutable result binding * owns no runtime slot: calls rewrite to the target's promise lowering, @@ -164,11 +165,14 @@ export function registerPromisifiedBuiltinDecl( : target?.module === "dns" && isPromisifiedDnsMember(target.member) ? { kind: "promisified-dns" as const, member: target.member } : null; + // A typed callback-last function value: the call lowers to a native + // promisified function, and the binding holds it like any closure. + if (!projection && callbackPromisifyShape(lowerer, e, locOf(e)) !== null) return false; if (!projection) { lowerer.noLowering( "util.promisify of this target", argNode ?? e, - "child_process.exec, child_process.execFile, setTimeout, setImmediate, stream.finished, stream.pipeline, the zlib callback one-shots, the crypto callback functions (randomBytes, pbkdf2, scrypt, hkdf, randomFill, randomInt, generateKey), dns.lookup, dns.lookupService and the fs callback functions with an fs/promises counterpart are the supported targets", + "child_process.exec, child_process.execFile, setTimeout, setImmediate, stream.finished, stream.pipeline, the zlib callback one-shots, the crypto callback functions (randomBytes, pbkdf2, scrypt, hkdf, randomFill, randomInt, generateKey), dns.lookup, dns.lookupService, the fs callback functions with an fs/promises counterpart, and typed callback-last functions whose callback takes (error?: Error | null, value?) are the supported targets", ); } const symbol = lowerer.checker.getSymbolAtLocation(nameNode); diff --git a/packages/compiler/src/frontend/lowering/builtins/ipc.ts b/packages/compiler/src/frontend/lowering/builtins/ipc.ts index cf9d96117..90448854d 100644 --- a/packages/compiler/src/frontend/lowering/builtins/ipc.ts +++ b/packages/compiler/src/frontend/lowering/builtins/ipc.ts @@ -1,7 +1,17 @@ import * as ts from "../../ts7/adapter.js"; import { type Lowerer } from "../lowerer.js"; import { locOf } from "../../program.js"; -import { BOOL, type IrExpr, type IrType, STRING, type SrcLoc } from "../../../ir/ir.js"; +import { + BOOL, + type IrExpr, + type IrStmt, + type IrType, + STRING, + type SrcLoc, + UNDEFINED_T, +} from "../../../ir/ir.js"; +import { nodeThrowExpr } from "../../../ir/build.js"; +import { processHasIpc } from "./process.js"; /** Method calls on ChildProcess receivers: `child.on("exit"|"error", cb)` * registers a listener with the event loop's child registry. The event @@ -86,8 +96,54 @@ export function ipcSendCallback(lowerer: Lowerer, node: ts.Expression): IrExpr { return cb; } +/** process.send in a program without fork targets. This binary never opens + * a channel then, so only a Node.js parent's NODE_CHANNEL_FD can define + * process.send, and that channel has no lowering: a call fences when it runs. + * Without any channel the calls behave as Node's do on an undefined member: + * `process.send?.(...)` skips its arguments and answers undefined, and + * `process.send(...)` evaluates them and throws a TypeError. The message is + * never sent, so its type is not checked. */ +function lowerChannelLessSend(lowerer: Lowerer, call: ts.CallExpression): IrExpr { + const loc = locOf(call); + const fence = (type: IrType): IrExpr => + nodeThrowExpr( + 0, + "SC2020", + "process.send over an IPC channel opened by a Node.js parent (NODE_CHANNEL_FD) is not supported yet", + type, + loc, + ); + const hasIpc = processHasIpc(lowerer, loc); + if (call.questionDotToken) { + const type = lowerer.mapTypeOf(lowerer.typeOf(call)) ?? BOOL; + const absent: IrExpr = + type.kind === "bool" + ? { kind: "boolLit", value: false, type: BOOL, loc } + : lowerer.coerceToExpected( + { kind: "unitLit", unit: "undefined", type: UNDEFINED_T, loc }, + type, + ); + return { kind: "ternary", cond: hasIpc, then: fence(type), else_: absent, type, loc }; + } + const stmts: IrStmt[] = call.arguments.nodes.map((arg) => ({ + kind: "exprStmt", + expr: lowerer.lowerExpr(ts.isSpreadElement(arg) ? arg.expression : arg), + loc: locOf(arg), + })); + const result: IrExpr = { + kind: "ternary", + cond: hasIpc, + then: fence(BOOL), + else_: nodeThrowExpr(1, "", "process.send is not a function", BOOL, loc), + type: BOOL, + loc, + }; + return stmts.length === 0 ? result : { kind: "seqExpr", stmts, result, type: BOOL, loc }; +} + export function lowerProcessIpcSend(lowerer: Lowerer, call: ts.CallExpression): IrExpr { const loc = locOf(call); + if (lowerer.forkTargets.length === 0) return lowerChannelLessSend(lowerer, call); if ( call.arguments.nodes.length < 1 || call.arguments.nodes.length > 2 || diff --git a/packages/compiler/src/frontend/lowering/builtins/process.ts b/packages/compiler/src/frontend/lowering/builtins/process.ts index 555eaf84f..dc2f5ee89 100644 --- a/packages/compiler/src/frontend/lowering/builtins/process.ts +++ b/packages/compiler/src/frontend/lowering/builtins/process.ts @@ -17,6 +17,7 @@ import { type IrExpr, type IrLibFn, type IrStmt, + type IrType, STRING, type SrcLoc, UNDEFINED_T, @@ -30,6 +31,144 @@ import { canonicalBuiltinModule } from "../../builtin-modules.js"; import { lowerProcessIpcSend, ipcMessageListener, ipcDisconnectListener } from "./ipc.js"; import { lowerDiagnosticsSubscriber, lowerTracingArguments } from "./async-context.js"; +/** Whether the process has an IPC channel, Node's test for defining + * process.send and process.disconnect: a channel this fork child opened + * (programs with fork targets only), or a non-empty NODE_CHANNEL_FD from a + * Node.js parent, the variable Node itself tests at startup. Built from the + * existing environment read, so the presence test links no runtime code. */ +export function processHasIpc(lowerer: Lowerer, loc: SrcLoc): IrExpr { + const envT = lowerer.envValueType(); + const unionId = (envT as IrType & { kind: "union" }).unionId; + const stringTag = lowerer.armTag(unionId, STRING); + const value = lowerer.declareHiddenLocal("%channelFd", envT); + const fromNode: IrExpr = { + kind: "seqExpr", + stmts: [ + { + kind: "varDecl", + localId: value.id, + init: { + kind: "libCall", + fn: "process.envGet", + args: [strLit("NODE_CHANNEL_FD", loc)], + type: envT, + loc, + }, + loc, + }, + ], + result: { + kind: "logical", + op: "&&", + left: { + kind: "unionIsTag", + unionId, + tag: stringTag, + negated: false, + value: varRef(value.id, envT, loc), + type: BOOL, + loc, + }, + right: { + kind: "toBool", + operand: { + kind: "unionNarrow", + unionId, + tag: stringTag, + value: varRef(value.id, envT, loc), + type: STRING, + loc, + }, + type: BOOL, + loc, + }, + type: BOOL, + loc, + }, + type: BOOL, + loc, + }; + if (lowerer.forkTargets.length === 0) return fromNode; + return { + kind: "logical", + op: "||", + left: { kind: "libCall", fn: "process.ipcOpen", args: [], type: BOOL, loc }, + right: fromNode, + type: BOOL, + loc, + }; +} + +/** True when the value of `node` is consumed only by ToBoolean: a + * condition, the operand of `!`, `Boolean(node)`, or an operand of `&&` / + * `||` whose own value is consumed that way (or discarded, for the left + * operand). */ +export function isTruthinessOnlyRead(lowerer: Lowerer, node: ts.Expression): boolean { + let child: ts.Node = node; + let parent = node.parent; + while (parent !== undefined && ts.isParenthesizedExpression(parent)) { + child = parent; + parent = parent.parent; + } + if (parent === undefined) return false; + if (ts.isIfStatement(parent) || ts.isWhileStatement(parent) || ts.isDoStatement(parent)) + return parent.expression === child; + if (ts.isForStatement(parent)) return parent.condition === child; + if (ts.isConditionalExpression(parent)) return parent.condition === child; + if (ts.isPrefixUnaryExpression(parent)) return parent.operator === ts.SyntaxKind.ExclamationToken; + if (ts.isCallExpression(parent)) { + return ( + !parent.questionDotToken && + parent.arguments.nodes.length === 1 && + parent.arguments.nodes[0] === child && + lowerer.isStdlibGlobal(parent.expression, "Boolean") + ); + } + if (ts.isBinaryExpression(parent)) { + const op = parent.operatorToken.kind; + if (op !== ts.SyntaxKind.AmpersandAmpersandToken && op !== ts.SyntaxKind.BarBarToken) + return false; + if (isTruthinessOnlyRead(lowerer, parent)) return true; + let outer: ts.Node | undefined = parent.parent; + while (outer !== undefined && ts.isParenthesizedExpression(outer)) outer = outer.parent; + return parent.left === child && outer !== undefined && ts.isExpressionStatement(outer); + } + return false; +} + +/** Whether a program source reads process.exitCode. Such a program + * records its exit-code assignments (process.setExitCodeTracked), so a + * read can tell an unset code from 0; other programs keep the untracked + * write. Computed once per lowering pass. */ +export function programReadsExitCode(lowerer: Lowerer): boolean { + if (lowerer.readsExitCode !== null) return lowerer.readsExitCode; + let found = false; + const walk = (node: ts.Node): void => { + if (found) return; + if ( + ts.isPropertyAccessExpression(node) && + node.name.text === "exitCode" && + !( + ts.isBinaryExpression(node.parent) && + node.parent.left === node && + node.parent.operatorToken.kind === ts.SyntaxKind.EqualsToken + ) && + lowerer.stdlibGlobalMember(node, "process") === "exitCode" + ) { + found = true; + return; + } + ts.forEachChild(node, walk); + }; + for (const sf of lowerer.program.getImplementationSourceFiles()) { + if (sf.isDeclarationFile) continue; + walk(sf); + if (found) break; + } + lowerer.readsExitCode = found; + return found; +} + /** The pinned Node release the runtime reports as process.versions.node * (packages/runtime/src/scr_lib.c SCR_NODE_COMPAT_VERSION, kept equal to * internal/compatibility/node-v24.json by test/node-pin.test.ts). */ @@ -374,9 +513,45 @@ export function lowerProcessProperty( const member = lowerer.stdlibGlobalMember(expr, "process"); if (member === null) return null; const loc = locOf(expr); + // `if (process.send)`: Node defines process.send and process.disconnect + // only when the process has an IPC channel. A read whose value only + // feeds a truthiness test answers that presence; other value uses keep + // the member fence. + if ( + (member === "send" || member === "disconnect") && + !expr.questionDotToken && + isTruthinessOnlyRead(lowerer, expr) + ) { + return processHasIpc(lowerer, loc); + } if (member === "versions") { return { kind: "libCall", fn: "process.versions", args: [], type: DYN, loc }; } + // process.exitCode: undefined until something assigns it, then the + // assigned code. A read the checker narrowed to number follows an + // assignment, so the code is set. + if (member === "exitCode" && !expr.questionDotToken) { + const current: IrExpr = { + kind: "libCall", + fn: "process.currentExitCode", + args: [], + type: F64, + loc, + }; + if (lowerer.mapTypeOf(lowerer.typeOf(expr))?.kind === "f64") return current; + const type: IrType = { kind: "union", unionId: lowerer.unions.intern([F64, UNDEFINED_T]) }; + return { + kind: "ternary", + cond: { kind: "libCall", fn: "process.hasExitCode", args: [], type: BOOL, loc }, + then: lowerer.coerceToExpected(current, type), + else_: lowerer.coerceToExpected( + { kind: "unitLit", unit: "undefined", type: UNDEFINED_T, loc }, + type, + ), + type, + loc, + }; + } // process.features as a value (`"sea" in process.features`): an object // holding none of the flags, consistent with the member reads above. if (member === "features") { @@ -1575,10 +1750,36 @@ export function lowerProcessMethodCall( } if (member === "exit") { const arg = call.arguments.nodes[0]; - const code: IrExpr = + let code: IrExpr = arg !== undefined ? lowerer.lowerExprExpecting(arg, F64) : { kind: "libCall", fn: "process.currentExitCode", args: [], type: F64, loc }; + // process.exit(code) assigns process.exitCode before the 'exit' + // listeners run. Only a program that reads the property can observe + // that, so only such a program records the assignment. + if (arg !== undefined && programReadsExitCode(lowerer)) { + const binding = lowerer.declareHiddenLocal("%exitCode", F64); + code = { + kind: "seqExpr", + stmts: [ + { kind: "varDecl", localId: binding.id, init: code, loc }, + { + kind: "exprStmt", + expr: { + kind: "libCall", + fn: "process.setExitCodeTracked", + args: [varRef(binding.id, F64, loc)], + type: VOID, + loc, + }, + loc, + }, + ], + result: varRef(binding.id, F64, loc), + type: F64, + loc, + }; + } return { kind: "libCall", fn: "process.exit", args: [code], type: VOID, loc }; } return null; // process.argv(...) etc. are tsc errors before lowering @@ -1594,6 +1795,16 @@ export function lowerProcessOptionalMethodCall( lowerer: Lowerer, expr: ts.CallExpression, ): IrExpr | null { + // `process.send!(message)`: the asserted direct call of the optional + // member (the @types/node spelling under strict null checks). + if ( + !expr.questionDotToken && + ts.isNonNullExpression(expr.expression) && + ts.isPropertyAccessExpression(expr.expression.expression) && + !expr.expression.expression.questionDotToken && + lowerer.stdlibGlobalMember(expr.expression.expression, "process") === "send" + ) + return lowerProcessIpcSend(lowerer, expr); if (!expr.questionDotToken) return null; if (!ts.isPropertyAccessExpression(expr.expression)) return null; const member = lowerer.stdlibGlobalMember(expr.expression, "process"); diff --git a/packages/compiler/src/frontend/lowering/builtins/promises.ts b/packages/compiler/src/frontend/lowering/builtins/promises.ts index 2656da18b..48106d082 100644 --- a/packages/compiler/src/frontend/lowering/builtins/promises.ts +++ b/packages/compiler/src/frontend/lowering/builtins/promises.ts @@ -65,6 +65,9 @@ export function lowerPromiseStaticCall( const compatible = (inner: IrType): boolean => { if (typeEquals(inner, resultT.inner)) return true; if (!resultArms) return false; + // A Promise entry fulfills with undefined, which the result + // holds when the checker folded `void` into its undefined arm. + if (inner.kind === "void") return resultArms.some((a) => a.kind === "undefinedT"); if (inner.kind === "union") { const arms = lowerer.unions.get(inner.unionId)?.arms ?? []; return arms.every((a) => resultArms.some((b) => typeEquals(a, b))); diff --git a/packages/compiler/src/frontend/lowering/builtins/promisify-callback.ts b/packages/compiler/src/frontend/lowering/builtins/promisify-callback.ts new file mode 100644 index 000000000..cdb8a1fbb --- /dev/null +++ b/packages/compiler/src/frontend/lowering/builtins/promisify-callback.ts @@ -0,0 +1,377 @@ +import * as ts from "../../ts7/adapter.js"; +import { type Lowerer } from "../lowerer.js"; +import { varRef } from "../../../ir/build.js"; +import { + BOOL, + type IrExpr, + type IrLocal, + type IrParam, + type IrStmt, + type IrType, + type SrcLoc, + VOID, + funcOf, + isUnitType, + typeEquals, + typeKey, +} from "../../../ir/ir.js"; + +const ERROR_T: IrType = { kind: "object", className: "%Error" }; + +/** The parts of `util.promisify(fn)` for a typed callback-last function: + * `fn(...args, (err, value) => void)` becomes `(...args) => Promise`. */ +export interface CallbackShape { + fnT: IrType & { kind: "func" }; + resultT: IrType & { kind: "func" }; + args: readonly IrType[]; + callbackT: IrType & { kind: "func" }; + /** The parameter's declared type: callbackT, or `callbackT | undefined` + * for an optional callback. */ + callbackParamT: IrType; + inner: IrType; + /** The error parameter's union and the tag of its Error arm, or null when + * the callback declares no error parameter. */ + error: { type: IrType; errorTag: number; unitTags: number[] } | null; +} + +function callbackShape( + lowerer: Lowerer, + fnT: IrType | null, + resultT: IrType | null, + loc: SrcLoc, +): CallbackShape | null { + if (fnT?.kind !== "func" || resultT?.kind !== "func") return null; + if (fnT.rest || fnT.restAbi || fnT.argumentsAll || resultT.rest || resultT.restAbi) return null; + const callbackParamT = fnT.params[fnT.params.length - 1]; + // An optional callback (`close(callback?: (err?: Error) => void)`) takes + // its function arm; the call passes the callback into the union. + const callbackT = + callbackParamT?.kind === "union" + ? (() => { + const arms = lowerer.unions.get(callbackParamT.unionId)?.arms ?? []; + const fns = arms.filter((arm) => arm.kind === "func"); + return fns.length === 1 && + arms.every((arm) => arm.kind === "func" || arm.kind === "undefinedT") + ? fns[0] + : undefined; + })() + : callbackParamT; + if (callbackT?.kind !== "func" || callbackT.ret.kind !== "void") return null; + if (callbackT.rest || callbackT.restAbi || callbackT.params.length > 2) return null; + const args = fnT.params.slice(0, -1); + if ( + resultT.ret.kind !== "promise" || + resultT.params.length !== args.length || + !resultT.params.every((type, i) => typeEquals(type, args[i]!)) + ) + return null; + const inner = resultT.ret.inner; + let error: CallbackShape["error"] = null; + const errorT = callbackT.params[0]; + if (errorT !== undefined) { + if (errorT.kind !== "union") return null; + const arms = lowerer.unions.get(errorT.unionId)?.arms ?? []; + const errorTags = arms.flatMap((arm, tag) => + arm.kind === "object" && + (arm.className === "%Error" || lowerer.isSubclassOf(arm.className, "%Error")) + ? [tag] + : [], + ); + const unitTags = arms.flatMap((arm, tag) => (isUnitType(arm) ? [tag] : [])); + if (errorTags.length !== 1 || errorTags.length + unitTags.length !== arms.length) return null; + error = { type: errorT, errorTag: errorTags[0]!, unitTags }; + } + if (inner.kind !== "void") { + const valueT = callbackT.params[1]; + if (valueT === undefined) return null; + const probe: IrExpr = { kind: "varRef", localId: "%probe", type: valueT, loc }; + if (!typeEquals(lowerer.coerceToExpected(probe, inner).type, inner)) return null; + } + return { fnT, resultT, args, callbackT, callbackParamT: callbackParamT!, inner, error }; +} + +/** The callback shape of a `promisify(fn)` call, or null. */ +export function callbackPromisifyShape( + lowerer: Lowerer, + call: ts.CallExpression, + loc: SrcLoc, +): CallbackShape | null { + if (call.questionDotToken || call.arguments.nodes.length !== 1) return null; + const argNode = call.arguments.nodes[0]!; + if (ts.isSpreadElement(argNode)) return null; + return callbackShape( + lowerer, + lowerer.mapTypeOf(lowerer.typeOf(argNode)), + lowerer.mapTypeOf(lowerer.typeOf(call)), + loc, + ); +} + +function captureParam(localId: string, type: IrType): IrParam { + return { localId, name: localId, type }; +} + +function boxedLocal(localId: string, type: IrType): IrLocal { + return { id: localId, name: localId, type, mutable: false, boxed: true }; +} + +function plainLocal(localId: string, type: IrType): IrLocal { + return { id: localId, name: localId, type, mutable: false }; +} + +/** `util.promisify(fn)` over a typed callback-last function value (a bound + * method, a function declaration, a closure): a native function that calls + * `fn` with the arguments and a callback, and settles its promise from the + * callback like Node's wrapper: a truthy error rejects, otherwise the first + * value fulfills. A synchronous throw rejects (the executor's rule). Null + * when the argument or result does not have that typed shape. */ +export function lowerCallbackPromisify( + lowerer: Lowerer, + call: ts.CallExpression, + loc: SrcLoc, +): IrExpr | null { + const shape = callbackPromisifyShape(lowerer, call, loc); + if (shape === null) return null; + const argNode = call.arguments.nodes[0]!; + let fn = lowerer.lowerExpr(argNode); + if (!typeEquals(fn.type, shape.fnT)) fn = lowerer.coerceToExpected(fn, shape.fnT); + if (!typeEquals(fn.type, shape.fnT)) return null; + return { + kind: "call", + callee: promisifyMaker(lowerer, shape, loc), + args: [fn], + type: shape.resultT, + loc, + }; +} + +/** Interns the helper family for one (function, result) type pair and + * answers the maker: `(fn) => closure(...args) => Promise`. */ +function promisifyMaker(lowerer: Lowerer, shape: CallbackShape, loc: SrcLoc): string { + const key = `promisify:${typeKey(shape.fnT)}=>${typeKey(shape.resultT)}`; + const existing = lowerer.arrHofHelpers.get(key); + if (existing) return existing; + const base = `%promisify.${lowerer.arrHofHelpers.size}`; + lowerer.arrHofHelpers.set(key, base); + const { fnT, resultT, args, callbackT, callbackParamT, inner, error } = shape; + const promiseT = resultT.ret; + const resolveT = funcOf(inner.kind === "void" ? [] : [inner], VOID); + const rejectT = funcOf([ERROR_T], VOID); + const executorT = funcOf([resolveT, rejectT], VOID); + const argIds = args.map((_, i) => `a${i}.0`); + + // The callback: (err, value) => err ? reject(err) : resolve(value). + const cbParams: IrParam[] = []; + callbackT.params.forEach((type, i) => + cbParams.push(captureParam(i === 0 ? "err.0" : "value.0", type)), + ); + const cbBody: IrStmt[] = []; + if (error !== null) { + const err = varRef("err.0", error.type, loc); + let truthy: IrExpr | null = null; + for (const tag of error.unitTags) { + const part: IrExpr = { + kind: "unionIsTag", + unionId: (error.type as IrType & { kind: "union" }).unionId, + tag, + negated: true, + value: err, + type: BOOL, + loc, + }; + truthy = + truthy === null + ? part + : { kind: "logical", op: "&&", left: truthy, right: part, type: BOOL, loc }; + } + const errorArm = lowerer.unions.get((error.type as IrType & { kind: "union" }).unionId)!.arms[ + error.errorTag + ]!; + let reason: IrExpr = { + kind: "unionNarrow", + unionId: (error.type as IrType & { kind: "union" }).unionId, + tag: error.errorTag, + value: err, + type: errorArm, + loc, + }; + if (!typeEquals(errorArm, ERROR_T)) + reason = { kind: "upcast", value: reason, type: ERROR_T, loc }; + const reject: IrStmt[] = [ + { + kind: "exprStmt", + expr: { + kind: "callValue", + callee: varRef("rej.0", rejectT, loc), + args: [reason], + type: VOID, + loc, + }, + loc, + }, + { kind: "return", value: null, loc }, + ]; + cbBody.push( + truthy === null ? reject[0]! : { kind: "if", cond: truthy, then: reject, else_: null, loc }, + ); + if (truthy === null) cbBody.push(reject[1]!); + } + const value = + inner.kind === "void" + ? [] + : [lowerer.coerceToExpected(varRef("value.0", callbackT.params[1]!, loc), inner)]; + cbBody.push({ + kind: "exprStmt", + expr: { + kind: "callValue", + callee: varRef("res.0", resolveT, loc), + args: value, + type: VOID, + loc, + }, + loc, + }); + lowerer.liftedFns.push({ + name: `${base}.cb`, + params: cbParams, + returnType: VOID, + captures: [captureParam("res.0", resolveT), captureParam("rej.0", rejectT)], + locals: [ + boxedLocal("res.0", resolveT), + boxedLocal("rej.0", rejectT), + ...cbParams.map((p) => plainLocal(p.localId, p.type)), + ], + body: cbBody, + loc, + }); + lowerer.liftedFns.push({ + name: `${base}.mkcb`, + params: [captureParam("res.0", resolveT), captureParam("rej.0", rejectT)], + returnType: callbackT, + locals: [boxedLocal("res.0", resolveT), boxedLocal("rej.0", rejectT)], + body: [ + { + kind: "return", + value: { + kind: "closure", + fnName: `${base}.cb`, + captures: ["res.0", "rej.0"], + type: callbackT, + loc, + }, + loc, + }, + ], + loc, + }); + + // The executor: (resolve, reject) => fn(...args, callback). + const captured: IrParam[] = [captureParam("f.0", fnT)]; + args.forEach((type, i) => captured.push(captureParam(argIds[i]!, type))); + lowerer.liftedFns.push({ + name: `${base}.exec`, + params: [captureParam("res.0", resolveT), captureParam("rej.0", rejectT)], + returnType: VOID, + captures: captured, + locals: [ + ...captured.map((p) => boxedLocal(p.localId, p.type)), + plainLocal("res.0", resolveT), + plainLocal("rej.0", rejectT), + ], + body: [ + { + kind: "exprStmt", + expr: { + kind: "callValue", + callee: varRef("f.0", fnT, loc), + args: [ + ...args.map((type, i) => varRef(argIds[i]!, type, loc)), + lowerer.coerceToExpected( + { + kind: "call", + callee: `${base}.mkcb`, + args: [varRef("res.0", resolveT, loc), varRef("rej.0", rejectT, loc)], + type: callbackT, + loc, + }, + callbackParamT, + ), + ], + // The function's own return type; the value is discarded. + type: fnT.ret, + loc, + }, + loc, + }, + ], + loc, + }); + lowerer.liftedFns.push({ + name: `${base}.mkexec`, + params: captured, + returnType: executorT, + locals: captured.map((p) => boxedLocal(p.localId, p.type)), + body: [ + { + kind: "return", + value: { + kind: "closure", + fnName: `${base}.exec`, + captures: captured.map((p) => p.localId), + type: executorT, + loc, + }, + loc, + }, + ], + loc, + }); + + // The promisified function: (...args) => new Promise(executor). + const implParams: IrParam[] = []; + args.forEach((type, i) => implParams.push(captureParam(argIds[i]!, type))); + lowerer.liftedFns.push({ + name: `${base}.impl`, + params: implParams, + returnType: promiseT, + captures: [captureParam("f.0", fnT)], + locals: [boxedLocal("f.0", fnT), ...implParams.map((p) => plainLocal(p.localId, p.type))], + body: [ + { + kind: "return", + value: { + kind: "newPromise", + executor: { + kind: "call", + callee: `${base}.mkexec`, + args: [ + varRef("f.0", fnT, loc), + ...implParams.map((p) => varRef(p.localId, p.type, loc)), + ], + type: executorT, + loc, + }, + type: promiseT, + loc, + }, + loc, + }, + ], + loc, + }); + lowerer.liftedFns.push({ + name: base, + params: [captureParam("f.0", fnT)], + returnType: resultT, + locals: [boxedLocal("f.0", fnT)], + body: [ + { + kind: "return", + value: { kind: "closure", fnName: `${base}.impl`, captures: ["f.0"], type: resultT, loc }, + loc, + }, + ], + loc, + }); + return base; +} diff --git a/packages/compiler/src/frontend/lowering/call-signatures.ts b/packages/compiler/src/frontend/lowering/call-signatures.ts index aae73076b..954122a16 100644 --- a/packages/compiler/src/frontend/lowering/call-signatures.ts +++ b/packages/compiler/src/frontend/lowering/call-signatures.ts @@ -33,6 +33,7 @@ import { isStreamUndefCallExpr } from "./lower-server.js"; import { nsPathPrefix } from "./lower-namespaces.js"; import { declSymbolOf } from "./lower-modules.js"; import { npmStaticPackageOfPath } from "../npm-static.js"; +import { restPackingAdapterFits } from "./coercions/function-adapters.js"; import { implicitMonoFile, implicitAnyParamSymbolsOf } from "./generic-functions.js"; /** How a parameter participates in CALL-SITE COMPLETION (the frontend @@ -608,6 +609,21 @@ export function requireExactArityValue( // optional/defaulted params spell their `T | undefined` slots (mapType's // completed-signature contract), so omitted trailing args complete with // the undefined arm like any direct call. + // A nested function declaration whose only non-required parameter is a + // typed rest: its value carries the typed-rest ABI its own type spells, + // and every use converts it at its slot (same signature, or the rest + // packing adapter for fixed parameters). + if ( + contextual === null && + ts.isFunctionDeclaration(blame) && + blame.name !== undefined && + funcType.kind === "func" && + funcType.restAbi === "typed" && + shapes.every((s) => s.mode === "required" || s.mode === "rest") + ) { + const own = lowerer.mapTypeOf(lowerer.typeOf(blame.name)); + if (own && typeEquals(own, funcType)) return; + } const target = contextual ? lowerer.checker.getContextualType(contextual) : undefined; const mapped = target ? lowerer.mapTypeOf(target) @@ -668,6 +684,16 @@ export function requireExactArityValue( ) { return; } + // A typed-rest function in a slot with fixed parameters (`log(...m: + // string[])` into `(m: string) => void`): the slot coercion's packing + // adapter collects the slot's arguments into the rest array. + if ( + mappedFn?.kind === "func" && + funcType.kind === "func" && + restPackingAdapterFits(lowerer, funcType, mappedFn) + ) { + return; + } // An uncontextualized npm-static implementation function can still // have a declaration-backed completed ABI: the package's .d.ts // projection supplies the body-facing parameter/return types while diff --git a/packages/compiler/src/frontend/lowering/class-attributes.ts b/packages/compiler/src/frontend/lowering/class-attributes.ts new file mode 100644 index 000000000..4e7bbe801 --- /dev/null +++ b/packages/compiler/src/frontend/lowering/class-attributes.ts @@ -0,0 +1,440 @@ +import * as ts from "../ts7/adapter.js"; +import { + BOOL, + CLASS_ATTRIBUTES, + CLASS_FROZEN, + CLASS_NOT_EXTENSIBLE, + CLASS_SEALED, + DYN, + F64, + FIELD_NOT_WRITABLE, + STRING, + VOID, + canConvertToDyn, + canDynCheckTo, + classFieldAttributesName, + isClassOwnEnumerableFieldName, + isDynTypedRefType, + type IrExpr, + type IrFunction, + type IrStmt, + type IrType, + type SrcLoc, +} from "../../ir/ir.js"; +import { numLit, strLit, varRef } from "../../ir/build.js"; +import { transformStmtList } from "../../ir/traverse.js"; +import { locOf } from "../program.js"; +import { type Lowerer, PoisonError } from "./lowerer.js"; +import type { ClassInfo } from "./lower-classes.js"; +import { classPropertiesHelper } from "./class-dynamic-dispatch.js"; +import { runtimeRooted } from "./class-descriptors.js"; + +/* Object.freeze, Object.seal, Object.preventExtensions and + * Object.defineProperty on class instances. Only the hierarchies these calls + * name pay for them: such a hierarchy gets a CLASS_ATTRIBUTES integrity word + * per instance, an attribute word per redefined field, and a check before + * each write to a declared field (guardClassAttributeWrites). The instance's + * checked view carries the attributes, so reflection answers from it. */ + +type ClassObjectType = Extract; + +function attributeClass(lowerer: Lowerer, target: IrExpr): ClassInfo | null { + if (!isDynTypedRefType(target.type)) return null; + const info = lowerer.classes.get(target.type.className); + if (!info || runtimeRooted(info) || info.decl == null) return null; + return info; +} + +/** Adds a zero-initialized f64 to every class of the hierarchy, at the same + * offset, like the property bag. */ +function addHiddenField(info: ClassInfo, name: string): void { + let root = info; + while (root.base && !root.base.def.runtime) root = root.base; + if (root.fields.has(name)) return; + const index = root.def.fields.length; + const add = (current: ClassInfo): void => { + if (!current.fields.has(name)) { + current.fields.set(name, F64); + current.def.fields.splice(index, 0, { name, type: F64 }); + } + current.subclasses.forEach(add); + }; + add(root); +} + +function hiddenFieldGet(receiver: IrExpr, className: string, field: string, loc: SrcLoc): IrExpr { + return { kind: "fieldGet", obj: receiver, className, field, type: F64, loc }; +} + +/** Object.freeze, Object.seal and Object.preventExtensions of a class + * instance: sets the instance's integrity bits, then applies the operation to + * the properties added to the instance. Answers the instance. */ +export function lowerClassIntegrity( + lowerer: Lowerer, + call: ts.CallExpression, + member: "freeze" | "seal" | "preventExtensions", + target: IrExpr, +): IrExpr | null { + const info = attributeClass(lowerer, target); + if (!info) return null; + addHiddenField(info, CLASS_ATTRIBUTES); + const loc = locOf(call); + const type = target.type as ClassObjectType; + const bits = + member === "freeze" + ? CLASS_NOT_EXTENSIBLE | CLASS_SEALED | CLASS_FROZEN + : member === "seal" + ? CLASS_NOT_EXTENSIBLE | CLASS_SEALED + : CLASS_NOT_EXTENSIBLE; + const slot = lowerer.declareHiddenLocal("%integrityReceiver", type); + const receiver = (): IrExpr => varRef(slot.id, type, loc); + const bag: IrExpr = { + kind: "call", + callee: classPropertiesHelper(lowerer, loc).name, + args: [lowerer.coerceToExpected(receiver(), DYN)], + type: DYN, + loc, + }; + const operation = { + freeze: "dyn.freeze", + seal: "dyn.seal", + preventExtensions: "dyn.preventExtensions", + } as const; + return { + kind: "seqExpr", + stmts: [ + { kind: "varDecl", localId: slot.id, init: target, loc }, + { + kind: "fieldSet", + obj: receiver(), + className: type.className, + field: CLASS_ATTRIBUTES, + value: { + kind: "bin", + op: "|", + left: hiddenFieldGet(receiver(), type.className, CLASS_ATTRIBUTES, loc), + right: numLit(bits, loc), + type: F64, + loc, + }, + attributesChecked: true, + loc, + }, + { + kind: "exprStmt", + expr: { kind: "libCall", fn: operation[member], args: [bag], type: DYN, loc }, + loc, + }, + ], + result: receiver(), + type, + loc, + }; +} + +/** An object literal with a get or set property. */ +function literalAccessorDescriptor(node: ts.Expression): boolean { + while (ts.isParenthesizedExpression(node)) node = node.expression; + return ( + ts.isObjectLiteralExpression(node) && + node.properties.nodes.some( + (property) => + property.name !== undefined && + (ts.isIdentifier(property.name) || ts.isStringLiteral(property.name)) && + (property.name.text === "get" || property.name.text === "set"), + ) + ); +} + +/** Object.defineProperty with a string-literal key naming a declared field + * of a class instance: a data descriptor changes the field's value and + * attributes, validated as Node validates ordinary objects. Answers the + * instance, or null when the call is not of that form. An object-literal + * accessor descriptor is refused at compile time; other accessor + * descriptors throw SC2020 when the call runs. */ +export function lowerClassFieldDefine( + lowerer: Lowerer, + call: ts.CallExpression, + target: IrExpr, +): IrExpr | null { + const info = attributeClass(lowerer, target); + const key = call.arguments.nodes[1]!; + if (!info || !ts.isStringLiteral(key)) return null; + const field = key.text; + const fieldType = info.fields.get(field); + if ( + fieldType === undefined || + !isClassOwnEnumerableFieldName(field) || + [...(info.symbolFields?.values() ?? [])].includes(field) + ) + return null; + if (literalAccessorDescriptor(call.arguments.nodes[2]!)) { + lowerer.pushDiag({ + code: "SC2020", + message: `Object.defineProperty with an accessor descriptor for the declared field '${field}' is not supported yet`, + loc: locOf(call), + }); + throw new PoisonError(); + } + const getRecord = (id: string) => lowerer.shapes.get(id); + const getUnion = (id: string) => lowerer.unions.get(id); + if ( + !canConvertToDyn(fieldType, getRecord, getUnion) || + !canDynCheckTo(fieldType, getRecord, getUnion) + ) + return null; + const attributesField = classFieldAttributesName(field); + addHiddenField(info, CLASS_ATTRIBUTES); + addHiddenField(info, attributesField); + const loc = locOf(call); + const type = target.type as ClassObjectType; + const receiverSlot = lowerer.declareHiddenLocal("%defineReceiver", type); + const descriptorSlot = lowerer.declareHiddenLocal("%defineDescriptor", DYN); + const holderSlot = lowerer.declareHiddenLocal("%defineHolder", DYN); + const receiver = (): IrExpr => varRef(receiverSlot.id, type, loc); + const descriptor = (): IrExpr => varRef(descriptorSlot.id, DYN, loc); + const holder = (): IrExpr => varRef(holderSlot.id, DYN, loc); + const name = (): IrExpr => strLit(field, loc); + const current: IrExpr = { + kind: "fieldGet", + obj: receiver(), + className: type.className, + field, + type: fieldType, + loc, + }; + const defined: IrExpr = { + kind: "libCall", + fn: "class.definedValue", + args: [holder(), name()], + type: DYN, + loc, + }; + return { + kind: "seqExpr", + stmts: [ + { kind: "varDecl", localId: receiverSlot.id, init: target, loc }, + { + kind: "varDecl", + localId: descriptorSlot.id, + init: lowerer.lowerExprExpecting(call.arguments.nodes[2]!, DYN), + loc, + }, + { + kind: "varDecl", + localId: holderSlot.id, + init: { + kind: "libCall", + fn: "class.defineField", + args: [ + lowerer.coerceToExpected(current, DYN), + hiddenFieldGet(receiver(), type.className, attributesField, loc), + hiddenFieldGet(receiver(), type.className, CLASS_ATTRIBUTES, loc), + name(), + descriptor(), + ], + type: DYN, + loc, + }, + loc, + }, + { + kind: "if", + cond: { + kind: "libCall", + fn: "class.descriptorHasValue", + args: [descriptor()], + type: BOOL, + loc, + }, + then: [ + { + kind: "fieldSet", + obj: receiver(), + className: type.className, + field, + value: lowerer.coerceToExpected(defined, fieldType), + attributesChecked: true, + loc, + }, + ], + else_: null, + loc, + }, + { + kind: "fieldSet", + obj: receiver(), + className: type.className, + field: attributesField, + value: { + kind: "libCall", + fn: "class.definedBits", + args: [holder(), name()], + type: F64, + loc, + }, + attributesChecked: true, + loc, + }, + ], + result: receiver(), + type, + loc, + }; +} + +function classDisplayName(info: ClassInfo): string { + return info.def.jsName ?? info.decl?.name?.text ?? info.def.name.replace(/^%/, ""); +} + +/** The instance's own class name for an error message: a helper testing the + * most derived classes of the hierarchy first. */ +function instanceClassName( + lowerer: Lowerer, + info: ClassInfo, + receiver: IrExpr, + helpers: Map, + loc: SrcLoc, +): IrExpr { + if (info.subclasses.length === 0) return strLit(classDisplayName(info), loc); + let helper = helpers.get(info.def.name); + if (!helper) { + const type: IrType = { kind: "object", className: info.def.name }; + const value = varRef("value", type, loc); + const descendants = (current: ClassInfo): ClassInfo[] => + current.subclasses.flatMap((child) => [...descendants(child), child]); + const body: IrStmt[] = descendants(info).map((child): IrStmt => ({ + kind: "if", + cond: { kind: "instanceOf", value, className: child.def.name, type: BOOL, loc }, + then: [{ kind: "return", value: strLit(classDisplayName(child), loc), loc }], + else_: null, + loc, + })); + body.push({ kind: "return", value: strLit(classDisplayName(info), loc), loc }); + helper = { + name: `%class.instanceName.${helpers.size}`, + params: [{ localId: "value", name: "value", type }], + returnType: STRING, + locals: [{ id: "value", name: "value", type, mutable: false }], + body, + loc, + }; + helpers.set(info.def.name, helper); + } + return { kind: "call", callee: helper.name, args: [receiver], type: STRING, loc }; +} + +/** Checks the integrity word, and the field's attribute word when it has one, + * before every write to a declared field of a hierarchy that has them. A + * read-only field throws Node's TypeError in strict mode code; a sloppy-mode + * write leaves it unchanged. Other classes keep their plain stores. */ +export function guardClassAttributeWrites(lowerer: Lowerer, functions: IrFunction[]): void { + const guarded = new Set( + [...lowerer.classes.values()] + .filter((info) => info.def.fields.some((field) => field.name === CLASS_ATTRIBUTES)) + .map((info) => info.def.name), + ); + if (guarded.size === 0) return; + const helpers = new Map(); + for (const fn of functions) { + const local = (type: IrType, loc: SrcLoc): Extract => { + const id = `%attributeWrite.${fn.locals.length}`; + fn.locals.push({ id, name: id, type, mutable: false }); + return { kind: "varRef", localId: id, type, loc }; + }; + fn.body = transformStmtList(fn.body, { + stmt: (statement) => { + if ( + statement.kind !== "fieldSet" || + statement.attributesChecked || + !guarded.has(statement.className) || + !isClassOwnEnumerableFieldName(statement.field) + ) + return statement; + const info = lowerer.classes.get(statement.className)!; + const loc = statement.loc; + const receiver = local(statement.obj.type, loc); + const value = local(statement.value.type, loc); + const masked = (field: string, bit: number): IrExpr => ({ + kind: "bin", + op: "&", + left: hiddenFieldGet(receiver, statement.className, field, loc), + right: numLit(bit, loc), + type: F64, + loc, + }); + const attributesField = classFieldAttributesName(statement.field); + let readOnly = masked( + CLASS_ATTRIBUTES, + statement.define ? CLASS_NOT_EXTENSIBLE : CLASS_FROZEN, + ); + if (!statement.define && info.fields.has(attributesField)) { + readOnly = { + kind: "bin", + op: "|", + left: readOnly, + right: masked(attributesField, FIELD_NOT_WRITABLE), + type: F64, + loc, + }; + } + const reject: IrStmt[] = statement.define + ? [ + { + kind: "exprStmt", + expr: { + kind: "libCall", + fn: "class.throwNotExtensible", + args: [strLit(statement.field, loc)], + type: VOID, + loc, + }, + loc, + }, + ] + : statement.sloppy + ? [] + : [ + { + kind: "exprStmt", + expr: { + kind: "libCall", + fn: "class.throwReadOnly", + args: [ + strLit(statement.field, loc), + instanceClassName(lowerer, info, receiver, helpers, loc), + ], + type: VOID, + loc, + }, + loc, + }, + ]; + return { + kind: "block", + body: [ + { kind: "varDecl", localId: receiver.localId, init: statement.obj, loc }, + { kind: "varDecl", localId: value.localId, init: statement.value, loc }, + { + kind: "if", + cond: { + kind: "bin", + op: "!==", + left: readOnly, + right: numLit(0, loc), + type: BOOL, + loc, + }, + then: reject, + else_: [{ ...statement, obj: receiver, value, attributesChecked: true }], + loc, + }, + ], + loc, + }; + }, + expr: (expression) => expression, + }); + } + for (const helper of helpers.values()) functions.push(helper); +} diff --git a/packages/compiler/src/frontend/lowering/class-descriptors.ts b/packages/compiler/src/frontend/lowering/class-descriptors.ts index 473ed2496..cb980f0d0 100644 --- a/packages/compiler/src/frontend/lowering/class-descriptors.ts +++ b/packages/compiler/src/frontend/lowering/class-descriptors.ts @@ -23,7 +23,7 @@ function bagOnlyProperty(owner: ClassInfo, name: string): boolean { return !owner.fields.has(name) && owner.subclasses.every((child) => bagOnlyProperty(child, name)); } -function runtimeRooted(info: ClassInfo): boolean { +export function runtimeRooted(info: ClassInfo): boolean { for (let current: ClassInfo | null = info; current; current = current.base) { if ( current.def.runtime || diff --git a/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts b/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts index d2ef7f6cd..2587a4b65 100644 --- a/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts +++ b/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts @@ -2695,6 +2695,7 @@ export class ClassDynamicDispatch { !findGenericMethodOn(lowerer, info, dispatch.name) && dispatch.name !== "constructor" && !info.fields.has(dispatch.name) && + !(dispatch.name === "stack" && lowerer.isSubclassOf(info.def.name, "%Error")) && !findMethodOn(lowerer, info, `get:${dispatch.name}`) && !findMethodOn(lowerer, info, `set:${dispatch.name}`)) ) @@ -4525,6 +4526,110 @@ export class ClassDynamicDispatch { }, ]; } + // An Error's own `stack`: a string is the native error's stack, which + // typed reads share; any other value is kept with the instance's own + // properties, which a read checks first. A class whose chain declares + // a `stack` accessor answers through it (the accessor code below). + if ( + name === "stack" && + !info.fields.has(name) && + lowerer.isSubclassOf(info.def.name, "%Error") && + !findMethodOn(lowerer, info, "get:stack") && + !findMethodOn(lowerer, info, "set:stack") + ) { + const key: IrExpr = { kind: "strLit", value: name, type: STRING, loc }; + const error = lowerer.upcastTo(receiver, "%Error"); + if (write) { + const helper = classPropertiesHelper(lowerer, loc); + this.generated.add(helper); + const store: IrStmt = { + kind: "exprStmt", + expr: { + kind: "libCall", + fn: "dyn.keySet", + args: [ + { + kind: "call", + callee: helper.name, + args: [lowerer.coerceToExpected(receiver, DYN)], + type: DYN, + loc, + }, + key, + varRef("p.1", DYN, loc), + ], + type: VOID, + loc, + }, + loc, + }; + return [ + { + kind: "if", + cond: { + kind: "dynTest", + test: "string", + value: varRef("p.1", DYN, loc), + type: BOOL, + loc, + }, + then: [ + { + kind: "exprStmt", + expr: { + kind: "libCall", + fn: "error.setStack", + args: [error, lowerer.coerceToExpected(varRef("p.1", DYN, loc), STRING)], + type: VOID, + loc, + }, + loc, + }, + { + kind: "if", + cond: this.readBagHas( + lowerer, + "dyn.hasKey", + this.readPropertyBag(lowerer, receiver, loc), + key, + loc, + ), + then: [store], + else_: null, + loc, + }, + ], + else_: [store], + loc, + }, + { kind: "return", value: null, loc }, + ]; + } + const bag = this.readPropertyBag(lowerer, receiver, loc); + return [ + { + kind: "return", + value: { + kind: "ternary", + cond: this.readBagHas(lowerer, "dyn.hasKey", bag, key, loc), + then: { + kind: "libCall", + fn: "dyn.bagGet", + args: [bag, key, lowerer.coerceToExpected(receiver, DYN)], + type: DYN, + loc, + }, + else_: lowerer.coerceToExpected( + { kind: "libCall", fn: "error.stack", args: [error], type: STRING, loc }, + DYN, + ), + type: DYN, + loc, + }, + loc, + }, + ]; + } if ((name === "message" || name === "name") && lowerer.isSubclassOf(info.def.name, "%Error")) return write ? [ diff --git a/packages/compiler/src/frontend/lowering/class-protocols.ts b/packages/compiler/src/frontend/lowering/class-protocols.ts index 65d74ab9d..5a27f0815 100644 --- a/packages/compiler/src/frontend/lowering/class-protocols.ts +++ b/packages/compiler/src/frontend/lowering/class-protocols.ts @@ -722,12 +722,6 @@ export class ClassProtocols { return true; } } - // An interface extending a class-backed protocol holds values of that - // protocol (`interface TupleType extends Type`), so an assertion from - // the base to it keeps the instance and its methods. - const target = type.isTypeReference() ? type.getTarget() : type; - if (target?.isClassOrInterface()) - for (const base of this.checker.getBaseTypes(target)) if (this.protocol(base)) return true; return false; } } diff --git a/packages/compiler/src/frontend/lowering/coercions/function-adapters.ts b/packages/compiler/src/frontend/lowering/coercions/function-adapters.ts index ed0f1f91c..3d8817ab1 100644 --- a/packages/compiler/src/frontend/lowering/coercions/function-adapters.ts +++ b/packages/compiler/src/frontend/lowering/coercions/function-adapters.ts @@ -112,12 +112,15 @@ export function coercibleValue(lowerer: Lowerer, src: IrType, dst: IrType): bool } /** Whether all parameters and the result adapt without a trap-only path. - * Rest signatures are excluded; extra destination parameters are ignored. */ + * Rest signatures are excluded, except a typed-rest source in a slot with + * fixed parameters (the rest packing adapter); extra destination + * parameters are ignored. */ export function cleanFuncAdaptable( lowerer: Lowerer, src: IrType & { kind: "func" }, dst: IrType & { kind: "func" }, ): boolean { + if (restPackingAdapterFits(lowerer, src, dst)) return true; if (src.rest === true || dst.rest === true) return false; if (src.params.length > dst.params.length) return false; for (let i = 0; i < src.params.length; i++) { @@ -128,15 +131,145 @@ export function cleanFuncAdaptable( return src.ret.kind === "void" && dst.ret.kind === "dyn"; } +/** Whether a typed-rest function (`(...m: T[]) => R`) can serve a slot + * with fixed parameters (`(m: T) => void`): the slot's parameters convert + * to the function's fixed parameters, then to the rest element type. */ +export function restPackingAdapterFits( + lowerer: Lowerer, + fromT: IrType & { kind: "func" }, + toT: IrType & { kind: "func" }, +): boolean { + if (fromT.rest !== true || fromT.restAbi !== "typed" || toT.rest === true) return false; + const fixed = fromT.params.length - 1; + const restT = fromT.params[fixed]; + if (restT?.kind !== "array" || toT.params.length < fixed) return false; + for (let i = 0; i < toT.params.length; i++) { + if (!lowerer.coercibleValue(toT.params[i]!, i < fixed ? fromT.params[i]! : restT.elem)) + return false; + } + return toT.ret.kind === "void" || lowerer.coercibleValue(fromT.ret, toT.ret); +} + +/** The adapter for restPackingAdapterFits: forwards the fixed arguments and + * packs the remaining ones into the rest array, as a JavaScript call of the + * function with those arguments does. */ +function restPackingAdapter( + lowerer: Lowerer, + fromT: IrType & { kind: "func" }, + toT: IrType & { kind: "func" }, + loc: SrcLoc, +): string { + const key = `fnrestadapt:${typeKey(fromT)}:${typeKey(toT)}`; + const existing = lowerer.coercions.retags.get(key); + if (existing) return existing; + const name = `%fn.adapt.${lowerer.coercions.retags.size}`; + lowerer.coercions.retags.set(key, name); + lowerer.freshClosureAdapters.add(name); + const fixed = fromT.params.length - 1; + const restT = fromT.params[fixed] as IrType & { kind: "array" }; + const params: IrParam[] = toT.params.map((t, i) => ({ + localId: `a.${i}`, + name: `a${i}`, + type: t, + })); + const arg = (i: number, want: IrType): IrExpr => + lowerer.coerceToExpected( + { kind: "varRef", localId: `a.${i}`, type: toT.params[i]!, loc }, + want, + ); + const args: IrExpr[] = []; + for (let i = 0; i < fixed; i++) args.push(arg(i, fromT.params[i]!)); + const packed: IrExpr[] = []; + for (let i = fixed; i < toT.params.length; i++) packed.push(arg(i, restT.elem)); + args.push({ kind: "arrayLit", elems: packed, type: restT, loc }); + const call: IrExpr = { + kind: "callValue", + callee: { kind: "varRef", localId: "f.0", type: fromT, loc }, + receiver: { kind: "libCall", fn: "dyn.this", args: [], type: DYN, loc }, + args, + type: fromT.ret, + loc, + }; + const body: IrStmt[] = + toT.ret.kind === "void" + ? [ + { kind: "exprStmt", expr: call, loc }, + { kind: "return", value: null, loc }, + ] + : [{ kind: "return", value: lowerer.coerceToExpected(call, toT.ret), loc }]; + lowerer.liftedFns.push(...buildFunctionAdapter(name, fromT, toT, params, [], body, loc)); + return name; +} + +/** Adapt a fixed-parameter function to a checked-dynamic rest signature + * without fixed parameters (`() => {}` into mock.fn()'s `(...args: any[]) + * => undefined`): the adapter receives the hidden argument array and + * passes its element i as parameter i, undefined when the caller passed + * fewer arguments, as JS does. Null unless every parameter is + * checked-dynamic, which holds a missing argument as undefined, and the + * result is discarded or converts. */ +function funcIntoDynRestAdapter( + lowerer: Lowerer, + fromT: IrType & { kind: "func" }, + toT: IrType & { kind: "func" }, + loc: SrcLoc, +): string | null { + if (fromT.rest === true || fromT.params.some((p) => p.kind !== "dyn")) return null; + if (toT.ret.kind !== "void" && !lowerer.coercibleValue(fromT.ret, toT.ret)) return null; + const key = `fnadapt:${typeKey(fromT)}:${typeKey(toT)}`; + const existing = lowerer.coercions.retags.get(key); + if (existing) return existing; + const name = `%fn.adapt.${lowerer.coercions.retags.size}`; + lowerer.coercions.retags.set(key, name); + lowerer.freshClosureAdapters.add(name); + const params: IrParam[] = [{ localId: "rest.0", name: "args", type: DYN }]; + const call: IrExpr = { + kind: "callValue", + callee: { kind: "varRef", localId: "f.0", type: fromT, loc }, + receiver: { kind: "libCall", fn: "dyn.this", args: [], type: DYN, loc }, + args: fromT.params.map((_, i): IrExpr => ({ + kind: "dynKeyGet", + value: { kind: "varRef", localId: "rest.0", type: DYN, loc }, + key: { kind: "strLit", value: String(i), type: STRING, loc }, + type: DYN, + loc, + })), + type: fromT.ret, + loc, + }; + let body: IrStmt[]; + if (toT.ret.kind === "void") { + body = [ + { kind: "exprStmt", expr: call, loc }, + { kind: "return", value: null, loc }, + ]; + } else { + const result = lowerer.coerceToExpected(call, toT.ret); + if (!typeEquals(result.type, toT.ret)) + throw new InternalCompilerError("lowerer bug: probed fn-adapter return stopped coercing"); + body = [{ kind: "return", value: result, loc }]; + } + lowerer.liftedFns.push(...buildFunctionAdapter(name, fromT, toT, params, [], body, loc)); + return name; +} + /** Adapt a function value to a destination signature, forwarding and * converting its arguments and result. Unsupported parameter/result pairs - * produce checked traps when called; rest signatures remain refused. */ + * produce checked traps when called. Rest signatures are refused, except a + * typed-rest source in a slot with fixed parameters, which the rest + * packing adapter serves, and a fixed-parameter function into a + * checked-dynamic rest signature without fixed parameters + * (funcIntoDynRestAdapter). */ export function funcCoerceAdapter( lowerer: Lowerer, fromT: IrType & { kind: "func" }, toT: IrType & { kind: "func" }, loc: SrcLoc, ): string | null { + if (restPackingAdapterFits(lowerer, fromT, toT)) + return restPackingAdapter(lowerer, fromT, toT, loc); + if (toT.rest === true && toT.restAbi === undefined && toT.params.length === 0) + return funcIntoDynRestAdapter(lowerer, fromT, toT, loc); if (fromT.rest === true || toT.rest === true) return null; if (fromT.params.length > toT.params.length) return null; // Piece dispositions beyond coercibleValue, all CHECKER-APPROVED diff --git a/packages/compiler/src/frontend/lowering/expressions/hosted-narrowing.ts b/packages/compiler/src/frontend/lowering/expressions/hosted-narrowing.ts new file mode 100644 index 000000000..87a7ba38e --- /dev/null +++ b/packages/compiler/src/frontend/lowering/expressions/hosted-narrowing.ts @@ -0,0 +1,157 @@ +/* Narrowings that refine record arms without naming a stored arm. */ +import type { Lowerer } from "../lowerer.js"; +import { type IrExpr, type IrType, type SrcLoc, typeEquals } from "../../../ir/ir.js"; + +/** A checker narrowing can refine the arms of a union of records with an + * intersection: a predicate `r is Route & { path: string }`, or the + * `"path" in r` narrowing, which intersects arms that do not declare the + * name with `Record<"path", unknown>`. Each narrowed arm then maps to a + * new record shape that is not an arm of the stored union, although the + * value never changed layout. A stored record arm HOSTS a narrowed record + * when both name the same members, each narrowed member refined to a + * subset of the stored slot type (or to `unknown`); a member an assertion + * attached to the stored shape (assertion-members.ts) may be missing from + * the narrowed one. A required member the stored arm does not declare + * cannot exist on its values: native records are closed, so `in` already + * answers false there and the narrowed arm is uninhabited. + * + * Only unions of two or more record arms without a literal discriminant + * qualify: discriminated unions narrow by their discriminant, and a + * record with unit arms keeps its own predicate-strengthening reads. + * + * Returns the stored arms that can hold a value of the narrowed type, or + * null when the union does not qualify, when some narrowed arm is neither + * a stored arm nor a record, or when no stored arm is excluded (there is + * nothing to narrow). */ +export function hostedArmTags( + lowerer: Lowerer, + fromId: string, + narrowed: IrType, +): Set | null { + const from = lowerer.unions.get(fromId); + if (!from || !recordUnion(lowerer, fromId)) return null; + const narrowedArms = + narrowed.kind === "union" ? lowerer.unions.get(narrowed.unionId)?.arms : [narrowed]; + if (!narrowedArms || narrowedArms.length === 0) return null; + const allowed = new Set(); + let refined = false; + for (const arm of narrowedArms) { + const direct = lowerer.armTag(fromId, arm); + if (direct >= 0) { + allowed.add(direct); + continue; + } + if (arm.kind !== "record") return null; + refined = true; + from.arms.forEach((stored, tag) => { + if (stored.kind === "record" && recordHosts(lowerer, stored.shapeId, arm.shapeId)) { + allowed.add(tag); + } + }); + } + if (!refined || allowed.size === 0 || allowed.size === from.arms.length) return null; + return allowed; +} + +/** A union of at least two record arms without a literal discriminant. */ +function recordUnion(lowerer: Lowerer, unionId: string): boolean { + const def = lowerer.unions.get(unionId); + if (!def || def.discriminant) return false; + return def.arms.filter((arm) => arm.kind === "record").length >= 2; +} + +/** Whether every value of `narrowed` (a checker refinement of a value + * stored as `storage`) lives in the stored layout: each narrowed arm is a + * stored arm or a record a stored record arm hosts (see hostedArmTags), + * and at least one needs the hosting. A union storage must qualify as in + * hostedArmTags; a single record only when an assertion attached members + * to it. A binding initialized by such an assertion keeps the stored type, + * so its member writes reach the asserted object. */ +export function hostsAll(lowerer: Lowerer, storage: IrType, narrowed: IrType): boolean { + if (typeEquals(storage, narrowed)) return false; + let storedArms: readonly IrType[]; + if (storage.kind === "union") { + if (!recordUnion(lowerer, storage.unionId)) return false; + storedArms = lowerer.unions.get(storage.unionId)!.arms; + } else if (storage.kind === "record" && lowerer.shapes.attachedNames(storage.shapeId).length) { + storedArms = [storage]; + } else { + return false; + } + const narrowedArms = + narrowed.kind === "union" ? lowerer.unions.get(narrowed.unionId)?.arms : [narrowed]; + if (!narrowedArms || narrowedArms.length === 0) return false; + let refined = false; + for (const arm of narrowedArms) { + if (storedArms.some((stored) => typeEquals(stored, arm))) continue; + if (arm.kind !== "record") return false; + const hosted = storedArms.some( + (stored) => stored.kind === "record" && recordHosts(lowerer, stored.shapeId, arm.shapeId), + ); + if (!hosted) return false; + refined = true; + } + return refined; +} + +function recordHosts(lowerer: Lowerer, storedId: string, narrowedId: string): boolean { + const stored = lowerer.shapes.get(storedId); + const narrowed = lowerer.shapes.get(narrowedId); + if (!stored || !narrowed || stored.tuple || narrowed.tuple) return false; + if (stored.indexValue || narrowed.indexValue) return false; + const attached = lowerer.shapes.attachedNames(storedId); + for (const sf of stored.fields) { + if (!narrowed.fields.some((nf) => nf.name === sf.name) && !attached.includes(sf.name)) + return false; + } + for (const nf of narrowed.fields) { + const sf = stored.fields.find((f) => f.name === nf.name); + if (!sf || !slotRefines(lowerer, nf.type, sf.type)) return false; + } + return true; +} + +/** Whether a stored slot of type `stored` can be read as `narrowed`: the + * same type, `unknown`, or a subset of the stored union's arms (a read + * site narrows from the stored slot exactly as it does for a predicate + * strengthening one record). */ +function slotRefines(lowerer: Lowerer, narrowed: IrType, stored: IrType): boolean { + if (typeEquals(narrowed, stored) || narrowed.kind === "dyn") return true; + if (stored.kind !== "union") return false; + if (narrowed.kind !== "union") return lowerer.armTag(stored.unionId, narrowed) >= 0; + const arms = lowerer.unions.get(narrowed.unionId)?.arms; + return !!arms && arms.every((arm) => lowerer.armTag(stored.unionId, arm) >= 0); +} + +/** The stored view of a hosted narrowing (see hostedArmTags): one stored + * record arm extracted with the checked helper, or the sub-union of the + * hosting arms re-tagged with the excluded arms as traps. Both keep the + * value's stored layout, so member reads and writes reach the same + * object. Null when the narrowing is not a hosted one. */ +export function hostedStorageView( + lowerer: Lowerer, + value: IrExpr, + narrowed: IrType, + loc: SrcLoc, +): IrExpr | null { + if (value.type.kind !== "union") return null; + const fromId = value.type.unionId; + const allowed = hostedArmTags(lowerer, fromId, narrowed); + if (!allowed) return null; + const from = lowerer.unions.get(fromId)!; + if (allowed.size === 1) { + const arm = from.arms[[...allowed][0]!]!; + const helper = lowerer.narrowedArmHelper(fromId, arm, loc); + return helper ? lowerer.checkedNarrowCall(helper, value, arm, loc) : null; + } + const arms = from.arms.filter((_, tag) => allowed.has(tag)); + const subId = lowerer.unions.transform(from, arms); + const trappable = new Set(); + from.arms.forEach((_, tag) => { + if (!allowed.has(tag)) trappable.add(tag); + }); + const helper = lowerer.unionRetagHelper(fromId, subId, loc, trappable); + if (!helper) return null; + const type: IrType = { kind: "union", unionId: subId }; + return { kind: "call", callee: helper, args: [value], type, loc }; +} diff --git a/packages/compiler/src/frontend/lowering/expressions/identifiers.ts b/packages/compiler/src/frontend/lowering/expressions/identifiers.ts index 1a1ad6e1e..27ce7327e 100644 --- a/packages/compiler/src/frontend/lowering/expressions/identifiers.ts +++ b/packages/compiler/src/frontend/lowering/expressions/identifiers.ts @@ -365,19 +365,25 @@ export function lowerIdentifierExpr(lowerer: Lowerer, expr: ts.Identifier, loc: // MODULE compile-time constants, the containing file's location // (Node's exact values when the same tree runs in place; WASI uses // the equivalent guest-visible path; locals shadowing the names - // resolved above). In a file with ESM syntax Node never defines them - // (ReferenceError) — fence rather than invent a value there. + // resolved above), the same strings as import.meta.dirname/filename. + // In a JavaScript ES module Node never defines them, so the read throws + // Node's catchable ReferenceError. A .ts/.tsx file keeps them even when + // Node's strip-types rule would load it as ESM: TypeScript projects are + // commonly built as CommonJS bundles (tsc, esbuild, tsx) where these + // globals exist, and @types/node declares them for every file. if ( (expr.text === "__dirname" || expr.text === "__filename") && lowerer.isStdlibGlobal(expr, expr.text) ) { const sf = expr.getSourceFile(); - if (isNodeEsmFile(sf, lowerer.program)) { - lowerer.unsupported( - "SC1090", - expr, - `'${expr.text}' in an ES module (Node defines the CJS module globals only in CommonJS modules)`, - ); + if (isNodeEsmFile(sf, lowerer.program) && !/\.tsx?$/.test(sf.fileName)) { + return { + kind: "libCall", + fn: "global.undefRead", + args: [{ kind: "strLit", value: expr.text, type: STRING, loc }], + type: STRING, + loc, + }; } // The WASI runner mounts the host cwd at guest `/` and the host temp // directory at `/tmp`. Bake the matching GUEST spelling so resource diff --git a/packages/compiler/src/frontend/lowering/expressions/narrowing.ts b/packages/compiler/src/frontend/lowering/expressions/narrowing.ts index f0a78a1c5..fc78e3a38 100644 --- a/packages/compiler/src/frontend/lowering/expressions/narrowing.ts +++ b/packages/compiler/src/frontend/lowering/expressions/narrowing.ts @@ -29,6 +29,7 @@ import { isOptionalProcessStreamProperty } from "../builtins/process.js"; import { literalValues } from "../../literal-values.js"; import { literalUnionArm } from "../../union-discriminants.js"; import { checkedArmExtraction } from "../coercions/unions.js"; +import { hostedStorageView, hostsAll } from "./hosted-narrowing.js"; import { nodeThrowExpr, varRef } from "../../../ir/build.js"; import { InternalCompilerError } from "../../../errors.js"; import { @@ -659,6 +660,12 @@ export function maybeNarrow(lowerer: Lowerer, expr: IrExpr, node: ts.Node): IrEx }; } } + // A narrowing that refines record arms with an intersection keeps the + // stored layout: view the value as the stored arms that can hold it. + if (narrowed?.kind === "union") { + const hosted = hostedStorageView(lowerer, expr, narrowed, expr.loc); + if (hosted) return hosted; + } // A checker type narrowed to a UNIT arm (the `=== undefined` branch) // also stays union-typed: a unit arm has no payload to extract, and // nothing useful reads such a value anyway. (Standalone undefined maps @@ -668,6 +675,14 @@ export function maybeNarrow(lowerer: Lowerer, expr: IrExpr, node: ts.Node): IrEx return expr; } const tag = lowerer.armTag(expr.type.unionId, narrowed); + if ( + tag < 0 && + narrowed.kind === "record" && + !lowerer.unions.get(expr.type.unionId)?.discriminant + ) { + const hosted = hostedStorageView(lowerer, expr, narrowed, expr.loc); + if (hosted) return hosted; + } if (tag < 0) { // The checker may refine fields inside a discriminated record too. // That creates a different mapped shape, but does not change the @@ -926,6 +941,10 @@ export function runtimeOptionalReceiverRead( loc, ); if (!helper) { + // Intersection-refined record arms read through their stored + // layout (the predicate `r is Route & { path: string }`). + const hosted = hostedStorageView(lowerer, varRef(local.id, local.type, loc), narrowed, loc); + if (hosted) return hosted; lowerer.unsupported( "SC1090", expr, @@ -946,6 +965,10 @@ export function runtimeOptionalReceiverRead( // path below; mixed storage also needs to validate its concrete class. if (view && (lowerer.unions.get(local.type.unionId)?.arms.length ?? 0) > 2) return view; } + if (narrowed.kind === "record" && lowerer.armTag(local.type.unionId, narrowed) < 0) { + const hosted = hostedStorageView(lowerer, varRef(local.id, local.type, loc), narrowed, loc); + if (hosted) return hosted; + } const undefTag = lowerer.armTag(local.type.unionId, UNDEFINED_T); const def = lowerer.unions.get(local.type.unionId); if (!def || undefTag < 0) @@ -1302,6 +1325,12 @@ export function lowerAsExpression( ) { return lowerer.coerceInto(expr, inner, target); } + // `job as Job & { startedAt: number }` on a record: the asserted + // record refines the stored one, so the assertion keeps the object + // (a width copy would sever writes through the asserted view). + if (inner.type.kind === "record" && target && hostsAll(lowerer, inner.type, target)) { + return inner; + } // Static assertions normally erase, but record layouts are // monomorphic: a consumer selected from the asserted shape must see // that shape physically. Reuse the ordinary slot coercion so plain @@ -1328,6 +1357,12 @@ export function lowerAsExpression( return lowerer.checkedNarrowCall(helper, inner, target, locOf(expr)); } } + // An intersection refining the stored record arms keeps the value + // (hosted-narrowing.ts): the stored arms that can hold it. + if (target && (target.kind === "union" || target.kind === "record")) { + const hosted = hostedStorageView(lowerer, inner, target, locOf(expr)); + if (hosted) return hosted; + } } // `node.parent as Sub | undefined`: a union target whose class arm is // narrower than the stored class gets the same checked class view as diff --git a/packages/compiler/src/frontend/lowering/expressions/relational.ts b/packages/compiler/src/frontend/lowering/expressions/relational.ts index d1a9b8b3a..146766a46 100644 --- a/packages/compiler/src/frontend/lowering/expressions/relational.ts +++ b/packages/compiler/src/frontend/lowering/expressions/relational.ts @@ -38,7 +38,7 @@ import { lowerCryptoKeyInstanceof } from "../builtins/webcrypto.js"; import { lowerNetAddressInstanceof } from "../builtins/net-address.js"; import { fenceEarlyNsMemberRef, nsMemberIdentOf } from "../lower-namespaces.js"; import { maybePromiseInstanceOf } from "../maybe-promise.js"; -import { isSafeToDiscard } from "./evaluation-safety.js"; +import { isSafeToDiscard, isSafeToRepeat } from "./evaluation-safety.js"; import { type ClassInfo, storedClassValueType } from "../lower-classes.js"; import { classInstanceOf } from "../class-dynamic-dispatch.js"; import { InternalCompilerError } from "../../../errors.js"; @@ -1491,13 +1491,14 @@ export function lowerInExpression( // arm answers membership STATICALLY (a declared non-optional field is // always present, an undeclared name never is), so the whole test // collapses to runtime TAG tests — tsc's own narrowing then types the - // branches (the discriminating-`in` idiom). Optional (undefined-armed) - // fields and tuple/index-signature arms answer per VALUE, not per arm - // — those unions keep the fence below. + // branches (the discriminating-`in` idiom). An arm declaring the key + // as an optional (undefined-armed) slot answers per VALUE instead: + // its tag test joins that payload's presence test (see + // perValueUnionIn). Tuple/index-signature arms keep the fence below. if (recv.type.kind === "union") { const unionId = recv.type.unionId; const arms = lowerer.unions.get(unionId)?.arms ?? []; - const answers: { tag: number; has: boolean }[] = []; + const answers: { tag: number; has: boolean | "slot"; arm: IrType }[] = []; let staticAnswers = arms.length > 0; for (const arm of arms) { const tag = lowerer.armTag(unionId, arm); @@ -1514,14 +1515,17 @@ export function lowerInExpression( f.type.kind === "union" && lowerer.armTag(f.type.unionId, UNDEFINED_T) >= 0 ) { - staticAnswers = false; // an optional slot: presence is per-value - break; + answers.push({ tag, has: "slot", arm }); // presence is per-value + continue; } // Accessor properties are own properties to `in` (Node answers // true without invoking the getter) — either slot present makes // the name a member. const acc = shape.fields.some((x) => x.name === `%get:${key}` || x.name === `%set:${key}`); - answers.push({ tag, has: f !== undefined || acc || inherited }); + answers.push({ tag, has: f !== undefined || acc || inherited, arm }); + } + if (staticAnswers && answers.some((a) => a.has === "slot")) { + return perValueUnionIn(lowerer, recv, unionId, key, answers, loc); } if (staticAnswers) { const pureRecv = @@ -1673,6 +1677,75 @@ export function lowerInExpression( ); } +/** `"k" in u` over a union of fixed record shapes where some arm declares + * `k` as an optional slot. Arms without the name answer false and arms + * with an always-present member answer true, both by tag; an optional + * slot answers its payload's own-property presence (an explicit + * undefined is present, an omitted or deleted property is not), read + * through the checked arm extraction the tag test has just proven. The + * receiver evaluates once: a non-repeatable one is stabilized in a + * hidden local first. */ +function perValueUnionIn( + lowerer: Lowerer, + recv: IrExpr, + unionId: string, + key: string, + answers: readonly { tag: number; has: boolean | "slot"; arm: IrType }[], + loc: SrcLoc, +): IrExpr { + const recvType = recv.type; + const stable = isSafeToRepeat(recv) ? null : lowerer.declareHiddenLocal("%inRecv", recvType); + const ref = (): IrExpr => (stable ? varRef(stable.id, recvType, loc) : recv); + const isTag = (tag: number): IrExpr => ({ + kind: "unionIsTag", + unionId, + tag, + negated: false, + value: ref(), + type: BOOL, + loc, + }); + let out: IrExpr | null = null; + for (const a of answers) { + if (a.has === false) continue; + let test = isTag(a.tag); + if (a.has === "slot" && a.arm.kind === "record") { + test = { + kind: "logical", + op: "&&", + left: test, + right: { + kind: "recordHas", + obj: { + kind: "unionNarrow", + unionId, + tag: a.tag, + value: ref(), + type: a.arm, + loc, + }, + shapeId: a.arm.shapeId, + field: key, + type: BOOL, + loc, + }, + type: BOOL, + loc, + }; + } + out = out ? { kind: "logical", op: "||", left: out, right: test, type: BOOL, loc } : test; + } + const result: IrExpr = out ?? { kind: "boolLit", value: false, type: BOOL, loc }; + if (!stable) return result; + return { + kind: "seqExpr", + stmts: [{ kind: "varDecl", localId: stable.id, init: recv, loc }], + result, + type: BOOL, + loc, + }; +} + /** The runtime-key `in` (see lowerInExpression): `k in r` where k is a * runtime string and r an index-signature record — an interned * `%rec.haskey.(k, r)` walks the declared names (a string-equality diff --git a/packages/compiler/src/frontend/lowering/expressions/typeof.ts b/packages/compiler/src/frontend/lowering/expressions/typeof.ts index 30d24e22a..c56ef4097 100644 --- a/packages/compiler/src/frontend/lowering/expressions/typeof.ts +++ b/packages/compiler/src/frontend/lowering/expressions/typeof.ts @@ -13,6 +13,7 @@ import { } from "../../../ir/ir.js"; import { lowerWebAssemblyTypeof } from "../builtins/webassembly.js"; import { isNavigatorGlobal } from "../builtins/navigator.js"; +import { processHasIpc } from "../builtins/process.js"; import { lowerPerfHooksTypeof } from "../builtins/performance.js"; import { isNodeModuleValue } from "../lower-node-module.js"; import { builtinClassRefOf } from "../builtins/class-values.js"; @@ -27,7 +28,7 @@ import { typeofAnswer, } from "../lower-exprs.js"; import { tryLowerExpression } from "./try-lower-expression.js"; -import { isJsSourceFile } from "../../program.js"; +import { isJsSourceFile, isNodeEsmFile } from "../../program.js"; import { isSafeToDiscard, isSafeToRepeat } from "./evaluation-safety.js"; import { varRef } from "../../../ir/build.js"; import { isExecExceptionType, refuseExecExceptionCode } from "../builtins/errors.js"; @@ -38,8 +39,41 @@ export function lowerTypeofExpr(lowerer: Lowerer, expr: ts.TypeOfExpression, loc if (webAssembly) return webAssembly; if (lowerer.isStdlibGlobal(expr.expression, "process")) return { kind: "strLit", value: "object", type: STRING, loc }; + { + let operand = expr.expression; + while (ts.isParenthesizedExpression(operand)) operand = operand.expression; + const ipcMember = + ts.isPropertyAccessExpression(operand) && !operand.questionDotToken + ? lowerer.stdlibGlobalMember(operand, "process") + : null; + if (ipcMember === "send" || ipcMember === "disconnect") { + return { + kind: "ternary", + cond: processHasIpc(lowerer, loc), + then: { kind: "strLit", value: "function", type: STRING, loc }, + else_: { kind: "strLit", value: "undefined", type: STRING, loc }, + type: STRING, + loc, + }; + } + } if (lowerer.isStdlibGlobal(expr.expression, "fetch")) return { kind: "strLit", value: "function", type: STRING, loc }; + // `__dirname` / `__filename` in a JavaScript ES module, where Node never + // defines them: typeof answers "undefined" instead of throwing. + { + let operand = expr.expression; + while (ts.isParenthesizedExpression(operand)) operand = operand.expression; + if ( + ts.isIdentifier(operand) && + (operand.text === "__dirname" || operand.text === "__filename") && + lowerer.isStdlibGlobal(operand, operand.text) + ) { + const sf = operand.getSourceFile(); + if (isNodeEsmFile(sf, lowerer.program) && !/\.tsx?$/.test(sf.fileName)) + return { kind: "strLit", value: "undefined", type: STRING, loc }; + } + } if (isNavigatorGlobal(lowerer, expr.expression)) return { kind: "strLit", value: "object", type: STRING, loc }; const performance = lowerPerfHooksTypeof(lowerer, expr.expression); diff --git a/packages/compiler/src/frontend/lowering/expressions/updates.ts b/packages/compiler/src/frontend/lowering/expressions/updates.ts index 8b98389b6..2270f6043 100644 --- a/packages/compiler/src/frontend/lowering/expressions/updates.ts +++ b/packages/compiler/src/frontend/lowering/expressions/updates.ts @@ -1,5 +1,6 @@ /* Increment, decrement and prefix unary operators, and compound field updates. */ import * as ts from "../../ts7/adapter.js"; +import { isStrictCode } from "../strict-mode.js"; import { isPrivateWriteReceiver } from "../private-brands.js"; import type { Lowerer } from "../lowerer.js"; import { @@ -751,6 +752,7 @@ export function fieldSetStmt( className: target.className, field: target.field, value, + ...(isStrictCode(blame) ? {} : { sloppy: true as const }), loc, } : { diff --git a/packages/compiler/src/frontend/lowering/key-order-witness.ts b/packages/compiler/src/frontend/lowering/key-order-witness.ts index ba8001792..437a29563 100644 --- a/packages/compiler/src/frontend/lowering/key-order-witness.ts +++ b/packages/compiler/src/frontend/lowering/key-order-witness.ts @@ -224,12 +224,30 @@ export function applyKeyOrderWitnesses(lowerer: Lowerer, fns: readonly IrFunctio const loops: number[] = []; let loopIds = 0; const loopKey = (): string => loops.join("/"); + /** Bindings initialized by another binding's reference (`const view = + * value`, the snapshot a union-receiver write takes), so writes through + * either count as writes through the one object. */ + const aliases = new Map(); + const bindingOf = (obj: IrExpr): string | null => { + let o = obj; + while (o.kind === "unionNarrow" || o.kind === "downcast") o = o.value; + if (o.kind !== "varRef") return null; + let id = o.localId; + const seen = new Set(); + for (;;) { + const alias = aliases.get(id); + if (!alias || alias.fn !== fnName || inits.get(id)?.count !== 1 || seen.has(id)) return id; + seen.add(id); + id = alias.target; + } + }; const noteInit = (id: string, init: IrExpr): void => { const previous = inits.get(id); if (previous !== undefined) { previous.count++; return; } + if (init.kind === "varRef") aliases.set(id, { target: init.localId, fn: fnName }); const fresh = init.kind === "recordLit" || init.kind === "recordClone" || @@ -315,7 +333,7 @@ export function applyKeyOrderWitnesses(lowerer: Lowerer, fns: readonly IrFunctio field: st.field, kind: st.value.kind === "fieldAbsent" ? "delete" : "write", fn: fnName, - binding: st.obj.kind === "varRef" ? st.obj.localId : null, + binding: bindingOf(st.obj), loop: loopKey(), pos, }); diff --git a/packages/compiler/src/frontend/lowering/lower-calls.ts b/packages/compiler/src/frontend/lowering/lower-calls.ts index c51b75b03..edd36b9cc 100644 --- a/packages/compiler/src/frontend/lowering/lower-calls.ts +++ b/packages/compiler/src/frontend/lowering/lower-calls.ts @@ -229,6 +229,7 @@ import { } from "./optional-arguments.js"; import { fenceSymbolFieldCopy } from "./symbol-fields.js"; import { lowerClassDataDescriptor, lowerClassDescriptorRead } from "./class-descriptors.js"; +import { lowerClassFieldDefine, lowerClassIntegrity } from "./class-attributes.js"; import { classStaticDataFor } from "./class-static-data.js"; import { lowerUrlFactory } from "./lower-url.js"; import { nullishValueUnitOf } from "./binding-analysis.js"; @@ -7789,6 +7790,8 @@ function lowerObjectStaticCall( if (target && declaredTarget && isDynTypedRefType(declaredTarget)) target = lowerer.coerceToExpected(target, declaredTarget); if (target && member === "defineProperty") { + const field = lowerClassFieldDefine(lowerer, call, target); + if (field) return field; const native = lowerClassDataDescriptor(lowerer, call, member, target); if (native) return native; } @@ -7931,6 +7934,19 @@ function lowerObjectStaticCall( loc, }; } + if (member === "preventExtensions" || member === "seal") { + const integrity = lowerClassIntegrity(lowerer, call, member, value); + if (integrity) return integrity; + } else if (isDynTypedRefType(value.type)) { + // The instance's checked view carries its integrity state. + return { + kind: "libCall", + fn: member === "isSealed" ? "dyn.isSealed" : "dyn.isExtensible", + args: [lowerer.coerceToExpected(value, DYN)], + type: BOOL, + loc: locOf(call), + }; + } if (value.type.kind === "dyn") { const operation = { preventExtensions: "dyn.preventExtensions", @@ -8001,6 +8017,8 @@ function lowerObjectStaticCall( ) ) return value; + const integrity = lowerClassIntegrity(lowerer, call, "freeze", value); + if (integrity) return integrity; lowerer.noLowering( "Object.freeze of a possibly-aliased value", call, diff --git a/packages/compiler/src/frontend/lowering/lower-classes.ts b/packages/compiler/src/frontend/lowering/lower-classes.ts index 1525c6137..241c6f580 100644 --- a/packages/compiler/src/frontend/lowering/lower-classes.ts +++ b/packages/compiler/src/frontend/lowering/lower-classes.ts @@ -89,7 +89,6 @@ import { isCjsJsFile, isJsSourceFile, isModuleExportsAccess, - isNodeTypesPath, locOf, } from "../program.js"; import { PoisonError, dynFallbackType, jsFuncNameOf, newFnCtx, own } from "./lowerer.js"; @@ -1053,9 +1052,11 @@ export function builtinStreamInfoOf( if (rec.lib === symbol.name) irName = name; } if (!irName) return null; + // Both the bundled declarations and @types/node declare the classes + // inside `declare module "stream"` (@types/node nests them in the module's + // `internal` namespace). const declared = symbol.declarations.some((d) => { if (!ts.isClassDeclaration(d)) return false; - if (isNodeTypesPath(d.getSourceFile().fileName)) return false; let node: ts.Node | undefined = d.parent; while (node) { if (ts.isModuleDeclaration(node) && ts.isStringLiteral(node.name)) { @@ -8256,6 +8257,7 @@ export function fieldInitStmts(lowerer: Lowerer, info: ClassInfo, thisLocal: IrL className: info.def.name, field: f.name, value: dynUndefinedExpr(loc), + define: true, loc, }); } @@ -8271,6 +8273,7 @@ export function fieldInitStmts(lowerer: Lowerer, info: ClassInfo, thisLocal: IrL className: info.def.name, field: f.name, value, + define: true, loc: locOf(f.initializer), }); } catch (e) { diff --git a/packages/compiler/src/frontend/lowering/lower-inspect.ts b/packages/compiler/src/frontend/lowering/lower-inspect.ts index 6f01cee19..3be155402 100644 --- a/packages/compiler/src/frontend/lowering/lower-inspect.ts +++ b/packages/compiler/src/frontend/lowering/lower-inspect.ts @@ -21,6 +21,7 @@ import { type SrcLoc, canConvertToDyn, canDynCheckTo, + CLASS_FIELD_ATTRIBUTES_PREFIX, classDynViewSupported, DYN_CLASS_PROPERTIES, isClassOwnEnumerableFieldName, @@ -500,12 +501,16 @@ function classInspectsThroughView(lowerer: Lowerer, info: ClassInfo | undefined) ); } -/** Whether a class holds properties added outside its declared layout, which - * only its checked-dynamic view can list. Decided after lowering, when the +/** Whether a class holds properties added outside its declared layout, or + * fields Object.defineProperty can make non-enumerable, which only its + * checked-dynamic view lists as Node does. Decided after lowering, when the * class has its final fields. */ function classInspectsAddedProperties(lowerer: Lowerer, info: ClassInfo): boolean { return ( - info.def.fields.some((field) => field.name === DYN_CLASS_PROPERTIES) && + info.def.fields.some( + (field) => + field.name === DYN_CLASS_PROPERTIES || field.name.startsWith(CLASS_FIELD_ATTRIBUTES_PREFIX), + ) && classDynViewSupported( info.def.fields, (id) => lowerer.shapes.get(id), diff --git a/packages/compiler/src/frontend/lowering/lower-mock.ts b/packages/compiler/src/frontend/lowering/lower-mock.ts index 4f446bb89..be68abaca 100644 --- a/packages/compiler/src/frontend/lowering/lower-mock.ts +++ b/packages/compiler/src/frontend/lowering/lower-mock.ts @@ -154,7 +154,12 @@ function mockWrapperFactory( const { fn, call, calls, once, onceEntry, result, args } = mockContextFields(lowerer, ctxType)!; const impl = `${name}.impl`; const ctx = () => varRef("ctx.0", ctxType, loc); + // A checked-dynamic rest function (mock.fn()'s default type) takes its + // arguments as one trailing dyn array, recorded as the call's arguments + // and spread into the implementation. + const dynRest = fn.rest === true && fn.restAbi === undefined; const params = fn.params.map((type, i) => ({ localId: `p${i}.0`, name: `p${i}`, type })); + if (dynRest) params.push({ localId: "rest.0", name: "args", type: DYN }); const paramRefs = () => params.map((p) => varRef(p.localId, p.type, loc)); const argumentsValue = (): IrExpr => fn.rest @@ -219,13 +224,23 @@ function mockWrapperFactory( loc, }; }; - const invoke: IrExpr = { - kind: "callValue", - callee: varRef("f.0", fn, loc), - args: paramRefs(), - type: fn.ret, - loc, - }; + const invoke: IrExpr = dynRest + ? { + kind: "dynCall", + callee: { kind: "dynFrom", value: varRef("f.0", fn, loc), type: DYN, loc }, + calleeName: "implementation", + args: paramRefs(), + spreads: [{ arg: 0, what: "args" }], + type: DYN, + loc, + } + : { + kind: "callValue", + callee: varRef("f.0", fn, loc), + args: paramRefs(), + type: fn.ret, + loc, + }; const tryBody: IrStmt[] = voidReturn ? [ { kind: "exprStmt", expr: invoke, loc }, @@ -629,7 +644,37 @@ function requireUndefinedResultArm( } } -/** mock.fn(original, implementation?) and t.mock.fn(...). */ +/** `%mock.noop.N`: mock.fn()'s original, Node's `function () {}` — it + * returns undefined. Null when the function type's return type cannot hold + * undefined. */ +function mockNoopOriginal( + lowerer: Lowerer, + ctxType: IrType, + fn: FuncType, + loc: SrcLoc, +): IrExpr | null { + const voidReturn = fn.ret.kind === "void" || fn.ret.kind === "undefinedT"; + const undefinedValue = voidReturn ? null : lowerer.wrappedUndefined(fn.ret, loc); + if (!voidReturn && !undefinedValue) return null; + const name = liftOnce(lowerer, ctxType, "noop", (name) => { + const params = fn.params.map((type, i) => ({ localId: `p${i}.0`, name: `p${i}`, type })); + if (fn.rest === true && fn.restAbi === undefined) + params.push({ localId: "rest.0", name: "args", type: DYN }); + lowerer.liftedFns.push({ + name, + params, + returnType: fn.ret, + locals: params.map((p) => local(p.localId, p.name, p.type)), + body: [{ kind: "return", value: undefinedValue, loc }], + loc, + }); + }); + return { kind: "closure", fnName: name, captures: [], type: fn, loc }; +} + +/** mock.fn(original?, implementation?) and t.mock.fn(...). Without an + * original, the mock's original and implementation are a function that + * returns undefined, as in Node. */ export function lowerMockFn( lowerer: Lowerer, call: ts.CallExpression, @@ -658,11 +703,19 @@ export function lowerMockFn( const { fn } = fields; requireUndefinedResultArm(lowerer, fields, call, what); const args = call.arguments.nodes; - if (args.length === 0 || args.length > 2 || args.some(ts.isSpreadElement)) { + if (args.length > 2 || args.some(ts.isSpreadElement)) { lowerer.noLowering( `${what} with ${args.length} arguments`, call, - "the supported forms are mock.fn(original) and mock.fn(original, implementation); mock.fn() and mock options have no lowering", + "the supported forms are mock.fn(), mock.fn(original) and mock.fn(original, implementation); mock options have no lowering", + ); + } + const noop = args.length === 0 ? mockNoopOriginal(lowerer, ctxType!, fn, loc) : null; + if (args.length === 0 && !noop) { + lowerer.noLowering( + `${what}() of a function returning '${lowerer.fmt(fn.ret)}'`, + call, + "mock.fn() without an original returns undefined: pass an original, or use a function type whose return type includes undefined", ); } const stmts: IrStmt[] = []; @@ -670,7 +723,7 @@ export function lowerMockFn( stmts.push({ kind: "varDecl", localId: originalLocal.id, - init: lowerer.lowerExprExpecting(args[0]!, fn), + init: noop ?? lowerer.lowerExprExpecting(args[0]!, fn), loc, }); const implementation = diff --git a/packages/compiler/src/frontend/lowering/lower-stmts.ts b/packages/compiler/src/frontend/lowering/lower-stmts.ts index c79f2a9cd..9831ad0be 100644 --- a/packages/compiler/src/frontend/lowering/lower-stmts.ts +++ b/packages/compiler/src/frontend/lowering/lower-stmts.ts @@ -1,4 +1,5 @@ import { lowerTupleAliasDecl, stripTypeWrappers } from "./typed-arguments.js"; +import { hostsAll } from "./expressions/hosted-narrowing.js"; import { matchAllRowsReadByElements, storedMatchAllRowsReadByElements, @@ -16,7 +17,7 @@ import { InternalCompilerError } from "../../errors.js"; * flow (if/while/for/for-of/do, switch, try/catch, abrupt completions), * explicit resource management, and blocked-binding poisoning. */ import * as ts from "../ts7/adapter.js"; -import { processDataMemberValue } from "./builtins/process.js"; +import { processDataMemberValue, programReadsExitCode } from "./builtins/process.js"; import { requireInitFlagStmt } from "./require-initialization.js"; import { isJsonRequireDecl } from "./json-require.js"; import { bindingSource } from "../binding-source.js"; @@ -87,11 +88,11 @@ import { isCjsJsFile, isEsModuleStamp, isJsSourceFile, - isNodeEsmFile, locOf, requireEarlyReference, requireSpecOf, } from "../program.js"; +import { isStrictCode } from "./strict-mode.js"; import { COMPOUND_ASSIGN_OPS, type CompoundOp, @@ -5498,6 +5499,23 @@ export function lowerVarDecl( settledType = init.type; } } + // `const view = route as Route & { order: number }`: the asserted type + // refines the record arms the value is stored in (the assertion keeps + // that value, see hosted-narrowing.ts). The binding keeps the stored + // type, so writes through it reach the asserted object. An annotation + // the stored type hosts (`const view: Route & { order: number } = ...`) + // keeps it too: coercing into the annotated layout would copy the + // value and read members the writes have not created yet. + if ( + !isLet && + decl.initializer && + isAssertionInitializer(decl.initializer) && + (settledType.kind === "union" || settledType.kind === "record") && + (init.type.kind === "union" || init.type.kind === "record") && + hostsAll(lowerer, init.type, settledType) + ) { + settledType = init.type; + } // An exact class behind a generic interface keeps its native // representation so generic calls can specialize against its body. if ( @@ -6175,35 +6193,6 @@ export function lowerTry(lowerer: Lowerer, stmt: ts.TryStatement): IrStmt { }; } -function hasUseStrictDirective(statements: readonly ts.Statement[]): boolean { - for (const statement of statements) { - if (!ts.isExpressionStatement(statement) || !ts.isStringLiteral(statement.expression)) - return false; - if (statement.expression.text === "use strict") return true; - } - return false; -} - -function isStrictDelete(node: ts.DeleteExpression): boolean { - for (let parent: ts.Node | undefined = node.parent; parent; parent = parent.parent) { - if (ts.isClassDeclaration(parent) || ts.isClassExpression(parent)) return true; - if ( - ts.isFunctionLike(parent) && - parent.body && - ts.isBlock(parent.body) && - hasUseStrictDirective(parent.body.statements.nodes) - ) - return true; - if (ts.isSourceFile(parent)) { - return ( - hasUseStrictDirective(parent.statements.nodes) || - (!isCjsJsFile(parent) && (isNodeEsmFile(parent) || ts.isExternalModule(parent))) - ); - } - } - return false; -} - /** Statement-position `delete`: process.env keys → process.envUnset * (unsetenv), pure `Record` keys → recordKeyDelete (the * overflow Map delete), declared OPTIONAL fields → the field's ABSENT @@ -6319,7 +6308,7 @@ function lowerDeleteStatement(lowerer: Lowerer, expr: ts.DeleteExpression): IrSt obj = lowerer.coerceToExpected(obj, DYN); } if (obj.type.kind === "dyn") { - const strict = { kind: "boolLit", value: isStrictDelete(expr), type: BOOL, loc } as const; + const strict = { kind: "boolLit", value: isStrictCode(expr), type: BOOL, loc } as const; if (ts.isElementAccessExpression(target)) { const key = lowerer.lowerExprExpecting(target.argumentExpression, DYN); return { @@ -7305,9 +7294,12 @@ export function lowerExprStatement(lowerer: Lowerer, expr: ts.Expression): IrStm ) { const loc = locOf(expr); const value = lowerer.lowerExprExpecting(expr.right, F64); + const fn = programReadsExitCode(lowerer) + ? "process.setExitCodeTracked" + : "process.setExitCode"; return { kind: "exprStmt", - expr: { kind: "libCall", fn: "process.setExitCode", args: [value], type: VOID, loc }, + expr: { kind: "libCall", fn, args: [value], type: VOID, loc }, loc, }; } @@ -11392,3 +11384,9 @@ function ownPrototypeRef( } return false; } + +function isAssertionInitializer(node: ts.Expression): boolean { + let e = node; + while (ts.isParenthesizedExpression(e)) e = e.expression; + return ts.isAsExpression(e) || ts.isTypeAssertion(e); +} diff --git a/packages/compiler/src/frontend/lowering/lower-stream.ts b/packages/compiler/src/frontend/lowering/lower-stream.ts index 5769e1e0a..dd0ddcc0e 100644 --- a/packages/compiler/src/frontend/lowering/lower-stream.ts +++ b/packages/compiler/src/frontend/lowering/lower-stream.ts @@ -999,6 +999,15 @@ export function lowerStreamNew(lowerer: Lowerer, expr: ts.NewExpression, info: C }; } +/** True when a value of Node's argument type `passed` widens implicitly + * into the declared parameter type `declared` (one of its union arms, or a + * sub-union of it). */ +function widensInto(lowerer: Lowerer, passed: IrType, declared: IrType, loc: SrcLoc): boolean { + if (declared.kind !== "union") return false; + const probe: IrExpr = { kind: "varRef", localId: "%probe", type: passed, loc }; + return typeEquals(lowerer.coerceToExpected(probe, declared).type, declared); +} + /* ── user subclasses (`extends Readable`, the underscore-override form) ── */ /** The wrapper closure binding one overridden underscore method as the @@ -1075,10 +1084,17 @@ function streamMethodWrapper( ); } } else if (!typeEquals(got.type, want)) { + // A declared supertype of what Node passes (`chunk: Buffer | string`) + // is sound: the wrapper takes Node's value and widens it into the + // declared parameter. + if (widensInto(lowerer, want, got.type, loc)) { + paramTypes.push(want); + continue; + } lowerer.noLowering( `a '${methodName}' method whose parameter ${i} is '${lowerer.fmt(got.type)}' where Node passes '${lowerer.fmt(want)}'`, blame, - "annotate the parameter with the Node signature's type (chunks are Buffers here)", + "annotate the parameter with the Node signature's type or a supertype of it (chunks are Buffers here)", ); } paramTypes.push(got.type); @@ -1094,7 +1110,11 @@ function streamMethodWrapper( for (let i = 0; i < paramTypes.length; i++) { const local = lowerer.declareHiddenLocal(`a${i}`, paramTypes[i]!); params.push({ localId: local.id, name: local.name, type: paramTypes[i]! }); - argRefs.push({ kind: "varRef", localId: local.id, type: paramTypes[i]!, loc }); + const arg: IrExpr = { kind: "varRef", localId: local.id, type: paramTypes[i]!, loc }; + const declaredType = declared[i]!.type; + argRefs.push( + typeEquals(arg.type, declaredType) ? arg : lowerer.coerceToExpected(arg, declaredType), + ); } const thisRef: IrExpr = { kind: "varRef", localId: thisLocal.id, type: thisType, loc }; let call: IrExpr; diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index e27bf5105..6c9ccd0b7 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -1,4 +1,5 @@ import { onlyForwarded } from "./typed-arguments.js"; +import { extendAssertedRecordMembers } from "./assertion-members.js"; import { jsDynamicDeclarations } from "./js-dynamic-objects.js"; import { lifetimeRegionType, @@ -117,6 +118,7 @@ import { InternalCompilerError } from "../../errors.js"; import { defaultAfterUndefined, lowerStaticallyUndefinedArgument } from "./optional-arguments.js"; import { type ProtocolDispatch } from "./class-protocol-dispatch.js"; import { ClassDynamicDispatch, trackClassFieldCreation } from "./class-dynamic-dispatch.js"; +import { guardClassAttributeWrites } from "./class-attributes.js"; import { ClassProtocols } from "./class-protocols.js"; import { earlyReadVarSymbols } from "./var-hoisting.js"; import { recordTextCodecClass } from "../../ir/ir.js"; @@ -1981,6 +1983,8 @@ export class Lowerer { readonly symbolFieldIdentities = new Map(); readonly httpClientFnBindings = new Map(); streamPropMutatedSyms: Set | null = null; + /** Whether some source reads process.exitCode (processReadsExitCode). */ + readsExitCode: boolean | null = null; requestInitPropMutatedSyms: Set | null = null; emitterEventTable: Map | null = null; emitterComputedPatterns: ComputedEventPattern[] = []; @@ -6794,6 +6798,10 @@ export class Lowerer { } this.collectProgram(parts); this.speculativeTypeQueries(() => { + extendAssertedRecordMembers( + this, + parts.map((fp) => fp.sf), + ); this.analyzeRuntimeOptionalArrayReads(parts); this.analyzeDateJsonResults(parts); this.analyzeGuardedRecordReadGlobals(parts); @@ -6942,6 +6950,7 @@ export class Lowerer { finalizeClassMethodValues(this, functions); finalizePrivateBrands(this, functions); trackClassFieldCreation(this, functions); + guardClassAttributeWrites(this, functions); // Globals typed by a class that never REGISTERED (a JS class whose // collection fenced — Symbol-keyed fields, an unsupported base): the @@ -7201,6 +7210,10 @@ export class Lowerer { }; this.collectProgram(parts); this.speculativeTypeQueries(() => { + extendAssertedRecordMembers( + this, + parts.map((fp) => fp.sf), + ); this.analyzeRuntimeOptionalArrayReads(parts); this.analyzeDateJsonResults(parts); this.analyzeGuardedRecordReadGlobals(parts); diff --git a/packages/compiler/src/frontend/lowering/strict-mode.ts b/packages/compiler/src/frontend/lowering/strict-mode.ts new file mode 100644 index 000000000..86653c1d3 --- /dev/null +++ b/packages/compiler/src/frontend/lowering/strict-mode.ts @@ -0,0 +1,34 @@ +import * as ts from "../ts7/adapter.js"; +import { isCjsJsFile, isNodeEsmFile } from "../program.js"; + +function hasUseStrictDirective(statements: readonly ts.Statement[]): boolean { + for (const statement of statements) { + if (!ts.isExpressionStatement(statement) || !ts.isStringLiteral(statement.expression)) + return false; + if (statement.expression.text === "use strict") return true; + } + return false; +} + +/** Whether `node` runs in strict mode code: inside a class body, a function + * with a "use strict" directive, a module, or a script that starts with + * "use strict". CommonJS JavaScript is sloppy otherwise. */ +export function isStrictCode(node: ts.Node): boolean { + for (let parent: ts.Node | undefined = node.parent; parent; parent = parent.parent) { + if (ts.isClassDeclaration(parent) || ts.isClassExpression(parent)) return true; + if ( + ts.isFunctionLike(parent) && + parent.body && + ts.isBlock(parent.body) && + hasUseStrictDirective(parent.body.statements.nodes) + ) + return true; + if (ts.isSourceFile(parent)) { + return ( + hasUseStrictDirective(parent.statements.nodes) || + (!isCjsJsFile(parent) && (isNodeEsmFile(parent) || ts.isExternalModule(parent))) + ); + } + } + return false; +} diff --git a/packages/compiler/src/frontend/lowering/surfaces.ts b/packages/compiler/src/frontend/lowering/surfaces.ts index 0f582258a..ba1ea3c08 100644 --- a/packages/compiler/src/frontend/lowering/surfaces.ts +++ b/packages/compiler/src/frontend/lowering/surfaces.ts @@ -2677,8 +2677,8 @@ export const AMBIENT_SURFACE_FNS: readonly AmbientSurfaceRow[] = [ id: "node-builtin.process.exitCode", kind: "node-builtin", name: "process.exitCode", - fns: ["process.setExitCode"], - note: "numeric writes in statement position set the implicit exit status; process.exit() reads it", + fns: ["process.setExitCode", "process.setExitCodeTracked", "process.hasExitCode"], + note: "numeric writes in statement position set the implicit exit status; reads answer the assigned code or undefined; process.exit() reads it", }, // ── the tls CA store (lowerTlsCaCall / lowerTlsRootCertificates): the // host's trust anchors, read and replaced. Dedicated paths, and @@ -2990,7 +2990,7 @@ export const BUILTIN_MODULE_FENCE_HINTS: Record< }, util: { promisify: - "the lowered shapes are const bindings over child_process.execFile, child_process.exec, setTimeout, setImmediate, stream.finished, stream.pipeline, the zlib callback one-shots, the crypto callback functions, dns.lookup, dns.lookupService and the fs callback functions with an fs/promises counterpart; call the bound function directly", + "the lowered shapes are const bindings over child_process.execFile, child_process.exec, setTimeout, setImmediate, stream.finished, stream.pipeline, the zlib callback one-shots, the crypto callback functions, dns.lookup, dns.lookupService and the fs callback functions with an fs/promises counterpart, plus typed callback-last function values whose callback takes (error?: Error | null, value?); call the bound function directly", }, "stream/consumers": { blob: diff --git a/packages/compiler/src/frontend/mock-shapes.ts b/packages/compiler/src/frontend/mock-shapes.ts index 1baf2cd04..50781c272 100644 --- a/packages/compiler/src/frontend/mock-shapes.ts +++ b/packages/compiler/src/frontend/mock-shapes.ts @@ -12,10 +12,11 @@ * object; empty for mock.fn * * A MockFunctionCall is { arguments, error, result, this }: the - * arguments as a tuple of F's parameters, the result as F's return type or - * undefined (a checked-dynamic value for functions returning void or - * undefined, since a record field cannot have a unit type), and error and - * this as checked-dynamic values. */ + * arguments as a tuple of F's parameters (a checked-dynamic array for + * mock.fn()'s `(...args: any[]) => undefined`), the result as F's return + * type or undefined (a checked-dynamic value for functions returning void + * or undefined, since a record field cannot have a unit type), and error + * and this as checked-dynamic values. */ import { DYN, F64, NULL_T, UNDEFINED_T, VOID, typeKey, type IrType } from "../ir/ir.js"; import type { TypeMapperCtx } from "./type-mapper.js"; @@ -34,11 +35,17 @@ function orUndefined(t: IrType, reg: MockShapeRegistries): IrType { } /** The arguments type of a call record: a tuple of the parameters, or the - * rest array itself for a function whose only parameter is a rest array. - * Null for other rest shapes. A function without parameters records the + * rest array itself for a function whose only parameter is a rest array, + * or a checked-dynamic array for a function returning nothing whose only + * parameter is a checked-dynamic rest (mock.fn()'s default, `(...args: + * any[]) => undefined`). Null for other rest shapes. A function without parameters records the * empty tuple, which maps to an array of the unit-only union, as * type-mapper.ts maps `[]`. */ function mockArgumentsType(fn: IrType & { kind: "func" }, reg: MockShapeRegistries): IrType | null { + if (fn.rest && fn.restAbi === undefined) { + const returnsNothing = fn.ret.kind === "void" || fn.ret.kind === "undefinedT"; + return fn.params.length === 0 && returnsNothing ? DYN : null; + } if (fn.rest) { if (fn.params.length !== 1 || fn.params[0]!.kind !== "array") return null; return fn.params[0]!; diff --git a/packages/compiler/src/frontend/npm-static-bundled-chunks.test.ts b/packages/compiler/src/frontend/npm-static-bundled-chunks.test.ts new file mode 100644 index 000000000..6bc830d74 --- /dev/null +++ b/packages/compiler/src/frontend/npm-static-bundled-chunks.test.ts @@ -0,0 +1,300 @@ +import { spawnSync } from "node:child_process"; +import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { basename, join } from "node:path"; +import { afterAll, expect, test } from "vitest"; +import { nodeFrontendServices } from "./services-node.js"; +import { rewriteBundlerCjsExports } from "./npm-static-rewrite.js"; +import type { BundledChunkSplit } from "./npm-static-bundled-chunks.js"; + +const keyedHelper = `var __getOwnPropNames = Object.getOwnPropertyNames; +var __commonJS = (cb, mod) => function __require() { + return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; +}; +`; + +const dirs: string[] = []; +afterAll(() => { + for (const dir of dirs) rmSync(dir, { recursive: true, force: true }); +}); + +function split(source: string, file = "/pkg/dist/index.js") { + return nodeFrontendServices().splitCjsChunks(source, file); +} + +function run(file: string) { + const result = spawnSync(process.execPath, [file], { encoding: "utf8" }); + return { status: result.status, stdout: result.stdout, stderr: result.stderr }; +} + +/** Runs the original bundle and its split modules side by side. */ +function expectSameBehavior(source: string): BundledChunkSplit { + const dir = mkdtempSync(join(tmpdir(), "scriptc-chunks-")); + dirs.push(dir); + const original = join(dir, "original.js"); + writeFileSync(original, source); + const bundle = join(dir, "index.js"); + const result = split(source, bundle); + expect(result).not.toBeNull(); + expect(result).not.toHaveProperty("degrade"); + const answer = result as BundledChunkSplit; + writeFileSync(bundle, answer.text); + for (const chunk of answer.chunks) writeFileSync(chunk.path, chunk.source); + // Respelled calls and blanked declarations keep the line structure. + expect(answer.text.split("\n")).toHaveLength(source.split("\n").length); + const oracle = run(original); + expect(oracle.status).toBe(0); + expect(run(bundle)).toEqual(oracle); + return answer; +} + +test("chunks run once, at their first require, with shared module instances", () => { + const answer = expectSameBehavior(`"use strict"; +${keyedHelper} +var require_b = __commonJS({ + "b.js"(exports2, module2) { + console.log("b init"); + module2.exports = { n: 1 }; + } +}); +var require_a = __commonJS({ + "a.js"(exports2) { + console.log("a init"); + var b = require_b(); + exports2.value = b.n + 1; + exports2.keys = Object.keys({ exports2 }); + } +}); +console.log("start"); +console.log(require_a().value, require_a().value, require_b().n, require_a().keys); +`); + expect(answer.chunks.map((c) => basename(c.path))).toEqual([ + "index.js.require_b.cjs", + "index.js.require_a.cjs", + ]); + expect(answer.text).not.toContain("require_a()"); +}); + +test("the arrow helper form and minified spellings split the same way", () => { + expectSameBehavior( + `var __commonJS=(cb,mod)=>()=>(mod||cb((mod={exports:{}}).exports,mod),mod.exports);` + + `var require_x=__commonJS((exports,module)=>{module.exports={v:"x"}});` + + `console.log(require_x().v,require_x()===require_x());`, + ); +}); + +test("esbuild 0.11's Object.keys spelling is recognized", () => { + expectSameBehavior(`var __commonJS = (cb, mod) => function __require() { + return mod || (0, cb[Object.keys(cb)[0]])((mod = {exports: {}}).exports, mod), mod.exports; +}; +var require_y = __commonJS({ + "y.js"(exports) { + exports.y = 3; + } +}); +console.log(require_y().y); +`); +}); + +test("pure helpers a chunk reads travel with it", () => { + const answer = expectSameBehavior(`${keyedHelper}var __defProp = Object.defineProperty; +var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); +var require_named = __commonJS({ + "named.js"(exports2, module2) { + module2.exports = __name(function () {}, "renamed"); + } +}); +console.log(require_named().name); +`); + const chunk = answer.chunks[0]!.source; + expect(chunk).toContain("var __name ="); + expect(chunk).toContain("var __defProp = Object.defineProperty;"); +}); + +test("a helper travels with a chunk whose nested scopes declare its name", () => { + const answer = expectSameBehavior(`${keyedHelper}function label(n) { + return "label " + n; +} +function wrap(n) { + function inner() { + var label = "inner"; + return label; + } + return label(n) + " " + inner(); +} +var require_e = __commonJS({ + "e.js"(exports2, module2) { + function nested() { + var label = "nested"; + return label; + } + var seen = []; + for (let label of ["loop"]) seen.push(label); + try { + throw "caught"; + } catch (label) { + seen.push(label); + } + { + const label = "block"; + seen.push(label); + } + module2.exports = { outer: label(1), indirect: (0, label)(3), wrapped: wrap(2), nested: nested(), seen }; + } +}); +var e = require_e(); +console.log(e.outer, e.indirect, e.wrapped, e.nested, e.seen.join(",")); +`); + const chunk = answer.chunks[0]!.source; + expect(chunk).toContain("function label(n)"); + expect(chunk).toContain("function wrap(n)"); +}); + +test("a named function expression that only calls itself travels with a chunk", () => { + const answer = expectSameBehavior(`${keyedHelper}var fact = function self(n) { + return n <= 1 ? 1 : n * self(n - 1); +}; +var kind = function self(self) { + return typeof self; +}; +var outer = function self() { + var self = 2; + return self; +}; +var require_g = __commonJS({ + "g.js"(exports2, module2) { + module2.exports = [fact(5), kind(1), outer()]; + } +}); +console.log(require_g().join(",")); +`); + const chunk = answer.chunks[0]!.source; + expect(chunk).toContain("var fact = function self(n)"); +}); + +test("a helper a parameter default reads travels with a chunk whose body declares its name", () => { + const answer = expectSameBehavior(`${keyedHelper}var ten = function () { + return 10; +}; +var add = function (n, base = ten()) { + var ten = 1; + return n + base + ten; +}; +var require_g = __commonJS({ + "g.js"(exports2, module2) { + module2.exports = add(1); + } +}); +console.log(require_g()); +`); + const chunk = answer.chunks[0]!.source; + expect(chunk).toContain("var ten = function ()"); +}); + +test.each([ + ["exports it", `function tally() {}`, `module2.exports = { tally };`], + ["reads its properties", `function tally() {}`, `exports2.calls = tally.calls;`], + ["constructs it", `function tally() {}`, `exports2.made = new tally();`], + ["stores an arrow helper", `var tally = () => 1;`, `exports2.fn = tally;`], + [ + "calls a function that reads its own name as a value", + `var tally = function self() { self.n = (self.n || 0) + 1; return self.n; };`, + `exports2.count = tally();`, + ], + [ + "calls a function whose parameter default reads its own name", + `var tally = function self(box = self) { box.n = (box.n || 0) + 1; return box.n; };`, + `exports2.count = tally();`, + ], + [ + "calls a function whose parameter default reads its own name past a body var of that name", + `var tally = function self(box = self) { var self = 0; box.n = (box.n || 0) + 1; return box.n + self; };`, + `exports2.count = tally();`, + ], +])("a chunk that %s keeps the package refused", (_, declaration, statement) => { + const result = split(`${keyedHelper}${declaration} +var require_f = __commonJS({ + "f.js"(exports2, module2) { + ${statement} + } +}); +require_f(); +`); + expect(result).toEqual({ + degrade: expect.stringContaining( + "uses the bundle's top-level function 'tally' as a value", + ) as string, + }); +}); + +test.each([ + ["reads its factory's receiver", `console.log(this);`], + ["reads its factory's arguments object", `console.log(arguments.length);`], + ["returns from its top level", `if (exports2) return;`], + ["uses its module object beyond module.exports", `console.log(module2.id);`], + ["reads __filename", `console.log(__filename);`], + ["rebinds its module parameters", `function f(module2) { return module2; }`], + ["reads the bundle's top-level binding 'state'", `console.log(state);`], +])("a chunk that %s keeps the package refused", (reason, statement) => { + const result = split(`${keyedHelper}var state = { n: 1 }; +var require_c = __commonJS({ + "c.js"(exports2, module2) { + ${statement} + } +}); +require_c(); +`); + expect(result).toEqual({ degrade: expect.stringContaining(reason) as string }); +}); + +test("a factory used other than as a direct call keeps the package refused", () => { + const result = + split(`${keyedHelper}var require_d = __commonJS({ "d.js"(exports2) { exports2.d = 1; } }); +var load = require_d; +load(); +`); + expect(result).toEqual({ + degrade: expect.stringContaining("used other than as a direct call") as string, + }); +}); + +test("an unrecognized helper body keeps the package refused", () => { + const result = + split(`var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod); +var require_e = __commonJS((exports) => { exports.e = 1; }); +require_e(); +`); + expect(result).toEqual({ degrade: expect.stringContaining("__commonJS helper") as string }); +}); + +test("files without chunks and ES-module bundles are left alone", () => { + expect(split("exports.x = 1;\n")).toBeNull(); + expect( + split(`${keyedHelper}var require_f = __commonJS({ "f.js"(e) { e.f = 1; } }); +export default require_f(); +`), + ).toBeNull(); +}); + +test("an identifier-minified bundle names its lost helper names", () => { + // esbuild --minify output: the helper shapes survive, their names do not. + const result = split( + `var c=(r,e)=>()=>(e||r((e={exports:{}}).exports,e),e.exports);` + + `var x=c((t,m)=>{m.exports={v:1}});console.log(x().v);`, + ); + expect(result).toEqual({ degrade: expect.stringContaining("identifier-minified") as string }); +}); + +test("an ES-module bundle of CommonJS chunks names the chunk interop", () => { + const result = rewriteBundlerCjsExports( + `${keyedHelper}var __toESM = (mod) => mod; +var require_g = __commonJS({ "g.js"(exports) { exports.g = 1; } }); +var import_g = __toESM(require_g()); +export const g = import_g.g; +`, + "/pkg/dist/index.js", + ); + expect(result).toEqual({ + degrade: expect.stringContaining("bundled CommonJS modules") as string, + }); +}); diff --git a/packages/compiler/src/frontend/npm-static-bundled-chunks.ts b/packages/compiler/src/frontend/npm-static-bundled-chunks.ts new file mode 100644 index 000000000..33ff865ec --- /dev/null +++ b/packages/compiler/src/frontend/npm-static-bundled-chunks.ts @@ -0,0 +1,858 @@ +/* Bundled CommonJS chunks: esbuild wraps every CommonJS module it bundles + * in a lazily evaluated factory, + * + * var require_x = __commonJS({ "src/x.js"(exports, module) { BODY } }); + * + * and each `require_x()` runs BODY once (later calls answer the cached + * module.exports, a call during BODY answers the partial one). That is + * exactly a CommonJS module that the bundle requires at the call's + * position, so each recognized chunk becomes its own module: a virtual + * sibling file `..cjs` holding BODY, its module + * parameters respelled as the CommonJS globals, and every `require_x()` + * call becomes `require("./..cjs")`. The program then + * compiles chunks with the ordinary CommonJS machinery: evaluation at the + * require's position, the run-once module cache, export tables, and the + * __toESM interop erasure. + * + * Only shapes whose meaning is verified move: the helper must be one of + * the recognized __commonJS spellings, every reference to a factory + * binding must be a direct call, and BODY must not observe the + * difference between a factory and a module (its `this`, `arguments`, + * `__filename`, the module object beyond `module.exports`, a top-level + * return, or a binding of the bundle other than the pure helpers copied + * beside it). Anything else answers a degrade reason. */ +import * as ts from "./ts7/syntax.js"; +import { helperTokens } from "./helper-tokens.js"; + +export interface BundledChunkSplit { + /** The bundle with chunk declarations blanked and calls respelled. */ + text: string; + /** Virtual sibling modules, in declaration order. */ + chunks: { path: string; source: string }[]; +} + +interface Edit { + start: number; + end: number; + text: string; +} + +const CHUNK_SHAPE_DEGRADE = + "its shipped JS bundles CommonJS modules through the __commonJS helper in a shape the " + + "recognizer cannot verify"; + +/** The helper a counter-suffixed bundler name spells (`__commonJS2`). */ +function helperBase(name: string): string { + return /^__[A-Za-z]+\d+$/.test(name) ? name.replace(/\d+$/, "") : name; +} + +function unparen(e: ts.Expression): ts.Expression { + while (ts.isParenthesizedExpression(e)) e = e.expression; + return e; +} + +/** True for an identifier occurrence that reads or writes a binding (as + * opposed to a property name, a label, or a declaration's own name). + * Shorthand property values count: `{ x }` reads x. */ +function isReference(id: ts.Identifier): boolean { + const p = id.parent; + if (p === undefined) return false; + if (ts.isPropertyAccessExpression(p)) return p.expression === id; + if (ts.isPropertyAssignment(p)) return p.initializer === id; + if ( + ts.isMethodDeclaration(p) || + ts.isPropertyDeclaration(p) || + ts.isGetAccessorDeclaration(p) || + ts.isSetAccessorDeclaration(p) + ) + return p.name !== id; + if (ts.isBindingElement(p)) return p.initializer === id; + if (ts.isVariableDeclaration(p) || ts.isParameter(p)) return p.initializer === id; + if ( + ts.isFunctionDeclaration(p) || + ts.isFunctionExpression(p) || + ts.isClassDeclaration(p) || + ts.isClassExpression(p) + ) + return p.name !== id; + if (ts.isLabeledStatement(p)) return p.label !== id; + if (ts.isBreakStatement(p) || ts.isContinueStatement(p)) return false; + if (ts.isMetaProperty(p)) return false; + return true; +} + +/** Names a subtree declares anywhere inside it: variables (destructuring + * included), parameters, function and class names, catch bindings. */ +function declaredNames(root: ts.Node): Set { + const names = new Set(); + // Binding patterns nest BindingElements, which the walk visits too. + ts.walkPreorder(root, (n) => { + if (n.kind >= ts.SyntaxKind.FirstJSDocNode && n.kind <= ts.SyntaxKind.LastJSDocNode) + return "skip"; + if (ts.isVariableDeclaration(n) || ts.isParameter(n) || ts.isBindingElement(n)) { + const name = n.name; + if (name !== undefined && ts.isIdentifier(name)) names.add(name.text); + } else if ( + (ts.isFunctionDeclaration(n) || + ts.isFunctionExpression(n) || + ts.isClassDeclaration(n) || + ts.isClassExpression(n)) && + n.name !== undefined + ) + names.add(n.name.text); + return undefined; + }); + return names; +} + +/** Adds the identifiers a binding name (an identifier or a destructuring + * pattern) declares. */ +function addBindingNames(name: ts.BindingName, out: Set): void { + if (ts.isIdentifier(name)) { + out.add(name.text); + return; + } + for (const element of name.elements.nodes) { + if (!ts.isOmittedExpression(element) && element.name !== undefined) + addBindingNames(element.name, out); + } +} + +function isFunctionScope(n: ts.Node): n is ts.SignatureDeclaration { + return ( + ts.isFunctionDeclaration(n) || + ts.isFunctionExpression(n) || + ts.isArrowFunction(n) || + ts.isMethodDeclaration(n) || + ts.isGetAccessorDeclaration(n) || + ts.isSetAccessorDeclaration(n) || + ts.isConstructorDeclaration(n) + ); +} + +/** The `var` names a function body declares, without entering nested + * functions. Function declarations in nested blocks count as block-scoped + * here; treating one as free only adds an unused helper copy. */ +function varScopedNames(body: ts.Node): Set { + const out = new Set(); + ts.walkPreorder(body, (n) => { + if (n !== body && (isFunctionScope(n) || ts.isClassDeclaration(n) || ts.isClassExpression(n))) + return "skip"; + if (ts.isVariableDeclarationList(n) && (n.flags & ts.NodeFlags.BlockScoped) === 0) { + for (const d of n.declarations.nodes) addBindingNames(d.name, out); + } + return undefined; + }); + return out; +} + +/** The names a function binds for references in its parameter list + * (default values, computed keys of destructuring patterns): its + * parameters and a function expression's own name. Parameter initializers + * run in their own scope, so the body's `var`s are not visible there. */ +function parameterScopeNames(scope: ts.SignatureDeclaration): Set { + const out = new Set(); + for (const param of scope.parameters.nodes) addBindingNames(param.name, out); + if (ts.isFunctionExpression(scope) && scope.name !== undefined) out.add(scope.name.text); + return out; +} + +const NO_NAMES: ReadonlySet = new Set(); + +/** True when `node` lies inside `ancestor` (or is it). */ +function within(node: ts.Node, ancestor: ts.Node): boolean { + for (let n: ts.Node | undefined = node; n !== undefined; n = n.parent) + if (n === ancestor) return true; + return false; +} + +/** The names one scope node binds for the references inside it. */ +function scopeNames(scope: ts.Node): Set { + const out = new Set(); + if (isFunctionScope(scope)) { + for (const param of scope.parameters.nodes) addBindingNames(param.name, out); + if (ts.isFunctionExpression(scope) && scope.name !== undefined) out.add(scope.name.text); + if (scope.body !== undefined) for (const name of varScopedNames(scope.body)) out.add(name); + } else if (ts.isBlock(scope) || ts.isSourceFile(scope) || ts.isCaseBlock(scope)) { + const statements = ts.isCaseBlock(scope) + ? scope.clauses.nodes.flatMap((clause) => clause.statements.nodes) + : scope.statements.nodes; + for (const stmt of statements) { + if ((ts.isFunctionDeclaration(stmt) || ts.isClassDeclaration(stmt)) && stmt.name) + out.add(stmt.name.text); + else if ( + ts.isVariableStatement(stmt) && + (stmt.declarationList.flags & ts.NodeFlags.BlockScoped) !== 0 + ) { + for (const d of stmt.declarationList.declarations.nodes) addBindingNames(d.name, out); + } + } + } else if (ts.isCatchClause(scope) && scope.variableDeclaration !== undefined) { + addBindingNames(scope.variableDeclaration.name, out); + } else if ( + (ts.isForStatement(scope) || ts.isForInStatement(scope) || ts.isForOfStatement(scope)) && + scope.initializer !== undefined && + ts.isVariableDeclarationList(scope.initializer) && + (scope.initializer.flags & ts.NodeFlags.BlockScoped) !== 0 + ) { + for (const d of scope.initializer.declarations.nodes) addBindingNames(d.name, out); + } else if ( + (ts.isClassDeclaration(scope) || ts.isClassExpression(scope)) && + scope.name !== undefined + ) { + out.add(scope.name.text); + } + return out; +} + +/** Resolves references against one bundle's scopes, keeping each scope's + * names for the bundle's split. */ +class BundleScopes { + private readonly names = new Map>(); + private readonly parameterNames = new Map>(); + + private of(scope: ts.Node): Set { + let names = this.names.get(scope); + if (names === undefined) { + names = scopeNames(scope); + this.names.set(scope, names); + } + return names; + } + + /** The names `scope` binds for a reference reached through its child + * `from`: a function's parameter list sees its parameters and own name + * but not its body's `var`s, and a computed method name sees none of + * them. */ + private visible(scope: ts.Node, from: ts.Node): ReadonlySet { + if (!isFunctionScope(scope) || from === scope.body) return this.of(scope); + if (from === scope.name) return NO_NAMES; + let names = this.parameterNames.get(scope); + if (names === undefined) { + names = parameterScopeNames(scope); + this.parameterNames.set(scope, names); + } + return names; + } + + /** True when a scope between the reference and `root` (inclusive) + * binds its name. */ + boundWithin(id: ts.Identifier, root: ts.Node): boolean { + for (let from: ts.Node = id, n = id.parent; n !== undefined; from = n, n = n.parent) { + if (this.visible(n, from).has(id.text)) return true; + if (n === root) return false; + } + return false; + } + + /** True when a scope inside `root` (exclusive) binds the reference's + * name. */ + boundInside(id: ts.Identifier, root: ts.Node): boolean { + for ( + let from: ts.Node = id, n = id.parent; + n !== undefined && n !== root; + from = n, n = n.parent + ) { + if (this.visible(n, from).has(id.text)) return true; + } + return false; + } + + /** True when the reference resolves to a top-level binding of `sf`. */ + topLevel(id: ts.Identifier, sf: ts.SourceFile): boolean { + for ( + let from: ts.Node = id, n = id.parent; + n !== undefined && n !== sf; + from = n, n = n.parent + ) { + if (this.visible(n, from).has(id.text)) return false; + } + return true; + } +} + +/** Every binding reference inside a subtree. */ +function references(root: ts.Node): ts.Identifier[] { + const out: ts.Identifier[] = []; + ts.walkPreorder(root, (n) => { + if (n.kind >= ts.SyntaxKind.FirstJSDocNode && n.kind <= ts.SyntaxKind.LastJSDocNode) + return "skip"; + if (ts.isIdentifier(n) && isReference(n)) out.push(n); + return undefined; + }); + return out; +} + +/** True when the reference is written (assignment target, update operand, + * loop target, delete operand). */ +function isWritten(id: ts.Identifier): boolean { + let child: ts.Node = id; + for (let p = child.parent; p !== undefined; child = p, p = p.parent) { + if (ts.isBinaryExpression(p)) { + return ( + p.left === child && + p.operatorToken.kind >= ts.SyntaxKind.FirstAssignment && + p.operatorToken.kind <= ts.SyntaxKind.LastAssignment + ); + } + if (ts.isPrefixUnaryExpression(p) || ts.isPostfixUnaryExpression(p)) { + return ( + p.operator === ts.SyntaxKind.PlusPlusToken || p.operator === ts.SyntaxKind.MinusMinusToken + ); + } + if (ts.isForInStatement(p) || ts.isForOfStatement(p)) return p.initializer === child; + if (ts.isDeleteExpression(p)) return true; + if ( + !( + ts.isParenthesizedExpression(p) || + ts.isArrayLiteralExpression(p) || + ts.isObjectLiteralExpression(p) || + ts.isShorthandPropertyAssignment(p) || + ts.isPropertyAssignment(p) || + ts.isSpreadElement(p) + ) + ) + return false; + } + return false; +} + +/** The recognized __commonJS helper forms, as token templates over the + * helper's own parameter names (CB, MOD) and the own-key reader (KEYS: + * esbuild's `__getOwnPropNames` alias, or `Object.keys` in esbuild 0.11): + * keyed: `(CB, MOD) => function [name]() { return ; }` — the + * factory is the only method of an object literal; + * arrow: `(CB, MOD) => () => ()` — the factory itself. */ +const KEYED_RETURN = + "MOD || (0, CB[KEYS(CB)[0]])((MOD = { exports: {} }).exports, MOD), MOD.exports"; +const ARROW_BODY = "(MOD || CB((MOD = { exports: {} }).exports, MOD), MOD.exports)"; + +function template(text: string, cb: string, mod: string, keys: string): string | null { + return helperTokens( + text + .replace(/\bCB\b/g, cb) + .replace(/\bMOD\b/g, mod) + .replace(/\bKEYS\b/g, keys), + ); +} + +type HelperForm = "keyed" | "arrow"; + +/** The helper's form, or null when its initializer is not recognized. + * `ownNames` answers whether an identifier names the verified + * Object.getOwnPropertyNames alias. */ +function helperForm( + init: ts.Expression, + sf: ts.SourceFile, + ownNames: (name: string) => boolean, +): HelperForm | null { + const fn = unparen(init); + if (!ts.isArrowFunction(fn) || fn.parameters.nodes.length !== 2) return null; + const params = fn.parameters.nodes.map((p) => + ts.isIdentifier(p.name) && p.initializer === undefined && p.dotDotDotToken === undefined + ? p.name.text + : null, + ); + const [cb, mod] = params; + if (cb === null || mod === null || cb === undefined || mod === undefined || cb === mod) + return null; + if (ts.isBlock(fn.body)) return null; + const body = unparen(fn.body); + if (ts.isFunctionExpression(body)) { + if (body.parameters.nodes.length !== 0 || body.asteriskToken || body.modifiers?.nodes.length) + return null; + const stmts = body.body.statements.nodes; + const ret = stmts[0]; + if (stmts.length !== 1 || ret === undefined || !ts.isReturnStatement(ret) || !ret.expression) + return null; + const actual = helperTokens(ret.expression.getText(sf)); + if (actual === null) return null; + // The own-key reader: esbuild's alias or the 0.11 spelling. + const keysRef = /\[\s*([A-Za-z_$][\w$]*(?:\.keys)?)\s*\(/.exec(ret.expression.getText(sf)); + const keys = keysRef?.[1]; + if (keys === undefined || (keys !== "Object.keys" && !ownNames(keys))) return null; + return actual === template(KEYED_RETURN, cb, mod, keys) ? "keyed" : null; + } + if (ts.isArrowFunction(body) && body.parameters.nodes.length === 0 && !ts.isBlock(body.body)) { + const actual = helperTokens(body.body.getText(sf)); + return actual !== null && actual === template(ARROW_BODY, cb, mod, "") ? "arrow" : null; + } + return null; +} + +/** A top-level declaration the virtual chunk can copy verbatim: a pure + * helper whose evaluation runs nothing (a function, or a member read of + * Object such as `Object.create` / `Object.prototype.hasOwnProperty`). */ +function copyableInitializer(init: ts.Expression | undefined): boolean { + if (init === undefined) return false; + const e = unparen(init); + if (ts.isArrowFunction(e) || ts.isFunctionExpression(e)) return true; + let walk: ts.Expression = e; + let depth = 0; + while (ts.isPropertyAccessExpression(walk) && ts.isIdentifier(walk.name)) { + walk = walk.expression; + depth++; + } + return depth > 0 && ts.isIdentifier(walk) && walk.text === "Object"; +} + +/** A top-level declaration whose value is a function. */ +function isFunctionValued(node: ts.VariableDeclaration | ts.FunctionDeclaration): boolean { + if (ts.isFunctionDeclaration(node)) return true; + const init = node.initializer && unparen(node.initializer); + return init !== undefined && (ts.isArrowFunction(init) || ts.isFunctionExpression(init)); +} + +/** True when a named function expression initializer (`var f = function + * self() {}`) uses its own name only as a callee: `self` is the copied + * function, so it is as observable as the top-level binding. */ +function selfCalledOnly( + node: ts.VariableDeclaration | ts.FunctionDeclaration, + scopes: BundleScopes, +): boolean { + if (!ts.isVariableDeclaration(node) || node.initializer === undefined) return true; + const fn = unparen(node.initializer); + if (!ts.isFunctionExpression(fn) || fn.name === undefined || fn.body === undefined) return true; + const self = fn.name.text; + // A parameter of the same name hides the function's own name everywhere + // in it; a `var` of the same name hides it only in the body, since the + // parameter initializers do not see the body's `var`s. + if (fn.parameters.nodes.some((p) => bindsName(p.name, self))) return true; + const bodyVar = varScopedNames(fn.body).has(self); + const body = fn.body; + return references(fn).every( + (r) => + r.text !== self || scopes.boundInside(r, fn) || (bodyVar && within(r, body)) || isCallee(r), + ); +} + +/** True when a binding name (an identifier or a destructuring pattern) + * declares `name`. */ +function bindsName(binding: ts.BindingName, name: string): boolean { + const names = new Set(); + addBindingNames(binding, names); + return names.has(name); +} + +/** True when a reference is the function a call invokes: `f(...)`, or the + * `(0, f)(...)` form that drops the receiver. */ +function isCallee(id: ts.Identifier): boolean { + let callee: ts.Node = id; + const p = id.parent; + if ( + p !== undefined && + ts.isBinaryExpression(p) && + p.operatorToken.kind === ts.SyntaxKind.CommaToken && + p.right === id && + p.parent !== undefined && + ts.isParenthesizedExpression(p.parent) + ) + callee = p.parent; + while (callee.parent !== undefined && ts.isParenthesizedExpression(callee.parent)) + callee = callee.parent; + const call = callee.parent; + return call !== undefined && ts.isCallExpression(call) && call.expression === callee; +} + +/** One recognized chunk declaration. */ +interface Chunk { + name: string; + stmt: ts.VariableStatement; + params: string[]; + body: ts.Block; + path: string; + spec: string; +} + +interface TopLevel { + stmt: ts.Statement; + /** The declarator (or function declaration) itself. */ + node: ts.VariableDeclaration | ts.FunctionDeclaration; +} + +/** True when the file opens with a "use strict" directive prologue. */ +function strictPrologue(stmts: readonly ts.Statement[]): boolean { + for (const s of stmts) { + if (!ts.isExpressionStatement(s) || !ts.isStringLiteral(s.expression)) return false; + if (s.expression.text === "use strict") return true; + } + return false; +} + +/** A cheap text probe: the helper's name, or its minified body's + * `(m={exports:{}}).exports,m` fragment. */ +export function mayBundleChunks(source: string): boolean { + return ( + source.includes("__commonJS") || + /\{\s*exports\s*:\s*\{\s*\}\s*\}\s*\)\s*\.exports\s*,/.test(source) + ); +} + +/** Splits a CommonJS bundle's __commonJS chunks into virtual modules (see + * the header). Null: no chunks (or not a CommonJS bundle) — serve the file + * unchanged. */ +export function splitBundledChunks( + sf: ts.SourceFile, + filePath: string, +): BundledChunkSplit | { degrade: string } | null { + const source = sf.text; + if (!mayBundleChunks(source)) return null; + // ES-module bundles have no require: their chunks keep the existing + // treatment (the Bun factory recognizer, or the ESM interop refusal). + if (!filePath.endsWith(".cjs") && ts.isExternalModule(sf)) return null; + + const top = new Map(); + const duplicate = new Set(); + for (const stmt of sf.statements.nodes) { + const add = (name: string, node: TopLevel["node"]): void => { + if (top.has(name)) duplicate.add(name); + else top.set(name, { stmt, node }); + }; + if (ts.isFunctionDeclaration(stmt) && stmt.name !== undefined) add(stmt.name.text, stmt); + else if (ts.isVariableStatement(stmt)) { + for (const d of stmt.declarationList.declarations.nodes) { + if (ts.isIdentifier(d.name)) add(d.name.text, d); + } + } + } + const helperNames = [...top.keys()].filter((n) => helperBase(n) === "__commonJS"); + + // Every name a recognizer relies on is written nowhere and declared + // once at the top level; `Object` itself is never rebound. + const allRefs = references(sf); + const written = new Set(); + for (const r of allRefs) if (isWritten(r)) written.add(r.text); + const declaredAnywhere = declaredNames(sf); + if (declaredAnywhere.has("Object") || written.has("Object")) + return { degrade: CHUNK_SHAPE_DEGRADE }; + const stable = (name: string): boolean => !duplicate.has(name) && !written.has(name); + // Names whose every reference in the bundle is a callee: `f(...)` or the + // `(0, f)(...)` form. + const scopes = new BundleScopes(); + const valueUsed = new Set(); + for (const r of allRefs) if (!isCallee(r) && scopes.topLevel(r, sf)) valueUsed.add(r.text); + const calledOnly = (name: string): boolean => !valueUsed.has(name); + const ownNames = (name: string): boolean => { + const t = top.get(name); + if (t === undefined || !stable(name) || !ts.isVariableDeclaration(t.node)) return false; + const init = t.node.initializer; + return ( + init !== undefined && + helperTokens(init.getText(sf)) === helperTokens("Object.getOwnPropertyNames") + ); + }; + if (helperNames.length === 0) { + // The helper's shape under another name: an identifier-minified bundle, + // whose other interop helpers lost their names as well. + for (const [name, t] of top) { + const init = ts.isVariableDeclaration(t.node) ? t.node.initializer : undefined; + if (init !== undefined && stable(name) && helperForm(init, sf, ownNames) !== null) + return { + degrade: + "its shipped JS is an identifier-minified bundle whose bundler-interop helpers " + + "(__commonJS and the rest) have lost the names the recognizers verify", + }; + } + return null; + } + const forms = new Map(); + for (const name of helperNames) { + const t = top.get(name)!; + const init = ts.isVariableDeclaration(t.node) ? t.node.initializer : undefined; + const form = init !== undefined && stable(name) ? helperForm(init, sf, ownNames) : null; + if (form === null) return { degrade: CHUNK_SHAPE_DEGRADE }; + forms.set(name, form); + } + + // The chunk declarations: `var NAME = HELPER(factory);`, one declarator. + const chunks = new Map(); + const base = filePath.replace(/^.*[\\/]/, ""); + for (const [name, t] of top) { + if (!ts.isVariableDeclaration(t.node) || t.node.initializer === undefined) continue; + const call = unparen(t.node.initializer); + if (!ts.isCallExpression(call) || !ts.isIdentifier(call.expression)) continue; + const form = forms.get(call.expression.text); + if (form === undefined) continue; + const stmt = t.stmt; + if ( + !ts.isVariableStatement(stmt) || + stmt.declarationList.declarations.nodes.length !== 1 || + call.arguments.nodes.length !== 1 || + !stable(name) + ) + return { degrade: CHUNK_SHAPE_DEGRADE }; + let factory: ts.Node = call.arguments.nodes[0]!; + if (form === "keyed") { + if (!ts.isObjectLiteralExpression(factory) || factory.properties.nodes.length !== 1) + return { degrade: CHUNK_SHAPE_DEGRADE }; + factory = factory.properties.nodes[0]!; + if (!ts.isMethodDeclaration(factory) || ts.isComputedPropertyName(factory.name)) + return { degrade: CHUNK_SHAPE_DEGRADE }; + } else { + factory = unparen(factory as ts.Expression); + if (!ts.isArrowFunction(factory) && !ts.isFunctionExpression(factory)) + return { degrade: CHUNK_SHAPE_DEGRADE }; + } + const fn = factory as ts.MethodDeclaration | ts.ArrowFunction | ts.FunctionExpression; + if ( + fn.body === undefined || + !ts.isBlock(fn.body) || + fn.asteriskToken !== undefined || + (fn.modifiers?.nodes.length ?? 0) > 0 || + fn.parameters.nodes.length > 2 + ) + return { degrade: CHUNK_SHAPE_DEGRADE }; + const params: string[] = []; + for (const p of fn.parameters.nodes) { + if (!ts.isIdentifier(p.name) || p.initializer !== undefined || p.dotDotDotToken) + return { degrade: CHUNK_SHAPE_DEGRADE }; + params.push(p.name.text); + } + const file = `${base}.${name}.cjs`; + chunks.set(name, { + name, + stmt, + params, + body: fn.body, + path: filePath.slice(0, filePath.length - base.length) + file, + spec: `./${file}`, + }); + } + if (chunks.size === 0) return null; + + // Every factory reference is a direct zero-argument call. Calls inside a + // chunk body belong to that chunk's text, the rest to the bundle's. + const callEdits: Edit[] = []; + for (const r of allRefs) { + const chunk = chunks.get(r.text); + if (chunk === undefined) continue; + const call = r.parent; + if ( + call === undefined || + !ts.isCallExpression(call) || + call.expression !== r || + call.arguments.nodes.length !== 0 || + call.questionDotToken !== undefined + ) + return { + degrade: `its bundled CommonJS module factory '${r.text}' is used other than as a direct call`, + }; + callEdits.push({ + start: call.getStart(sf), + end: call.getEnd(), + text: `require(${JSON.stringify(chunk.spec)})`, + }); + } + for (const name of chunks.keys()) { + if (declaredAnywhere.has(name) && countDeclarations(sf, name) > 1) + return { degrade: CHUNK_SHAPE_DEGRADE }; + } + + const bundleStrict = strictPrologue(sf.statements.nodes); + const out: BundledChunkSplit["chunks"] = []; + const bundleEdits: Edit[] = [...callEdits]; + for (const chunk of chunks.values()) { + const result = chunkSource( + sf, + chunk, + chunks, + top, + stable, + calledOnly, + scopes, + callEdits, + bundleStrict, + ); + if (typeof result !== "string") return result; + out.push({ path: chunk.path, source: result }); + bundleEdits.push({ start: chunk.stmt.getStart(sf), end: chunk.stmt.getEnd(), text: "" }); + } + return { text: applyEdits(source, 0, source.length, bundleEdits), chunks: out }; +} + +function countDeclarations(sf: ts.SourceFile, name: string): number { + let n = 0; + ts.walkPreorder(sf, (node) => { + if ( + (ts.isVariableDeclaration(node) || + ts.isParameter(node) || + ts.isBindingElement(node) || + ts.isFunctionDeclaration(node) || + ts.isFunctionExpression(node) || + ts.isClassDeclaration(node) || + ts.isClassExpression(node)) && + node.name !== undefined && + ts.isIdentifier(node.name) && + node.name.text === name + ) + n++; + return undefined; + }); + return n; +} + +/** Applies non-overlapping edits inside [from, to): replaced spans keep + * their newlines (blanked text stays line-aligned); edits nested in a + * blanked span vanish with it. */ +function applyEdits(source: string, from: number, to: number, edits: Edit[]): string { + const inside = edits + .filter((e) => e.start >= from && e.end <= to) + .sort((a, b) => a.start - b.start || b.end - a.end); + let text = ""; + let at = from; + for (const e of inside) { + if (e.start < at) continue; // nested in an earlier edit + text += source.slice(at, e.start); + const replaced = source.slice(e.start, e.end); + // A respelled span keeps its line breaks after the replacement. + text += + e.text === "" ? replaced.replace(/[^\r\n]/g, " ") : e.text + replaced.replace(/[^\n]/g, ""); + at = e.end; + } + return text + source.slice(at, to); +} + +/** The virtual module text for one chunk, or a degrade reason. */ +function chunkSource( + sf: ts.SourceFile, + chunk: Chunk, + chunks: ReadonlyMap, + top: ReadonlyMap, + stable: (name: string) => boolean, + calledOnly: (name: string) => boolean, + scopes: BundleScopes, + callEdits: readonly Edit[], + bundleStrict: boolean, +): string | { degrade: string } { + const why = (reason: string): { degrade: string } => ({ + degrade: `its bundled CommonJS module '${chunk.name}' ${reason}`, + }); + const [exportsParam, moduleParam] = chunk.params; + const body = chunk.body; + const local = declaredNames(body); + for (const p of chunk.params) { + if ( + local.has(p) || + (p !== "exports" && local.has("exports")) || + (p !== "module" && local.has("module")) + ) + return why( + "rebinds its module parameters inside its body (an identifier-minified bundle the recognizer cannot respell)", + ); + } + + /* The factory's own frame must stay unobservable: its receiver, its + * arguments object, and a top-level return have no module equivalent. */ + let frameUse: string | null = null; + /** The innermost frame boundary between a node and the chunk body. */ + const frameOf = (n: ts.Node): "factory" | "arrow" | "function" => { + let arrow = false; + for (let p = n.parent; p !== undefined && p !== body; p = p.parent) { + if ( + ts.isFunctionDeclaration(p) || + ts.isFunctionExpression(p) || + ts.isMethodDeclaration(p) || + ts.isGetAccessorDeclaration(p) || + ts.isSetAccessorDeclaration(p) || + ts.isConstructorDeclaration(p) || + ts.isClassDeclaration(p) || + ts.isClassExpression(p) + ) + return "function"; + if (ts.isArrowFunction(p)) arrow = true; + } + return arrow ? "arrow" : "factory"; + }; + ts.walkPreorder(body, (n) => { + if (frameUse !== null) return "stop"; + if (n.kind >= ts.SyntaxKind.FirstJSDocNode && n.kind <= ts.SyntaxKind.LastJSDocNode) + return "skip"; + let use: string | null = null; + if (n.kind === ts.SyntaxKind.ThisKeyword) use = "reads its factory's receiver (`this`)"; + else if (n.kind === ts.SyntaxKind.SuperKeyword) use = "reads `super` at its top level"; + else if (ts.isIdentifier(n) && n.text === "arguments" && isReference(n)) + use = "reads its factory's arguments object"; + else if (ts.isMetaProperty(n)) use = "reads a meta property at its top level"; + else if (ts.isReturnStatement(n)) { + if (frameOf(n) === "factory") frameUse = "returns from its top level"; + return undefined; + } + if (use !== null && frameOf(n) !== "function") frameUse = use; + return undefined; + }); + if (frameUse !== null) return why(frameUse); + + const edits: Edit[] = callEdits.filter((e) => e.start >= body.pos && e.end <= body.end); + const copied = new Set(); + const copyQueue: string[] = []; + for (const r of references(body)) { + const name = r.text; + if (chunks.has(name)) continue; // a call edit already respells it + if (name === exportsParam || name === moduleParam) { + if (name === moduleParam) { + const p = r.parent; + if (!ts.isPropertyAccessExpression(p) || p.expression !== r || p.name.text !== "exports") + return why("uses its module object beyond module.exports"); + } + const global = name === exportsParam ? "exports" : "module"; + if (name !== global) { + const p = r.parent; + edits.push( + ts.isShorthandPropertyAssignment(p) + ? { start: r.getStart(sf), end: r.getEnd(), text: `${name}: ${global}` } + : { start: r.getStart(sf), end: r.getEnd(), text: global }, + ); + } + continue; + } + // The factory function itself binds the parameters and `var`s. + if (scopes.boundWithin(r, body.parent ?? body)) continue; + if (name === "exports" || name === "module") + return why(`reads the bundle's own '${name}' binding`); + if (name === "__filename") return why("reads __filename, which names the bundle file"); + if (top.has(name)) { + if (!copied.has(name)) { + copied.add(name); + copyQueue.push(name); + } + } + } + + // The pure helpers BODY reads, closed over their own top-level reads. + const header: string[] = []; + for (let i = 0; i < copyQueue.length; i++) { + const name = copyQueue[i]!; + const t = top.get(name)!; + const node = t.node; + const pure = + stable(name) && + (ts.isFunctionDeclaration(node) || + (ts.isVariableDeclaration(node) && copyableInitializer(node.initializer))); + if (!pure || chunks.has(name)) + return why(`reads the bundle's top-level binding '${name}', which it cannot carry along`); + // Each chunk gets its own copy, so a function's identity and its + // properties must stay unobservable: the bundle only ever calls it. + if (isFunctionValued(node) && (!calledOnly(name) || !selfCalledOnly(node, scopes))) + return why( + `uses the bundle's top-level function '${name}' as a value, which its chunks cannot share`, + ); + for (const r of references(node)) { + if (scopes.boundWithin(r, node) || !top.has(r.text) || copied.has(r.text)) continue; + copied.add(r.text); + copyQueue.push(r.text); + } + } + // Declaration order of the bundle: a helper reads only earlier ones. + const ordered = [...copied].sort( + (a, b) => top.get(a)!.node.getStart(sf) - top.get(b)!.node.getStart(sf), + ); + for (const name of ordered) { + const node = top.get(name)!.node; + header.push(ts.isFunctionDeclaration(node) ? node.getText(sf) : `var ${node.getText(sf)};`); + } + + const bodyStrict = strictPrologue(body.statements.nodes); + const text = applyEdits(sf.text, body.getStart(sf) + 1, body.getEnd() - 1, edits); + const prologue = bundleStrict || bodyStrict ? '"use strict";\n' : ""; + return prologue + (header.length > 0 ? header.join("\n") + "\n" : "") + text + "\n"; +} diff --git a/packages/compiler/src/frontend/npm-static-rewrite-host.ts b/packages/compiler/src/frontend/npm-static-rewrite-host.ts index ee36f58d1..524239901 100644 --- a/packages/compiler/src/frontend/npm-static-rewrite-host.ts +++ b/packages/compiler/src/frontend/npm-static-rewrite-host.ts @@ -2,7 +2,12 @@ import { dirname, join, resolve as resolvePath } from "node:path"; import { cjsLexedExportsOfFile, cjsVisibleNames } from "./cjs-syntax.js"; import type { Ts7SourceParser } from "./ts7/source-parser.js"; -import { trackedDirectoryExists, trackedFileExists, trackedReadFile } from "./input-tracker.js"; +import { + isVirtualSource, + trackedDirectoryExists, + trackedFileExists, + trackedReadFile, +} from "./input-tracker.js"; import { resolveExports } from "./resolve.js"; import { packageNameOfSpecifier } from "./workspace-registry.js"; import { isBundlerCjsCandidate, rewriteBundlerCjsSyntax } from "./npm-static-rewrite-syntax.js"; @@ -134,7 +139,7 @@ function requireTargetEsModuleStamped( } catch { /* unlexable — keep probing */ } - if (src.includes("__toCommonJS(")) return true; + if (/__toCommonJS\d*\(/.test(src)) return true; const fwd = /module\.exports\s*=\s*require\(\s*["']([^"']+)["']\s*\)/.exec(src); if (fwd !== null) return requireTargetEsModuleStamped(parser, file, fwd[1]!, depth + 1); return false; @@ -163,9 +168,12 @@ export function rewriteBundlerCjsWithParser( parser: Ts7SourceParser, source: string, filePath: string, + liveExportTable?: (filePath: string) => void, ): string | { degrade: string } | null { if (!isBundlerCjsCandidate(source)) return null; return rewriteBundlerCjsSyntax(parser.parse(filePath, source, "js"), filePath, { + liveExportTable, + requireOnly: isVirtualSource, requireTargetEsModuleStamped: (from, spec) => requireTargetEsModuleStamped(parser, from, spec), starTargetNames: (from, spec) => { const target = resolveRelativeCjs(from, spec); diff --git a/packages/compiler/src/frontend/npm-static-rewrite-syntax.ts b/packages/compiler/src/frontend/npm-static-rewrite-syntax.ts index 7938471f3..1730e1153 100644 --- a/packages/compiler/src/frontend/npm-static-rewrite-syntax.ts +++ b/packages/compiler/src/frontend/npm-static-rewrite-syntax.ts @@ -12,6 +12,70 @@ import { rewriteBundledFunctionImports } from "./npm-static-bundled-cjs.js"; export interface CjsRewriteHost { requireTargetEsModuleStamped(fromFile: string, specifier: string): boolean; starTargetNames(fromFile: string, specifier: string): Set; + /** Told when the rewritten file's tail export table stands for a live + * getter table the original installed before it could run another + * module's code (liveExportTableFirst): a module of a require cycle that + * captured the export object early observes exactly that table. */ + liveExportTable?: ((filePath: string) => void) | undefined; + /** True for a module only ever loaded through require() (a bundle's + * CommonJS chunk split into its own module): no ES-module importer + * exists, so Node's lexer-visible name set bounds nothing and every + * name the export plumbing defines is part of its surface. */ + requireOnly?: ((filePath: string) => boolean) | undefined; +} + +/** True when esbuild's export object (`module.exports = __toCommonJS(T)` + * over a getter table `__export(T, {...})`) is installed before anything + * that can run another module's code, and nothing else writes the export + * object. Every statement before the installation is then a declaration, + * a directive, or the getter-table call (whose getters run only when + * read), and variable initializers evaluate no calls outside function + * bodies. The getters read live bindings, so the object a cycle partner + * captures early answers every later read exactly like the final table. */ +function liveExportTableFirst( + sf: ts.SourceFile, + installAt: number, + target: string, + exportTables: readonly { target: string }[], +): boolean { + if (!exportTables.some((t) => t.target === target)) return false; + let callsOutsideFunctions = false; + const noCalls = (root: ts.Node): boolean => { + callsOutsideFunctions = false; + ts.walkPreorder(root, (n) => { + if (callsOutsideFunctions) return "stop"; + if (ts.isArrowFunction(n) || ts.isFunctionExpression(n) || ts.isFunctionDeclaration(n)) + return "skip"; + if (ts.isCallExpression(n) || ts.isNewExpression(n) || ts.isTaggedTemplateExpression(n)) + callsOutsideFunctions = true; + return undefined; + }); + return !callsOutsideFunctions; + }; + let sawTable = false; + for (const stmt of sf.statements.nodes) { + if (stmt.getStart(sf) >= installAt) return sawTable; + if (ts.isFunctionDeclaration(stmt) || ts.isEmptyStatement(stmt)) continue; + if (ts.isExpressionStatement(stmt) && ts.isStringLiteral(stmt.expression)) continue; + if (ts.isVariableStatement(stmt)) { + if ( + stmt.declarationList.declarations.nodes.every( + (d) => ts.isIdentifier(d.name) && (d.initializer === undefined || noCalls(d.initializer)), + ) + ) + continue; + return false; + } + if (ts.isExpressionStatement(stmt) && ts.isCallExpression(stmt.expression)) { + const table = exportCallOf(stmt.expression); + if (table !== null && table.target === target) { + sawTable = true; + continue; + } + } + return false; + } + return false; } export function isBundlerCjsCandidate(source: string): boolean { @@ -24,16 +88,31 @@ export function isBundlerCjsCandidate(source: string): boolean { ); } +/** The helper a bundler-emitted name spells. esbuild suffixes a helper + * that a bundled module declares again with a counter (a bundled esbuild + * dist inside another bundle carries `__export2`, `__toCommonJS2`, …); the + * recognizers below verify each call's shape, so the counter carries no + * meaning beyond the name collision it avoided. */ +export function bundlerHelperBase(name: string): string { + return /^__[A-Za-z]+\d+$/.test(name) ? name.replace(/\d+$/, "") : name; +} + /** The last NAME of a callee: bare identifier or any member chain's final - * name (`tslib_1.__exportStar` → "__exportStar"). */ + * name (`tslib_1.__exportStar` → "__exportStar"), helper counters dropped. */ function calleeNameOf(call: ts.CallExpression): string | null { const callee = call.expression; - if (ts.isIdentifier(callee)) return callee.text; + if (ts.isIdentifier(callee)) return bundlerHelperBase(callee.text); if (ts.isPropertyAccessExpression(callee) && ts.isIdentifier(callee.name)) - return callee.name.text; + return bundlerHelperBase(callee.name.text); return null; } +/** True for an identifier spelling the named helper (counter-suffixed + * spellings included). */ +function isHelperIdent(node: ts.Node, base: string): node is ts.Identifier { + return ts.isIdentifier(node) && bundlerHelperBase(node.text) === base; +} + /** An entry the canonical table can carry. */ interface EntrySpec { /** The export name (may need string-key spelling). */ @@ -194,13 +273,8 @@ interface ToEsmPlan { * __copyProps(…, mod) call copying onto a conditional that tests * isNodeMode/__esModule and defines "default" with value mod. Comments and * formatting are free; the SHAPE is the contract. */ -function recognizedToEsmDecl(stmt: ts.Statement): boolean { - if (!ts.isVariableStatement(stmt)) return false; - const decls = stmt.declarationList.declarations.nodes; - if (decls.length !== 1) return false; - const d = decls[0]!; - if (!ts.isIdentifier(d.name) || d.name.text !== "__toESM" || d.initializer === undefined) - return false; +function recognizedToEsmDecl(d: ts.VariableDeclaration): boolean { + if (!isHelperIdent(d.name, "__toESM") || d.initializer === undefined) return false; let init: ts.Expression = d.initializer; while (ts.isParenthesizedExpression(init)) init = init.expression; if (!ts.isArrowFunction(init)) return false; @@ -218,12 +292,7 @@ function recognizedToEsmDecl(stmt: ts.Statement): boolean { expr = expr.right; } while (ts.isParenthesizedExpression(expr)) expr = expr.expression; - if ( - !ts.isCallExpression(expr) || - !ts.isIdentifier(expr.expression) || - expr.expression.text !== "__copyProps" - ) - return false; + if (!ts.isCallExpression(expr) || !isHelperIdent(expr.expression, "__copyProps")) return false; if (expr.arguments.nodes.length < 2) return false; const from = expr.arguments.nodes[1]!; if (!ts.isIdentifier(from) || from.text !== mod) return false; @@ -234,12 +303,7 @@ function recognizedToEsmDecl(stmt: ts.Statement): boolean { if (!condText.includes(isNodeMode) || !condText.includes("__esModule")) return false; let stamp: ts.Expression = to.whenTrue; while (ts.isParenthesizedExpression(stamp)) stamp = stamp.expression; - if ( - !ts.isCallExpression(stamp) || - !ts.isIdentifier(stamp.expression) || - stamp.expression.text !== "__defProp" - ) - return false; + if (!ts.isCallExpression(stamp) || !isHelperIdent(stamp.expression, "__defProp")) return false; if (stamp.arguments.nodes.length !== 3) return false; const nameArg = stamp.arguments.nodes[1]!; if (!ts.isStringLiteral(nameArg) || nameArg.text !== "default") return false; @@ -278,19 +342,23 @@ function planToEsmInterop(sf: ts.SourceFile, filePath: string, host: CjsRewriteH degrade: reason, }); - const declStmts = sf.statements.nodes.filter( - (s) => - ts.isVariableStatement(s) && - s.declarationList.declarations.nodes.some( - (d) => ts.isIdentifier(d.name) && d.name.text === "__toESM", - ), - ); + // Top-level declarators of the helper, any statement shape (minified + // dists share one `var` among several helpers), by spelling. + const decls = new Map(); + for (const s of sf.statements.nodes) { + if (!ts.isVariableStatement(s)) continue; + for (const d of s.declarationList.declarations.nodes) { + if (!isHelperIdent(d.name, "__toESM")) continue; + const list = decls.get(d.name.text); + if (list === undefined) decls.set(d.name.text, [d]); + else list.push(d); + } + } const refs: ts.Identifier[] = []; const calls: ts.CallExpression[] = []; const collect = (n: ts.Node): void => { - if (ts.isIdentifier(n) && n.text === "__toESM") refs.push(n); - if (ts.isCallExpression(n) && ts.isIdentifier(n.expression) && n.expression.text === "__toESM") - calls.push(n); + if (isHelperIdent(n, "__toESM")) refs.push(n); + if (ts.isCallExpression(n) && isHelperIdent(n.expression, "__toESM")) calls.push(n); }; ts.walkPreorder(sf, collect); if (calls.length === 0 && refs.length === 0) return plan; @@ -298,15 +366,16 @@ function planToEsmInterop(sf: ts.SourceFile, filePath: string, host: CjsRewriteH // a helper that escapes as a VALUE is outside the recognized shape. const callees = new Set(calls.map((c) => c.expression)); const declNames = new Set(); - for (const s of declStmts) { - for (const d of (s as ts.VariableStatement).declarationList.declarations.nodes) { - if (ts.isIdentifier(d.name) && d.name.text === "__toESM") declNames.add(d.name); - } - } + for (const list of decls.values()) for (const d of list) declNames.add(d.name); if (refs.some((r) => !callees.has(r) && !declNames.has(r))) return fail(TO_ESM_ESCAPE_DEGRADE); if (calls.length === 0) return plan; // declared but never called — dead helper - if (declStmts.length !== 1 || !recognizedToEsmDecl(declStmts[0]!)) - return fail(TO_ESM_SHAPE_DEGRADE); + // Every spelling a call uses must be declared exactly once, in the + // recognized shape. + for (const call of calls) { + const list = decls.get((call.expression as ts.Identifier).text); + if (list === undefined || list.length !== 1 || !recognizedToEsmDecl(list[0]!)) + return fail(TO_ESM_SHAPE_DEGRADE); + } /** Module-scope interop bindings: name → whether `.default` IS the * module (pad the access) rather than a member read of its `default`. */ @@ -467,16 +536,20 @@ export function rewriteBundlerCjsSyntax( // ESM bundles can contain closed CommonJS function factories. Recognize // those before refusing interop that needs a dynamic module namespace. - if (ts.isExternalModule(sf)) { + // TypeScript forces every `.cjs` file to be an external module + // (isFileForcedToBeModuleByFormat), yet Node always loads one as + // CommonJS: the extension, not that flag, decides the format there. + if (!filePath.endsWith(".cjs") && ts.isExternalModule(sf)) { const bundled = rewriteBundledFunctionImports(source, sf); if (bundled !== null) return bundled; - return source.includes("__toESM(") - ? { - degrade: - "its ES-module dist routes an external dependency through the __toESM " + - "bundler-interop helper, which cannot compile natively in ESM output", - } - : null; + if (!source.includes("__toESM(")) return null; + return { + degrade: source.includes("__commonJS") + ? "its ES-module bundle reaches bundled CommonJS modules through the __commonJS and " + + "__toESM bundler-interop helpers, which cannot compile natively in ESM output" + : "its ES-module dist routes an external dependency through the __toESM " + + "bundler-interop helper, which cannot compile natively in ESM output", + }; } // The __toESM interop pass (see the section header): wrapper call sites @@ -644,6 +717,11 @@ export function rewriteBundlerCjsSyntax( } const lexed = cjsLexedExportsOfFile(sf); + if (host.requireOnly?.(filePath) === true) { + for (const t of exportTables) for (const [name] of t.entries) lexed.exports.add(name); + for (const d of definePropEntries) lexed.exports.add(d.name); + for (const m of memberStmts) for (const name of m.names) lexed.exports.add(name); + } let emitTable = sawBundleShape && (lexed.exports.size > 0 || lexed.reexports.length > 0); const starSpecs: string[] = []; @@ -814,21 +892,35 @@ export function rewriteBundlerCjsSyntax( "__reExport", "__toESM", "__toCommonJS", + "__commonJS", "__exportStar", "__createBinding", "__setModuleDefault", "__importStar", "__importDefault", ]); + /* Helper declarators by name. A minified dist shares one `var` among + * several helpers (`var __getProtoOf=…,__hasOwnProp=…;`): each + * declarator is its own candidate, and the statement pads whole only + * once every declarator in it is dead. */ const helperDecls = new Map(); + const helperStmts: ts.VariableStatement[] = []; for (const stmt of sf.statements.nodes) { if (!ts.isVariableStatement(stmt)) continue; const decls = stmt.declarationList.declarations.nodes; - if (decls.length !== 1) continue; - const d = decls[0]!; - if (ts.isIdentifier(d.name) && HELPER_NAMES.has(d.name.text)) { - helperDecls.set(d.name.text, { start: stmt.getStart(sf), end: stmt.getEnd() }); + let anyHelper = false; + for (const d of decls) { + if (!ts.isIdentifier(d.name) || !HELPER_NAMES.has(bundlerHelperBase(d.name.text))) continue; + if (helperDecls.has(d.name.text)) continue; // redeclared: keep the first, never sweep twice + anyHelper = true; + helperDecls.set( + d.name.text, + decls.length === 1 + ? { start: stmt.getStart(sf), end: stmt.getEnd() } + : { start: d.getStart(sf), end: d.getEnd() }, + ); } + if (anyHelper && decls.length > 1) helperStmts.push(stmt); } // every identifier occurrence of a helper name outside its own // declarator name node, position-recorded for the span test @@ -862,8 +954,39 @@ export function rewriteBundlerCjsSyntax( } } } + // A swept declarator of a shared statement pads with its separating + // comma, and the statement pads whole once every declarator is dead: + // padding the bare declarator span would leave a dangling comma. + const declaratorSpans = new Set<{ start: number; end: number }>(); + const declaratorPads: { start: number; end: number }[] = []; + for (const stmt of helperStmts) { + const decls = stmt.declarationList.declarations.nodes; + const deadAt = decls.map((d) => { + if (!ts.isIdentifier(d.name) || !removed.has(d.name.text)) return false; + const span = helperDecls.get(d.name.text)!; + if (span.start !== d.getStart(sf)) return false; + declaratorSpans.add(span); + return true; + }); + if (deadAt.every((x) => x)) { + declaratorPads.push({ start: stmt.getStart(sf), end: stmt.getEnd() }); + continue; + } + // Dead declarators before the last live one take their trailing + // comma; the dead tail after it pads from that declarator's end. + const lastLive = deadAt.lastIndexOf(false); + for (let i = 0; i < decls.length; i++) { + if (!deadAt[i]) continue; + const d = decls[i]!; + declaratorPads.push( + i < lastLive + ? { start: d.getStart(sf), end: decls[i + 1]!.getStart(sf) } + : { start: decls[lastLive]!.getEnd(), end: d.getEnd() }, + ); + } + } neutralize.length = 0; - neutralize.push(...dead); + neutralize.push(...dead.filter((span) => !declaratorSpans.has(span)), ...declaratorPads); } /* ── emission ─────────────────────────────────────────────────────── */ @@ -875,6 +998,15 @@ export function rewriteBundlerCjsSyntax( if (!emitTable) return text; // interop pads only — nothing appends + if ( + esbuildMainTarget !== null && + tablePos !== null && + memberStmts.length === 0 && + definePropEntries.length === 0 && + liveExportTableFirst(sf, tablePos, esbuildMainTarget, exportTables) + ) + host.liveExportTable?.(filePath); + const lines: string[] = [""]; const hasAnyFallback = [...entries.values()].some((s) => s.kind === "any"); if (hasAnyFallback) { diff --git a/packages/compiler/src/frontend/npm-static.ts b/packages/compiler/src/frontend/npm-static.ts index 3c8fa22e9..7dffd05f7 100644 --- a/packages/compiler/src/frontend/npm-static.ts +++ b/packages/compiler/src/frontend/npm-static.ts @@ -13,7 +13,13 @@ import { registerWorkspacePackage, workspacePackageOfPath, } from "./workspace-registry.js"; -import { trackedExists, trackedReadFile, trackedRealpath } from "./input-tracker.js"; +import { + clearVirtualSources, + setVirtualSource, + trackedExists, + trackedReadFile, + trackedRealpath, +} from "./input-tracker.js"; let activePackages: ReadonlySet = new Set(); let declarationOverloads: ReadonlyMap = new Map(); @@ -28,12 +34,24 @@ const offenders = new Map(); * the host probes the same file many times, and the rewrite parses. */ const rewriteCache = new Map(); +/** Rewritten files whose tail export table stands for a live getter table + * installed before the file could run another module's code (the rewrite + * certifies it; require-cycle admission consults it). */ +const liveExportFiles = new Set(); + +export function npmStaticLiveExportTable(path: string): boolean { + return liveExportFiles.has(path.split("\\").join("/")); +} + export function setNpmStaticPackages>(packages: T): void { activePackages = new Set(packages); declarationOverloads = new Map(); declarationProperties = new Map(); offenders.clear(); + fileDegrades.clear(); rewriteCache.clear(); + liveExportFiles.clear(); + clearVirtualSources(); untypedPkgCache.clear(); realpathProbed.clear(); } @@ -81,6 +99,21 @@ export function npmStaticOffenders(): ReadonlyMap { return offenders; } +/** Files whose bundler interop the rewrite could not finish this attempt, + * with their package and reason. A file refuses its package only when + * the compiled program can load it (npmStaticLoadableFiles): a package is + * judged by what its imported subpaths reach, not by every file it ships. */ +const fileDegrades = new Map(); + +export function npmStaticFileDegrades(): ReadonlyMap { + return fileDegrades; +} + +function reportFileDegrade(path: string, pkg: string, reason: string): void { + const key = path.split("\\").join("/"); + if (!fileDegrades.has(key)) fileDegrades.set(key, { pkg, reason }); +} + /** The DefinitelyTyped name mangling ("@scope/pkg" → "scope__pkg") — the * @types twin that must hide alongside the package's own declarations. */ function mangledTypesName(name: string): string { @@ -362,7 +395,18 @@ export function npmStaticFsShadow(services: FrontendServices): NpmStaticFsShadow if (hit !== undefined) return hit ?? undefined; let rewritten: string | null = null; try { - const original = trackedReadFile(path); + let original = trackedReadFile(path); + // A bundle's CommonJS chunks move to their own modules first, so + // every later pass (and this shadow, for each chunk) sees plain + // CommonJS module text. + const split = original === null ? null : services.splitCjsChunks(original, path); + if (split !== null && "degrade" in split) { + reportFileDegrade(path, target.pkg, split.degrade); + original = null; + } else if (split !== null) { + for (const chunk of split.chunks) setVirtualSource(chunk.path, chunk.source); + original = split.text; + } if (original !== null) { const namepaths = services.jsDocNamepaths(path, original); const source = namepaths ?? original; @@ -385,9 +429,10 @@ export function npmStaticFsShadow(services: FrontendServices): NpmStaticFsShadow classFields?.text ?? source, path, + (live) => liveExportFiles.add(live.split("\\").join("/")), ); if (answer !== null && typeof answer === "object") { - reportNpmStaticOffender(target.pkg, answer.degrade); + reportFileDegrade(path, target.pkg, answer.degrade); } else { rewritten = answer ?? @@ -395,7 +440,8 @@ export function npmStaticFsShadow(services: FrontendServices): NpmStaticFsShadow propertyProjected?.text ?? findWidened?.text ?? classFields?.text ?? - namepaths; + namepaths ?? + (split !== null ? original : null); } } } catch { @@ -434,6 +480,14 @@ function looksUnminified(source: string): boolean { return source.length / lines.length <= 200; } +/** A whitespace/syntax-minified bundler dist whose interop helpers keep + * their names (`var __toESM=…,__toCommonJS=mod=>…`): the recognizers + * verify those shapes and refuse the package precisely when they cannot, + * so the minification heuristic need not refuse it up front. */ +function namedBundlerHelpers(source: string): boolean { + return /\b__(?:toCommonJS|commonJS|toESM)\d*\s*=\s*(?:\(|[A-Za-z_$][\w$]*\s*=>)/.test(source); +} + function hasTransformMarkers(source: string): boolean { return TRANSFORM_MARKERS.some((m) => source.includes(m)); } @@ -456,7 +510,8 @@ export function npmStaticIneligibleReason( if (jsEntry === null) return "no runtime JS entry resolves"; const source = trackedReadFile(jsEntry); if (source === null) return `its runtime entry ${jsEntry} cannot be read`; - if (!looksUnminified(source)) return "its shipped JS looks minified"; + if (!looksUnminified(source) && !namedBundlerHelpers(source)) + return "its shipped JS looks minified"; if (hasTransformMarkers(source)) { return "its shipped JS carries build-transform markers (bundled/transpiled dist)"; } diff --git a/packages/compiler/src/frontend/pipeline.ts b/packages/compiler/src/frontend/pipeline.ts index 024c69d98..676b2bb3e 100644 --- a/packages/compiler/src/frontend/pipeline.ts +++ b/packages/compiler/src/frontend/pipeline.ts @@ -10,11 +10,13 @@ import { checkPreflight, isNodeTypesPath, locOf, + npmStaticLoadableFiles, requiresOf, resolveNpmImport, type LoadResult, } from "./program.js"; import { + npmStaticFileDegrades, npmStaticIneligibleReason, npmStaticOffenders, npmStaticPackageOfPath, @@ -458,6 +460,15 @@ function loadFrontend( const typecheckRefused = new Set(); while (effective.size > 0) { const reasons = new Map(npmStaticOffenders()); + // Bundler interop the rewrite could not finish refuses its package + // only where the compiled program can load the file. + if (npmStaticFileDegrades().size > 0) { + const loadable = npmStaticLoadableFiles(load.program, load.moduleOrder); + for (const [file, { pkg, reason }] of npmStaticFileDegrades()) { + if (!reasons.has(pkg) && (loadable === null || loadable.has(file))) + reasons.set(pkg, reason); + } + } for (const d of preflight) { const pkg = npmStaticPackageOfPath(d.loc.file); if (pkg !== null && !reasons.has(pkg)) diff --git a/packages/compiler/src/frontend/program.ts b/packages/compiler/src/frontend/program.ts index 906d901fe..192a36ef9 100644 --- a/packages/compiler/src/frontend/program.ts +++ b/packages/compiler/src/frontend/program.ts @@ -68,6 +68,7 @@ import { isNpmStaticPackage, npmStaticActive, npmStaticFsShadow, + npmStaticLiveExportTable, npmStaticPackageOfPath, reportNpmStaticOffender, setNpmStaticDeclarationOverloads, @@ -1438,6 +1439,8 @@ export function requireBindingEarlyUse7( k: number, decl: ts.VariableDeclaration, precedingDecls: readonly ts.VariableDeclaration[], + /** A reference of the binding whose early evaluation is known exact. */ + exactEarlyRead?: (reference: ts.Identifier) => boolean, ): ts.Identifier | null { const checker = program.getTypeChecker(); const bindings = new Set(); @@ -1669,6 +1672,7 @@ export function requireBindingEarlyUse7( const sym = symbolOf(n); if (sym === undefined) return undefined; if (bindings.has(sym)) { + if (exactEarlyRead?.(n) === true) return undefined; hit = n; return "stop"; } @@ -3119,6 +3123,16 @@ function cjsCycleRefusal7( // anything else can change what a module of the cycle observes. let stray: ts.Node | null = null; const settled = new Set(stmts.slice(0, firstEntry).filter(exportStatement)); + // A bundler export table the rewrite certified (npmStaticLiveExportTable) + // stands for the live getter object the original installed before its + // first require: the tail table settles as if it ran there. + const tail = stmts[stmts.length - 1]; + if ( + tail !== undefined && + npmStaticLiveExportTable(m.fileName) && + cjsExportAssignmentOf(tail)?.kind === "table" + ) + settled.add(tail); ts.walkPreorder(m, (n) => { if (stray !== null) return "stop"; if (ts.isStatement(n) && settled.has(n)) return "skip"; @@ -3171,7 +3185,9 @@ function cjsCycleRefusal7( if (req.decl === null) continue; const dep = resolveImport7(program, m, req.spec, "require"); if (dep === null || !members.has(dep)) continue; - const reached = requireBindingEarlyUse7(program, m, stmts.length, req.decl, []); + const reached = requireBindingEarlyUse7(program, m, stmts.length, req.decl, [], (id) => + functionExportRead7(id, dep), + ); if (reached !== null) { return `the top-level code of ${m.fileName} can use its binding '${reached.text}' of a module of the cycle (${lineOf(reached)}) while the cycle initializes`; } @@ -3181,6 +3197,161 @@ function cjsCycleRefusal7( return null; } +/** True when `use` initializes a top-level const whose every other + * reference is a value of an export statement (`module.exports = { … }` + * or `exports.n = …`). */ +function publishedAlias7(use: ts.Node, decl: ts.Node | undefined): boolean { + if ( + decl === undefined || + !ts.isVariableDeclaration(decl) || + decl.initializer !== use || + !ts.isIdentifier(decl.name) + ) + return false; + const list = decl.parent; + const stmt = list?.parent; + const sf = stmt?.parent; + if ( + list === undefined || + (list.flags & ts.NodeFlags.Const) === 0 || + stmt === undefined || + !ts.isVariableStatement(stmt) || + sf === undefined || + !ts.isSourceFile(sf) + ) + return false; + const alias = decl.name; + const published = new Set(); + for (const st of sf.statements.nodes) { + const cjs = cjsExportAssignmentOf(st); + if (cjs === null) continue; + if (cjs.kind === "member") published.add(cjs.value); + else if (cjs.obj !== null) { + for (const p of cjs.obj.properties.nodes) { + if (ts.isPropertyAssignment(p) && !ts.isComputedPropertyName(p.name)) + published.add(p.initializer); + else if (ts.isShorthandPropertyAssignment(p)) published.add(p.name); + } + } + } + let kept = false; + ts.walkPreorder(sf, (n) => { + if (kept) return "stop"; + if (!ts.isIdentifier(n) || n === alias || n.text !== alias.text) return undefined; + const p = n.parent; + if ( + p !== undefined && + ((ts.isPropertyAccessExpression(p) && p.name === n) || + (ts.isPropertyAssignment(p) && p.name === n)) + ) + return undefined; + if (!published.has(n)) kept = true; + return undefined; + }); + return !kept; +} + +/** True for a plain read `binding.name` (not a call, not a write, not a + * value that is stored or passed on) of the cycle member `dep` whose + * export `name` is a top-level function declaration nothing reassigns. + * Every member settles its exports before entering the cycle, so Node + * reads that same function whenever the read runs, and the compiled read + * resolves to the declaration itself. */ +function functionExportRead7(id: ts.Identifier, dep: ts.SourceFile): boolean { + const access = id.parent; + if (!ts.isPropertyAccessExpression(access) || access.expression !== id) return false; + // The value the read yields: through parentheses and the right operand + // of a comma, so `(0, binding.name)(...)` is the call it spells. + let use: ts.Node = access; + for (;;) { + const p = use.parent; + if (ts.isParenthesizedExpression(p)) use = p; + else if ( + ts.isBinaryExpression(p) && + p.operatorToken.kind === ts.SyntaxKind.CommaToken && + p.right === use + ) + use = p; + else break; + } + const parent = use.parent; + // Only a use that neither runs the function nor keeps it is a read: a + // stored alias (`const f = binding.name`, `{ f: binding.name }`) or an + // argument can be called while the cycle initializes. A top-level const + // that only publishes it (`module.exports = { name: f }`, the static + // rewrite's re-export spelling) runs nothing: a module that reads the + // export back checks that read itself. + const observes = + publishedAlias7(use, parent) || + ts.isExpressionStatement(parent) || + ts.isTypeOfExpression(parent) || + ts.isVoidExpression(parent) || + (ts.isPrefixUnaryExpression(parent) && parent.operator === ts.SyntaxKind.ExclamationToken) || + (ts.isBinaryExpression(parent) && + (parent.operatorToken.kind === ts.SyntaxKind.EqualsEqualsEqualsToken || + parent.operatorToken.kind === ts.SyntaxKind.ExclamationEqualsEqualsToken || + parent.operatorToken.kind === ts.SyntaxKind.EqualsEqualsToken || + parent.operatorToken.kind === ts.SyntaxKind.ExclamationEqualsToken)) || + ((ts.isIfStatement(parent) || ts.isWhileStatement(parent) || ts.isDoStatement(parent)) && + parent.expression === use) || + (ts.isConditionalExpression(parent) && parent.condition === use); + if (!observes) return false; + const name = access.name.text; + // The exported value's identifier: a table entry or a member export. + const values: ts.Expression[] = []; + for (const stmt of dep.statements.nodes) { + if (!ts.isExpressionStatement(stmt) || !ts.isBinaryExpression(stmt.expression)) continue; + const e = stmt.expression; + if (e.operatorToken.kind !== ts.SyntaxKind.EqualsToken) continue; + if (isModuleExportsAccess(e.left)) { + if (!ts.isObjectLiteralExpression(e.right)) return false; + for (const p of e.right.properties.nodes) { + if (ts.isSpreadAssignment(p)) return false; + if (ts.isShorthandPropertyAssignment(p) && p.name.text === name) + values.push(p.name as ts.Identifier); + else if ( + ts.isPropertyAssignment(p) && + (ts.isIdentifier(p.name) || ts.isStringLiteral(p.name)) && + p.name.text === name + ) + values.push(p.initializer); + } + } else if ( + ts.isPropertyAccessExpression(e.left) && + e.left.name.text === name && + ((ts.isIdentifier(e.left.expression) && e.left.expression.text === "exports") || + isModuleExportsAccess(e.left.expression)) + ) { + values.push(e.right); + } + } + const value = values.length === 1 ? values[0]! : null; + if (value === null || !ts.isIdentifier(value)) return false; + const fnName = value.text; + if ( + !dep.statements.nodes.some( + (st) => ts.isFunctionDeclaration(st) && st.name?.text === fnName && st.body !== undefined, + ) + ) + return false; + let rebound = false; + ts.walkPreorder(dep, (n) => { + if (rebound) return "stop"; + if ( + (ts.isBinaryExpression(n) && + ts.isIdentifier(n.left) && + n.left.text === fnName && + n.operatorToken.kind >= ts.SyntaxKind.FirstAssignment && + n.operatorToken.kind <= ts.SyntaxKind.LastAssignment) || + (ts.isVariableDeclaration(n) && ts.isIdentifier(n.name) && n.name.text === fnName) || + (ts.isFunctionDeclaration(n) && n.name?.text === fnName && !ts.isSourceFile(n.parent)) + ) + rebound = true; + return undefined; + }); + return !rebound; +} + /* ── exact cyclic initialization ───────────────────────────────────────── * Cycles that fail the declaration-only rule still evaluate exactly like * Node when their shape is representable: the guarded %init calls already @@ -5930,6 +6101,58 @@ export function orderedImportsOf( return out; } +/** The files a compiled program can load: the module order closed over + * every literal require() and import() call it contains (deferred and + * nested ones included) and, for opted-in package JavaScript, over every + * import and export declaration as well. A package file outside this set + * is reachable only through types, so whatever it spells cannot affect + * the compiled program. Null when a reachable file loads a computed + * specifier: then any file may load. */ +export function npmStaticLoadableFiles( + program: ts.Program, + moduleOrder: readonly ts.SourceFile[], +): Set | null { + const seen = new Set(); + const work: ts.SourceFile[] = []; + const add = (sf: ts.SourceFile | null | undefined): void => { + if (sf === null || sf === undefined || seen.has(sf) || sf.isDeclarationFile) return; + seen.add(sf); + work.push(sf); + }; + for (const sf of moduleOrder) add(sf); + let computed = false; + while (work.length > 0 && !computed) { + const sf = work.pop()!; + const packageJs = + npmStaticPackageOfPath(sf.fileName) !== null && isJsSourceFileName(sf.fileName); + const follow = (spec: string, kind: "import" | "require"): void => { + if (canonicalBuiltinModule(spec) !== null) return; + add(resolveImport7(program, sf, spec, kind) ?? npmStaticDepSf7(program, sf, spec, kind)); + }; + ts.walkPreorder(sf, (n) => { + if (computed) return "stop"; + if (ts.isCallExpression(n)) { + const isImport = n.expression.kind === ts.SyntaxKind.ImportKeyword; + const isRequire = ts.isIdentifier(n.expression) && n.expression.text === "require"; + if (!isImport && !isRequire) return undefined; + const arg = n.arguments.nodes[0]; + if (arg !== undefined && ts.isStringLiteralLike(arg)) + follow(arg.text, isImport ? "import" : "require"); + else if (packageJs || isImport) computed = true; + } else if ( + packageJs && + (ts.isImportDeclaration(n) || ts.isExportDeclaration(n)) && + n.moduleSpecifier !== undefined && + ts.isStringLiteral(n.moduleSpecifier) + ) { + follow(n.moduleSpecifier.text, "import"); + } + return undefined; + }); + } + return computed ? null : new Set([...seen].map((sf) => sf.fileName.split("\\").join("/"))); +} + /** The program source file a bare specifier reaches when it names an * opted-in --npm-static package (the shipped-JS entry preflight admitted * as a module edge), else null. No offender reporting here — preflight diff --git a/packages/compiler/src/frontend/services.ts b/packages/compiler/src/frontend/services.ts index ca4dd2b36..c6bb24ae5 100644 --- a/packages/compiler/src/frontend/services.ts +++ b/packages/compiler/src/frontend/services.ts @@ -2,6 +2,11 @@ import { Ts7SourceParser, type Ts7SourceKind } from "./ts7/source-parser.js"; import { Ts7Host, type Ts7ApiFactory, type Ts7HostOptions } from "./ts7/program-host.js"; import { rewriteBundlerCjsWithParser } from "./npm-static-rewrite-host.js"; import { isBundlerCjsCandidate } from "./npm-static-rewrite-syntax.js"; +import { + mayBundleChunks, + splitBundledChunks, + type BundledChunkSplit, +} from "./npm-static-bundled-chunks.js"; import * as syntax from "./npm-static-declaration-syntax.js"; import type { SourceFile } from "./ts7/ast-types.js"; @@ -55,10 +60,21 @@ export class FrontendServices { this.ensureOpen(); return new Ts7Host(this.createApi, { ...options, cwd: options?.cwd ?? this.cwd }); } - rewriteCjs(source: string, path: string): string | { degrade: string } | null { + rewriteCjs( + source: string, + path: string, + liveExportTable?: (path: string) => void, + ): string | { degrade: string } | null { this.ensureOpen(); if (!isBundlerCjsCandidate(source)) return null; - return rewriteBundlerCjsWithParser(this.sourceParser(), source, path); + return rewriteBundlerCjsWithParser(this.sourceParser(), source, path, liveExportTable); + } + /** A CommonJS bundle's __commonJS chunks as their own modules (null: no + * chunks; a degrade reason: chunks the recognizer cannot move). */ + splitCjsChunks(source: string, path: string): BundledChunkSplit | { degrade: string } | null { + this.ensureOpen(); + if (!mayBundleChunks(source)) return null; + return splitBundledChunks(this.parse(path, source, "js"), path); } nullableClassFields(path: string, source: string): syntax.NpmStaticOverloadRewrite | null { return syntax.applyNpmStaticNullableClassFields(this.parse(path, source, "js"), source); diff --git a/packages/compiler/src/frontend/type-mapper.ts b/packages/compiler/src/frontend/type-mapper.ts index b6447454b..bd6723dfc 100644 --- a/packages/compiler/src/frontend/type-mapper.ts +++ b/packages/compiler/src/frontend/type-mapper.ts @@ -6,6 +6,7 @@ import type { IrRecordShape, IrType, IrUnionDef, IrUnionDiscriminant } from "../ import { BYTES_ELEMENT_NAME, arrayOf, + isDynTypedRefType, BOOL, bytesOf, canConvertToDyn, @@ -125,6 +126,14 @@ export function jsOpenObjectType( : type; } +/** The modules whose RequestOptions interface maps to the checked dynamic. */ +const HTTP_REQUEST_OPTION_MODULES: ReadonlySet = new Set([ + "http", + "node:http", + "https", + "node:https", +]); + /** The WebCrypto interfaces whose values are checked-dynamic: the crypto * global, SubtleCrypto, CryptoKey handles and the algorithm dictionaries * (scr_webcrypto.c). */ @@ -325,7 +334,7 @@ export class ShapeRegistry { ) { return; } - shape.declaredOrder = declaredOrder; + shape.declaredOrder = this.withExtendedNames(shape.id, declaredOrder); this.declaredOrderPriority.set(shape.id, this.currentDeclaredOrderPriority); } @@ -341,7 +350,7 @@ export class ShapeRegistry { } const shape = this.byId.get(shapeId); if (!shape) continue; - shape.declaredOrder = best.order; + shape.declaredOrder = this.withExtendedNames(shapeId, best.order); this.declaredOrderPriority.set(shapeId, best.priority); } for (const finalize of this.derivedDeclaredOrderFinalizers) finalize(); @@ -374,7 +383,7 @@ export class ShapeRegistry { // still obeys first-writer-wins. if (shape?.declaredOrder !== originalOrder) return; const next = order(); - if (next !== undefined) shape.declaredOrder = next; + if (next !== undefined) shape.declaredOrder = this.withExtendedNames(shape.id, next); }; } @@ -482,6 +491,53 @@ export class ShapeRegistry { get(shapeId: string): IrRecordShape | undefined { return this.byId.get(shapeId); } + + /** Names extendShape added to a shape, which every key-order table the + * shape adopts lists after its declared names. */ + private readonly extendedNames = new Map(); + + /** The members extendShape attached to a shape (empty for most). */ + attachedNames(shapeId: string): readonly string[] { + return this.extendedNames.get(shapeId) ?? []; + } + + private withExtendedNames(shapeId: string, order: string[]): string[] { + const extra = this.extendedNames.get(shapeId); + if (!extra) return order; + const missing = extra.filter((name) => !order.includes(name)); + return missing.length === 0 ? order : [...order, ...missing]; + } + + /** Adds optional fields to an interned shape in place: members a type + * assertion attaches to the shape's values (`r as R & { tag: string }` + * followed by `view.tag = ...`; see assertion-members.ts). Every value + * of the shape holds them absent until a write through such a view, so + * the shape keeps its id, both structural keys resolve to it, and its + * key-order table lists the new names after the declared ones. Runs + * before any body lowers; existing layouts change, so the revision + * advances like a closing recursive placeholder. */ + extendShape(shapeId: string, extra: readonly { name: string; type: IrType }[]): void { + const shape = this.byId.get(shapeId); + if (!shape || shape.tuple || extra.length === 0) return; + const fresh = extra.filter((f) => !shape.fields.some((x) => x.name === f.name)); + if (fresh.length === 0) return; + shape.fields = [...shape.fields, ...fresh].sort((a, b) => + a.name < b.name ? -1 : a.name > b.name ? 1 : 0, + ); + this.extendedNames.set(shapeId, [ + ...(this.extendedNames.get(shapeId) ?? []), + ...fresh.map((f) => f.name), + ]); + if (shape.declaredOrder) + shape.declaredOrder = this.withExtendedNames(shapeId, shape.declaredOrder); + const candidates = this.declaredOrderCandidates.get(shapeId); + if (candidates) { + for (const c of candidates) c.order = this.withExtendedNames(shapeId, c.order); + } + this.revision++; + const key = this.keyOf(shape.fields, false, shape.indexValue); + if (!this.byKey.has(key)) this.byKey.set(key, shapeId); + } } /** The frontend's union interner — mirrors ShapeRegistry. A union's @@ -1543,6 +1599,10 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { contextResolutions++; return DYN; } + // Readonly over a program class (Object.freeze's result type) is the + // same instance. + const readonlyClass = readonlyClassType(type, ctx); + if (readonlyClass !== null) return readonlyClass; if (resolveTypeParam && type.flags & ts.TypeFlags.TypeParameter) { const bound = resolveTypeParam(type); if (bound) { @@ -2461,6 +2521,21 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // spawn consumes a presence-sensitive option bag, including optional // stdio and env values. Keep it checked-dynamic for native normalization. if (isStdlibInterface("SpawnOptions")) return DYN; + // http.RequestOptions / https.RequestOptions as a declared type: an + // option record whose members include functions, agents and signals. + // Values ride checked-dynamic storage, like SpawnOptions. + if ( + psym?.name === "RequestOptions" && + psym.declarations.some((d) => { + if (!ts.isInterfaceDeclaration(d) || !ctx.isStdlibFile(d.getSourceFile())) return false; + for (let p: ts.Node | undefined = d.parent; p !== undefined; p = p.parent) { + if (ts.isModuleDeclaration(p) && ts.isStringLiteral(p.name)) + return HTTP_REQUEST_OPTION_MODULES.has(p.name.text); + } + return false; + }) + ) + return DYN; // exactly like npm-declared types. Static fetch has one deliberately // narrower native representation: Response is an opaque checked- @@ -4388,10 +4463,13 @@ function mapCallSignature(sig: ts.Signature, ctx: TypeMapperCtx): IrType | null // params). Declaration files keep their existing paths: library // callback slots such as String#replace's `(substring, ...args: any[])` // have intrinsic lowerings that inspect the unmapped type. + // node:test's default mock function type, `(...args: any[]) => + // undefined` (the type parameter default of mock.fn), takes the same + // ABI: mock.fn() without an original records whatever its callers pass. if ( rest && decl !== undefined && - !decl.getSourceFile().isDeclarationFile && + (!decl.getSourceFile().isDeclarationFile || isNodeTestDefaultFunctionParam(decl, ctx)) && (pt?.kind === "dyn" || (!pt && isAnyArrayType(checker.getTypeOfSymbol(p), checker))) ) { dynRest = true; @@ -5347,7 +5425,8 @@ function inferSymbolOf(t: ts.Type, _ctx: TypeMapperCtx): ts.Symbol | undefined { /** `checked extends (...args: any) => infer R` — the shape of the standard * library's ReturnType, over a concrete function type: R binds to its * result. Parameters must be `any` (they constrain nothing); anything else - * stays undecided. A primitive never matches a function type. */ + * stays undecided. A primitive never matches a function type, and neither + * does a function that needs more arguments than fixed parameters pass. */ function matchInferSignature( checked: ts.Type, extendsType: ts.Type, @@ -5370,12 +5449,41 @@ function matchInferSignature( const gotSigs = checker.getCallSignatures(checked); const got = gotSigs[0]; if (gotSigs.length !== 1 || !got || got.getTypeParameters().length > 0) return undefined; + // A pattern with fixed parameters admits only functions that need no + // more arguments than it passes: `(a: any) => infer R` rejects + // `(a: string, b: string) => R`, as TypeScript's assignability does. + const wantArity = signatureArity(want); + const gotArity = signatureArity(got); + if ( + wantArity !== undefined && + gotArity !== undefined && + !wantArity.rest && + gotArity.required > want.getParameters().length + ) + return false; const wantRet = checker.getReturnTypeOfSignature(want); const infer = inferSymbolOf(wantRet, ctx); if (infer) return new Map([[infer, checker.getReturnTypeOfSignature(got)]]); return wantRet.flags & ts.TypeFlags.Any ? new Map() : undefined; } +/** How many arguments a call of `sig` must pass (up to its last required + * parameter), and whether it takes a rest parameter. Undefined when a + * parameter has no declaration to read this from; the arity check is then + * skipped. */ +function signatureArity(sig: ts.Signature): { required: number; rest: boolean } | undefined { + let required = 0; + let rest = false; + const params = sig.getParameters(); + for (let i = 0; i < params.length; i++) { + const decl = params[i]!.valueDeclaration; + if (decl === undefined || !ts.isParameter(decl)) return undefined; + if (decl.dotDotDotToken !== undefined) rest = true; + else if (decl.questionToken === undefined && decl.initializer === undefined) required = i + 1; + } + return { required, rest }; +} + /** `checked extends PromiseLike` (or `Promise`) over the * standard library's Promise: U binds to the settled type. An object with * no `then` member is no thenable. Everything else stays undecided. */ @@ -5420,6 +5528,19 @@ function mentionsMappedParams( return false; } +function readonlyClassType(type: ts.Type, ctx: TypeMapperCtx): IrType | null { + const alias = type.getAliasSymbol(); + if (alias?.name !== "Readonly") return null; + const args = type.getAliasTypeArguments(); + if (args.length !== 1 || !args[0]) return null; + const isLibAlias = alias.declarations.some( + (d) => ts.isTypeAliasDeclaration(d) && ctx.isStdlibFile(d.getSourceFile()), + ); + if (!isLibAlias) return null; + const mapped = mapType(args[0], ctx); + return mapped !== null && isDynTypedRefType(mapped) ? mapped : null; +} + function mapGenericUtilityAlias(widened: ts.Type, ctx: TypeMapperCtx): IrType | null { const { resolveTypeParam, shapes, unions } = ctx; if (!resolveTypeParam) return null; @@ -6698,6 +6819,23 @@ function dynSubsumableUnionArm(arm: IrType, ctx: TypeMapperCtx): boolean { } } +/** A parameter of a function type that is the default of a type parameter + * declared in node:test's standard-library declarations (MockTracker.fn's + * `F extends Function = (...args: any[]) => undefined`). */ +function isNodeTestDefaultFunctionParam(decl: ts.Declaration, ctx: TypeMapperCtx): boolean { + const fnType = decl.parent; + return ( + ts.isParameter(decl) && + fnType !== undefined && + ts.isFunctionTypeNode(fnType) && + fnType.parent !== undefined && + ts.isTypeParameterDeclaration(fnType.parent) && + fnType.parent.defaultType === fnType && + ctx.isStdlibFile(decl.getSourceFile()) && + isDeclaredInAmbientModule(decl, "test") + ); +} + /** `any[]` (or `readonly any[]`): an array type whose element is `any`. */ export function isAnyArrayType(t: ts.Type, checker: ts.TypeChecker): boolean { if (!checker.isArrayType(t)) return false; diff --git a/packages/compiler/src/ir/builtin-effects.ts b/packages/compiler/src/ir/builtin-effects.ts index 14bf8760d..03fa5c6fd 100644 --- a/packages/compiler/src/ir/builtin-effects.ts +++ b/packages/compiler/src/ir/builtin-effects.ts @@ -346,6 +346,7 @@ export const MAY_THROW_LIB_FNS: ReadonlySet = new Set([ "process.kill", "process.killNum", "process.setExitCode", + "process.setExitCodeTracked", // Node's argument grammar throws ERR_INVALID_ARG_TYPE at the call. "process.emitWarning", // cpuUsage(prev)'s field validation: negative/non-finite prev fields @@ -600,6 +601,9 @@ export const MAY_THROW_LIB_FNS: ReadonlySet = new Set([ "dyn.mapSeedEntry", "dyn.freeze", "dyn.isFrozen", + "class.defineField", + "class.throwReadOnly", + "class.throwNotExtensible", "dyn.toString", "dyn.valueOf", "string.fromCodePoint", diff --git a/packages/compiler/src/ir/builtin-signatures.ts b/packages/compiler/src/ir/builtin-signatures.ts index 36b2bedc8..b807c7b42 100644 --- a/packages/compiler/src/ir/builtin-signatures.ts +++ b/packages/compiler/src/ir/builtin-signatures.ts @@ -206,6 +206,23 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ "dyn.in": { argTypes: [DYN, STRING], result: BOOL }, "dyn.freeze": { argTypes: [DYN], result: DYN }, "dyn.isFrozen": { argTypes: [DYN], result: BOOL }, + /** Object.defineProperty on a declared class field (scr_json.c): the + * field's current value (borrowed), its FIELD_* attribute word, the + * instance's CLASS_* integrity word, the field name and the descriptor. + * Validates as Node does (TypeError "Cannot redefine property: f") and + * answers a one-property holder of the defined value and attributes (+1), + * read back with class.definedBits and class.definedValue. */ + "class.defineField": { argTypes: [DYN, F64, F64, STRING, DYN], result: DYN }, + "class.definedBits": { argTypes: [DYN, STRING], result: F64 }, + "class.definedValue": { argTypes: [DYN, STRING], result: DYN }, + /** Whether a property descriptor has a `value` member. */ + "class.descriptorHasValue": { argTypes: [DYN], result: BOOL }, + /** Throws Node's TypeError for a write to a read-only class field: the + * field name and the instance's class name. */ + "class.throwReadOnly": { argTypes: [STRING, STRING], result: VOID }, + /** Throws Node's TypeError for a class field initializer on a + * non-extensible instance: the field name. */ + "class.throwNotExtensible": { argTypes: [STRING], result: VOID }, "dyn.nativeSetNew": { argTypes: [DYN], result: DYN }, "dyn.nativeSetIs": { argTypes: [DYN], result: BOOL }, "dyn.nativeMapIs": { argTypes: [DYN], result: BOOL }, @@ -2405,6 +2422,9 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ /** Rest listeners use the checked function bridge to pack event arguments. */ "child.onDyn": { argTypes: [CHILD_T, STRING, DYN], result: VOID }, "process.connected": { argTypes: [], result: BOOL }, + /** Whether this fork child opened its channel; part of process.send's + * presence in programs with fork targets. Never throws. */ + "process.ipcOpen": { argTypes: [], result: BOOL }, "process.send": { argTypes: [STRING], result: BOOL }, "process.sendCb": { argTypes: [STRING, null], result: BOOL }, "process.disconnect": { argTypes: [], result: VOID }, @@ -3039,8 +3059,13 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ "process.exit": { argTypes: [F64], result: VOID }, /** Numeric process.exitCode write (integer validation, implicit exit status). */ "process.setExitCode": { argTypes: [F64], result: VOID }, + /** The process.exitCode write of a program that also reads the + * property: the same validation, and the code counts as assigned. */ + "process.setExitCodeTracked": { argTypes: [F64], result: VOID }, /** The code for process.exit() with no argument, or zero when unset. */ "process.currentExitCode": { argTypes: [], result: F64 }, + /** Whether process.exitCode reads a code rather than undefined. Never throws. */ + "process.hasExitCode": { argTypes: [], result: BOOL }, "process.cwd": { argTypes: [], result: STRING }, /** getpid(2) / getuid(2): zero args → f64. POSIX-only target, so both * always answer (the checker's `getuid?` optionality covers Windows — @@ -3262,6 +3287,7 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ /** SameValue over checked values, including NaN, signed zero and reference identity. */ "dyn.sameValue": { argTypes: [DYN, DYN], result: BOOL }, "error.stack": { argTypes: [null], result: STRING }, + "error.setStack": { argTypes: [null, STRING], result: VOID }, "error.stackLimitGet": { argTypes: [], result: F64 }, "error.stackLimitSet": { argTypes: [F64], result: VOID }, // Receiver (any error-hierarchy object) and the program-dependent diff --git a/packages/compiler/src/ir/ir.ts b/packages/compiler/src/ir/ir.ts index 9a8f1121c..d6ff0fe72 100644 --- a/packages/compiler/src/ir/ir.ts +++ b/packages/compiler/src/ir/ir.ts @@ -1932,8 +1932,23 @@ export type IrStmt = | { kind: "return"; value: IrExpr | null; awaited?: true; loc: SrcLoc } /** Field write `obj.f = v` — statement-only, like `assign`/`arraySet`. * Evaluation order: obj, then value. The old value is released; ownership - * of a refcounted new value MOVES into the object. */ - | { kind: "fieldSet"; obj: IrExpr; className: string; field: string; value: IrExpr; loc: SrcLoc } + * of a refcounted new value MOVES into the object. `sloppy` marks a write + * in sloppy-mode code, which skips a read-only field instead of throwing. + * `define` marks a class field initializer, which defines the property + * (a non-extensible instance rejects it). `attributesChecked` marks a + * write that already honors the instance's property attributes + * (CLASS_ATTRIBUTES). */ + | { + kind: "fieldSet"; + obj: IrExpr; + className: string; + field: string; + value: IrExpr; + sloppy?: true; + define?: true; + attributesChecked?: true; + loc: SrcLoc; + } /** Record field write `r.f = v` — mirrors `fieldSet` exactly (evaluation * order obj then value; old value released; refcounted new value moved * in), with a shape id in place of a class name. */ @@ -4078,6 +4093,27 @@ export function isClassOwnEnumerableFieldName(name: string): boolean { /** Compiler-owned storage for properties added through untyped references. */ export const DYN_CLASS_PROPERTIES = "%dynProperties"; +/** Compiler-owned integrity state of a class instance whose hierarchy is + * passed to Object.freeze, Object.seal, Object.preventExtensions or + * Object.defineProperty: an f64 holding CLASS_NOT_EXTENSIBLE, CLASS_SEALED + * and CLASS_FROZEN bits. Other hierarchies have no such field. */ +export const CLASS_ATTRIBUTES = "%attributes"; +export const CLASS_NOT_EXTENSIBLE = 1; +export const CLASS_SEALED = 2; +export const CLASS_FROZEN = 4; + +/** Compiler-owned attributes of one declared field that Object.defineProperty + * changes: an f64 holding FIELD_NOT_WRITABLE, FIELD_NOT_ENUMERABLE and + * FIELD_NOT_CONFIGURABLE bits (zero is an ordinary data property). */ +export const CLASS_FIELD_ATTRIBUTES_PREFIX = "%attr:"; +export const FIELD_NOT_WRITABLE = 1; +export const FIELD_NOT_ENUMERABLE = 2; +export const FIELD_NOT_CONFIGURABLE = 4; + +export function classFieldAttributesName(field: string): string { + return CLASS_FIELD_ATTRIBUTES_PREFIX + field; +} + /** A class capsule can always preserve its exact native identity. Its * optional property view additionally needs converters in both directions; * fields such as Maps may remain opaque without preventing the round trip. */ diff --git a/packages/compiler/src/ir/runtime-features.ts b/packages/compiler/src/ir/runtime-features.ts index 69656576b..4ba157d74 100644 --- a/packages/compiler/src/ir/runtime-features.ts +++ b/packages/compiler/src/ir/runtime-features.ts @@ -211,6 +211,7 @@ const CHILD_PROCESS_LIB_FNS: ReadonlySet = new Set([ "process.connected", "process.disconnect", "process.forkTarget", + "process.ipcOpen", "process.onDisconnect", "process.onMessage", "process.send", diff --git a/packages/compiler/src/ir/validate.ts b/packages/compiler/src/ir/validate.ts index 1c069531f..ec37ddcf8 100644 --- a/packages/compiler/src/ir/validate.ts +++ b/packages/compiler/src/ir/validate.ts @@ -4109,6 +4109,7 @@ function validateFunction( if (checkLibEmitterCall(e, sig.result)) return; if ( e.fn === "error.stack" || + e.fn === "error.setStack" || e.fn === "error.cause" || e.fn === "error.hasCause" || e.fn === "error.setCause" || diff --git a/packages/compiler/surface-manifest.json b/packages/compiler/surface-manifest.json index 41fef3232..1fc595f5f 100644 --- a/packages/compiler/surface-manifest.json +++ b/packages/compiler/surface-manifest.json @@ -2002,7 +2002,7 @@ "kind": "node-builtin", "name": "process.exitCode", "status": "static", - "note": "numeric writes in statement position set the implicit exit status; process.exit() reads it" + "note": "numeric writes in statement position set the implicit exit status; reads answer the assigned code or undefined; process.exit() reads it" }, { "id": "node-builtin.process.getBuiltinModule", @@ -2546,7 +2546,7 @@ "name": "util.promisify", "status": "unsupported", "code": "SC2020", - "note": "the lowered shapes are const bindings over child_process.execFile, child_process.exec, setTimeout, setImmediate, stream.finished, stream.pipeline, the zlib callback one-shots, the crypto callback functions, dns.lookup, dns.lookupService and the fs callback functions with an fs/promises counterpart; call the bound function directly" + "note": "the lowered shapes are const bindings over child_process.execFile, child_process.exec, setTimeout, setImmediate, stream.finished, stream.pipeline, the zlib callback one-shots, the crypto callback functions, dns.lookup, dns.lookupService and the fs callback functions with an fs/promises counterpart, plus typed callback-last function values whose callback takes (error?: Error | null, value?); call the bound function directly" }, { "id": "node-builtin.util.stripVTControlCharacters", diff --git a/packages/compiler/test/class-attributes.test.ts b/packages/compiler/test/class-attributes.test.ts new file mode 100644 index 000000000..1a6fea654 --- /dev/null +++ b/packages/compiler/test/class-attributes.test.ts @@ -0,0 +1,84 @@ +import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { expect, test } from "vitest"; +import { compile, deserializeModule } from "../src/index.js"; +import type { IrModule } from "../src/ir/ir.js"; + +async function lowerModule(source: string, file = "main.ts"): Promise { + const dir = await mkdtemp(join(tmpdir(), "scriptc-class-attributes-")); + try { + const entry = join(dir, file); + const outPath = join(dir, "main.ir.json"); + await writeFile(entry, source); + const result = await compile(entry, { outDir: dir, outPath, outputKind: "ir" }); + if (!result.ok) + throw new Error(result.diagnostics.map((d) => `${d.code}: ${d.message}`).join("\n")); + return deserializeModule(await readFile(outPath, "utf8")); + } finally { + await rm(dir, { recursive: true, force: true }); + } +} + +function fieldsOf(mod: IrModule, className: string): string[] { + const cls = (mod.classes ?? []).find( + (c) => c.name === className || c.name.endsWith(`.${className}`), + ); + if (!cls) throw new Error(`no class ${className}`); + return cls.fields.map((f) => f.name); +} + +function functionsCalling(mod: IrModule, fn: string): string[] { + return mod.functions + .filter((f) => JSON.stringify(f.body).includes(`"fn":"${fn}"`)) + .map((f) => f.name); +} + +test("only hierarchies passed to an integrity call get attribute state and write checks", async () => { + const mod = await lowerModule(` +export {}; +class Point { x = 1; } +class Other { x = 1; } +function movePoint(p: Point): void { p.x = 2; } +function moveOther(o: Other): void { o.x = 2; } +const p = new Point(); +Object.freeze(p); +movePoint(p); +moveOther(new Other()); +`); + expect(fieldsOf(mod, "Point")).toContain("%attributes"); + expect(fieldsOf(mod, "Other")).not.toContain("%attributes"); + const checked = functionsCalling(mod, "class.throwReadOnly"); + expect(checked).toContain("movePoint"); + expect(checked).not.toContain("moveOther"); +}); + +test("a redefined field gets its own attribute word", async () => { + const mod = await lowerModule(` +export {}; +class Config { mode = "dev"; level = 1; } +const c = new Config(); +Object.defineProperty(c, "mode", { writable: false }); +`); + const fields = fieldsOf(mod, "Config"); + expect(fields).toContain("%attributes"); + expect(fields).toContain("%attr:mode"); + expect(fields).not.toContain("%attr:level"); +}); + +test("sloppy-mode writes skip a read-only field without throwing", async () => { + const mod = await lowerModule( + ` +class Box { constructor() { this.x = 1; } } +const b = new Box(); +Object.freeze(b); +b.x = 2; +`, + "main.cjs", + ); + expect(fieldsOf(mod, "Box")).toContain("%attributes"); + // Class bodies are strict mode code; the top-level write is not. + const throwing = functionsCalling(mod, "class.throwReadOnly"); + expect(throwing.length).toBeGreaterThan(0); + expect(throwing.every((name) => name.includes("constructor"))).toBe(true); +}); diff --git a/packages/compiler/test/native-class-descriptors.test.ts b/packages/compiler/test/native-class-descriptors.test.ts index d4aff3949..bd0a06dac 100644 --- a/packages/compiler/test/native-class-descriptors.test.ts +++ b/packages/compiler/test/native-class-descriptors.test.ts @@ -12,7 +12,7 @@ function fixture(source: string) { } test.each([ - "Object.defineProperty(value, 'x', {value: 2, writable: false});", + "Object.defineProperty(value, 'x', {get() { return 2; }});", "Object.defineProperties(value, {x: {value: 2}});", ])("keeps unsafe native descriptor changes fenced: %s", (operation) => { const { dir, entry } = fixture( @@ -30,6 +30,42 @@ test.each([ } }); +test("data descriptor changes on native fields match Node", async () => { + const { dir, entry } = fixture(` +class Value { constructor() { this.x = 1; } } +const value = new Value(); +Object.defineProperty(value, 'x', { value: 2, writable: false }); +console.log(value.x, JSON.stringify(Object.getOwnPropertyDescriptor(value, 'x'))); +value.x = 3; +console.log(value.x, Object.keys(value)); +`); + try { + const result = await compile(entry, { + outDir: dir, + outPath: join(dir, "program"), + sanitize: process.env["SCRIPTC_SAN"] === "1", + }); + if (!result.ok) throw new Error(JSON.stringify(result.diagnostics)); + const run = spawnSync(result.binaryPath, { encoding: "utf8", timeout: 30_000 }); + const oracle = spawnSync(process.execPath, [entry], { encoding: "utf8", timeout: 30_000 }); + expect(run.error).toBeUndefined(); + expect(oracle.status).toBe(0); + expect({ + status: run.status, + signal: run.signal, + stdout: run.stdout, + stderr: run.stderr, + }).toEqual({ + status: oracle.status, + signal: oracle.signal, + stdout: oracle.stdout, + stderr: oracle.stderr, + }); + } finally { + rmSync(dir, { recursive: true, force: true }); + } +}); + test("JS overrides with different return types match Node", async () => { const { dir, entry } = fixture(` class Base { copy(value = 1) { return value; } } diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5467-typed-array-loop-versions.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5467-typed-array-loop-versions.ts.json new file mode 100644 index 000000000..a4be59696 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5467-typed-array-loop-versions.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/5467-typed-array-loop-versions.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5468-typed-array-loop-aliasing.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5468-typed-array-loop-aliasing.ts.json new file mode 100644 index 000000000..517cbab4c --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5468-typed-array-loop-aliasing.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/5468-typed-array-loop-aliasing.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5469-typed-array-loop-initializer-rebinding.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5469-typed-array-loop-initializer-rebinding.ts.json new file mode 100644 index 000000000..79c7d0c63 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5469-typed-array-loop-initializer-rebinding.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/5469-typed-array-loop-initializer-rebinding.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5816-fixed-into-unknown-rest.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5816-fixed-into-unknown-rest.ts.json new file mode 100644 index 000000000..8354f5c48 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5816-fixed-into-unknown-rest.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/5816-fixed-into-unknown-rest.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5916-dyn-error-stack-property.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5916-dyn-error-stack-property.ts.json new file mode 100644 index 000000000..fa5b5460f --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5916-dyn-error-stack-property.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/5916-dyn-error-stack-property.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6004-class-instance-integrity.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6004-class-instance-integrity.ts.json new file mode 100644 index 000000000..4d21236c0 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6004-class-instance-integrity.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6004-class-instance-integrity.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6005-class-instance-integrity-sloppy.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6005-class-instance-integrity-sloppy.cjs.json new file mode 100644 index 000000000..1101d2180 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6005-class-instance-integrity-sloppy.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6005-class-instance-integrity-sloppy.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6064-protocol-subinterface-assertions.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6064-protocol-subinterface-assertions.ts.json deleted file mode 100644 index e81d7e9e7..000000000 --- a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6064-protocol-subinterface-assertions.ts.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "order": [ - "/tests/corpus/6064-protocol-subinterface-assertions.ts" - ], - "diags": [] -} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6398-conditional-fixed-arity.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6398-conditional-fixed-arity.ts.json new file mode 100644 index 000000000..878f45b09 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6398-conditional-fixed-arity.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6398-conditional-fixed-arity.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6400-dyn-error-stack-property-js.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6400-dyn-error-stack-property-js.js.json new file mode 100644 index 000000000..7bea48883 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6400-dyn-error-stack-property-js.js.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6400-dyn-error-stack-property-js.js" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6401-dyn-error-own-property-names.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6401-dyn-error-own-property-names.ts.json new file mode 100644 index 000000000..cf96c4f96 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6401-dyn-error-own-property-names.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6401-dyn-error-own-property-names.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6402-dyn-error-own-property-names-js.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6402-dyn-error-own-property-names-js.js.json new file mode 100644 index 000000000..49cf88884 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6402-dyn-error-own-property-names-js.js.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6402-dyn-error-own-property-names-js.js" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6420-process-send-presence.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6420-process-send-presence.ts.json new file mode 100644 index 000000000..69db221e2 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6420-process-send-presence.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6420-process-send-presence.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6421-process-send-presence-fork/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6421-process-send-presence-fork/main.ts.json new file mode 100644 index 000000000..abde055bf --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6421-process-send-presence-fork/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6421-process-send-presence-fork/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6422-process-exitcode-read.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6422-process-exitcode-read.ts.json new file mode 100644 index 000000000..15abd3cdc --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6422-process-exitcode-read.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6422-process-exitcode-read.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6423-process-exitcode-default.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6423-process-exitcode-default.ts.json new file mode 100644 index 000000000..31bf2584e --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6423-process-exitcode-default.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6423-process-exitcode-default.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6424-process-exit-keeps-exitcode.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6424-process-exit-keeps-exitcode.ts.json new file mode 100644 index 000000000..ce051e3fa --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6424-process-exit-keeps-exitcode.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6424-process-exit-keeps-exitcode.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6425-stream-hook-wider-chunk.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6425-stream-hook-wider-chunk.ts.json new file mode 100644 index 000000000..4c6db1400 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6425-stream-hook-wider-chunk.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6425-stream-hook-wider-chunk.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6426-promise-race-void-entry.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6426-promise-race-void-entry.ts.json new file mode 100644 index 000000000..e4d66ecf4 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6426-promise-race-void-entry.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6426-promise-race-void-entry.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6427-http-request-options-type.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6427-http-request-options-type.ts.json new file mode 100644 index 000000000..3e403ece6 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6427-http-request-options-type.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6427-http-request-options-type.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6428-promisify-callback-functions.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6428-promisify-callback-functions.ts.json new file mode 100644 index 000000000..76c3e65d0 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6428-promisify-callback-functions.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6428-promisify-callback-functions.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6429-esm-js-dirname.mjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6429-esm-js-dirname.mjs.json new file mode 100644 index 000000000..0168f85ee --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6429-esm-js-dirname.mjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6429-esm-js-dirname.mjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6445-rest-function-fixed-slot.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6445-rest-function-fixed-slot.ts.json new file mode 100644 index 000000000..b39c2fa38 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6445-rest-function-fixed-slot.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6445-rest-function-fixed-slot.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6446-rest-function-nested-declaration.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6446-rest-function-nested-declaration.ts.json new file mode 100644 index 000000000..41c034395 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6446-rest-function-nested-declaration.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6446-rest-function-nested-declaration.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6460-in-union-optional-slots.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6460-in-union-optional-slots.ts.json new file mode 100644 index 000000000..8491507eb --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6460-in-union-optional-slots.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6460-in-union-optional-slots.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6461-predicate-intersection-narrowing.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6461-predicate-intersection-narrowing.ts.json new file mode 100644 index 000000000..3ce078006 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6461-predicate-intersection-narrowing.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6461-predicate-intersection-narrowing.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6462-assertion-attached-members.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6462-assertion-attached-members.ts.json new file mode 100644 index 000000000..36e333373 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6462-assertion-attached-members.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6462-assertion-attached-members.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6480-catch-typeof-untyped-throws.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6480-catch-typeof-untyped-throws.ts.json new file mode 100644 index 000000000..38b99f9fc --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6480-catch-typeof-untyped-throws.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6480-catch-typeof-untyped-throws.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/boot-path-node-surface-fences.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/boot-path-node-surface-fences.ts.json new file mode 100644 index 000000000..de58c3423 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/boot-path-node-surface-fences.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/diagnostics/boot-path-node-surface-fences.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/class-field-accessor-descriptor.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/class-field-accessor-descriptor.ts.json new file mode 100644 index 000000000..3d6639169 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/class-field-accessor-descriptor.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/diagnostics/class-field-accessor-descriptor.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/promisify-callback-functions.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/promisify-callback-functions.ts.json new file mode 100644 index 000000000..a4a753c3d --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/promisify-callback-functions.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/node-types/promisify-callback-functions.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/stream-subclass-hooks.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/stream-subclass-hooks.ts.json new file mode 100644 index 000000000..295d7deda --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/stream-subclass-hooks.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/node-types/stream-subclass-hooks.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2558-bundled-chunks/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2558-bundled-chunks/main.ts.json new file mode 100644 index 000000000..cc5bc69c8 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2558-bundled-chunks/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/npm/cases/2558-bundled-chunks/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2559-cjs-cycle/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2559-cjs-cycle/main.ts.json new file mode 100644 index 000000000..382eef7b4 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2559-cjs-cycle/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/npm/cases/2559-cjs-cycle/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2560-cjs-cycle-partner-first/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2560-cjs-cycle-partner-first/main.ts.json new file mode 100644 index 000000000..f609f128e --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2560-cjs-cycle-partner-first/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/npm/cases/2560-cjs-cycle-partner-first/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2561-cjs-cycle-early-read/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2561-cjs-cycle-early-read/main.ts.json new file mode 100644 index 000000000..be3b2af16 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2561-cjs-cycle-early-read/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/npm/cases/2561-cjs-cycle-early-read/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2562-subpath-leaf/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2562-subpath-leaf/main.ts.json new file mode 100644 index 000000000..68cde3de1 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2562-subpath-leaf/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/npm/cases/2562-subpath-leaf/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2564-chunk-helper-shadowing/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2564-chunk-helper-shadowing/main.ts.json new file mode 100644 index 000000000..90e304cbe --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2564-chunk-helper-shadowing/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/npm/cases/2564-chunk-helper-shadowing/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2565-chunk-shared-function/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2565-chunk-shared-function/main.ts.json new file mode 100644 index 000000000..6443d170b --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2565-chunk-shared-function/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/npm/cases/2565-chunk-shared-function/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2566-cjs-cycle-indirect-call/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2566-cjs-cycle-indirect-call/main.ts.json new file mode 100644 index 000000000..f51a29951 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2566-cjs-cycle-indirect-call/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/npm/cases/2566-cjs-cycle-indirect-call/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2571-cjs-cycle-stored-function/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2571-cjs-cycle-stored-function/main.ts.json new file mode 100644 index 000000000..c3e0442f9 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2571-cjs-cycle-stored-function/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/npm/cases/2571-cjs-cycle-stored-function/main.ts" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_alloc.c b/packages/runtime/src/scr_alloc.c index 246d27f0a..935736612 100644 --- a/packages/runtime/src/scr_alloc.c +++ b/packages/runtime/src/scr_alloc.c @@ -935,12 +935,19 @@ static void scr_sa_trim(void) { * interrupts every running thread of the process to flush its TLB, and * glibc's trim locks the arenas other threads allocate from, so discards * wait for the last worker of a group, which hands them to one helper; - * - the helper waits SCR_SA_DISCARD_DELAY_MS (100 by default) and stops if + * - the helper waits a delay (scr_sa_discard_delay_ms: 1 s to start, + * doubled each time workers start again soon after a discard) and stops if * a worker started meanwhile: a program that starts its next workers or * exits within that time does not return pages it would fault back in or * never touch again, and the last worker of the next group hands the * discards to a new helper; a thread that finds every arena claimed * discards one waiting arena and claims it; + * - once the event loop of the thread that is not a worker runs, the + * helper then discards one arena at a time, and only while that loop + * waits with nothing ready (scr_sa_loop_waiting): a program that does + * its next work on that thread alone (a compiler rebuilding one file in + * watch mode) would otherwise have it slowed by the discards. A loop that + * does not wait gets them SCR_SA_DISCARD_DELAY_MAX_MS after the delay; * - the helper then returns the pages * it carved (MADV_DONTNEED; the start of the first page, which it may * share with older blocks, is cleared), frees the recorded system blocks, @@ -1214,12 +1221,20 @@ void scr_sa_thread_shares(void) { /* Worker threads that started and have not parked. */ static _Atomic(unsigned) scr_sa_workers_live; +#ifdef SCR_SA_TRIM +static void scr_sa_group_starting(void); +#endif + /* Library archives run on their embedder's threads and keep their memory. */ static void scr_sa_worker_starting(void) { #ifndef SCR_LIB scr_sa_worker = true; +#ifdef SCR_SA_TRIM + if (atomic_fetch_add_explicit(&scr_sa_workers_live, 1, memory_order_relaxed) == 0) scr_sa_group_starting(); +#else atomic_fetch_add_explicit(&scr_sa_workers_live, 1, memory_order_relaxed); #endif +#endif } #define SCR_SA_WORKER_STARTING() scr_sa_worker_starting() #endif @@ -1309,28 +1324,91 @@ static bool scr_sa_drain_some(bool one) { static bool scr_sa_drain(void) { return scr_sa_drain_some(false); } -/* SCR_SA_DISCARD_DELAY_MS: how long the trim helper waits after the last - * worker parks before it returns their memory (default 100). */ -static long scr_sa_discard_delay_ms(void) { - const char *delay = getenv("SCR_SA_DISCARD_DELAY_MS"); - if (!delay) return 100; - long ms = strtol(delay, NULL, 10); +/* How long the trim helper waits after the last worker parks before it + * returns their memory: SCR_SA_DISCARD_DELAY_MS (1000 by default) to start. + * Each time workers start again within ten delays of a discard, the delay + * doubles, up to SCR_SA_DISCARD_DELAY_MAX_MS (10000 by default): a program + * that keeps starting workers (a compiler rebuilding in watch mode, a + * server) would fault the returned pages straight back in. Memory that + * stays idle for the grown delay still goes back. */ +static _Atomic(long) scr_sa_delay_grown_ms; /* 0 until a discard proved too early */ +static _Atomic(long) scr_sa_discarded_ms; /* when the helper last discarded; 0: not since */ + +static long scr_sa_env_ms(const char *name, long fallback) { + const char *value = getenv(name); + if (!value) return fallback; + long ms = strtol(value, NULL, 10); return ms < 0 ? 0 : ms; } +static long scr_sa_discard_delay_ms(void) { + long start = scr_sa_env_ms("SCR_SA_DISCARD_DELAY_MS", 1000); + long max = scr_sa_env_ms("SCR_SA_DISCARD_DELAY_MAX_MS", 10000); + long grown = atomic_load_explicit(&scr_sa_delay_grown_ms, memory_order_relaxed); + long delay = grown > start ? grown : start; + return max >= start && delay > max ? max : delay; +} + +static long scr_sa_now_ms(void) { + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + return (long)now.tv_sec * 1000 + now.tv_nsec / 1000000 + 1; +} + +/* The first worker of a group starts: a discard shortly before means the + * delay was too short for this program. */ +static void scr_sa_group_starting(void) { + long at = atomic_exchange_explicit(&scr_sa_discarded_ms, 0, memory_order_relaxed); + if (!at) return; + long delay = scr_sa_discard_delay_ms(); + if (scr_sa_now_ms() - at >= 10 * (delay > 10 ? delay : 10)) return; + long max = scr_sa_env_ms("SCR_SA_DISCARD_DELAY_MAX_MS", 10000); + long grown = delay > 0 ? 2 * delay : 1; + if (grown > max) grown = max; + if (grown > atomic_load_explicit(&scr_sa_delay_grown_ms, memory_order_relaxed)) + atomic_store_explicit(&scr_sa_delay_grown_ms, grown, memory_order_relaxed); +} + +/* The event loop of the thread that is not a worker: an odd count while it + * waits with nothing ready, an even one while it runs, 0 until it first + * waits. Only that thread writes it. */ +static _Atomic(unsigned long) scr_sa_loop_turns; + +void scr_sa_loop_waiting(bool waiting) { + if (scr_sa_worker) return; + unsigned long turns = atomic_load_explicit(&scr_sa_loop_turns, memory_order_relaxed); + if ((bool)(turns & 1) != waiting) atomic_store_explicit(&scr_sa_loop_turns, turns + 1, memory_order_relaxed); +} + /* Waits out the delay first, and stops if a worker starts meanwhile: the * discards then wait for the last worker of that group. A program that exits * within the delay does not spend its last milliseconds returning pages, and * one that starts its next workers within it does not return pages while - * they start. */ + * they start. Once the event loop runs, each discard then waits until the + * loop has been in one wait for a step, or until the maximum delay has + * passed. */ static void *scr_sa_drain_main(void *arg) { (void)arg; - for (long left = scr_sa_discard_delay_ms(); left > 0; left -= 5) { - struct timespec pause = {0, (left < 5 ? left : 5) * 1000000L}; + long delay = scr_sa_discard_delay_ms(), step = delay > 200 ? 20 : 5; + for (long left = delay; left > 0; left -= step) { + struct timespec pause = {0, (left < step ? left : step) * 1000000L}; nanosleep(&pause, NULL); if (atomic_load_explicit(&scr_sa_workers_live, memory_order_acquire) != 0) return NULL; } - if (atomic_load_explicit(&scr_sa_workers_live, memory_order_acquire) == 0) scr_sa_drain(); + long max = scr_sa_env_ms("SCR_SA_DISCARD_DELAY_MAX_MS", 10000); + unsigned long seen = 0; + for (long waited = 0; atomic_load_explicit(&scr_sa_workers_live, memory_order_acquire) == 0;) { + unsigned long turns = atomic_load_explicit(&scr_sa_loop_turns, memory_order_relaxed); + if (turns == 0 || ((turns & 1) && turns == seen) || waited >= max) { + if (!scr_sa_drain_some(true)) break; + atomic_store_explicit(&scr_sa_discarded_ms, scr_sa_now_ms(), memory_order_relaxed); + continue; + } + seen = turns; + struct timespec pause = {0, step * 1000000L}; + nanosleep(&pause, NULL); + waited += step; + } return NULL; } @@ -1361,6 +1439,7 @@ void scr_sa_wait_trims(void) { #else void scr_sa_wait_trims(void) {} void scr_sa_thread_shares(void) {} +void scr_sa_loop_waiting(bool waiting) { (void)waiting; } #endif static void scr_sa_park_arena(void); @@ -1564,6 +1643,7 @@ void *scr_mem_realloc(void *p, size_t n) { return realloc(p, n); } void scr_sa_thread_park(void) {} void scr_sa_wait_trims(void) {} void scr_sa_thread_shares(void) {} +void scr_sa_loop_waiting(bool waiting) { (void)waiting; } void scr_sa_free_foreign(void *p) { free(p); } #endif #endif diff --git a/packages/runtime/src/scr_alloc.test.c b/packages/runtime/src/scr_alloc.test.c index aa6b0b60b..6c83afdf9 100644 --- a/packages/runtime/src/scr_alloc.test.c +++ b/packages/runtime/src/scr_alloc.test.c @@ -700,11 +700,91 @@ static void check_discard_delay(void) { assert(pthread_join(holder, NULL) == 0); assert(pthread_join(plain, NULL) == 0); scr_sa_wait_trims(); - unsetenv("SCR_SA_DISCARD_DELAY_MS"); + setenv("SCR_SA_DISCARD_DELAY_MS", "100", 1); assert(resident_pages(first, discard_hi) < total / 2); discard_in_old_arena = true; } +/* A discard that workers follow within ten delays was too early: the next + * delay is twice as long, and memory idle for that long still goes back. */ +static void *plain_discard_worker(void *arg) { + (void)arg; + scr_sa_thread_starting(); + allocate_discardable(); + scr_sa_thread_park(); + return NULL; +} + +static void check_discard_delay_grows(void) { + size_t page = (size_t)sysconf(_SC_PAGESIZE); + setenv("SCR_SA_DISCARD_DELAY_MS", "100", 1); + setenv("SCR_SA_DISCARD_DELAY_MAX_MS", "10000", 1); + discard_blocks = malloc(DISCARD_BLOCKS * sizeof *discard_blocks); + assert(discard_blocks); + /* A plain thread holds the arena whose slice the earlier checks filled. */ + atomic_store(&discard_step, 0); + pthread_t plain; + assert(pthread_create(&plain, NULL, holding_thread, NULL) == 0); + wait_step(1); + run(plain_discard_worker); /* returned after the delay */ + pthread_t t; + assert(pthread_create(&t, NULL, plain_discard_worker, NULL) == 0); + assert(pthread_join(t, NULL) == 0); + uintptr_t first = (discard_lo + page - 1) & ~(uintptr_t)(page - 1); + size_t total = (discard_hi - first) / page; + struct timespec pause = {0, 140000000L}; + nanosleep(&pause, NULL); + assert(resident_pages(first, discard_hi) > total / 2); /* the delay is 200 ms or more now */ + scr_sa_wait_trims(); + assert(resident_pages(first, discard_hi) < total / 2); + setenv("SCR_SA_DISCARD_DELAY_MS", "100", 1); + setenv("SCR_SA_DISCARD_DELAY_MAX_MS", "0", 1); + atomic_store(&discard_step, 3); + assert(pthread_join(plain, NULL) == 0); + free(discard_blocks); +} + +/* Once the event loop runs, discards wait for it to wait with nothing + * ready; a loop that does not wait gets them SCR_SA_DISCARD_DELAY_MAX_MS + * after the delay. The test's thread plays the loop's. */ +static void check_discard_waits_for_loop(void) { + size_t page = (size_t)sysconf(_SC_PAGESIZE); + setenv("SCR_SA_DISCARD_DELAY_MS", "100", 1); + setenv("SCR_SA_DISCARD_DELAY_MAX_MS", "10000", 1); + discard_blocks = malloc(DISCARD_BLOCKS * sizeof *discard_blocks); + assert(discard_blocks); + atomic_store(&discard_step, 0); + pthread_t plain; + assert(pthread_create(&plain, NULL, holding_thread, NULL) == 0); + wait_step(1); + scr_sa_loop_waiting(true); + scr_sa_loop_waiting(false); /* the loop runs */ + pthread_t t; + assert(pthread_create(&t, NULL, plain_discard_worker, NULL) == 0); + assert(pthread_join(t, NULL) == 0); + uintptr_t first = (discard_lo + page - 1) & ~(uintptr_t)(page - 1); + size_t total = (discard_hi - first) / page; + struct timespec pause = {0, 900000000L}; + nanosleep(&pause, NULL); + assert(resident_pages(first, discard_hi) > total / 2); /* past the delay */ + scr_sa_loop_waiting(true); + scr_sa_wait_trims(); + assert(resident_pages(first, discard_hi) < total / 2); + /* A loop that keeps running. */ + scr_sa_loop_waiting(false); + setenv("SCR_SA_DISCARD_DELAY_MAX_MS", "300", 1); + assert(pthread_create(&t, NULL, plain_discard_worker, NULL) == 0); + assert(pthread_join(t, NULL) == 0); + first = (discard_lo + page - 1) & ~(uintptr_t)(page - 1); + total = (discard_hi - first) / page; + scr_sa_wait_trims(); + assert(resident_pages(first, discard_hi) < total / 2); + setenv("SCR_SA_DISCARD_DELAY_MAX_MS", "0", 1); + atomic_store(&discard_step, 3); + assert(pthread_join(plain, NULL) == 0); + free(discard_blocks); +} + static void check_discard(void) { discard_blocks = malloc(DISCARD_BLOCKS * sizeof *discard_blocks); assert(discard_blocks); @@ -819,10 +899,15 @@ int main(void) { check_heap_huge_pages(); #endif #if defined(SCR_WORKERS) && SCR_SMALL_ALLOC + /* A short discard delay, which stays at its start until check_discard_delay_grows. */ + setenv("SCR_SA_DISCARD_DELAY_MS", "100", 1); + setenv("SCR_SA_DISCARD_DELAY_MAX_MS", "0", 1); check_threads(); #ifdef __linux__ check_trim(); check_discard(); + check_discard_delay_grows(); + check_discard_waits_for_loop(); #endif printf("allocator checks passed: size-class workers\n"); #else diff --git a/packages/runtime/src/scr_async.c b/packages/runtime/src/scr_async.c index 155f91be4..8185422af 100644 --- a/packages/runtime/src/scr_async.c +++ b/packages/runtime/src/scr_async.c @@ -4410,6 +4410,9 @@ static bool scr_loop_run_pass(ScrPromise *top_level, bool first_checkpoint) { #endif #if !defined(_WIN32) && !defined(__wasi__) if (due > now) scr_fiber_stacks_clear(); +#endif +#ifdef SCR_WORKERS + if (due > now) scr_sa_loop_waiting(true); #endif bool evw = scr_events_watching_fn != NULL && scr_events_watching_fn(); #ifdef SCR_WORKERS @@ -4564,6 +4567,9 @@ static bool scr_loop_run_pass(ScrPromise *top_level, bool first_checkpoint) { now = due; } } +#ifdef SCR_WORKERS + scr_sa_loop_waiting(false); +#endif while (scr_ntimers > 0 && scr_timers[0].deadline_ms <= now) { ScrTimer t = scr_timer_pop(); /* Timer callbacks are plain sync closures, run on the main stack. diff --git a/packages/runtime/src/scr_child.c b/packages/runtime/src/scr_child.c index 184e3be9b..ec685656d 100644 --- a/packages/runtime/src/scr_child.c +++ b/packages/runtime/src/scr_child.c @@ -6410,6 +6410,7 @@ void scr_child_ipc_on_disconnect(ScrChild *c, ScrClosure *cb, bool once) { bool scr_process_ipc_connected(void) { return scr_process_ipc != NULL && scr_process_ipc->connected; } +bool scr_process_ipc_open(void) { return scr_process_ipc != NULL; } bool scr_process_ipc_send(ScrStr *json) { return scr_ipc_send(scr_process_ipc, json, NULL, NULL); } diff --git a/packages/runtime/src/scr_child.h b/packages/runtime/src/scr_child.h index 6492aefe8..f59e02e74 100644 --- a/packages/runtime/src/scr_child.h +++ b/packages/runtime/src/scr_child.h @@ -182,6 +182,9 @@ void scr_child_ipc_on_disconnect(ScrChild *c, ScrClosure *cb /*moves*/, bool once); double scr_process_fork_target(double target_count); bool scr_process_ipc_connected(void); +/* Whether this fork child opened its channel (process.send's presence in + * a program with fork targets; disconnecting keeps it). */ +bool scr_process_ipc_open(void); bool scr_process_ipc_send(ScrStr *json); bool scr_process_ipc_send_cb(ScrStr *json, ScrClosure *cb /*moves*/, ScrIpcSendFn fn); diff --git a/packages/runtime/src/scr_dyn.h b/packages/runtime/src/scr_dyn.h index a3d506a10..6fdbbb408 100644 --- a/packages/runtime/src/scr_dyn.h +++ b/packages/runtime/src/scr_dyn.h @@ -937,7 +937,17 @@ double scr_dyn_arr_len(const ScrDyn *d); ScrDyn *scr_dyn_arr_at(const ScrDyn *d, double i); ScrDyn *scr_dyn_arr_named_get(const ScrDyn *d, const ScrStr *key); void scr_dyn_obj_set(ScrDyn *obj, const char *key, size_t key_len, ScrDyn *value); +void scr_dyn_obj_set_field(ScrDyn *obj, const char *key, size_t key_len, ScrDyn *value); ScrDyn *scr_dyn_define_property(ScrDyn *target, ScrDyn *key, ScrDyn *descriptor); +void scr_dyn_class_field_attributes(ScrDyn *view, ScrStr *name, double bits); +void scr_dyn_class_view_attributes(ScrDyn *view, double bits); +ScrDyn *scr_class_define_field(ScrDyn *current, double field_bits, double object_bits, ScrStr *name, + ScrDyn *descriptor); +double scr_class_defined_bits(ScrDyn *holder, ScrStr *name); +ScrDyn *scr_class_defined_value(ScrDyn *holder, ScrStr *name); +bool scr_class_descriptor_has_value(ScrDyn *descriptor); +void scr_class_throw_read_only(ScrStr *field, ScrStr *owner); +void scr_class_throw_not_extensible(ScrStr *field); ScrDyn *scr_dyn_get_own_property_descriptor(ScrDyn *target, ScrDyn *key); ScrDyn *scr_dyn_array_proto_call(ScrDyn *target, ScrStr *method, ScrDyn *args); ScrDyn *scr_dyn_array_prototype(void); /* +1, installs native method adapters */ diff --git a/packages/runtime/src/scr_error.c b/packages/runtime/src/scr_error.c index 6f616c35a..4f514681b 100644 --- a/packages/runtime/src/scr_error.c +++ b/packages/runtime/src/scr_error.c @@ -354,6 +354,12 @@ ScrStr *scr_error_stack(ScrError *e) { return scr_str_retain(e->stack); } +void scr_error_set_stack(ScrError *e, ScrStr *stack) { + ScrStr *replacement = scr_str_retain(stack); + scr_str_release(e->stack); + e->stack = replacement; +} + bool scr_error_is(const void *obj) { const ScrVt *vt = ((const ScrError *)obj)->vt; return scr_error_vts[SCR_ERR_ERROR].pre <= vt->pre && diff --git a/packages/runtime/src/scr_error.h b/packages/runtime/src/scr_error.h index bc64be4d2..a7c921f23 100644 --- a/packages/runtime/src/scr_error.h +++ b/packages/runtime/src/scr_error.h @@ -252,6 +252,9 @@ void scr_error_install_message_reader(ScrStr *(*reader)(ScrError *)); void scr_error_install_name_reader(ScrStr *(*reader)(ScrError *)); ScrStr *scr_error_default_name(const ScrError *e); ScrStr *scr_error_stack(ScrError *e); +/* Replaces the error's stack string, as an assignment to `stack` does. + * Borrowed operands. */ +void scr_error_set_stack(ScrError *e, ScrStr *stack); ScrError *scr_error_retain(ScrError *e); void scr_error_release(ScrError *e); /* dispatches through e->vt */ diff --git a/packages/runtime/src/scr_json.c b/packages/runtime/src/scr_json.c index b8c2accea..ceddf1397 100644 --- a/packages/runtime/src/scr_json.c +++ b/packages/runtime/src/scr_json.c @@ -1585,8 +1585,12 @@ static void scr_dyn_obj_put(ScrDyn *obj, const char *key, size_t key_len, ScrDyn } if (obj->non_extensible) { scr_dyn_release(value); - static const char message[] = "Cannot add property, object is not extensible"; - scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1); + ScrJsonBuf buffer; + scr_jb_init(&buffer); + scr_jb_puts(&buffer, "Cannot add property "); + scr_jb_write(&buffer, key, key_len); + scr_jb_puts(&buffer, ", object is not extensible"); + scr_throw_error(SCR_ERR_TYPE, scr_jb_finish(&buffer)); return; } if (obj->v.obj.len == obj->v.obj.cap) { @@ -1922,9 +1926,40 @@ ScrDyn *scr_dyn_set_prototype(ScrDyn *object, ScrDyn *prototype) { return scr_dyn_retain(object); } +/* A class instance's integrity state lives in its native layout, which only + * statically typed calls can reach (the compiler adds the state to the + * hierarchies they name). Through an untyped value the operation throws. */ +static bool scr_dyn_class_integrity_fence(const ScrDyn *object, const char *operation) { + if (object->kind != SCR_DYN_TYPED_REF) return false; + ScrJsonBuf buffer; + scr_jb_init(&buffer); + scr_jb_puts(&buffer, operation); + scr_jb_puts(&buffer, " of a class instance held in an untyped value is not supported yet"); + ScrStr *message = scr_jb_finish(&buffer); + scr_throw_error_msg_code(SCR_ERR_ERROR, message->data, message->len, "SC2020"); + scr_str_release(message); + return true; +} + +/* Integrity queries on a class instance answer from its checked view, which + * carries the instance's attributes (scr_dyn_class_view_attributes). */ +static bool scr_dyn_typed_ref_view_answer(ScrDyn *object, bool (*query)(const ScrDyn *), + bool *answer) { + if (object->kind != SCR_DYN_TYPED_REF) return false; + ScrDyn *view = scr_dyn_typed_ref_materialize(object); + *answer = scr_exc_pending() ? false : query(view); + scr_dyn_release(view); + return true; +} + +static bool scr_dyn_is_extensible_view(const ScrDyn *view) { + return (view->kind == SCR_DYN_OBJ || view->kind == SCR_DYN_ARR) && !view->non_extensible; +} + ScrDyn *scr_dyn_prevent_extensions(ScrDyn *object) { if (scr_dyn_class_reflection_fence(object)) return NULL; if (object->kind == SCR_DYN_PROXY) { scr_dyn_proxy_unsupported("Object.preventExtensions"); return NULL; } + if (scr_dyn_class_integrity_fence(object, "Object.preventExtensions")) return NULL; if (object->kind == SCR_DYN_OBJ || object->kind == SCR_DYN_ARR) { object->non_extensible = true; if (object->kind == SCR_DYN_ARR && object->v.arr.properties) @@ -1936,12 +1971,15 @@ ScrDyn *scr_dyn_prevent_extensions(ScrDyn *object) { bool scr_dyn_is_extensible(ScrDyn *object) { if (scr_dyn_class_reflection_fence(object)) return false; if (object->kind == SCR_DYN_PROXY) { scr_dyn_proxy_unsupported("Object.isExtensible"); return false; } - return (object->kind == SCR_DYN_OBJ || object->kind == SCR_DYN_ARR) && !object->non_extensible; + bool answer; + if (scr_dyn_typed_ref_view_answer(object, scr_dyn_is_extensible_view, &answer)) return answer; + return scr_dyn_is_extensible_view(object); } ScrDyn *scr_dyn_seal(ScrDyn *object) { if (scr_dyn_class_reflection_fence(object)) return NULL; if (object->kind == SCR_DYN_PROXY) { scr_dyn_proxy_unsupported("Object.seal"); return NULL; } + if (scr_dyn_class_integrity_fence(object, "Object.seal")) return NULL; if (object->symbol_properties) scr_dyn_release(scr_dyn_seal(object->symbol_properties)); if (object->kind == SCR_DYN_OBJ) { object->non_extensible = true; @@ -1958,6 +1996,8 @@ ScrDyn *scr_dyn_seal(ScrDyn *object) { bool scr_dyn_is_sealed(const ScrDyn *object) { if (scr_dyn_class_reflection_fence(object)) return false; if (object->kind == SCR_DYN_PROXY) { scr_dyn_proxy_unsupported("Object.isSealed"); return false; } + bool answer; + if (scr_dyn_typed_ref_view_answer((ScrDyn *)object, scr_dyn_is_sealed, &answer)) return answer; if (object->symbol_properties && !scr_dyn_is_sealed(object->symbol_properties)) return false; if (object->kind == SCR_DYN_ARR) return object->non_extensible && (object->v.arr.len == 0 || object->v.arr.sealed) && @@ -4316,6 +4356,25 @@ void scr_dyn_obj_set(ScrDyn *obj, const char *key, size_t key_len, ScrDyn *value scr_dyn_obj_put(obj, key, key_len, value); } +/* A class instance's view takes its native field values whatever the + * attributes copied into it (a frozen instance's view is built the same way + * as any other). Consumes value. */ +void scr_dyn_obj_set_field(ScrDyn *obj, const char *key, size_t key_len, ScrDyn *value) { + ScrDynEntry *existing = obj->v.obj.index ? scr_dyn_find_entry(obj, key, key_len) + : scr_dyn_find_linear_entry(obj, key, key_len); + if (existing && !existing->accessor) { + scr_dyn_region_store(obj, value); + ScrDyn *old = existing->value; + existing->value = value; + scr_dyn_release(old); + return; + } + bool non_extensible = obj->non_extensible; + obj->non_extensible = false; + scr_dyn_obj_put(obj, key, key_len, value); + obj->non_extensible = non_extensible; +} + static ScrStr *scr_dyn_property_key(const ScrDyn *key) { return scr_dyn_string_coerce_js(key); } @@ -6178,9 +6237,12 @@ ScrDyn *scr_dyn_define_property(ScrDyn *target, ScrDyn *key, ScrDyn *descriptor) (!accessor && ((!prior->writable && writable) || (!prior->writable && value && !scr_dyn_property_same_value(prior->value, value)))))) { scr_dyn_descriptor_fields_drop(fields); + ScrJsonBuf buffer; + scr_jb_init(&buffer); + scr_jb_puts(&buffer, "Cannot redefine property: "); + scr_jb_write(&buffer, name->data, name->len); scr_str_release(name); - static const char msg[] = "Cannot redefine property"; - scr_throw_error_msg(SCR_ERR_TYPE, msg, sizeof msg - 1); + scr_throw_error(SCR_ERR_TYPE, scr_jb_finish(&buffer)); return NULL; } if (prior) { @@ -6360,6 +6422,28 @@ void scr_dyn_throw(ScrDyn *d) { scr_dyn_release(d); return; } + /* A checked-dynamic primitive throws as the primitive itself, as a + * statically typed `throw 5` does: catch bindings test and extract the + * same snapshot kinds (`typeof e === "number"`), and a primitive has no + * identity to keep. */ + if (d->kind == SCR_DYN_NUM) { + double value = d->v.num; + scr_dyn_release(d); + scr_throw_f64(value); + return; + } + if (d->kind == SCR_DYN_BOOL) { + bool value = d->v.b; + scr_dyn_release(d); + scr_throw_bool(value); + return; + } + if (d->kind == SCR_DYN_STR) { + ScrStr *value = scr_str_retain(d->v.str); + scr_dyn_release(d); + scr_throw_str(value); + return; + } scr_throw_ref_classified(d, scr_dyn_retain_v, scr_dyn_release_v, scr_dyn_trace_v, scr_dyn_is_object(d)); } @@ -6793,10 +6877,111 @@ static void scr_errdyn_teardown(void) { free(cache); } +/* An error's own `stack`: a non-enumerable accessor shared by every error, + * as V8 defines it. The getter formats the stack on first access, so its + * heading is the name and message at that point, and later reads answer + * the same string. Both functions find the error through the receiver or + * its prototype chain; on any other receiver the getter answers undefined + * and the setter changes nothing but the receiver's own accessor. */ +static SCR_TL ScrDyn *scr_error_stack_accessors[2]; +static SCR_TL bool scr_error_stack_accessors_registered; + +static void scr_error_stack_accessors_cleanup(void) { + for (size_t i = 0; i < 2; i++) { + scr_dyn_release(scr_error_stack_accessors[i]); + scr_error_stack_accessors[i] = NULL; + } + scr_error_stack_accessors_registered = false; +} + +/* The native error behind an accessor receiver. +1 or NULL. */ +static ScrError *scr_error_stack_holder(const ScrDyn *receiver) { + for (const ScrDyn *current = receiver; current; current = scr_dyn_proto_next(current)) { + ScrError *error = scr_errdyn_err_of(current); + if (error) return error; + if (current->kind != SCR_DYN_OBJ) return NULL; + } + return NULL; +} + +static ScrDyn *scr_error_stack_get_call(ScrClosure *closure, ScrDyn *const *args, size_t argc) { + (void)closure; (void)args; (void)argc; + ScrDyn *self = scr_dyn_this_get(); + ScrError *error = scr_error_stack_holder(self); + scr_dyn_release(self); + if (!error) return scr_dyn_retain(scr_dyn_undefined()); + ScrStr *stack = scr_error_stack(error); + scr_error_release(error); + /* A name or message getter that throws leaves its exception pending. */ + if (!stack) return NULL; + ScrDyn *out = scr_dyn_new_str(stack); + scr_str_release(stack); + return out; +} + +static ScrDyn *scr_error_stack_set_call(ScrClosure *closure, ScrDyn *const *args, size_t argc); + +/* Whether an own property is an error's `stack` accessor. */ +static bool scr_dyn_entry_is_error_stack(const ScrDynEntry *entry) { + return entry->accessor && entry->setter && entry->setter->kind == SCR_DYN_FUNC && + entry->setter->v.fn.thunk == scr_error_stack_set_call; +} + +static ScrDyn *scr_error_stack_set_call(ScrClosure *closure, ScrDyn *const *args, size_t argc) { + (void)closure; + ScrDyn *self = scr_dyn_this_get(); + ScrError *error = scr_error_stack_holder(self); + ScrDyn *value = argc > 0 ? args[0] : scr_dyn_undefined(); + if (error && value->kind == SCR_DYN_STR) { + scr_error_set_stack(error, value->v.str); + } else if (self->kind == SCR_DYN_OBJ) { + /* The native slot holds strings only, and a typed reference's + * snapshot names no error: the receiver's own accessor becomes a data + * property holding the value, which a snapshot's commit stores. */ + ScrStr *key = scr_str_new("stack", 5); + ScrDynEntry *entry = scr_dyn_entry(self, key); + scr_str_release(key); + if (entry && scr_dyn_entry_is_error_stack(entry)) { + ScrDyn *old_value = entry->value, *old_getter = entry->getter, *old_setter = entry->setter; + entry->value = scr_dyn_retain(value); + entry->getter = entry->setter = NULL; + entry->accessor = false; + entry->writable = true; + scr_dyn_release(old_value); + scr_dyn_release(old_getter); + scr_dyn_release(old_setter); + } + } + scr_error_release(error); + scr_dyn_release(self); + return scr_dyn_retain(scr_dyn_undefined()); +} + +static void scr_error_dyn_add_stack(ScrDyn *d) { + if (!scr_error_stack_accessors_registered) { + scr_error_stack_accessors_registered = true; + scr_atexit(scr_error_stack_accessors_cleanup); + } + if (!scr_error_stack_accessors[0]) { + scr_error_stack_accessors[0] = + scr_dyn_new_func(scr_closure_new(NULL, 0), scr_error_stack_get_call, 0, "native:error.stack", NULL); + scr_error_stack_accessors[1] = + scr_dyn_new_func(scr_closure_new(NULL, 0), scr_error_stack_set_call, 1, "native:error.stack", NULL); + } + scr_dyn_obj_set(d, "stack", 5, scr_dyn_retain(scr_dyn_undefined())); + ScrDynEntry *entry = &d->v.obj.entries[d->v.obj.len - 1]; + entry->accessor = true; + entry->getter = scr_dyn_retain(scr_error_stack_accessors[0]); + entry->setter = scr_dyn_retain(scr_error_stack_accessors[1]); + entry->enumerable = false; +} + ScrDyn *scr_error_dyn_fields(const ScrError *e) { ScrDyn *d = scr_dyn_new_obj(); scr_dyn_obj_set(d, "%error", 6, scr_dyn_new_bool(true)); /* the checked-dynamic tree's error marker */ d->v.obj.entries[d->v.obj.len - 1].enumerable = false; + /* Node lists `stack` first among an error's own properties. */ + scr_error_dyn_add_stack(d); if (e->name_present) { scr_dyn_obj_set(d, "name", 4, scr_dyn_new_str(e->name)); d->v.obj.entries[d->v.obj.len - 1].enumerable = e->name_enumerable; @@ -6818,11 +7003,6 @@ ScrDyn *scr_error_dyn_fields(const ScrError *e) { scr_dyn_obj_set(d, "message", 7, scr_dyn_new_str(e->message)); d->v.obj.entries[d->v.obj.len - 1].enumerable = e->message_enumerable; } - if (e->stack) { - /* Node's own `stack` is non-enumerable. */ - scr_dyn_obj_set(d, "stack", 5, scr_dyn_new_str(e->stack)); - d->v.obj.entries[d->v.obj.len - 1].enumerable = false; - } if (e->system_message) { /* SystemError: code, the read-only `info` context ({ message, path, * syscall, errno, code }), then errno, syscall, path and dest. */ @@ -10964,6 +11144,17 @@ static ScrDyn *scr_sc_clone(const ScrDyn *v, const ScrScParent *up, ScrDynRegion ScrDyn *out = scr_dyn_new_obj(); for (size_t i = 0; i < v->v.obj.len; i++) { const ScrDynEntry *e = &v->v.obj.entries[i]; + if (scr_dyn_entry_is_error_stack(e)) { + /* A stack that was already formatted copies as a non-enumerable + * data property; an unformatted one has nothing to copy. */ + ScrError *error = scr_errdyn_err_of(v); + if (error && error->stack) { + scr_dyn_obj_set(out, "stack", 5, scr_dyn_new_str(error->stack)); + out->v.obj.entries[out->v.obj.len - 1].enumerable = false; + } + scr_error_release(error); + continue; + } ScrDyn *c = scr_sc_clone(e->value, &self, region); if (c == NULL) { scr_dyn_release(out); @@ -11487,7 +11678,20 @@ ScrDyn *scr_dyn_get_own_property_names(const ScrDyn *value) { scr_dyn_release(table); return out; } - if (value->kind == SCR_DYN_OBJ) return scr_dyn_obj_own_keys(value); + if (value->kind == SCR_DYN_OBJ) { + ScrDyn *keys = scr_dyn_obj_own_keys(value); + if (!scr_dyn_obj_get(value, "%error", 6)) return keys; + /* The checked-dynamic tree's error marker is not a property. */ + ScrDyn *names = scr_dyn_new_arr(); + scr_dyn_arr_reserve(names, keys->v.arr.len); + for (size_t i = 0; i < keys->v.arr.len; i++) { + const ScrStr *key = keys->v.arr.items[i]->v.str; + if (key->len == 6 && memcmp(key->data, "%error", 6) == 0) continue; + scr_dyn_arr_push(names, scr_dyn_retain(keys->v.arr.items[i])); + } + scr_dyn_release(keys); + return names; + } if (value->kind == SCR_DYN_ARR || value->kind == SCR_DYN_STR || value->kind == SCR_DYN_BYTES) { ScrDyn *keys = scr_dyn_new_arr(); size_t length = value->kind == SCR_DYN_ARR ? value->v.arr.len : @@ -14069,6 +14273,7 @@ ScrStr *scr_dyn_to_string_method(const ScrDyn *d, const ScrStr *enc, const ScrSt ScrDyn *scr_dyn_freeze(ScrDyn *value) { if (scr_dyn_class_reflection_fence(value)) return NULL; + if (scr_dyn_class_integrity_fence(value, "Object.freeze")) return NULL; if (value->symbol_properties) scr_dyn_release(scr_dyn_freeze(value->symbol_properties)); if (value->kind == SCR_DYN_ARR) { value->non_extensible = true; @@ -14096,6 +14301,8 @@ ScrDyn *scr_dyn_freeze(ScrDyn *value) { bool scr_dyn_is_frozen(const ScrDyn *value) { if (scr_dyn_class_reflection_fence(value)) return false; + bool answer; + if (scr_dyn_typed_ref_view_answer((ScrDyn *)value, scr_dyn_is_frozen, &answer)) return answer; if (value->symbol_properties && !scr_dyn_is_frozen(value->symbol_properties)) return false; if (value->kind == SCR_DYN_ARR) return scr_dyn_is_sealed(value) && (value->v.arr.len == 0 || value->v.arr.frozen) && @@ -14150,6 +14357,130 @@ ScrDyn *scr_caught_to_dyn(const ScrCaught *c) { } } +/* ── class instance attributes ─────────────────────────────────────────── + * A hierarchy passed to Object.freeze, Object.seal, Object.preventExtensions + * or Object.defineProperty keeps an integrity state per instance and an + * attribute word per redefined field (CLASS_* and FIELD_* bits in the + * compiler's ir.ts). The checked view of an instance carries them, so + * reflection over the view answers as Node does. */ +enum { + SCR_CLASS_NOT_EXTENSIBLE = 1, + SCR_CLASS_SEALED = 2, + SCR_CLASS_FROZEN = 4, + SCR_FIELD_NOT_WRITABLE = 1, + SCR_FIELD_NOT_ENUMERABLE = 2, + SCR_FIELD_NOT_CONFIGURABLE = 4, +}; + +void scr_dyn_class_field_attributes(ScrDyn *view, ScrStr *name, double bits) { + unsigned b = (unsigned)bits; + if (b == 0 || view->kind != SCR_DYN_OBJ) return; + ScrDynEntry *entry = scr_dyn_entry(view, name); + if (!entry) return; + if (b & SCR_FIELD_NOT_WRITABLE) entry->writable = false; + if (b & SCR_FIELD_NOT_ENUMERABLE) entry->enumerable = false; + if (b & SCR_FIELD_NOT_CONFIGURABLE) entry->configurable = false; +} + +void scr_dyn_class_view_attributes(ScrDyn *view, double bits) { + unsigned b = (unsigned)bits; + if (b == 0 || view->kind != SCR_DYN_OBJ) return; + view->non_extensible = true; + for (size_t i = 0; i < view->v.obj.len; i++) { + ScrDynEntry *entry = &view->v.obj.entries[i]; + if (b & (SCR_CLASS_SEALED | SCR_CLASS_FROZEN)) entry->configurable = false; + if ((b & SCR_CLASS_FROZEN) && !entry->accessor) entry->writable = false; + } +} + +/* Object.defineProperty on a declared class field. The descriptor applies to + * a one-property object holding the field's current value and attributes, + * so validation and errors match ordinary objects. Answers that object (+1), + * or NULL with a pending exception. */ +ScrDyn *scr_class_define_field(ScrDyn *current, double field_bits, double object_bits, ScrStr *name, + ScrDyn *descriptor) { + unsigned f = (unsigned)field_bits, o = (unsigned)object_bits; + ScrDyn *holder = scr_dyn_new_obj(); + scr_dyn_obj_set(holder, name->data, name->len, scr_dyn_retain(current)); + ScrDynEntry *entry = scr_dyn_entry(holder, name); + entry->writable = !(f & SCR_FIELD_NOT_WRITABLE) && !(o & SCR_CLASS_FROZEN); + entry->enumerable = !(f & SCR_FIELD_NOT_ENUMERABLE); + entry->configurable = + !(f & SCR_FIELD_NOT_CONFIGURABLE) && !(o & (SCR_CLASS_SEALED | SCR_CLASS_FROZEN)); + ScrDyn *key = scr_dyn_new_str(name); + ScrDyn *defined = scr_dyn_define_property(holder, key, descriptor); + scr_dyn_release(key); + if (!defined) { + scr_dyn_release(holder); + return NULL; + } + scr_dyn_release(defined); + entry = scr_dyn_entry(holder, name); + if (entry->accessor) { + scr_dyn_release(holder); + ScrJsonBuf buffer; + scr_jb_init(&buffer); + scr_jb_puts(&buffer, "Object.defineProperty with an accessor descriptor for the declared field '"); + scr_jb_write(&buffer, name->data, name->len); + scr_jb_puts(&buffer, "' is not supported yet"); + ScrStr *message = scr_jb_finish(&buffer); + scr_throw_error_msg_code(SCR_ERR_ERROR, message->data, message->len, "SC2020"); + scr_str_release(message); + return NULL; + } + return holder; +} + +/* The field attribute word of a scr_class_define_field result. */ +double scr_class_defined_bits(ScrDyn *holder, ScrStr *name) { + const ScrDynEntry *entry = scr_dyn_entry(holder, name); + unsigned bits = 0; + if (!entry->writable) bits |= SCR_FIELD_NOT_WRITABLE; + if (!entry->enumerable) bits |= SCR_FIELD_NOT_ENUMERABLE; + if (!entry->configurable) bits |= SCR_FIELD_NOT_CONFIGURABLE; + return (double)bits; +} + +/* The value a scr_class_define_field result holds (+1). */ +ScrDyn *scr_class_defined_value(ScrDyn *holder, ScrStr *name) { + return scr_dyn_retain(scr_dyn_entry(holder, name)->value); +} + +/* Whether a property descriptor names a value (Node's ToPropertyDescriptor + * reads `value` with [[HasProperty]]). */ +bool scr_class_descriptor_has_value(ScrDyn *descriptor) { + if (descriptor->kind == SCR_DYN_TYPED_REF) { + ScrDyn *view = scr_dyn_typed_ref_materialize(descriptor); + bool has = !scr_exc_pending() && scr_class_descriptor_has_value(view); + scr_dyn_release(view); + return has; + } + return descriptor->kind == SCR_DYN_OBJ && scr_dyn_obj_get(descriptor, "value", 5) != NULL; +} + +/* Node's TypeError for a write to a read-only field of a class instance. */ +void scr_class_throw_read_only(ScrStr *field, ScrStr *owner) { + ScrJsonBuf buffer; + scr_jb_init(&buffer); + scr_jb_puts(&buffer, "Cannot assign to read only property '"); + scr_jb_write(&buffer, field->data, field->len); + scr_jb_puts(&buffer, "' of object '#<"); + scr_jb_write(&buffer, owner->data, owner->len); + scr_jb_puts(&buffer, ">'"); + scr_throw_error(SCR_ERR_TYPE, scr_jb_finish(&buffer)); +} + +/* Node's TypeError for a class field initializer on a non-extensible + * instance (a base constructor froze or sealed `this`). */ +void scr_class_throw_not_extensible(ScrStr *field) { + ScrJsonBuf buffer; + scr_jb_init(&buffer); + scr_jb_puts(&buffer, "Cannot define property "); + scr_jb_write(&buffer, field->data, field->len); + scr_jb_puts(&buffer, ", object is not extensible"); + scr_throw_error(SCR_ERR_TYPE, scr_jb_finish(&buffer)); +} + /* ── compiled generators through untyped values ─────────────────────── */ static SCR_TL ScrDyn *scr_generator_next_getter; diff --git a/packages/runtime/src/scr_lib.c b/packages/runtime/src/scr_lib.c index bd7d599ce..2e6f35f67 100644 --- a/packages/runtime/src/scr_lib.c +++ b/packages/runtime/src/scr_lib.c @@ -2037,6 +2037,19 @@ double scr_process_exit_code_or_zero(void) { return (double)scr_exit_code_hint_get(); } +static SCR_TL bool scr_exit_code_assigned = false; + +void scr_process_set_exit_code_tracked(double code) { + int converted; + if (!scr_process_exit_code_of(code, &converted)) return; + scr_exit_code_note(converted); + scr_exit_code_assigned = true; +} + +bool scr_process_has_exit_code(void) { + return scr_exit_code_assigned || scr_exit_code_hint_get() != 0; +} + #if defined(__linux__) #include #include diff --git a/packages/runtime/src/scr_lib.h b/packages/runtime/src/scr_lib.h index 4b1094d54..bae678292 100644 --- a/packages/runtime/src/scr_lib.h +++ b/packages/runtime/src/scr_lib.h @@ -149,6 +149,11 @@ void scr_process_exit(double code); extern const unsigned char scr_detached_exit; void scr_process_set_exit_code(double code); double scr_process_exit_code_or_zero(void); +/* The writer and the presence test of programs that read process.exitCode: + * the code reads undefined until an assignment (or a nonzero exit status + * the runtime chose, such as an uncaught exception's 1). */ +void scr_process_set_exit_code_tracked(double code); +bool scr_process_has_exit_code(void); /* process._exiting: true once the exit sequence began (process.exit or * the exit-listener runner set the flag). Never throws. */ extern SCR_TL bool scr_process_in_exit; diff --git a/packages/runtime/src/scr_message.c b/packages/runtime/src/scr_message.c index 1d01e5573..374b5469a 100644 --- a/packages/runtime/src/scr_message.c +++ b/packages/runtime/src/scr_message.c @@ -810,6 +810,12 @@ ScrDyn *scr_message_decode(const ScrMessage *message) { } if (!values[i]->v.arr.properties) values[i]->v.arr.properties = scr_dyn_new_obj(); scr_dyn_obj_set(values[i]->v.arr.properties, property->key, property->length, value); + } else if (node->kind == SCR_MESSAGE_ERROR && property->length == 5 && !memcmp(property->key, "stack", 5)) { + /* The new error's own `stack` accessor stores the value. */ + ScrStr *key = scr_str_new(property->key, property->length); + scr_dyn_key_set(values[i], key, value); + scr_str_release(key); + scr_dyn_release(value); } else scr_dyn_obj_set(values[i], property->key, property->length, value); } if (node->kind == SCR_MESSAGE_ERROR) { diff --git a/packages/runtime/src/scr_rc.h b/packages/runtime/src/scr_rc.h index eb28fb2e7..8611741da 100644 --- a/packages/runtime/src/scr_rc.h +++ b/packages/runtime/src/scr_rc.h @@ -352,6 +352,10 @@ void scr_sa_thread_starting(void); * pointer, or a process-wide cache keeps an immortal object. Its memory * then stays when it exits (scr_alloc.c). */ void scr_sa_thread_shares(void); +/* The event loop of the calling thread is about to wait with nothing ready + * (true), or is done waiting (false); worker threads are ignored. The + * memory of parked workers goes back while the loop waits (scr_alloc.c). */ +void scr_sa_loop_waiting(bool waiting); #define SCR_SA_FREE_FOREIGN(p) scr_sa_free_foreign(p) #else #define SCR_SA_FREE_FOREIGN(p) free(p) diff --git a/tests/corpus/5467-typed-array-loop-versions.ts b/tests/corpus/5467-typed-array-loop-versions.ts new file mode 100644 index 000000000..35a1da145 --- /dev/null +++ b/tests/corpus/5467-typed-array-loop-versions.ts @@ -0,0 +1,298 @@ +// Typed-array loops whose accesses are proved in bounds by one test before +// the loop run without per-element checks; every other entry runs the +// checked loop. Both must keep JavaScript's exact out-of-bounds behavior: +// reads of missing elements are undefined (NaN in arithmetic), writes are +// ignored, DataView accesses throw at the failing iteration, and +// fractional, negative and NaN indices never touch storage. + +const table: Int32Array = new Int32Array(256); +for (let n = 0; n < 256; n++) table[n] = (n * 2654435761) | 0; +const shortTable: Int32Array = new Int32Array(100); +for (let n = 0; n < 100; n++) shortTable[n] = n + 1; + +function masked(t: Int32Array, data: Uint8Array): number { + let h = 0; + for (let i = 0; i < data.length; i++) h = (h ^ t[(h ^ data[i]) & 0xff]) | 0; + return h; +} + +function maskedWrite(t: Int32Array, count: number): void { + for (let i = 0; i < count; i++) t[(i * 7) & 0xff] = i; +} + +const data = new Uint8Array(64); +for (let i = 0; i < data.length; i++) data[i] = (i * 37 + 11) & 0xff; +console.log("masked full", masked(table, data)); +console.log("masked short", masked(shortTable, data)); +const writable = new Int32Array(200); +maskedWrite(writable, 300); +console.log("masked write", writable[0], writable[7], writable[199], writable.length); + +// Counter plus an invariant base, in bounds and running off either end. +function shifted(src: Float64Array, out: Float64Array, base: number, n: number): number { + let sum = 0; + for (let i = 0; i < n; i++) { + out[base + i] = src[i] * 2; + sum += src[base + i - 1]; + } + return sum; +} + +const src = new Float64Array(8); +for (let i = 0; i < 8; i++) src[i] = i + 0.25; +for (const [base, n] of [ + [1, 4], + [0, 8], + [5, 8], + [-3, 6], + [2, 0], + [3, 2.5], +]) { + const out = new Float64Array(8); + const sum = shifted(src, out, base, n); + console.log("shifted", base, n, sum, Array.from(out).join(",")); +} + +// Descending loops, including ones that walk below zero. +function descending(a: Uint16Array, low: number): number { + let sum = 0; + for (let i = a.length - 1; i >= low; i--) { + a[i] = a[i] + 1000; + sum += a[i]; + } + return sum; +} +const u16 = new Uint16Array([65000, 1, 2, 3]); +console.log("descending", descending(u16, 0), Array.from(u16).join(",")); +console.log("descending below", descending(u16, -2), Array.from(u16).join(",")); + +// Fractional starts and offsets never name an element. +function fractional(a: Float32Array): string { + const seen: string[] = []; + for (let i = 0.5; i < 3; i++) seen.push(String(a[i])); + for (let i = 0; i < 3; i++) { + a[i + 0.5] = 99; + seen.push(String(a[i] + a[i - 0.5])); + } + return seen.join(" "); +} +console.log("fractional", fractional(new Float32Array([1.5, 2.5, 3.5]))); + +function nanIndex(a: Int8Array, offset: number): number { + let sum = 0; + for (let i = 0; i < a.length; i++) { + a[i + offset] = 120; + sum += a[i + offset] | 0; + } + return sum; +} +const i8 = new Int8Array([1, 2, 3]); +console.log("nan index", nanIndex(i8, NaN), nanIndex(i8, -0), Array.from(i8).join(",")); + +// Unproven limits keep the integer loop's guard: huge, infinite and NaN. +function limits(a: Uint8Array, limit: number): number { + let sum = 0; + for (let i = 0; i < limit; i++) { + if (i >= a.length + 2) break; + sum += a[i] | 0; + a[i] = i; + } + return sum; +} +const lim = new Uint8Array([5, 6, 7]); +console.log("limits", limits(lim, 2), limits(lim, 1e300), limits(lim, Infinity), limits(lim, NaN)); + +// A DataView access past the end throws after the earlier iterations ran. +const viewProgress: number[] = [0]; +function viewLoop(view: DataView, n: number): number { + let sum = 0; + for (let i = 0; i < n; i++) { + sum += view.getUint32(i * 4, true) + view.getUint8(i * 4 + 3); + viewProgress[0] = sum; + } + return sum; +} +function viewSum(view: DataView, n: number): number { + viewProgress[0] = -1; + try { + return viewLoop(view, n); + } catch (e) { + console.log("view throw", (e as Error).name, viewProgress[0]); + } + return viewProgress[0]; +} +const dvBytes = new Uint8Array(16); +for (let i = 0; i < 16; i++) dvBytes[i] = i * 3; +const view = new DataView(dvBytes.buffer); +console.log("view ok", viewSum(view, 4)); +console.log("view partial", viewSum(view, 6)); + +// Strided loops: the last counter value of a non-unit step is exact, so a +// record that ends at the last element still takes the unchecked loop and +// a partial trailing record does not. +function quads(words: Uint16Array): number { + let sum = 0; + for (let i = 0; i < words.length; i += 4) sum += words[i] + words[i + 3] * 2; + return sum; +} +console.log("quads", quads(new Uint16Array([1, 2, 3, 4, 5, 6, 7, 8]))); +console.log("quads partial", quads(new Uint16Array([1, 2, 3, 4, 5, 6]))); +function backTriples(words: Uint16Array): number { + let sum = 0; + for (let i = words.length - 1; i >= 0; i -= 3) sum = sum * 3 + words[i] + (words[i - 2] | 0); + return sum; +} +console.log("back triples", backTriples(new Uint16Array([1, 2, 3, 4, 5, 6]))); +console.log("back triples partial", backTriples(new Uint16Array([1, 2, 3, 4, 5, 6, 7]))); +function scaled(words: Uint16Array, n: number): number { + let sum = 0; + for (let i = 0; i < n; i++) sum += words[i * 2] - (words[(i + 1) * 2 - 1] | 0) + (words[9 - i] | 0); + return sum; +} +const scaledWords = new Uint16Array(10); +for (let i = 0; i < 10; i++) scaledWords[i] = i * i; +console.log("scaled", scaled(scaledWords, 5), scaled(scaledWords, 6), scaled(scaledWords, 11)); +// A negative zero index names element 0 and stays -0 as a number. +function negativeZero(words: Uint16Array, n: number): string { + const seen: string[] = []; + for (let i = 0; i < n; i++) { + const k = i * -1; + const m = (i - 1) * 2; + seen.push(`${words[k]}:${Object.is(k, -0)}:${words[m] | 0}:${Object.is(m, -0)}`); + } + return seen.join(" "); +} +console.log("negative zero", negativeZero(scaledWords, 3)); + +// Clamped and wrapping element types on the unchecked path. +function convert(c: Uint8ClampedArray, s: Int16Array): string { + for (let i = 0; i < 4; i++) { + c[i] = i * 100 - 50.5; + s[i] = i * 20000; + } + return Array.from(c).join(",") + " " + Array.from(s).join(","); +} +console.log("convert", convert(new Uint8ClampedArray(4), new Int16Array(4))); + +// Module receivers stay correct across calls in the loop body, including a +// call that writes the same arrays. +const left: Float64Array = new Float64Array(16); +const right: Float64Array = new Float64Array(16); +function poke(k: number): number { + right[k] = right[k] + 1; + return k; +} +for (let i = 0; i < 16; i++) left[i] = i; +let crossed = 0; +for (let i = 0; i < 16; i++) { + right[i] = left[i] * 3; + crossed += right[poke(i)]; +} +console.log("module receivers", crossed, right[0], right[15]); + +// A body that reassigns its receiver keeps reading the current binding. +function rebinding(first: Uint8Array, second: Uint8Array): number { + let current = first; + let sum = 0; + for (let i = 0; i < 4; i++) { + sum += current[i] | 0; + if (i === 1) current = second; + } + return sum; +} +console.log("rebinding", rebinding(new Uint8Array([1, 2, 3, 4]), new Uint8Array([10, 20]))); + +// A throwing body leaves the writes of earlier iterations in place. +function failing(a: Uint32Array, at: number): void { + for (let i = 0; i < a.length; i++) { + if (i === at) throw new Error("stop at " + i); + a[i] = 4294967295 - i; + } +} +const u32 = new Uint32Array(5); +try { + failing(u32, 3); +} catch (e) { + console.log((e as Error).message, Array.from(u32).join(",")); +} + +// An outer loop proved in bounds around an inner loop that is not. +function nested(grid: Int32Array, w: number, h: number): number { + let sum = 0; + for (let y = 0; y < h; y++) { + sum += grid[y & 7]; + for (let x = 0; x < w; x++) { + grid[y * w + x] = grid[y * w + x] + 1; + sum += grid[y * w + x] | 0; + } + } + return sum; +} +const grid = new Int32Array(16); +console.log("nested", nested(grid, 4, 4), nested(grid, 5, 4), nested(grid, 4, 5), grid.join(",")); + +// A matrix product over module-level constant arrays. +const N = 6; +const ma: Float64Array = new Float64Array(N * N); +const mb: Float64Array = new Float64Array(N * N); +const mc: Float64Array = new Float64Array(N * N); +for (let i = 0; i < N * N; i++) { + ma[i] = (i % 7) - 3; + mb[i] = (i % 5) * 0.5; +} +for (let i = 0; i < N; i++) { + for (let k = 0; k < N; k++) { + const aik = ma[i * N + k]; + const kOff = k * N; + const iOff = i * N; + for (let j = 0; j < N; j++) mc[iOff + j] += aik * mb[kOff + j]; + } +} +let trace = 0; +for (let i = 0; i < N; i++) trace += mc[i * N + i]; +console.log("matmul", trace, mc[1], mc[N * N - 1]); + +// Float16Array elements are two bytes; versioned loops bound them in +// elements, and stores round to nearest-even in the unchecked copy too. +function halves(src: Float16Array, out: Float16Array, n: number): number { + let sum = 0; + for (let i = 0; i < n; i++) { + out[i] = src[i + 1] * 1.001 + 0.1; + sum += src[i]; + } + return sum; +} +const halfSrc = new Float16Array(10); +for (let i = 0; i < halfSrc.length; i++) halfSrc[i] = i * 0.3 - 1; +const halfOut = new Float16Array(10); +console.log("f16 in bounds", halves(halfSrc, halfOut, 9), Array.from(halfOut).join(",")); +console.log("f16 past end", halves(halfSrc, halfOut, 12), Array.from(halfOut).join(",")); +const halfShort = new Float16Array(4); +console.log("f16 short out", halves(halfSrc, halfShort, 9), Array.from(halfShort).join(",")); +console.log("f16 rounding", halves(new Float16Array([65519, 2049, 1e-8, 0]), new Float16Array(3), 3)); + +function halfTable(t: Float16Array, n: number): number { + let sum = 0; + for (let i = 0; i < n; i++) sum += t[i & 7]; + return sum; +} +console.log("f16 masked", halfTable(new Float16Array([0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5]), 20)); +console.log("f16 masked short", halfTable(new Float16Array([0.5, 1.5, 2.5, 3.5, 4.5]), 20)); + +// DataView half-precision accesses are two bytes wide: the last odd offset +// throws. +function halfView(v: DataView, n: number): number { + let sum = 0; + for (let i = 0; i < n; i++) { + v.setFloat16(i * 2, i + 0.25, true); + sum += v.getFloat16(i * 2 + 1, false); + } + return sum; +} +const halfBytes = new DataView(new ArrayBuffer(11)); +console.log("f16 view", halfView(halfBytes, 4)); +try { + halfView(halfBytes, 6); +} catch (e) { + console.log((e as Error).message, new Uint8Array(halfBytes.buffer).join(",")); +} diff --git a/tests/corpus/5468-typed-array-loop-aliasing.ts b/tests/corpus/5468-typed-array-loop-aliasing.ts new file mode 100644 index 000000000..019913d1c --- /dev/null +++ b/tests/corpus/5468-typed-array-loop-aliasing.ts @@ -0,0 +1,107 @@ +// Element stores may alias any other view of the same buffer, whatever its +// element type, offset or length, but never a typed array's own length and +// data pointer. Loops that write through one view and read through another +// must observe every store in program order, also when the loop runs +// without per-element bounds checks. + +const buffer = new ArrayBuffer(64); +const f64 = new Float64Array(buffer); +const u8 = new Uint8Array(buffer); +const i32 = new Int32Array(buffer, 8, 8); + +// Writes through Float64Array observed through a Uint8Array in the same +// iteration. +let topBytes = 0; +for (let i = 0; i < f64.length; i++) { + f64[i] = -(i + 1) * 1.5; + topBytes += u8[i * 8 + 7]; +} +console.log("f64 then u8", topBytes, u8[7], u8[63]); + +// Byte stores observed through an overlapping Int32Array window. +let words = 0; +for (let i = 0; i < 32; i++) { + u8[8 + i] = i + 1; + words = (words + i32[i >> 2]) | 0; +} +console.log("u8 then i32", words, i32[0], i32[7]); + +// A shifted subarray reads what the previous iteration wrote: a prefix +// recurrence that any reordering or vector widening would break. +const chain = new Uint8Array(40); +chain[0] = 1; +const ahead = chain.subarray(1); +for (let i = 0; i < ahead.length; i++) ahead[i] = chain[i] + 2; +console.log("recurrence", chain[0], chain[1], chain[20], chain[39]); + +// Overlapping copies in both directions. +const values = new Int16Array(12); +for (let i = 0; i < values.length; i++) values[i] = i * 10; +const lower = values.subarray(0, 11); +const upper = values.subarray(1); +for (let i = 0; i < lower.length; i++) lower[i] = upper[i]; +console.log("copy down", Array.from(values).join(",")); +for (let i = 0; i < values.length; i++) values[i] = i * 10; +for (let i = 0; i < lower.length; i++) upper[i] = lower[i]; +console.log("copy up", Array.from(values).join(",")); + +// Same-array accumulation with a lagging read. +function lagged(a: Float64Array, lag: number): number { + for (let i = lag; i < a.length; i++) a[i] = a[i] + a[i - lag]; + return a[a.length - 1]; +} +const fib = new Float64Array(30); +fib[0] = 1; +fib[1] = 1; +console.log("lagged", lagged(fib, 1), lagged(fib, 2)); + +// Two parameters that are the same array, and a destination that aliases +// part of its own source. +function axpy(alpha: number, x: Float64Array, y: Float64Array, n: number): void { + for (let i = 0; i < n; i++) y[i] = y[i] + alpha * x[i]; +} +const same = new Float64Array([1, 2, 3, 4, 5, 6, 7, 8]); +axpy(2, same, same, same.length); +console.log("axpy same", Array.from(same).join(",")); +const whole = new Float64Array([1, 2, 3, 4, 5, 6, 7, 8]); +axpy(1, whole, whole.subarray(2), 6); +console.log("axpy shifted", Array.from(whole).join(",")); + +// Element stores never change a length or a module binding, even when a +// float store writes the bit pattern of a small length. +const meta: Float64Array = new Float64Array(4); +const lengths: number[] = []; +for (let i = 0; i < 4; i++) { + meta[i] = 5e-324 * (i + 1); + lengths.push(meta.length); +} +console.log("lengths", lengths.join(","), meta[3] > 0); + +// Global numbers next to typed-array stores keep their values. +let scale = 3; +const scaled: Float32Array = new Float32Array(6); +for (let i = 0; i < scaled.length; i++) { + scaled[i] = scale * i; + if (i === 2) scale = 0.5; +} +console.log("scaled", Array.from(scaled).join(",")); + +// Half-precision stores observed through integer views of the same buffer +// in the same iteration, and byte stores observed through Float16Array. +const halfBuffer = new ArrayBuffer(32); +const f16 = new Float16Array(halfBuffer); +const u16 = new Uint16Array(halfBuffer); +const halfBytes = new Uint8Array(halfBuffer); +let bits = 0; +for (let i = 0; i < f16.length; i++) { + f16[i] = (i - 8) * 0.75; + bits = (bits + u16[i]) | 0; +} +console.log("f16 then u16", bits, u16[0], u16[15]); +let halfSum = 0; +for (let i = 0; i < f16.length; i++) { + halfBytes[i * 2 + 1] = 0x3c + (i & 3); + halfBytes[i * 2] = i; + halfSum += f16[i]; +} +console.log("u8 then f16", halfSum, f16[0], f16[15]); diff --git a/tests/corpus/5469-typed-array-loop-initializer-rebinding.ts b/tests/corpus/5469-typed-array-loop-initializer-rebinding.ts new file mode 100644 index 000000000..f899b9d07 --- /dev/null +++ b/tests/corpus/5469-typed-array-loop-initializer-rebinding.ts @@ -0,0 +1,46 @@ +// A typed-array loop whose initializer rebinds the array it indexes: the +// bounds the loop relies on belong to the array the initializer installs, +// so reads past the shorter replacement are undefined (NaN in arithmetic) +// and writes past it are ignored, as in JavaScript. + +function sumAfterRebinding(data: Uint8Array, replacement: Uint8Array, n: number): number { + let total = 0; + for (let i = ((data = replacement), 0); i < n; i++) total += data[i]; + return total; +} + +function fillAfterRebinding(data: Int32Array, replacement: Int32Array, n: number): Int32Array { + for (let i = ((data = replacement), 0); i < n; i++) data[i] = i + 1; + return data; +} + +// The initializer assigns the receiver without declaring the counter. +function countAfterAssignment(data: Float64Array, replacement: Float64Array, n: number): number { + let i: number; + let total = 0; + for (data = replacement, i = 0; i < n; i++) total += data[i]; + return total; +} + +const long = new Uint8Array(64).fill(1); +const short = new Uint8Array(4).fill(2); +console.log("sum", sumAfterRebinding(long, short, 64)); +console.log("sum in bounds", sumAfterRebinding(long, short, 4)); + +const target = new Int32Array(64); +const small = new Int32Array(3); +const filled = fillAfterRebinding(target, small, 64); +console.log("fill", filled.length, Array.from(filled).join(","), target[0], target[63]); + +const wide = new Float64Array(32).fill(0.5); +const narrow = new Float64Array(2).fill(4); +console.log("count", countAfterAssignment(wide, narrow, 32)); +console.log("count in bounds", countAfterAssignment(wide, narrow, 2)); + +// A loop whose initializer leaves the receiver alone still runs normally. +function plainSum(data: Uint8Array, n: number): number { + let total = 0; + for (let i = 0; i < n; i++) total += data[i]; + return total; +} +console.log("plain", plainSum(long, 64), plainSum(short, 6)); diff --git a/tests/corpus/5816-fixed-into-unknown-rest.ts b/tests/corpus/5816-fixed-into-unknown-rest.ts new file mode 100644 index 000000000..f6d287021 --- /dev/null +++ b/tests/corpus/5816-fixed-into-unknown-rest.ts @@ -0,0 +1,39 @@ +// A function with only `unknown` parameters, or none, flows into a +// `(...args: unknown[]) => R` slot: each call passes the callers' +// arguments in order, undefined for a parameter the caller left out, and +// drops the extra ones, as JS calls do. Sync and async functions, stored +// in variables and arrays and passed as arguments. +type Handler = (...args: unknown[]) => void; +type AsyncHandler = (...args: unknown[]) => Promise; + +const none: Handler = () => console.log("none"); +const one: Handler = (a: unknown) => console.log("one", a); +const two: Handler = (a: unknown, b: unknown) => console.log("two", a, b); + +none(); +none(1, 2, 3); +one(); +one("x"); +one("x", "y"); +two(1); +two(1, [2], { three: 3 }); + +const handlers: Handler[] = [none, one, two]; +for (const h of handlers) h("all", true); + +function dispatch(h: Handler, ...args: unknown[]): void { + h(...args); +} +dispatch(two, "spread", null); +dispatch(one); + +const first: AsyncHandler = async (value: unknown) => value; +const pair: AsyncHandler = async (a: unknown, b: unknown) => [a, b]; + +async function main(): Promise { + console.log(await first(10, 20)); + console.log(await first()); + console.log(await pair("p")); + console.log(await pair("p", "q", "r")); +} +main().then(() => console.log("done")); diff --git a/tests/corpus/5916-dyn-error-stack-property.ts b/tests/corpus/5916-dyn-error-stack-property.ts new file mode 100644 index 000000000..2e1b42ef3 --- /dev/null +++ b/tests/corpus/5916-dyn-error-stack-property.ts @@ -0,0 +1,147 @@ +// Errors held as `any` or `unknown` have an own `stack` like Node's: a +// non-enumerable accessor that every error shares. The first read formats +// "Name: message" from the error's name and message at that point, and +// later reads answer the same string. Assigning a value replaces it, +// `delete` removes it, Object.keys, spread, Object.assign and +// JSON.stringify leave it out, `in` and Object.hasOwn find it, objects +// that inherit from an error read the error's stack. Typed and dynamic +// reads answer the same string, and a stack read through `any` captures +// frames. Only the first line is printed: frame lines differ between +// runtimes. + +class ParseError extends Error { + constructor(message: string) { + super(message); + this.name = "ParseError"; + } +} + +class PlainSubclass extends Error {} + +function dyn(value: unknown): any { + return value; +} + +function firstLine(value: unknown): string { + return typeof value === "string" ? JSON.stringify(value.split("\n")[0]) : `(${typeof value} ${String(value)})`; +} + +function describe(label: string, error: any): void { + console.log( + label, + "|", + typeof error.stack, + firstLine(error.stack), + "|", + Object.keys(error).join(","), + JSON.stringify(error), + "|", + "stack" in error, + Object.hasOwn(error, "stack"), + error.propertyIsEnumerable("stack"), + ); +} + +try { + throw new RangeError("thrown"); +} catch (error) { + describe("caught", error); +} + +const unknownError: unknown = new TypeError("bad thing"); +describe("unknown", unknownError); +describe("plain", dyn(new Error("plain"))); +describe("no message", dyn(new SyntaxError())); +describe("subclass", dyn(new ParseError("line 3"))); +describe("plain subclass", dyn(new PlainSubclass("inherited name"))); + +const coded = dyn(new Error("with code")); +coded.code = "E_CODE"; +describe("own property", coded); + +const descriptor = Object.getOwnPropertyDescriptor(dyn(new Error("a")), "stack")!; +const other = Object.getOwnPropertyDescriptor(dyn(new TypeError("b")), "stack")!; +console.log("descriptor", Object.keys(descriptor).join(","), descriptor.enumerable, descriptor.configurable); +console.log("shared accessors", typeof descriptor.get, descriptor.get === other.get, descriptor.set === other.set); +console.log("foreign receiver", descriptor.get!.call({}), firstLine(descriptor.get!.call(dyn(new TypeError("b"))))); +const subclassDescriptor = Object.getOwnPropertyDescriptor(dyn(new PlainSubclass("s")), "stack")!; +console.log("subclass descriptor", Object.keys(subclassDescriptor).join(","), subclassDescriptor.get === descriptor.get); + +const renamed = dyn(new Error("before")); +renamed.message = "after"; +renamed.name = "Renamed"; +console.log("first read", firstLine(renamed.stack)); +renamed.message = "later"; +console.log("later read", firstLine(renamed.stack)); +const subclassRenamed = dyn(new PlainSubclass("before")); +subclassRenamed.message = "after"; +console.log("subclass first read", firstLine(subclassRenamed.stack)); + +const replaced = dyn(new Error("replaced")); +replaced.stack = "custom stack\n at nowhere"; +describe("assigned", replaced); +console.log("assigned descriptor", Object.keys(Object.getOwnPropertyDescriptor(replaced, "stack")!).join(",")); +const subclassReplaced = dyn(new ParseError("replaced")); +subclassReplaced.stack = "subclass stack"; +console.log("subclass assigned", subclassReplaced.stack); +const typedReplaced = new ParseError("typed write"); +typedReplaced.stack = "typed subclass stack"; +console.log("typed subclass assigned", typedReplaced.stack, dyn(typedReplaced).stack); +const subclassNumbered = dyn(new PlainSubclass("numbered")); +subclassNumbered.stack = 7; +console.log("subclass number", subclassNumbered.stack, typeof subclassNumbered.stack); +subclassNumbered.stack = "string again"; +console.log("subclass string again", subclassNumbered.stack); +const numbered = dyn(new Error("numbered")); +numbered.stack = 5; +console.log("number", numbered.stack, typeof numbered.stack); +const removed = dyn(new Error("removed")); +delete removed.stack; +console.log("deleted", removed.stack, "stack" in removed, Object.hasOwn(removed, "stack")); + +const spreadSource = dyn(new Error("spread")); +console.log("spread", Object.keys({ ...spreadSource }).length, Object.keys(Object.assign({}, spreadSource)).length); +console.log("inherited", firstLine(Object.create(dyn(new RangeError("prototype"))).stack)); + +const typed = new Error("typed"); +const view = dyn(typed); +console.log("typed and dynamic", view.stack === typed.stack, firstLine(typed.stack)); +const typedSubclass = new ParseError("typed subclass"); +console.log("typed subclass", firstLine(dyn(typedSubclass).stack), dyn(typedSubclass).stack === typedSubclass.stack); +const assignedThroughView = new Error("typed"); +dyn(assignedThroughView).stack = "through the view"; +console.log("assigned through view", assignedThroughView.stack); + +function inner(): any { + return dyn(new Error("deep")); +} +function outer(): any { + return inner(); +} +const deep: string = outer().stack; +console.log("frames", deep.split("\n").length > 1, deep.includes("at inner"), deep.includes("at outer")); +function makeSubclass(): any { + return dyn(new PlainSubclass("deep")); +} +console.log("subclass frames", (makeSubclass().stack as string).includes("at makeSubclass")); + +const formatted = dyn(new Error("formatted")); +console.log("formatted before clone", firstLine(formatted.stack)); +console.log("clone keeps a formatted stack", firstLine(dyn(structuredClone(formatted)).stack)); + +class GrumpyError extends Error { + override get name(): string { + throw new Error("no name today"); + } +} +try { + console.log("throwing name getter", firstLine(dyn(new GrumpyError("grumpy")).stack)); +} catch (error) { + console.log("throwing name getter threw", (error as Error).message); +} + +function readKey(target: any, key: string): unknown { + return target[key]; +} +const computed = readKey(inner(), ["sta", "ck"].join("")) as string; +console.log("computed key", firstLine(computed), computed.includes("at inner")); diff --git a/tests/corpus/6004-class-instance-integrity.ts b/tests/corpus/6004-class-instance-integrity.ts new file mode 100644 index 000000000..7df516802 --- /dev/null +++ b/tests/corpus/6004-class-instance-integrity.ts @@ -0,0 +1,169 @@ +export {}; +import { inspect } from "node:util"; + +const message = (run: () => void): string => { + try { + run(); + return "no error"; + } catch (e) { + return `${e instanceof TypeError ? "TypeError" : "Error"}: ${(e as Error).message}`; + } +}; + +class Point { + x = 1; + y = 2; + label = "p"; + move(dx: number): void { + this.x += dx; + } +} + +const p = new Point(); +console.log(Object.isFrozen(p), Object.isSealed(p), Object.isExtensible(p)); +const same = Object.freeze(p); +console.log(same === p, Object.isFrozen(p), Object.isSealed(p), Object.isExtensible(p)); +console.log(message(() => { + p.x = 5; +})); +console.log(message(() => p.move(1))); +console.log(p.x, Object.keys(p)); +const q = new Point(); +q.x = 9; +console.log(q.x, Object.isFrozen(q)); + +class Point3 extends Point { + z = 3; +} +const p3 = new Point3(); +Object.freeze(p3); +console.log(message(() => { + p3.z = 1; +})); +const asBase: Point = new Point3(); +Object.freeze(asBase); +console.log(message(() => { + asBase.y = 0; +})); +console.log(asBase.y); + +class Counter { + n = 0; +} +const c = Object.seal(new Counter()); +c.n = 5; +console.log(c.n, Object.isSealed(c), Object.isFrozen(c), Object.isExtensible(c)); +const d = new Counter(); +Object.preventExtensions(d); +d.n = 2; +console.log(d.n, Object.isExtensible(d), Object.isSealed(d), Object.isFrozen(d)); +const loose: any = d; +console.log(message(() => { + loose.extra = 1; +})); + +class Config { + mode = "dev"; + level = 1; +} +const cfg = new Config(); +Object.defineProperty(cfg, "mode", { value: "prod", writable: false }); +console.log(cfg.mode, Object.keys(cfg), Object.values(cfg)); +console.log(message(() => { + cfg.mode = "x"; +})); +Object.defineProperty(cfg, "level", { enumerable: false }); +console.log(Object.keys(cfg), Object.entries(cfg), cfg.level); +Object.defineProperty(cfg, "level", { configurable: false }); +console.log(message(() => Object.defineProperty(cfg, "level", { enumerable: true }))); +console.log(JSON.stringify(Object.getOwnPropertyDescriptor(cfg, "mode"))); +console.log(JSON.stringify(Object.getOwnPropertyDescriptor(cfg, "level"))); +cfg.level = 7; +console.log(cfg.level); + +const fz = Object.freeze(new Config()); +console.log(message(() => Object.defineProperty(fz, "level", { value: 2 }))); +console.log(message(() => Object.defineProperty(fz, "level", { value: 1 }))); +console.log(fz.level, JSON.stringify(Object.getOwnPropertyDescriptor(fz, "level"))); + +class Frozen { + a = 1; + constructor() { + Object.freeze(this); + } +} +const frozenInCtor = new Frozen(); +console.log(frozenInCtor.a, Object.isFrozen(frozenInCtor)); +console.log(message(() => { + frozenInCtor.a = 2; +})); + +class Extended extends Frozen { + b = 2; +} +console.log(message(() => new Extended())); + +class Empty {} +const empty = new Empty(); +console.log(Object.isExtensible(empty), Object.isSealed(empty), Object.isFrozen(empty)); +Object.preventExtensions(empty); +console.log(Object.isExtensible(empty), Object.isSealed(empty), Object.isFrozen(empty)); + +class Totals { + count = 1; + items: string[] = []; +} +const t = new Totals(); +Object.freeze(t); +console.log(message(() => { + t.count += 1; +})); +console.log(message(() => { + t.count++; +})); +console.log(message(() => { + ({ value: t.count } = { value: 5 }); +})); +t.items.push("still mutable"); +console.log(t.count, t.items, Object.isFrozen(t.items)); +console.log(JSON.stringify(Object.getOwnPropertyDescriptor(t, "count"))); + +class Holder { + value: T; + constructor(value: T) { + this.value = value; + } +} +const h = Object.freeze(new Holder("text")); +console.log(h.value, Object.isFrozen(h)); +const h2 = new Holder(4); +Object.seal(h2); +h2.value = 5; +console.log(h2.value, Object.isSealed(h2)); + +class Settings { + mode = "dev"; + level = 1; +} +const settings = new Settings(); +console.log(settings); +Object.defineProperty(settings, "level", { enumerable: false }); +console.log(settings, inspect(settings)); +console.log([settings], { settings }); + +class Pair { + x = 1; + y = 2; +} +const pair = new Pair(); +Object.freeze(pair); +console.log(pair, Object.isFrozen(pair)); +const sealed = new Pair(); +(sealed as any).extra = "added"; +Object.seal(sealed); +console.log(sealed, Object.isSealed(sealed)); + +// JSON.stringify reads the same view: non-enumerable fields are skipped. +console.log(JSON.stringify(cfg), JSON.stringify(settings)); +console.log(JSON.stringify({ settings, pair, sealed })); +console.log(JSON.stringify([fz, t])); diff --git a/tests/corpus/6005-class-instance-integrity-sloppy.cjs b/tests/corpus/6005-class-instance-integrity-sloppy.cjs new file mode 100644 index 000000000..92c959d34 --- /dev/null +++ b/tests/corpus/6005-class-instance-integrity-sloppy.cjs @@ -0,0 +1,40 @@ +class Box { + constructor() { + this.x = 1; + } + bump() { + this.x = 2; + } +} + +const b = new Box(); +Object.freeze(b); +b.x = 5; +console.log(b.x, Object.isFrozen(b)); + +function strictWrite() { + "use strict"; + try { + b.x = 6; + } catch (e) { + console.log(e instanceof TypeError, e.message); + } +} +strictWrite(); + +try { + b.bump(); +} catch (e) { + console.log("class body:", e.message); +} +console.log(b.x); + +const s = new Box(); +Object.seal(s); +s.x = 3; +console.log(s.x, Object.isSealed(s), Object.isFrozen(s)); + +const plain = new Box(); +Object.defineProperty(plain, "x", { writable: false }); +plain.x = 9; +console.log(plain.x, JSON.stringify(Object.getOwnPropertyDescriptor(plain, "x"))); diff --git a/tests/corpus/6064-protocol-subinterface-assertions.ts b/tests/corpus/6064-protocol-subinterface-assertions.ts deleted file mode 100644 index 1d1656c5c..000000000 --- a/tests/corpus/6064-protocol-subinterface-assertions.ts +++ /dev/null @@ -1,80 +0,0 @@ -// An interface that extends a class-backed protocol holds values of that -// protocol, so asserting a base-typed value to it (`type as -// TupleTypeReference`, where TupleTypeReference redeclares `getTarget(): -// TupleType`) keeps the class instance: its methods run and its fields read -// as on the base type. -interface Type { - readonly flags: number; - getTarget(): Type | undefined; - isTypeReference(): this is TypeReference; - isTupleType(): this is TupleType; -} -interface ObjectType extends Type {} -interface TypeReference extends ObjectType { - getTarget(): Type; -} -interface InterfaceType extends TypeReference {} -interface TupleType extends InterfaceType { - readonly elementFlags: readonly number[]; - readonly fixedLength: number; -} -interface TupleTypeReference extends TypeReference { - getTarget(): TupleType; -} -class CompilerType { - flags: number; - target: CompilerType | undefined; - elementFlags: number[] = []; - fixedLength = 0; - constructor(flags: number, target?: CompilerType) { - this.flags = flags; - this.target = target; - } - getTarget(): CompilerType | undefined { - return this.target; - } - isTypeReference(): boolean { - return (this.flags & 4) !== 0; - } - isTupleType(): boolean { - return (this.flags & 8) !== 0; - } -} -const tuple = new CompilerType(8); -tuple.elementFlags = [1, 1, 2]; -tuple.fixedLength = 2; -const reference = new CompilerType(4, tuple); -const publicType = (t: CompilerType): Type => t as unknown as Type; -function describe(type: Type): string { - if (type.isTypeReference() && type.getTarget().isTupleType()) { - const ref = type as TupleTypeReference; - const target = ref.getTarget(); - return `tuple ${target.fixedLength} [${target.elementFlags.join(",")}]`; - } - return `flags ${type.flags}`; -} -const seen = new Set([publicType(tuple)]); -console.log(describe(publicType(reference)), describe(publicType(tuple)), seen.has(publicType(tuple))); - -interface Shape { - readonly sides: number; - area(): number; -} -interface Square extends Shape { - readonly side: number; -} -class SquareImpl implements Shape { - readonly sides = 4; - readonly side: number; - constructor(side: number) { - this.side = side; - } - area(): number { - return this.side * this.side; - } -} -const shapes: Shape[] = [new SquareImpl(3), new SquareImpl(5)]; -for (const shape of shapes) { - const square = shape as Square; - console.log(square.sides, square.side, square.area(), square === shape); -} diff --git a/tests/corpus/6398-conditional-fixed-arity.ts b/tests/corpus/6398-conditional-fixed-arity.ts new file mode 100644 index 000000000..beb75583a --- /dev/null +++ b/tests/corpus/6398-conditional-fixed-arity.ts @@ -0,0 +1,40 @@ +// Conditional types over function types with fixed `any` parameters, as +// a generic wrapper's specialization sees them: a function that needs +// more arguments than the pattern passes takes the false branch. +type FirstResult = F extends (a: any) => infer R ? R : "takes more"; +type NoArgResult = F extends () => infer R ? R : "takes some"; + +function label any>(fn: F, value: FirstResult): string { + const local: FirstResult = value; + return `${fn.name}: ${typeof local} ${String(local)}`; +} + +function labelNoArgs any>(fn: F, value: NoArgResult): string { + const local: NoArgResult = value; + return `${fn.name}: ${typeof local} ${String(local)}`; +} + +function none(): boolean { + return true; +} +function one(a: string): number { + return a.length; +} +function two(a: string, b: string): number { + return a.length + b.length; +} +function optional(a: string, b?: string): number { + return a.length + (b?.length ?? 0); +} +function rest(a: string, ...more: string[]): number { + return a.length + more.length; +} + +console.log(label(none, true)); +console.log(label(one, 3)); +console.log(label(two, "takes more")); +console.log(label(optional, 4)); +console.log(label(rest, 5)); +console.log(labelNoArgs(none, false)); +console.log(labelNoArgs(one, "takes some")); +console.log(labelNoArgs(optional, "takes some")); diff --git a/tests/corpus/6400-dyn-error-stack-property-js.js b/tests/corpus/6400-dyn-error-stack-property-js.js new file mode 100644 index 000000000..f0f666103 --- /dev/null +++ b/tests/corpus/6400-dyn-error-stack-property-js.js @@ -0,0 +1,148 @@ +// In JavaScript sources, errors have an own `stack` like Node's: a +// non-enumerable accessor that every error shares. The first read formats +// "Name: message" from the error's name and message at that point, and +// later reads answer the same string. Assigning a value replaces it, +// `delete` removes it, Object.keys, spread, Object.assign and +// JSON.stringify leave it out, `in` and Object.hasOwn find it, objects +// that inherit from an error read the error's stack. Typed and dynamic +// reads answer the same string, and a stack read through `any` captures +// frames. Only the first line is printed: frame lines differ between +// runtimes. + +class ParseError extends Error { + constructor(message) { + super(message); + this.name = "ParseError"; + } +} + +class PlainSubclass extends Error {} + +function dyn(value) { + return value; +} + +function firstLine(value) { + return typeof value === "string" ? JSON.stringify(value.split("\n")[0]) : `(${typeof value} ${String(value)})`; +} + +function describe(label, error) { + console.log( + label, + "|", + typeof error.stack, + firstLine(error.stack), + "|", + Object.keys(error).join(","), + JSON.stringify(error), + "|", + "stack" in error, + Object.hasOwn(error, "stack"), + error.propertyIsEnumerable("stack"), + ); +} + +try { + throw new RangeError("thrown"); +} catch (error) { + describe("caught", error); +} + +const unknownError = new TypeError("bad thing"); +describe("unknown", unknownError); +describe("plain", dyn(new Error("plain"))); +describe("no message", dyn(new SyntaxError())); +describe("subclass", dyn(new ParseError("line 3"))); +describe("plain subclass", dyn(new PlainSubclass("inherited name"))); + +const coded = dyn(new Error("with code")); +coded.code = "E_CODE"; +describe("own property", coded); + +const descriptor = dyn(Object.getOwnPropertyDescriptor(dyn(new Error("a")), "stack")); +const other = dyn(Object.getOwnPropertyDescriptor(dyn(new TypeError("b")), "stack")); +console.log("descriptor", Object.keys(descriptor).join(","), descriptor.enumerable, descriptor.configurable); +console.log("shared accessors", typeof descriptor.get, descriptor.get === other.get, descriptor.set === other.set); +console.log("foreign receiver", descriptor.get.call({}), firstLine(descriptor.get.call(dyn(new TypeError("b"))))); +const subclassDescriptor = dyn(Object.getOwnPropertyDescriptor(dyn(new PlainSubclass("s")), "stack")); +console.log("subclass descriptor", Object.keys(subclassDescriptor).join(","), subclassDescriptor.get === descriptor.get); + +const renamed = dyn(new Error("before")); +renamed.message = "after"; +renamed.name = "Renamed"; +console.log("first read", firstLine(renamed.stack)); +renamed.message = "later"; +console.log("later read", firstLine(renamed.stack)); +const subclassRenamed = dyn(new PlainSubclass("before")); +subclassRenamed.message = "after"; +console.log("subclass first read", firstLine(subclassRenamed.stack)); + +const replaced = dyn(new Error("replaced")); +replaced.stack = "custom stack\n at nowhere"; +describe("assigned", replaced); +console.log("assigned descriptor", Object.keys(Object.getOwnPropertyDescriptor(replaced, "stack")).join(",")); +const subclassReplaced = dyn(new ParseError("replaced")); +subclassReplaced.stack = "subclass stack"; +console.log("subclass assigned", subclassReplaced.stack); +const typedReplaced = new ParseError("typed write"); +typedReplaced.stack = "typed subclass stack"; +console.log("typed subclass assigned", typedReplaced.stack, dyn(typedReplaced).stack); +const subclassNumbered = dyn(new PlainSubclass("numbered")); +subclassNumbered.stack = 7; +console.log("subclass number", subclassNumbered.stack, typeof subclassNumbered.stack); +subclassNumbered.stack = "string again"; +console.log("subclass string again", subclassNumbered.stack); +const numbered = dyn(new Error("numbered")); +numbered.stack = 5; +console.log("number", numbered.stack, typeof numbered.stack); +const removed = dyn(new Error("removed")); +delete removed.stack; +console.log("deleted", removed.stack, "stack" in removed, Object.hasOwn(removed, "stack")); + +const spreadSource = dyn(new Error("spread")); +console.log("spread", Object.keys({ ...spreadSource }).length, Object.keys(Object.assign({}, spreadSource)).length); +console.log("inherited", firstLine(Object.create(dyn(new RangeError("prototype"))).stack)); + +const typed = new Error("typed"); +const view = dyn(typed); +console.log("typed and dynamic", view.stack === typed.stack, firstLine(typed.stack)); +const typedSubclass = new ParseError("typed subclass"); +console.log("typed subclass", firstLine(dyn(typedSubclass).stack), dyn(typedSubclass).stack === typedSubclass.stack); +const assignedThroughView = new Error("typed"); +dyn(assignedThroughView).stack = "through the view"; +console.log("assigned through view", assignedThroughView.stack); + +function inner() { + return dyn(new Error("deep")); +} +function outer() { + return inner(); +} +const deep = outer().stack; +console.log("frames", deep.split("\n").length > 1, deep.includes("at inner"), deep.includes("at outer")); +function makeSubclass() { + return dyn(new PlainSubclass("deep")); +} +console.log("subclass frames", makeSubclass().stack.includes("at makeSubclass")); + +const formatted = dyn(new Error("formatted")); +console.log("formatted before clone", firstLine(formatted.stack)); +console.log("clone keeps a formatted stack", firstLine(dyn(structuredClone(formatted)).stack)); + +class GrumpyError extends Error { + /** @returns {string} */ + get name() { + throw new Error("no name today"); + } +} +try { + console.log("throwing name getter", firstLine(dyn(new GrumpyError("grumpy")).stack)); +} catch (error) { + console.log("throwing name getter threw", error.message); +} + +function readKey(target, key) { + return target[key]; +} +const computed = readKey(inner(), ["sta", "ck"].join("")); +console.log("computed key", firstLine(computed), computed.includes("at inner")); diff --git a/tests/corpus/6401-dyn-error-own-property-names.ts b/tests/corpus/6401-dyn-error-own-property-names.ts new file mode 100644 index 000000000..1020cf014 --- /dev/null +++ b/tests/corpus/6401-dyn-error-own-property-names.ts @@ -0,0 +1,44 @@ +// The own property names of errors held as `any` or `unknown` match +// Node's: Object.getOwnPropertyNames, Reflect.ownKeys and +// Object.getOwnPropertyDescriptors list `stack`, `message` and the +// properties the error has, in order, and nothing else. + +class PlainSubclass extends Error {} + +function dyn(value: unknown): any { + return value; +} + +function names(label: string, error: any): void { + console.log( + label, + "|", + Object.getOwnPropertyNames(error).join(","), + "|", + Reflect.ownKeys(error).map(String).join(","), + "|", + Object.keys(Object.getOwnPropertyDescriptors(error)).join(","), + ); +} + +names("error", dyn(new Error("plain"))); +names("type error", dyn(new TypeError("bad thing"))); +names("no message", dyn(new RangeError())); +names("cause", dyn(new Error("outer", { cause: new Error("inner") }))); +names("subclass", dyn(new PlainSubclass("sub"))); + +const unknownError: unknown = new SyntaxError("unexpected"); +names("unknown", unknownError); + +const coded = dyn(new Error("with code")); +coded.code = "E_CODE"; +coded.detail = 1; +names("own properties", coded); + +try { + throw new URIError("thrown"); +} catch (error) { + names("caught", error); +} + +console.log("marker", Object.getOwnPropertyNames(dyn(new Error("x"))).some((name) => name.startsWith("%"))); diff --git a/tests/corpus/6402-dyn-error-own-property-names-js.js b/tests/corpus/6402-dyn-error-own-property-names-js.js new file mode 100644 index 000000000..6f6ca7fb7 --- /dev/null +++ b/tests/corpus/6402-dyn-error-own-property-names-js.js @@ -0,0 +1,44 @@ +// In JavaScript sources, the own property names of errors match +// Node's: Object.getOwnPropertyNames, Reflect.ownKeys and +// Object.getOwnPropertyDescriptors list `stack`, `message` and the +// properties the error has, in order, and nothing else. + +class PlainSubclass extends Error {} + +function dyn(value) { + return value; +} + +function names(label, error) { + console.log( + label, + "|", + Object.getOwnPropertyNames(error).join(","), + "|", + Reflect.ownKeys(error).map(String).join(","), + "|", + Object.keys(Object.getOwnPropertyDescriptors(error)).join(","), + ); +} + +names("error", dyn(new Error("plain"))); +names("type error", dyn(new TypeError("bad thing"))); +names("no message", dyn(new RangeError())); +names("cause", dyn(new Error("outer", { cause: new Error("inner") }))); +names("subclass", dyn(new PlainSubclass("sub"))); + +const unknownError = new SyntaxError("unexpected"); +names("unknown", unknownError); + +const coded = dyn(new Error("with code")); +coded.code = "E_CODE"; +coded.detail = 1; +names("own properties", coded); + +try { + throw new URIError("thrown"); +} catch (error) { + names("caught", error); +} + +console.log("marker", Object.getOwnPropertyNames(dyn(new Error("x"))).some((name) => name.startsWith("%"))); diff --git a/tests/corpus/6420-process-send-presence.ts b/tests/corpus/6420-process-send-presence.ts new file mode 100644 index 000000000..59773ab44 --- /dev/null +++ b/tests/corpus/6420-process-send-presence.ts @@ -0,0 +1,24 @@ +// process.send exists only with an IPC channel: presence checks in every +// truthiness position, typeof, and guarded calls. +const viaIpc = Boolean(process.send); +console.log("Boolean", viaIpc); +console.log("not", !process.send, !!process.disconnect); +console.log("typeof", typeof process.send, typeof process.disconnect); +console.log("ternary", process.send ? "ipc" : "stdout"); +if (process.send) { + process.send({ kind: "log", line: "never" }); +} else { + console.log("if: no channel"); +} +let polls = 0; +while (process.send && polls < 3) polls++; +console.log("while", polls); +process.send && process.send({ unused: true }); +const sink = !process.send || !process.disconnect ? "console" : "ipc"; +console.log("or", sink); +console.log("optional", process.send?.({ value: 1 })); +try { + process.send!({ value: 2 }); +} catch (e) { + console.log("direct", (e as Error).name, (e as Error).message); +} diff --git a/tests/corpus/6421-process-send-presence-fork/main.ts b/tests/corpus/6421-process-send-presence-fork/main.ts new file mode 100644 index 000000000..11a634672 --- /dev/null +++ b/tests/corpus/6421-process-send-presence-fork/main.ts @@ -0,0 +1,13 @@ +// process.send and process.disconnect exist in a forked child (an IPC +// channel) and not in the parent; the child's presence checks pick the +// channel and its guarded send reaches the parent. +import { fork } from "node:child_process"; + +console.log("parent", Boolean(process.send), typeof process.send, typeof process.disconnect); +const child = fork(new URL("./worker.ts", import.meta.url), [], { + stdio: ["ignore", "inherit", "inherit", "ipc"], +}); +child.on("message", (message: { sink: string; send: string; disconnect: string }) => { + console.log("child says", message.sink, message.send, message.disconnect); +}); +child.on("exit", (code) => console.log("child exit", code)); diff --git a/tests/corpus/6421-process-send-presence-fork/worker.ts b/tests/corpus/6421-process-send-presence-fork/worker.ts new file mode 100644 index 000000000..81e8288a7 --- /dev/null +++ b/tests/corpus/6421-process-send-presence-fork/worker.ts @@ -0,0 +1,6 @@ +const sink = process.send ? "ipc" : "stdout"; +if (process.send) { + process.send({ sink, send: typeof process.send, disconnect: typeof process.disconnect }); +} +if (!process.send) console.log("unreachable"); +process.disconnect(); diff --git a/tests/corpus/6422-process-exitcode-read.ts b/tests/corpus/6422-process-exitcode-read.ts new file mode 100644 index 000000000..c320b4a0d --- /dev/null +++ b/tests/corpus/6422-process-exitcode-read.ts @@ -0,0 +1,22 @@ +// @exit: 7 +// process.exitCode: unset reads undefined, assignments (including 0) read back, +// and process.exit(n) assigns the code before 'exit' listeners run. +console.log("initial", process.exitCode); +process.on("exit", (code) => { + console.log("exit listener", code, process.exitCode); +}); +process.exitCode = 0; +console.log("after 0", process.exitCode); +process.exitCode = 4; +const seen = process.exitCode; +console.log("after 4", seen, typeof process.exitCode); +process.exitCode = process.exitCode ?? 9; +console.log("kept", process.exitCode); +try { + process.exitCode = 1.5; +} catch (e) { + console.log("rejected", (e as Error).message, process.exitCode); +} +setTimeout(() => { + process.exit(7); +}, 1); diff --git a/tests/corpus/6423-process-exitcode-default.ts b/tests/corpus/6423-process-exitcode-default.ts new file mode 100644 index 000000000..5dbc72294 --- /dev/null +++ b/tests/corpus/6423-process-exitcode-default.ts @@ -0,0 +1,10 @@ +// @exit: 2 +// The exit status follows process.exitCode when the program ends normally. +function settle(failed: boolean): void { + if (failed) process.exitCode = process.exitCode ?? 2; +} +console.log("start", process.exitCode === undefined); +settle(true); +settle(true); +console.log("code", process.exitCode); +process.on("exit", () => console.log("exiting with", process.exitCode)); diff --git a/tests/corpus/6424-process-exit-keeps-exitcode.ts b/tests/corpus/6424-process-exit-keeps-exitcode.ts new file mode 100644 index 000000000..e5a0b86d2 --- /dev/null +++ b/tests/corpus/6424-process-exit-keeps-exitcode.ts @@ -0,0 +1,5 @@ +// @exit: 5 +// process.exit() without an argument keeps process.exitCode. +process.exitCode = 5; +process.on("exit", (code) => console.log("exit", code, process.exitCode)); +process.exit(); diff --git a/tests/corpus/6425-stream-hook-wider-chunk.ts b/tests/corpus/6425-stream-hook-wider-chunk.ts new file mode 100644 index 000000000..be46b18be --- /dev/null +++ b/tests/corpus/6425-stream-hook-wider-chunk.ts @@ -0,0 +1,46 @@ +// Stream hooks may declare a supertype of what Node passes: chunks typed +// Buffer | string still arrive as Buffers. +import { Transform, Writable } from "node:stream"; + +class LineSink extends Writable { + private rest = ""; + readonly lines: string[] = []; + _write(chunk: Buffer | string, encoding: BufferEncoding, cb: (err?: Error | null) => void): void { + console.log("chunk", typeof chunk, Buffer.isBuffer(chunk), encoding); + const parts = (this.rest + chunk.toString()).split("\n"); + this.rest = parts.pop() ?? ""; + this.lines.push(...parts); + cb(); + } + _final(cb: (err?: Error | null) => void): void { + if (this.rest !== "") this.lines.push(this.rest); + cb(); + } +} + +class Upper extends Transform { + _transform(chunk: string | Buffer, _enc: string, cb: (err?: Error | null, data?: string) => void): void { + cb(null, chunk.toString().toUpperCase()); + } +} + +class Failing extends Writable { + _write(chunk: Uint8Array | string, _enc: BufferEncoding, cb: (err?: Error | null) => void): void { + cb(new Error(`refused ${chunk.toString().length} bytes`)); + } +} + +const sink = new LineSink(); +sink.write("a\nb"); +sink.write(Buffer.from("c\nd")); +sink.end("e"); +sink.on("finish", () => console.log("lines", JSON.stringify(sink.lines))); + +const up = new Upper(); +up.on("data", (d: Buffer) => console.log("upper", d.toString())); +up.write("hello "); +up.end("world"); + +const bad = new Failing(); +bad.on("error", (e) => console.log("error", e.message)); +bad.write("xyz"); diff --git a/tests/corpus/6426-promise-race-void-entry.ts b/tests/corpus/6426-promise-race-void-entry.ts new file mode 100644 index 000000000..b33f3b664 --- /dev/null +++ b/tests/corpus/6426-promise-race-void-entry.ts @@ -0,0 +1,31 @@ +// Promise.race over Promise and Promise: the void +// entry fulfills the race with undefined. +interface Msg { id: string } +function delay(ms: number, value: T): Promise { + return new Promise((resolve) => setTimeout(() => resolve(value), ms)); +} +function poll(ms: number, id: string | null): Promise { + return delay(ms, id === null ? undefined : { id }); +} +function exited(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); +} +async function main(): Promise { + const a = await Promise.race([poll(5, "m1"), exited(50)]); + console.log("message first:", a ? a.id : "none"); + const b = await Promise.race([poll(50, "m2"), exited(5)]); + console.log("exit first:", b === undefined, b ? b.id : "none"); + const c = await Promise.race([exited(5), poll(1, null)]); + console.log("empty poll first:", c); + const settled = Promise.resolve(); + const d = await Promise.race([settled, poll(0, "m3")]); + console.log("settled void entry wins:", d); + const n = await Promise.race([delay(20, 42), exited(1)]); + console.log("number or void:", n); + try { + await Promise.race([poll(20, "late"), Promise.reject(new Error("boom"))]); + } catch (e) { + console.log("rejected:", (e as Error).message); + } +} +void main(); diff --git a/tests/corpus/6427-http-request-options-type.ts b/tests/corpus/6427-http-request-options-type.ts new file mode 100644 index 000000000..17cb2d855 --- /dev/null +++ b/tests/corpus/6427-http-request-options-type.ts @@ -0,0 +1,10 @@ +// http.RequestOptions as a declared type: a checked-dynamic option record. +import type http from "node:http"; +const socket = process.env.NO_SUCH_SOCKET_VAR; +const base: http.RequestOptions = socket ? { socketPath: socket } : { host: "127.0.0.1", port: 8080 }; +const full: http.RequestOptions = { ...base, path: "/x", method: "POST", headers: { "x-a": "1" } }; +console.log(JSON.stringify(base), JSON.stringify(full)); +function describe(o: http.RequestOptions): string { + return `${String(o.host)}:${String(o.port)}${String(o.path ?? "/")}`; +} +console.log(describe(full), describe({ host: "h" })); diff --git a/tests/corpus/6428-promisify-callback-functions.ts b/tests/corpus/6428-promisify-callback-functions.ts new file mode 100644 index 000000000..b899dd292 --- /dev/null +++ b/tests/corpus/6428-promisify-callback-functions.ts @@ -0,0 +1,80 @@ +// util.promisify over typed callback-only functions (bound methods, one with +// an optional callback, a function declaration, an inline arrow, a function +// that returns a value): the error argument rejects, the value fulfills, the +// return value is ignored, a synchronous throw rejects, and settlement is +// async. +import { promisify } from "node:util"; + +class Connection { + private closed = false; + private readonly name: string; + constructor(name: string) { + this.name = name; + } + close(callback: (error?: Error) => void): void { + setTimeout(() => { + if (this.closed) { + callback(new Error(`${this.name} already closed`)); + return; + } + this.closed = true; + callback(); + }, 1); + } + flush(callback?: (error?: Error) => void): void { + setImmediate(() => callback?.(this.closed ? new Error("flushed after close") : undefined)); + } + stats(callback: (error: Error | null, open: number) => void): void { + setImmediate(() => (this.closed ? callback(new RangeError("closed"), 0) : callback(null, 3))); + } +} + +function boom(callback: (error: Error | null) => void): void { + if (callback !== undefined) throw new TypeError("synchronous failure"); +} + +let pending = 0; +function size(callback: (error: Error | null, size: number) => void): number { + setImmediate(() => callback(null, 4)); + return ++pending; +} + +async function main(): Promise { + const c = new Connection("db"); + const stats = promisify(c.stats.bind(c)); + console.log("open", await stats()); + await promisify(c.flush.bind(c))(); + console.log("flushed"); + const closeAsync = promisify(c.close.bind(c)); + await closeAsync(); + console.log("closed once"); + try { + await closeAsync(); + } catch (e) { + console.log("second close:", (e as Error).message); + } + try { + await stats(); + } catch (e) { + console.log("stats:", (e as Error).name, (e as Error).message); + } + await promisify(c.flush.bind(c))().catch((e) => console.log("flush:", (e as Error).message)); + try { + await promisify(boom)(); + } catch (e) { + console.log("sync throw:", (e as Error).name, (e as Error).message); + } + const order: string[] = []; + const tick = promisify((done: (error?: Error) => void) => { + order.push("called"); + done(); + }); + const p = tick().then(() => order.push("settled")); + order.push("returned"); + await p; + console.log(order.join(", ")); + await promisify(new Connection("inline").close.bind(new Connection("inline")))(); + console.log("inline done"); + console.log("size", await promisify(size)(), pending); +} +void main(); diff --git a/tests/corpus/6429-esm-js-dirname.mjs b/tests/corpus/6429-esm-js-dirname.mjs new file mode 100644 index 000000000..2ecc4088a --- /dev/null +++ b/tests/corpus/6429-esm-js-dirname.mjs @@ -0,0 +1,15 @@ +// A JavaScript ES module has no __dirname or __filename (Node throws a +// ReferenceError where they are read); import.meta carries the location. +// TypeScript files keep the CommonJS globals as the source path. +try { + console.log(__dirname); +} catch (error) { + console.log("no __dirname:", error instanceof Error); +} +console.log(import.meta.filename.endsWith("6429-esm-js-dirname.mjs")); +console.log(typeof __dirname, typeof __filename); +try { + console.log(__filename.length); +} catch (error) { + console.log(error.name, error.message); +} diff --git a/tests/corpus/6445-rest-function-fixed-slot.ts b/tests/corpus/6445-rest-function-fixed-slot.ts new file mode 100644 index 000000000..e78e83836 --- /dev/null +++ b/tests/corpus/6445-rest-function-fixed-slot.ts @@ -0,0 +1,49 @@ +// A rest-parameter function in a slot with fixed parameters: the slot's +// arguments arrive in the rest array, as in a JavaScript call. +function log(...messages: string[]): void { + console.log(`log(${messages.length}):`, messages.join(" | ")); +} +function tagged(tag: string, ...values: number[]): number { + console.log(tag, values.join(",")); + return values.length; +} + +class Line { + private readonly sink: (m: string) => void; + constructor(sink: (m: string) => void) { + this.sink = sink; + } + emit(m: string): void { + this.sink(m); + } +} + +new Line(log).emit("one"); +const pair: (a: string, b: string) => void = log; +pair("x", "y"); +const none: () => void = log; +none(); +const count: (tag: string, a: number, b: number) => number = tagged; +console.log("count", count("t", 1, 2)); +["a", "b"].forEach((m) => new Line(log).emit(m)); + +// Nullable and optional slots: the value takes the slot's function arm. +class OptionalLine { + private readonly sink: ((m: string) => void) | null; + constructor(sink: ((m: string) => void) | null) { + this.sink = sink; + } + emit(m: string): void { + if (this.sink) this.sink(m); + else console.log("no sink:", m); + } +} +new OptionalLine(log).emit("nullable"); +new OptionalLine(null).emit("dropped"); +function relay(m: string, sink?: (m: string) => void): void { + sink?.(m); +} +relay("optional", log); +relay("absent"); +const maybe: ((a: string, b: string) => void) | undefined = log; +maybe?.("p", "q"); diff --git a/tests/corpus/6446-rest-function-nested-declaration.ts b/tests/corpus/6446-rest-function-nested-declaration.ts new file mode 100644 index 000000000..1b2d7aa17 --- /dev/null +++ b/tests/corpus/6446-rest-function-nested-declaration.ts @@ -0,0 +1,26 @@ +// Nested rest-parameter function declarations passed to a fixed slot. +class Line { + private readonly sink: (m: string) => void; + constructor(sink: (m: string) => void) { + this.sink = sink; + } + emit(m: string): void { + this.sink(m); + } +} +interface Logger { + line: () => Line; + log: (...messages: string[]) => void; +} +function makeLogger(prefix: string): Logger { + return { + line: () => new Line(log), + log, + }; + function log(...messages: string[]) { + console.log(prefix, messages.join(" ")); + } +} +const logger = makeLogger(">"); +logger.log("a", "b"); +logger.line().emit("c"); diff --git a/tests/corpus/6460-in-union-optional-slots.ts b/tests/corpus/6460-in-union-optional-slots.ts new file mode 100644 index 000000000..f381217b9 --- /dev/null +++ b/tests/corpus/6460-in-union-optional-slots.ts @@ -0,0 +1,53 @@ +// `"k" in u` over a union of records where some arms declare `k` as an +// optional slot: those arms answer by the value's own property presence +// (an explicit undefined is present, an omitted or deleted key is not), +// the other arms by their tag. +type Cond = { type: string; value?: string }; +type HandleRoute = { handle: string; src?: string; dest?: string; status?: number }; +type SourceRoute = { + src: string; + dest?: string; + headers?: { [key: string]: string }; + middlewarePath?: string; + continue?: boolean; + status?: number; + has?: Cond[]; +}; +type Route = HandleRoute | SourceRoute; + +const routes: Route[] = [ + { handle: "filesystem" }, + { src: "/a", middlewarePath: "/mw" }, + { src: "/b", dest: "/c", status: undefined }, + { handle: "miss", src: "/x", dest: undefined }, + { src: "/d", has: [{ type: "header" }] }, +]; + +for (const r of routes) { + console.log("handle" in r, "middlewarePath" in r, "dest" in r, "status" in r, "has" in r, "toString" in r); +} + +// A deleted optional key leaves the value; a later write brings it back. +const second = routes[1]!; +if ("middlewarePath" in second) { + delete second.middlewarePath; +} +console.log("middlewarePath" in second, JSON.stringify(second)); +if (!("handle" in second)) { + second.middlewarePath = "/again"; +} +console.log("middlewarePath" in second, JSON.stringify(second)); + +// The receiver evaluates once, even when it is a call. +let calls = 0; +function pick(i: number): Route { + calls++; + return routes[i]!; +} +console.log("dest" in pick(2), "dest" in pick(0), "src" in pick(3), calls); + +// `in` as a narrowing test inside a predicate and a filter callback. +function hasMiddleware(route: Route): boolean { + return "middlewarePath" in route && typeof route.middlewarePath === "string"; +} +console.log(routes.filter(hasMiddleware).length, routes.filter((r) => "status" in r).length); diff --git a/tests/corpus/6461-predicate-intersection-narrowing.ts b/tests/corpus/6461-predicate-intersection-narrowing.ts new file mode 100644 index 000000000..7e8ff9d8a --- /dev/null +++ b/tests/corpus/6461-predicate-intersection-narrowing.ts @@ -0,0 +1,70 @@ +// A predicate `r is Route & { path: string }` refines the record arms of +// a union without naming a stored arm: arms that do not declare `path` +// cannot hold such a value, the others are read through their own +// layout. The narrowed value can be read in place, in templates, in +// object literals and from closures. +type HandleRoute = { handle: string; src?: string; dest?: string }; +type SourceRoute = { src: string; dest?: string; path?: string; status?: number }; +type Route = HandleRoute | SourceRoute; + +function isPathRoute(route: Route): route is Route & { path: string } { + return "path" in route && typeof route.path === "string"; +} + +type Output = { path: string; index: number }; + +function collect(result: { names: Set; routes: Route[] }): Map { + const out = new Map(); + for (const route of result.routes) { + if (!isPathRoute(route)) continue; + let name = route.path; + if (!result.names.has(name)) name = name.replace(/^\//, ""); + if (!result.names.has(name)) { + throw new Error(`Mapping ${route.path} not found`); + } + out.set(name, { path: route.path, index: out.get(name)?.index ?? out.size }); + } + return out; +} + +const routes: Route[] = [ + { handle: "filesystem" }, + { src: "/a", path: "/one" }, + { src: "/b" }, + { src: "/c", path: "two" }, + { src: "/d", path: "/one", status: 308 }, +]; +console.log([...collect({ names: new Set(["one", "two"]), routes })]); +try { + collect({ names: new Set(), routes: [{ src: "/z", path: "/missing" }] }); +} catch (e) { + console.log((e as Error).message); +} + +// Closures capture the narrowed binding. +const readers: (() => string)[] = []; +for (const route of routes) { + if (!isPathRoute(route)) continue; + readers.push(() => `${route.src} -> ${route.path}`); +} +console.log(readers.map((read) => read())); + +// Three arms, two of which can hold the refinement: the narrowed value is +// either of them, and their shared members still read. +type A = { kind: string; a: number; tag?: string }; +type B = { kind: string; b: boolean; tag?: string }; +type C = { kind: string; c: string }; +type Item = A | B | C; +function isTagged(item: Item): item is Item & { tag: string } { + return "tag" in item && item.tag !== undefined; +} +const items: Item[] = [ + { kind: "a", a: 1, tag: "x" }, + { kind: "b", b: true }, + { kind: "c", c: "plain" }, + { kind: "b", b: false, tag: "y" }, +]; +for (const item of items) { + if (isTagged(item)) console.log(item.kind, item.tag.toUpperCase()); + else console.log(item.kind, "untagged"); +} diff --git a/tests/corpus/6462-assertion-attached-members.ts b/tests/corpus/6462-assertion-attached-members.ts new file mode 100644 index 000000000..8a2be31b7 --- /dev/null +++ b/tests/corpus/6462-assertion-attached-members.ts @@ -0,0 +1,58 @@ +// `route as Route & { order: number; internal: boolean }` followed by +// writes through the asserted view adds the members to the object the +// original binding names: they appear wherever the route is observed +// later, in the order the writes created them. +type HandleRoute = { handle: string; src?: string; dest?: string; status?: number }; +type SourceRoute = { src: string; dest?: string; path?: string; status?: number }; +type Route = HandleRoute | SourceRoute; + +function markInternal(input: Route[], out: Route[], order: number): void { + for (const route of input) { + const marked = route as Route & { internal: boolean; order: number }; + marked.internal = true; + marked.order = order; + out.unshift(route); + } +} + +const out: Route[] = [{ handle: "filesystem" }, { src: "/plain", dest: "/x" }]; +markInternal([{ src: "/a" }, { handle: "miss" }, { src: "/b", status: 404 }], out, 3); +console.log(JSON.stringify(out)); +console.log(out); +console.log(out.map((r) => ("internal" in (r as Route & { internal?: boolean }) ? "marked" : "plain"))); +console.log(out.map((r) => ({ ...r }))); + +// Values the assertion never reached keep no trace of the members. +const fresh: Route = { src: "/new" }; +console.log(JSON.stringify(fresh), Object.keys(fresh)); + +// A single record shape works the same way, and the asserted view reads +// back what it wrote. +type Job = { id: string; retries?: number }; +const job: Job = { id: "j1" }; +const tracked = job as Job & { startedAt: number }; +tracked.startedAt = 42; +console.log(tracked.startedAt + 1, JSON.stringify(job), Object.keys(job)); + +// An annotated binding of the asserted type keeps the object as well: +// the members appear on it once written, not before. +function stamp(job: Job): Job & { finishedAt: number } { + const done: Job & { finishedAt: number } = job as Job & { finishedAt: number }; + done.finishedAt = 7; + return done; +} +const second: Job = { id: "j2", retries: 1 }; +console.log(stamp(second).finishedAt, JSON.stringify(second), Object.keys(second)); + +const third: Job = { id: "j3" }; +const queued: Job & { queuedAt: number } = third as Job & { queuedAt: number }; +queued.queuedAt = 9; +console.log(queued.queuedAt, JSON.stringify(third), Object.keys(third)); + +function markAnnotated(route: Route): void { + const marked: Route & { internal: boolean } = route as Route & { internal: boolean }; + marked.internal = false; +} +const annotatedRoute: Route = { handle: "hit" }; +markAnnotated(annotatedRoute); +console.log(JSON.stringify(annotatedRoute)); diff --git a/tests/corpus/6480-catch-typeof-untyped-throws.ts b/tests/corpus/6480-catch-typeof-untyped-throws.ts new file mode 100644 index 000000000..98122f851 --- /dev/null +++ b/tests/corpus/6480-catch-typeof-untyped-throws.ts @@ -0,0 +1,40 @@ +// Values thrown from `unknown` storage read back through typeof tests on +// the catch binding, like the same values thrown with static types. +const thrown: unknown[] = [5, 0, -1.5, "text", "", true, false, null, undefined, { kind: "box" }]; +function describe(value: unknown): string { + try { + throw value; + } catch (e) { + const same = e === value; + if (typeof e === "number") return `${same} number ${e + 1}`; + if (typeof e === "string") return `${same} string ${e.length}`; + if (typeof e === "boolean") return `${same} boolean ${!e}`; + if (typeof e === "object") return `${same} object ${e === null ? "null" : "value"}`; + return `${same} ${typeof e}`; + } +} +for (const value of thrown) console.log(describe(value)); +function rethrow(value: unknown): void { + try { + throw value; + } catch (e) { + if (typeof e !== "string") throw e; + console.log("kept string", e); + } +} +for (const value of ["s", 7, false]) { + try { + rethrow(value); + } catch (e) { + console.log("rethrown", typeof e, String(e)); + } +} +let count = 0; +for (const value of thrown) { + try { + throw value; + } catch (e) { + if (typeof e === "number" && e > 0) count += e; + } +} +console.log(count); diff --git a/tests/diagnostics/boot-path-node-surface-fences.ts b/tests/diagnostics/boot-path-node-surface-fences.ts new file mode 100644 index 000000000..146a32aa1 --- /dev/null +++ b/tests/diagnostics/boot-path-node-surface-fences.ts @@ -0,0 +1,23 @@ +// The boot-path Node surface keeps its boundaries: process.send answers +// presence checks but stays fenced as a stored value, a stream hook must +// declare Node's argument type or a supertype of it, and util.promisify +// over a function value needs an Error-typed callback error. +import { promisify } from "node:util"; +import { Writable } from "node:stream"; + +const send = process.send; +console.log(typeof send); + +class Counter extends Writable { + _write(chunk: number, _encoding: BufferEncoding, cb: (err?: Error | null) => void): void { + console.log(chunk); + cb(); + } +} +new Counter().end("x"); + +function legacy(cb: (code: number, value: string) => void): void { + cb(0, "v"); +} +const legacyAsync = promisify(legacy); +legacyAsync().then((v) => console.log(v)); diff --git a/tests/diagnostics/class-field-accessor-descriptor.ts b/tests/diagnostics/class-field-accessor-descriptor.ts new file mode 100644 index 000000000..66078f6c2 --- /dev/null +++ b/tests/diagnostics/class-field-accessor-descriptor.ts @@ -0,0 +1,14 @@ +// Object.defineProperty can change a declared class field's value and +// attributes, but not turn it into an accessor: a literal get or set +// descriptor is refused at compile time, naming the field. +class Value { + x = 1; +} + +const value = new Value(); +Object.defineProperty(value, "x", { + get() { + return 2; + }, +}); +console.log(value.x); diff --git a/tests/diagnostics/maybe-promise-fences.ts b/tests/diagnostics/maybe-promise-fences.ts index efe541c9e..1dcccaf08 100644 --- a/tests/diagnostics/maybe-promise-fences.ts +++ b/tests/diagnostics/maybe-promise-fences.ts @@ -30,8 +30,3 @@ console.log(typeof widened); // `instanceof Promise` on a value that is never a promise. const record = { v: 1 }; console.log(record instanceof Promise); - -// A variadic `unknown[]` slot takes closures with the same variadic ABI. -type HandlerFn = (...args: unknown[]) => Promise; -const fixed: HandlerFn = async (first: unknown) => first; -fixed(1, 2).then((v) => console.log(v)); diff --git a/tests/diagnostics/mock-fences.ts b/tests/diagnostics/mock-fences.ts index 4425dfa5f..ef92f366f 100644 --- a/tests/diagnostics/mock-fences.ts +++ b/tests/diagnostics/mock-fences.ts @@ -1,8 +1,9 @@ -// node:test mocks lower for mock.fn(original[, implementation]), +// node:test mocks lower for mock.fn([original[, implementation]]), // mock.method(object, methodName[, implementation]) on records, their // t.mock twins, and the trackers' restoreAll and reset. The rest of the // mock surface is refused, each with a named diagnostic: mock.fn() without -// an original; mock.method on a class instance or a builtin object, with +// an original for a function type whose return type does not include +// undefined; mock.method on a class instance or a builtin object, with // an options object, or with an implementation of another type when the // result is used; and functions whose union return type has no undefined // arm (the calls log records undefined for calls that throw). @@ -11,8 +12,11 @@ import { mock, test } from "node:test"; const add = mock.fn((a: number, b: number) => a + b); // lowers add(1, 2); -const empty = mock.fn(); // no original function -empty(); +const empty = mock.fn(); // lowers +empty(1, "two"); + +const counted = mock.fn<() => number>(); // no original, returns number +counted(); const obj = { value: () => 1 }; mock.method(obj, "value"); // lowers diff --git a/tests/fixtures/esm-path-globals/lib/helper.ts b/tests/fixtures/esm-path-globals/lib/helper.ts new file mode 100644 index 000000000..61960c097 --- /dev/null +++ b/tests/fixtures/esm-path-globals/lib/helper.ts @@ -0,0 +1,2 @@ +export const helperDir: string = __dirname; +export const helperFile: string = __filename; diff --git a/tests/fixtures/esm-path-globals/main.ts b/tests/fixtures/esm-path-globals/main.ts new file mode 100644 index 000000000..d3d3c3b8a --- /dev/null +++ b/tests/fixtures/esm-path-globals/main.ts @@ -0,0 +1,12 @@ +// __dirname and __filename in TypeScript files with import syntax: each +// module reads its own source location, the same strings as +// import.meta.dirname and import.meta.filename. +import { basename, join } from "node:path"; +import { helperDir, helperFile } from "./lib/helper.ts"; + +console.log(typeof __dirname, typeof __filename); +console.log(basename(__dirname), basename(__filename)); +console.log(__dirname === import.meta.dirname, __filename === import.meta.filename); +console.log(join(__dirname, "lib") === helperDir, basename(helperFile)); +const home = typeof __dirname === "string" && __dirname.endsWith("/lib") ? join(__dirname, "..") : "/tmp"; +console.log(home); diff --git a/tests/fixtures/node-test/cases/mock-fn-default.ts b/tests/fixtures/node-test/cases/mock-fn-default.ts new file mode 100644 index 000000000..4666dfa5e --- /dev/null +++ b/tests/fixtures/node-test/cases/mock-fn-default.ts @@ -0,0 +1,72 @@ +// mock.fn() and t.mock.fn() without an original: the mock returns +// undefined and records the arguments of each call, whatever its callers +// pass. mockImplementation and mockImplementationOnce replace its +// behavior, and restore() brings back the function that returns undefined. +// mock.fn() records F's parameters, for a function type whose return +// type is void or includes undefined. +import assert from "node:assert"; +import { mock, test } from "node:test"; + +test("records calls with any arguments", () => { + const fn = mock.fn(); + fn(1, "two"); + fn(); + fn(true, null, [3]); + assert.strictEqual(fn.mock.callCount(), 3); + const [first, second, third] = fn.mock.calls; + assert.deepStrictEqual(first!.arguments, [1, "two"]); + assert.strictEqual(second!.arguments.length, 0); + assert.deepStrictEqual(third!.arguments, [true, null, [3]]); + assert.ok(first!.result === undefined); + assert.ok(first!.error === undefined); + assert.ok(first!.this === undefined); +}); + +test("changes implementations", () => { + const seen: string[] = []; + const fn = mock.fn(); + fn.mock.mockImplementation((...args: unknown[]) => { + seen.push(`impl ${args.length}`); + return undefined; + }); + fn.mock.mockImplementationOnce(() => { + seen.push("once"); + return undefined; + }); + fn("a"); + fn("a", "b"); + fn.mock.restore(); + fn("c"); + assert.deepStrictEqual(seen, ["once", "impl 2"]); + assert.strictEqual(fn.mock.callCount(), 3); + fn.mock.resetCalls(); + assert.strictEqual(fn.mock.callCount(), 0); +}); + +test("records a throwing implementation", () => { + const fn = mock.fn(); + fn.mock.mockImplementation(() => { + throw new Error("boom"); + }); + assert.throws(() => fn(7), { message: "boom" }); + const call = fn.mock.calls[0]!; + assert.deepStrictEqual(call.arguments, [7]); + assert.ok(call.error instanceof Error); +}); + +test("t.mock.fn() and typed mock.fn()", (t) => { + const fn = t.mock.fn(); + fn("x"); + assert.strictEqual(fn.mock.callCount(), 1); + const typed = t.mock.fn<(a: number, b: string) => void>(); + typed(1, "x"); + typed(2, "y"); + assert.strictEqual(typed.mock.callCount(), 2); + assert.strictEqual(typed.mock.calls[1]!.arguments[0], 2); + assert.strictEqual(typed.mock.calls[1]!.arguments[1], "y"); + const maybe = t.mock.fn<() => number | undefined>(); + assert.ok(maybe() === undefined); + maybe.mock.mockImplementation(() => 4); + assert.ok(maybe() === 4); + assert.ok(maybe.mock.calls[1]!.result === 4); +}); diff --git a/tests/fixtures/node-types/promisify-callback-functions.ts b/tests/fixtures/node-types/promisify-callback-functions.ts new file mode 100644 index 000000000..78be5e3bd --- /dev/null +++ b/tests/fixtures/node-types/promisify-callback-functions.ts @@ -0,0 +1,67 @@ +// util.promisify over typed callback-last functions with leading arguments +// under @types/node: bound methods, a function declaration, rejection from +// the callback error, a synchronous throw, and asynchronous settlement. +import { promisify } from "node:util"; + +class Client { + private closed = false; + private readonly name: string; + constructor(name: string) { + this.name = name; + } + close(callback: (error?: Error) => void): void { + setTimeout(() => { + if (this.closed) { + callback(new Error(`${this.name} already closed`)); + return; + } + this.closed = true; + callback(); + }, 1); + } + fetch(key: string, callback: (error: Error | null, value?: string) => void): void { + setImmediate(() => { + if (key === "") callback(new RangeError("empty key")); + else callback(null, `${this.name}:${key}`); + }); + } +} + +function sum(a: number, b: number, done: (err: Error | null, total: number) => void): void { + if (a < 0) throw new TypeError("negative"); + process.nextTick(() => done(null, a + b)); +} + +async function main(): Promise { + const c = new Client("db"); + const closeAsync = promisify(c.close.bind(c)); + await closeAsync(); + console.log("closed once"); + try { + await closeAsync(); + } catch (e) { + console.log("second close:", (e as Error).message); + } + const fetchAsync = promisify(c.fetch.bind(c)); + console.log(await fetchAsync("k")); + try { + await fetchAsync(""); + } catch (e) { + console.log("fetch:", (e as Error).name, (e as Error).message); + } + const sumAsync = promisify(sum); + console.log("sum", await sumAsync(2, 3)); + try { + await sumAsync(-1, 3); + } catch (e) { + console.log("sync throw:", (e as Error).name, (e as Error).message); + } + await promisify(new Client("inline").close.bind(new Client("inline")))(); + console.log("inline done"); + const order: string[] = []; + const p = sumAsync(1, 1).then((v) => order.push(`then ${v}`)); + order.push("after call"); + await p; + console.log(order.join(", ")); +} +void main(); diff --git a/tests/fixtures/node-types/stream-subclass-hooks.ts b/tests/fixtures/node-types/stream-subclass-hooks.ts new file mode 100644 index 000000000..0e7cfbad5 --- /dev/null +++ b/tests/fixtures/node-types/stream-subclass-hooks.ts @@ -0,0 +1,46 @@ +// Stream subclasses under @types/node: a Writable whose hooks declare a +// supertype of Node's chunk type, a Transform, and an erroring Writable. +import { Transform, Writable } from "node:stream"; + +class LineSink extends Writable { + private rest = ""; + readonly lines: string[] = []; + _write(chunk: Buffer | string, encoding: BufferEncoding, cb: (err?: Error | null) => void): void { + console.log("chunk", typeof chunk, Buffer.isBuffer(chunk), encoding); + const parts = (this.rest + chunk.toString()).split("\n"); + this.rest = parts.pop() ?? ""; + this.lines.push(...parts); + cb(); + } + _final(cb: (err?: Error | null) => void): void { + if (this.rest !== "") this.lines.push(this.rest); + cb(); + } +} + +class Upper extends Transform { + _transform(chunk: string | Buffer, _enc: string, cb: (err?: Error | null, data?: string) => void): void { + cb(null, chunk.toString().toUpperCase()); + } +} + +class Failing extends Writable { + _write(chunk: Uint8Array | string, _enc: BufferEncoding, cb: (err?: Error | null) => void): void { + cb(new Error(`refused ${chunk.toString().length} bytes`)); + } +} + +const sink = new LineSink(); +sink.write("a\nb"); +sink.write(Buffer.from("c\nd")); +sink.end("e"); +sink.on("finish", () => console.log("lines", JSON.stringify(sink.lines))); + +const up = new Upper(); +up.on("data", (d: Buffer) => console.log("upper", d.toString())); +up.write("hello "); +up.end("world"); + +const bad = new Failing(); +bad.on("error", (e) => console.log("error", e.message)); +bad.write("xyz"); diff --git a/tests/fixtures/npm/cases/2558-bundled-chunks/main.ts b/tests/fixtures/npm/cases/2558-bundled-chunks/main.ts new file mode 100644 index 000000000..5cbbc3dbc --- /dev/null +++ b/tests/fixtures/npm/cases/2558-bundled-chunks/main.ts @@ -0,0 +1,29 @@ +// esbuild's bundled CommonJS chunks: each package below is real esbuild +// output (0.25 unminified, 0.25 whitespace+syntax minified, 0.17) of one +// source that bundles CommonJS modules through the __commonJS helper. +// Every chunk compiles as its own CommonJS module: it runs once, at its +// first require (the top-level interop imports run while the bundle +// loads; `lazy` requires its chunk only when called), chunk-to-chunk +// requires share one module instance, an esbuild-built dist bundled as a +// chunk keeps its suffixed helpers, and the external dependency passes +// through the __toESM interop. gtcjsdist is a "type": "module" package +// whose CommonJS entry is a .cjs bundle using the node-mode interop. +import { describe, external, lazy, nested } from "gtchunk"; +import { describe as dws, external as ews, lazy as lws, nested as nws } from "gtchunkws"; +import { describe as d17, external as e17, lazy as l17, nested as n17 } from "gtchunk17"; +import { viaNodeMode } from "gtcjsdist"; + +console.log("main start"); +console.log("plain", describe()); +console.log("plain", external()); +console.log("plain", nested()); +console.log("plain", lazy(), lazy()); +console.log("minified", dws()); +console.log("minified", ews()); +console.log("minified", nws()); +console.log("minified", lws(), lws()); +console.log("older", d17()); +console.log("older", e17()); +console.log("older", n17()); +console.log("older", l17(), l17()); +console.log(viaNodeMode()); diff --git a/tests/fixtures/npm/cases/2559-cjs-cycle/main.ts b/tests/fixtures/npm/cases/2559-cjs-cycle/main.ts new file mode 100644 index 000000000..042102b64 --- /dev/null +++ b/tests/fixtures/npm/cases/2559-cjs-cycle/main.ts @@ -0,0 +1,11 @@ +// A require cycle between esbuild's per-file CommonJS outputs (index ↔ +// merge, index ↔ append). Each file installs its live getter table +// before its first require, so a partner that loads while index.js is +// still initializing sees index's partially initialized exports, and both +// partners call back into index from their functions. index.js re-exports +// mergeAll from merge.js through its getter table. +import { run, mergeAll } from "gtcycle"; + +console.log(run([" a ", "", "@x"], ["b", "@keep"])); +console.log(run(["c"], [])); +console.log(mergeAll(["@keep", " d"], ["@drop"]).join("|")); diff --git a/tests/fixtures/npm/cases/2560-cjs-cycle-partner-first/main.ts b/tests/fixtures/npm/cases/2560-cjs-cycle-partner-first/main.ts new file mode 100644 index 000000000..1ed90d432 --- /dev/null +++ b/tests/fixtures/npm/cases/2560-cjs-cycle-partner-first/main.ts @@ -0,0 +1,11 @@ +// The same esbuild CommonJS cycle entered through a partner: merge.js +// loads first, requires index.js, which requires merge.js back while +// merge is still initializing (its getter table is already installed) and +// re-exports mergeAll from it: the re-export reads merge's hoisted +// function while merge is still initializing. +import { mergeAll } from "gtcycle/dist/merge.js"; +import { run, mergeAll as reexported } from "gtcycle"; + +console.log(mergeAll([" x "], ["@keep"]).join(",")); +console.log(reexported === mergeAll); +console.log(run(["y"], ["@z"])); diff --git a/tests/fixtures/npm/cases/2561-cjs-cycle-early-read/main.ts b/tests/fixtures/npm/cases/2561-cjs-cycle-early-read/main.ts new file mode 100644 index 000000000..3efeb9eb1 --- /dev/null +++ b/tests/fixtures/npm/cases/2561-cjs-cycle-early-read/main.ts @@ -0,0 +1,6 @@ +// A require cycle whose partner reads a const export of index.js while +// index is still initializing: Node throws a ReferenceError (the binding +// is in its temporal dead zone), and scriptc refuses the cycle. +import { show } from "gtcyclebad"; + +console.log(show()); diff --git a/tests/fixtures/npm/cases/2562-subpath-leaf/main.ts b/tests/fixtures/npm/cases/2562-subpath-leaf/main.ts new file mode 100644 index 000000000..b34359cc7 --- /dev/null +++ b/tests/fixtures/npm/cases/2562-subpath-leaf/main.ts @@ -0,0 +1,10 @@ +// The program reaches gtsubpath only through its leaf data module (and a +// type from its main entry). The main entry reaches a file whose bundler +// interop the recognizer cannot verify, but no compiled code can load it, +// so the package compiles: a package is judged by what its imported +// subpaths reach. +import type { Manager } from "gtsubpath"; +import { managers } from "gtsubpath/dist/data.js"; + +const names: Manager[] = managers.filter((m) => m.lockfile.endsWith(".yaml")); +console.log(managers.length, names.map((m) => m.name).join(",")); diff --git a/tests/fixtures/npm/cases/2564-chunk-helper-shadowing/main.ts b/tests/fixtures/npm/cases/2564-chunk-helper-shadowing/main.ts new file mode 100644 index 000000000..d52b3731d --- /dev/null +++ b/tests/fixtures/npm/cases/2564-chunk-helper-shadowing/main.ts @@ -0,0 +1,7 @@ +// A bundled CommonJS chunk that calls the bundle's top-level `label` while +// a nested function of the chunk declares its own `label`: the chunk +// carries the top-level function along, and each reference keeps its own +// binding. +import { inner, outer } from "gtchunkshadow"; + +console.log(outer(), inner()); diff --git a/tests/fixtures/npm/cases/2565-chunk-shared-function/main.ts b/tests/fixtures/npm/cases/2565-chunk-shared-function/main.ts new file mode 100644 index 000000000..21ca81b40 --- /dev/null +++ b/tests/fixtures/npm/cases/2565-chunk-shared-function/main.ts @@ -0,0 +1,7 @@ +// Two bundled CommonJS chunks export the bundle's top-level `tally` and +// count calls on a property of it. Node prints "1 2 true": both chunks +// share one function. A per-chunk copy would split its identity and its +// properties, so the package stays refused. +import { counts, same } from "gtchunkshared"; + +console.log(counts(), same()); diff --git a/tests/fixtures/npm/cases/2566-cjs-cycle-indirect-call/main.ts b/tests/fixtures/npm/cases/2566-cjs-cycle-indirect-call/main.ts new file mode 100644 index 000000000..3cca145e0 --- /dev/null +++ b/tests/fixtures/npm/cases/2566-cjs-cycle-indirect-call/main.ts @@ -0,0 +1,7 @@ +// A require cycle whose partner calls an export of index.js with esbuild's +// `(0, import_index.peek)()` spelling while index is still initializing: +// peek reads a const in its temporal dead zone, so Node throws a +// ReferenceError, and scriptc refuses the cycle. +import { show } from "gtcyclecall"; + +console.log(show()); diff --git a/tests/fixtures/npm/cases/2571-cjs-cycle-stored-function/main.ts b/tests/fixtures/npm/cases/2571-cjs-cycle-stored-function/main.ts new file mode 100644 index 000000000..ae2be341a --- /dev/null +++ b/tests/fixtures/npm/cases/2571-cjs-cycle-stored-function/main.ts @@ -0,0 +1,8 @@ +// Require cycles whose partner stores an export of index.js (in a const, +// or in an object literal) and calls it while index is still +// initializing: the export reads a const in its temporal dead zone, so +// Node throws a ReferenceError, and scriptc refuses both cycles. +import { show } from "gtcyclealias"; +import { show as showBox } from "gtcyclebox"; + +console.log(show(), showBox()); diff --git a/tests/fixtures/npm/node_modules/gtchunk/dist/index.d.ts b/tests/fixtures/npm/node_modules/gtchunk/dist/index.d.ts new file mode 100644 index 000000000..bd3d4e582 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtchunk/dist/index.d.ts @@ -0,0 +1,4 @@ +export declare function describe(): string; +export declare function external(): string; +export declare function nested(): string; +export declare function lazy(): string; diff --git a/tests/fixtures/npm/node_modules/gtchunk/dist/index.js b/tests/fixtures/npm/node_modules/gtchunk/dist/index.js new file mode 100644 index 000000000..7820e3ae4 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtchunk/dist/index.js @@ -0,0 +1,132 @@ +var __create = Object.create; +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __getProtoOf = Object.getPrototypeOf; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __commonJS = (cb, mod) => function __require() { + return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; +}; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( + // If the importer is in node compatibility mode or this is not an ESM + // file that has been converted to a CommonJS file using a Babel- + // compatible transform (i.e. "__esModule" has not been set), then set + // "default" to the CommonJS "module.exports" for node compatibility. + isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, + mod +)); +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); + +// chunk/vendor/settings/counter.js +var require_counter = __commonJS({ + "chunk/vendor/settings/counter.js"(exports2, module2) { + var n = 0; + console.log("counter init"); + module2.exports = { + next() { + n += 1; + return n; + } + }; + } +}); + +// chunk/vendor/settings/index.js +var require_settings = __commonJS({ + "chunk/vendor/settings/index.js"(exports2) { + "use strict"; + console.log("settings init"); + var counter = require_counter(); + exports2.LABEL = "bundled"; + exports2.VERSION = 2; + exports2.bump = function() { + return counter.next(); + }; + } +}); + +// chunk/vendor/prebuilt/index.js +var require_prebuilt = __commonJS({ + "chunk/vendor/prebuilt/index.js"(exports2, module2) { + var __defProp2 = Object.defineProperty; + var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor; + var __getOwnPropNames2 = Object.getOwnPropertyNames; + var __hasOwnProp2 = Object.prototype.hasOwnProperty; + var __export2 = (target, all) => { + for (var name in all) + __defProp2(target, name, { get: all[name], enumerable: true }); + }; + var __copyProps2 = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames2(from)) + if (!__hasOwnProp2.call(to, key) && key !== except) + __defProp2(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc2(from, key)) || desc.enumerable }); + } + return to; + }; + var __toCommonJS2 = (mod) => __copyProps2(__defProp2({}, "__esModule", { value: true }), mod); + var prebuilt_exports = {}; + __export2(prebuilt_exports, { + PREBUILT_TAG: () => PREBUILT_TAG2, + greet: () => greet2 + }); + module2.exports = __toCommonJS2(prebuilt_exports); + function greet2(name) { + return "hello " + name; + } + var PREBUILT_TAG2 = "prebuilt"; + } +}); + +// chunk/vendor/lazy.js +var require_lazy = __commonJS({ + "chunk/vendor/lazy.js"(exports2, module2) { + console.log("lazy init"); + module2.exports = { value: "lazy-value" }; + } +}); + +// chunk/src/index.ts +var index_exports = {}; +__export(index_exports, { + describe: () => describe, + external: () => external, + lazy: () => lazy, + nested: () => nested +}); +module.exports = __toCommonJS(index_exports); +var import_settings = __toESM(require_settings()); +var core = __toESM(require("gtcore")); +var import_gtcore = __toESM(require("gtcore")); +var import_prebuilt = __toESM(require_prebuilt()); +function describe() { + return import_settings.LABEL + "@" + import_settings.default.VERSION + " bump " + (0, import_settings.bump)() + " " + (0, import_settings.bump)(); +} +function external() { + return core.tag + ":" + core.label(1) + ":" + import_gtcore.default.label(2); +} +function nested() { + return (0, import_prebuilt.greet)("chunk") + " " + import_prebuilt.PREBUILT_TAG; +} +function lazy() { + return require_lazy().value; +} +// Annotate the CommonJS export names for ESM import in node: +0 && (module.exports = { + describe, + external, + lazy, + nested +}); diff --git a/tests/fixtures/npm/node_modules/gtchunk/package.json b/tests/fixtures/npm/node_modules/gtchunk/package.json new file mode 100644 index 000000000..2812a5188 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtchunk/package.json @@ -0,0 +1,10 @@ +{ + "name": "gtchunk", + "version": "1.0.0", + "description": "esbuild 0.25.10 output (--bundle --platform=node --format=cjs --external:gtcore): bundled CommonJS modules behind the __commonJS helper, including an esbuild-built dist with suffixed helpers", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "dependencies": { + "gtcore": "1.0.0" + } +} diff --git a/tests/fixtures/npm/node_modules/gtchunk17/dist/index.d.ts b/tests/fixtures/npm/node_modules/gtchunk17/dist/index.d.ts new file mode 100644 index 000000000..bd3d4e582 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtchunk17/dist/index.d.ts @@ -0,0 +1,4 @@ +export declare function describe(): string; +export declare function external(): string; +export declare function nested(): string; +export declare function lazy(): string; diff --git a/tests/fixtures/npm/node_modules/gtchunk17/dist/index.js b/tests/fixtures/npm/node_modules/gtchunk17/dist/index.js new file mode 100644 index 000000000..afda6af56 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtchunk17/dist/index.js @@ -0,0 +1,132 @@ +var __create = Object.create; +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __getProtoOf = Object.getPrototypeOf; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __commonJS = (cb, mod) => function __require() { + return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; +}; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( + // If the importer is in node compatibility mode or this is not an ESM + // file that has been converted to a CommonJS file using a Babel- + // compatible transform (i.e. "__esModule" has not been set), then set + // "default" to the CommonJS "module.exports" for node compatibility. + isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, + mod +)); +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); + +// chunk/vendor/settings/counter.js +var require_counter = __commonJS({ + "chunk/vendor/settings/counter.js"(exports, module2) { + var n = 0; + console.log("counter init"); + module2.exports = { + next() { + n += 1; + return n; + } + }; + } +}); + +// chunk/vendor/settings/index.js +var require_settings = __commonJS({ + "chunk/vendor/settings/index.js"(exports) { + "use strict"; + console.log("settings init"); + var counter = require_counter(); + exports.LABEL = "bundled"; + exports.VERSION = 2; + exports.bump = function() { + return counter.next(); + }; + } +}); + +// chunk/vendor/prebuilt/index.js +var require_prebuilt = __commonJS({ + "chunk/vendor/prebuilt/index.js"(exports, module2) { + var __defProp2 = Object.defineProperty; + var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor; + var __getOwnPropNames2 = Object.getOwnPropertyNames; + var __hasOwnProp2 = Object.prototype.hasOwnProperty; + var __export2 = (target, all) => { + for (var name in all) + __defProp2(target, name, { get: all[name], enumerable: true }); + }; + var __copyProps2 = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames2(from)) + if (!__hasOwnProp2.call(to, key) && key !== except) + __defProp2(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc2(from, key)) || desc.enumerable }); + } + return to; + }; + var __toCommonJS2 = (mod) => __copyProps2(__defProp2({}, "__esModule", { value: true }), mod); + var prebuilt_exports = {}; + __export2(prebuilt_exports, { + PREBUILT_TAG: () => PREBUILT_TAG2, + greet: () => greet2 + }); + module2.exports = __toCommonJS2(prebuilt_exports); + function greet2(name) { + return "hello " + name; + } + var PREBUILT_TAG2 = "prebuilt"; + } +}); + +// chunk/vendor/lazy.js +var require_lazy = __commonJS({ + "chunk/vendor/lazy.js"(exports, module2) { + console.log("lazy init"); + module2.exports = { value: "lazy-value" }; + } +}); + +// chunk/src/index.ts +var src_exports = {}; +__export(src_exports, { + describe: () => describe, + external: () => external, + lazy: () => lazy, + nested: () => nested +}); +module.exports = __toCommonJS(src_exports); +var import_settings = __toESM(require_settings()); +var core = __toESM(require("gtcore")); +var import_gtcore = __toESM(require("gtcore")); +var import_prebuilt = __toESM(require_prebuilt()); +function describe() { + return import_settings.LABEL + "@" + import_settings.default.VERSION + " bump " + (0, import_settings.bump)() + " " + (0, import_settings.bump)(); +} +function external() { + return core.tag + ":" + core.label(1) + ":" + import_gtcore.default.label(2); +} +function nested() { + return (0, import_prebuilt.greet)("chunk") + " " + import_prebuilt.PREBUILT_TAG; +} +function lazy() { + return require_lazy().value; +} +// Annotate the CommonJS export names for ESM import in node: +0 && (module.exports = { + describe, + external, + lazy, + nested +}); diff --git a/tests/fixtures/npm/node_modules/gtchunk17/package.json b/tests/fixtures/npm/node_modules/gtchunk17/package.json new file mode 100644 index 000000000..73761f181 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtchunk17/package.json @@ -0,0 +1,10 @@ +{ + "name": "gtchunk17", + "version": "1.0.0", + "description": "the gtchunk source bundled by esbuild 0.17.19: chunk factories take (exports, module2)", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "dependencies": { + "gtcore": "1.0.0" + } +} diff --git a/tests/fixtures/npm/node_modules/gtchunkshadow/dist/index.d.ts b/tests/fixtures/npm/node_modules/gtchunkshadow/dist/index.d.ts new file mode 100644 index 000000000..238cf1744 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtchunkshadow/dist/index.d.ts @@ -0,0 +1,2 @@ +export declare function outer(): string; +export declare function inner(): string; diff --git a/tests/fixtures/npm/node_modules/gtchunkshadow/dist/index.js b/tests/fixtures/npm/node_modules/gtchunkshadow/dist/index.js new file mode 100644 index 000000000..0f89c7ec5 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtchunkshadow/dist/index.js @@ -0,0 +1,52 @@ +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __commonJS = (cb, mod) => function __require() { + return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; +}; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); +function label(n) { + return "label " + n; +} + +// chunk/vendor/shadow.js +var require_shadow = __commonJS({ + "chunk/vendor/shadow.js"(exports2, module2) { + function nested() { + var label = "nested"; + return label; + } + module2.exports = { outer: label(1), inner: nested() }; + } +}); + +// chunk/src/index.ts +var index_exports = {}; +__export(index_exports, { + inner: () => inner, + outer: () => outer +}); +module.exports = __toCommonJS(index_exports); +function outer() { + return require_shadow().outer; +} +function inner() { + return require_shadow().inner; +} +0 && (module.exports = { + inner, + outer +}); diff --git a/tests/fixtures/npm/node_modules/gtchunkshadow/package.json b/tests/fixtures/npm/node_modules/gtchunkshadow/package.json new file mode 100644 index 000000000..6830fbd2c --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtchunkshadow/package.json @@ -0,0 +1,7 @@ +{ + "name": "gtchunkshadow", + "version": "1.0.0", + "description": "esbuild's CommonJS bundle shape: a chunk calls a top-level function whose name a nested scope of the chunk also declares", + "main": "./dist/index.js", + "types": "./dist/index.d.ts" +} diff --git a/tests/fixtures/npm/node_modules/gtchunkshared/dist/index.d.ts b/tests/fixtures/npm/node_modules/gtchunkshared/dist/index.d.ts new file mode 100644 index 000000000..3a47a0495 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtchunkshared/dist/index.d.ts @@ -0,0 +1,2 @@ +export declare function same(): boolean; +export declare function counts(): string; diff --git a/tests/fixtures/npm/node_modules/gtchunkshared/dist/index.js b/tests/fixtures/npm/node_modules/gtchunkshared/dist/index.js new file mode 100644 index 000000000..0d3aacec8 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtchunkshared/dist/index.js @@ -0,0 +1,56 @@ +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __commonJS = (cb, mod) => function __require() { + return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; +}; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); +function tally() { + tally.calls = (tally.calls || 0) + 1; + return tally.calls; +} + +// chunk/vendor/a.js +var require_a = __commonJS({ + "chunk/vendor/a.js"(exports2, module2) { + module2.exports = { tally, first: tally() }; + } +}); + +// chunk/vendor/b.js +var require_b = __commonJS({ + "chunk/vendor/b.js"(exports2, module2) { + module2.exports = { tally, second: tally() }; + } +}); + +// chunk/src/index.ts +var index_exports = {}; +__export(index_exports, { + counts: () => counts, + same: () => same +}); +module.exports = __toCommonJS(index_exports); +function same() { + return require_a().tally === require_b().tally; +} +function counts() { + return require_a().first + " " + require_b().second; +} +0 && (module.exports = { + counts, + same +}); diff --git a/tests/fixtures/npm/node_modules/gtchunkshared/package.json b/tests/fixtures/npm/node_modules/gtchunkshared/package.json new file mode 100644 index 000000000..bfc90c5d2 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtchunkshared/package.json @@ -0,0 +1,7 @@ +{ + "name": "gtchunkshared", + "version": "1.0.0", + "description": "esbuild's CommonJS bundle shape: two chunks export one top-level function and count calls on a property of it", + "main": "./dist/index.js", + "types": "./dist/index.d.ts" +} diff --git a/tests/fixtures/npm/node_modules/gtchunkws/dist/index.d.ts b/tests/fixtures/npm/node_modules/gtchunkws/dist/index.d.ts new file mode 100644 index 000000000..bd3d4e582 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtchunkws/dist/index.d.ts @@ -0,0 +1,4 @@ +export declare function describe(): string; +export declare function external(): string; +export declare function nested(): string; +export declare function lazy(): string; diff --git a/tests/fixtures/npm/node_modules/gtchunkws/dist/index.js b/tests/fixtures/npm/node_modules/gtchunkws/dist/index.js new file mode 100644 index 000000000..f022baf09 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtchunkws/dist/index.js @@ -0,0 +1 @@ +var __create=Object.create;var __defProp=Object.defineProperty;var __getOwnPropDesc=Object.getOwnPropertyDescriptor;var __getOwnPropNames=Object.getOwnPropertyNames;var __getProtoOf=Object.getPrototypeOf,__hasOwnProp=Object.prototype.hasOwnProperty;var __commonJS=(cb,mod)=>function(){return mod||(0,cb[__getOwnPropNames(cb)[0]])((mod={exports:{}}).exports,mod),mod.exports};var __export=(target,all)=>{for(var name in all)__defProp(target,name,{get:all[name],enumerable:!0})},__copyProps=(to,from,except,desc)=>{if(from&&typeof from=="object"||typeof from=="function")for(let key of __getOwnPropNames(from))!__hasOwnProp.call(to,key)&&key!==except&&__defProp(to,key,{get:()=>from[key],enumerable:!(desc=__getOwnPropDesc(from,key))||desc.enumerable});return to};var __toESM=(mod,isNodeMode,target)=>(target=mod!=null?__create(__getProtoOf(mod)):{},__copyProps(isNodeMode||!mod||!mod.__esModule?__defProp(target,"default",{value:mod,enumerable:!0}):target,mod)),__toCommonJS=mod=>__copyProps(__defProp({},"__esModule",{value:!0}),mod);var require_counter=__commonJS({"chunk/vendor/settings/counter.js"(exports2,module2){var n=0;console.log("counter init");module2.exports={next(){return n+=1,n}}}});var require_settings=__commonJS({"chunk/vendor/settings/index.js"(exports2){"use strict";console.log("settings init");var counter=require_counter();exports2.LABEL="bundled";exports2.VERSION=2;exports2.bump=function(){return counter.next()}}});var require_prebuilt=__commonJS({"chunk/vendor/prebuilt/index.js"(exports2,module2){var __defProp2=Object.defineProperty,__getOwnPropDesc2=Object.getOwnPropertyDescriptor,__getOwnPropNames2=Object.getOwnPropertyNames,__hasOwnProp2=Object.prototype.hasOwnProperty,__export2=(target,all)=>{for(var name in all)__defProp2(target,name,{get:all[name],enumerable:!0})},__copyProps2=(to,from,except,desc)=>{if(from&&typeof from=="object"||typeof from=="function")for(let key of __getOwnPropNames2(from))!__hasOwnProp2.call(to,key)&&key!==except&&__defProp2(to,key,{get:()=>from[key],enumerable:!(desc=__getOwnPropDesc2(from,key))||desc.enumerable});return to},__toCommonJS2=mod=>__copyProps2(__defProp2({},"__esModule",{value:!0}),mod),prebuilt_exports={};__export2(prebuilt_exports,{PREBUILT_TAG:()=>PREBUILT_TAG2,greet:()=>greet2});module2.exports=__toCommonJS2(prebuilt_exports);function greet2(name){return"hello "+name}var PREBUILT_TAG2="prebuilt"}});var require_lazy=__commonJS({"chunk/vendor/lazy.js"(exports2,module2){console.log("lazy init");module2.exports={value:"lazy-value"}}});var index_exports={};__export(index_exports,{describe:()=>describe,external:()=>external,lazy:()=>lazy,nested:()=>nested});module.exports=__toCommonJS(index_exports);var import_settings=__toESM(require_settings()),core=__toESM(require("gtcore")),import_gtcore=__toESM(require("gtcore")),import_prebuilt=__toESM(require_prebuilt());function describe(){return import_settings.LABEL+"@"+import_settings.default.VERSION+" bump "+(0,import_settings.bump)()+" "+(0,import_settings.bump)()}function external(){return core.tag+":"+core.label(1)+":"+import_gtcore.default.label(2)}function nested(){return(0,import_prebuilt.greet)("chunk")+" "+import_prebuilt.PREBUILT_TAG}function lazy(){return require_lazy().value}0&&(module.exports={describe,external,lazy,nested}); diff --git a/tests/fixtures/npm/node_modules/gtchunkws/package.json b/tests/fixtures/npm/node_modules/gtchunkws/package.json new file mode 100644 index 000000000..5502f80a0 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtchunkws/package.json @@ -0,0 +1,10 @@ +{ + "name": "gtchunkws", + "version": "1.0.0", + "description": "the gtchunk source bundled by esbuild 0.25.10 with --minify-whitespace --minify-syntax: helpers share var statements", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "dependencies": { + "gtcore": "1.0.0" + } +} diff --git a/tests/fixtures/npm/node_modules/gtcjsdist/dist/index.cjs b/tests/fixtures/npm/node_modules/gtcjsdist/dist/index.cjs new file mode 100644 index 000000000..8836b5691 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtcjsdist/dist/index.cjs @@ -0,0 +1,43 @@ +var __create = Object.create; +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __getProtoOf = Object.getPrototypeOf; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( + // If the importer is in node compatibility mode or this is not an ESM + // file that has been converted to a CommonJS file using a Babel- + // compatible transform (i.e. "__esModule" has not been set), then set + // "default" to the CommonJS "module.exports" for node compatibility. + isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, + mod +)); +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); + +// cjsdist/src/index.ts +var index_exports = {}; +__export(index_exports, { + viaNodeMode: () => viaNodeMode +}); +module.exports = __toCommonJS(index_exports); +var core = __toESM(require("gtcore"), 1); +var import_gtcore = __toESM(require("gtcore"), 1); +function viaNodeMode() { + return import_gtcore.default.tag + "/" + core.label(7) + "/" + import_gtcore.default.label(8); +} +// Annotate the CommonJS export names for ESM import in node: +0 && (module.exports = { + viaNodeMode +}); diff --git a/tests/fixtures/npm/node_modules/gtcjsdist/dist/index.d.cts b/tests/fixtures/npm/node_modules/gtcjsdist/dist/index.d.cts new file mode 100644 index 000000000..6f77b9943 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtcjsdist/dist/index.d.cts @@ -0,0 +1 @@ +export declare function viaNodeMode(): string; diff --git a/tests/fixtures/npm/node_modules/gtcjsdist/package.json b/tests/fixtures/npm/node_modules/gtcjsdist/package.json new file mode 100644 index 000000000..8b416fca1 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtcjsdist/package.json @@ -0,0 +1,11 @@ +{ + "name": "gtcjsdist", + "version": "1.0.0", + "description": "a type-module package whose CommonJS entry is an esbuild 0.25.10 .cjs bundle using the node-mode __toESM interop", + "type": "module", + "main": "./dist/index.cjs", + "types": "./dist/index.d.cts", + "dependencies": { + "gtcore": "1.0.0" + } +} diff --git a/tests/fixtures/npm/node_modules/gtcycle/dist/append.d.ts b/tests/fixtures/npm/node_modules/gtcycle/dist/append.d.ts new file mode 100644 index 000000000..e33c31f90 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtcycle/dist/append.d.ts @@ -0,0 +1 @@ +export declare function appendAll(xs: string[], tail: string): string[]; diff --git a/tests/fixtures/npm/node_modules/gtcycle/dist/append.js b/tests/fixtures/npm/node_modules/gtcycle/dist/append.js new file mode 100644 index 000000000..cdfeb6c6c --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtcycle/dist/append.js @@ -0,0 +1,30 @@ +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); +var append_exports = {}; +__export(append_exports, { + appendAll: () => appendAll +}); +module.exports = __toCommonJS(append_exports); +var import_index = require("./index"); +function appendAll(xs, tail) { + return xs.some(import_index.isHandler) ? xs : xs.concat([tail]); +} +// Annotate the CommonJS export names for ESM import in node: +0 && (module.exports = { + appendAll +}); diff --git a/tests/fixtures/npm/node_modules/gtcycle/dist/index.d.ts b/tests/fixtures/npm/node_modules/gtcycle/dist/index.d.ts new file mode 100644 index 000000000..4b86308a9 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtcycle/dist/index.d.ts @@ -0,0 +1,4 @@ +export { mergeAll } from "./merge"; +export declare function isHandler(x: string): boolean; +export declare function normalize(xs: string[]): string[]; +export declare function run(a: string[], b: string[]): string; diff --git a/tests/fixtures/npm/node_modules/gtcycle/dist/index.js b/tests/fixtures/npm/node_modules/gtcycle/dist/index.js new file mode 100644 index 000000000..92f6d2a39 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtcycle/dist/index.js @@ -0,0 +1,44 @@ +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); +var index_exports = {}; +__export(index_exports, { + isHandler: () => isHandler, + mergeAll: () => import_merge2.mergeAll, + normalize: () => normalize, + run: () => run +}); +module.exports = __toCommonJS(index_exports); +var import_append = require("./append"); +var import_merge = require("./merge"); +var import_merge2 = require("./merge"); +function isHandler(x) { + return x.startsWith("@"); +} +function normalize(xs) { + return xs.map((x) => x.trim()).filter((x) => x.length > 0); +} +function run(a, b) { + return (0, import_append.appendAll)((0, import_merge.mergeAll)(a, b), "@end").join(","); +} +// Annotate the CommonJS export names for ESM import in node: +0 && (module.exports = { + isHandler, + mergeAll, + normalize, + run +}); diff --git a/tests/fixtures/npm/node_modules/gtcycle/dist/merge.d.ts b/tests/fixtures/npm/node_modules/gtcycle/dist/merge.d.ts new file mode 100644 index 000000000..bbe6b6a4d --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtcycle/dist/merge.d.ts @@ -0,0 +1 @@ +export declare function mergeAll(a: string[], b: string[]): string[]; diff --git a/tests/fixtures/npm/node_modules/gtcycle/dist/merge.js b/tests/fixtures/npm/node_modules/gtcycle/dist/merge.js new file mode 100644 index 000000000..114e7d8cf --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtcycle/dist/merge.js @@ -0,0 +1,30 @@ +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); +var merge_exports = {}; +__export(merge_exports, { + mergeAll: () => mergeAll +}); +module.exports = __toCommonJS(merge_exports); +var import_index = require("./index"); +function mergeAll(a, b) { + return (0, import_index.normalize)(a.concat(b)).filter((x) => !(0, import_index.isHandler)(x) || x === "@keep"); +} +// Annotate the CommonJS export names for ESM import in node: +0 && (module.exports = { + mergeAll +}); diff --git a/tests/fixtures/npm/node_modules/gtcycle/package.json b/tests/fixtures/npm/node_modules/gtcycle/package.json new file mode 100644 index 000000000..5b41b8a52 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtcycle/package.json @@ -0,0 +1,7 @@ +{ + "name": "gtcycle", + "version": "1.0.0", + "description": "esbuild 0.25.10 per-file CommonJS output (no bundling) of three modules that import each other", + "main": "./dist/index.js", + "types": "./dist/index.d.ts" +} diff --git a/tests/fixtures/npm/node_modules/gtcyclealias/dist/index.d.ts b/tests/fixtures/npm/node_modules/gtcyclealias/dist/index.d.ts new file mode 100644 index 000000000..5f4373e2f --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtcyclealias/dist/index.d.ts @@ -0,0 +1,3 @@ +export declare const LIMIT = 3; +export declare function show(): string; +export declare function peek(): number; diff --git a/tests/fixtures/npm/node_modules/gtcyclealias/dist/index.js b/tests/fixtures/npm/node_modules/gtcyclealias/dist/index.js new file mode 100644 index 000000000..77aca3c9b --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtcyclealias/dist/index.js @@ -0,0 +1,37 @@ +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); +var index_exports = {}; +__export(index_exports, { + LIMIT: () => LIMIT, + peek: () => peek, + show: () => show +}); +module.exports = __toCommonJS(index_exports); +var import_reader = require("./reader"); +const LIMIT = 3; +function peek() { + return LIMIT; +} +function show() { + return (0, import_reader.describe)(); +} +// Annotate the CommonJS export names for ESM import in node: +0 && (module.exports = { + LIMIT, + show +}); diff --git a/tests/fixtures/npm/node_modules/gtcyclealias/dist/reader.js b/tests/fixtures/npm/node_modules/gtcyclealias/dist/reader.js new file mode 100644 index 000000000..943e61a0a --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtcyclealias/dist/reader.js @@ -0,0 +1,34 @@ +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); +var reader_exports = {}; +__export(reader_exports, { + captured: () => captured, + describe: () => describe +}); +module.exports = __toCommonJS(reader_exports); +var import_index = require("./index"); +const read = import_index.peek; +const captured = read(); +function describe() { + return "limit " + captured; +} +// Annotate the CommonJS export names for ESM import in node: +0 && (module.exports = { + captured, + describe +}); diff --git a/tests/fixtures/npm/node_modules/gtcyclealias/package.json b/tests/fixtures/npm/node_modules/gtcyclealias/package.json new file mode 100644 index 000000000..9ee7441e0 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtcyclealias/package.json @@ -0,0 +1,7 @@ +{ + "name": "gtcyclealias", + "version": "1.0.0", + "description": "esbuild 0.25.10 per-file CommonJS output shape of a cycle whose partner stores an exported function in a const and calls it while that function's const binding is uninitialized", + "main": "./dist/index.js", + "types": "./dist/index.d.ts" +} diff --git a/tests/fixtures/npm/node_modules/gtcyclebad/dist/index.d.ts b/tests/fixtures/npm/node_modules/gtcyclebad/dist/index.d.ts new file mode 100644 index 000000000..0fa366319 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtcyclebad/dist/index.d.ts @@ -0,0 +1,2 @@ +export declare const LIMIT = 3; +export declare function show(): string; diff --git a/tests/fixtures/npm/node_modules/gtcyclebad/dist/index.js b/tests/fixtures/npm/node_modules/gtcyclebad/dist/index.js new file mode 100644 index 000000000..5bd708a37 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtcyclebad/dist/index.js @@ -0,0 +1,33 @@ +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); +var index_exports = {}; +__export(index_exports, { + LIMIT: () => LIMIT, + show: () => show +}); +module.exports = __toCommonJS(index_exports); +var import_reader = require("./reader"); +const LIMIT = 3; +function show() { + return (0, import_reader.describe)(); +} +// Annotate the CommonJS export names for ESM import in node: +0 && (module.exports = { + LIMIT, + show +}); diff --git a/tests/fixtures/npm/node_modules/gtcyclebad/dist/reader.js b/tests/fixtures/npm/node_modules/gtcyclebad/dist/reader.js new file mode 100644 index 000000000..a58bdaa51 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtcyclebad/dist/reader.js @@ -0,0 +1,33 @@ +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); +var reader_exports = {}; +__export(reader_exports, { + captured: () => captured, + describe: () => describe +}); +module.exports = __toCommonJS(reader_exports); +var import_index = require("./index"); +const captured = import_index.LIMIT; +function describe() { + return "limit " + captured; +} +// Annotate the CommonJS export names for ESM import in node: +0 && (module.exports = { + captured, + describe +}); diff --git a/tests/fixtures/npm/node_modules/gtcyclebad/package.json b/tests/fixtures/npm/node_modules/gtcyclebad/package.json new file mode 100644 index 000000000..793c97fc9 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtcyclebad/package.json @@ -0,0 +1,7 @@ +{ + "name": "gtcyclebad", + "version": "1.0.0", + "description": "esbuild 0.25.10 per-file CommonJS output of a cycle whose partner reads a const binding before its declaration runs", + "main": "./dist/index.js", + "types": "./dist/index.d.ts" +} diff --git a/tests/fixtures/npm/node_modules/gtcyclebox/dist/index.d.ts b/tests/fixtures/npm/node_modules/gtcyclebox/dist/index.d.ts new file mode 100644 index 000000000..5f4373e2f --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtcyclebox/dist/index.d.ts @@ -0,0 +1,3 @@ +export declare const LIMIT = 3; +export declare function show(): string; +export declare function peek(): number; diff --git a/tests/fixtures/npm/node_modules/gtcyclebox/dist/index.js b/tests/fixtures/npm/node_modules/gtcyclebox/dist/index.js new file mode 100644 index 000000000..77aca3c9b --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtcyclebox/dist/index.js @@ -0,0 +1,37 @@ +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); +var index_exports = {}; +__export(index_exports, { + LIMIT: () => LIMIT, + peek: () => peek, + show: () => show +}); +module.exports = __toCommonJS(index_exports); +var import_reader = require("./reader"); +const LIMIT = 3; +function peek() { + return LIMIT; +} +function show() { + return (0, import_reader.describe)(); +} +// Annotate the CommonJS export names for ESM import in node: +0 && (module.exports = { + LIMIT, + show +}); diff --git a/tests/fixtures/npm/node_modules/gtcyclebox/dist/reader.js b/tests/fixtures/npm/node_modules/gtcyclebox/dist/reader.js new file mode 100644 index 000000000..b8f8a927e --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtcyclebox/dist/reader.js @@ -0,0 +1,34 @@ +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); +var reader_exports = {}; +__export(reader_exports, { + captured: () => captured, + describe: () => describe +}); +module.exports = __toCommonJS(reader_exports); +var import_index = require("./index"); +const box = { read: import_index.peek }; +const captured = box.read(); +function describe() { + return "limit " + captured; +} +// Annotate the CommonJS export names for ESM import in node: +0 && (module.exports = { + captured, + describe +}); diff --git a/tests/fixtures/npm/node_modules/gtcyclebox/package.json b/tests/fixtures/npm/node_modules/gtcyclebox/package.json new file mode 100644 index 000000000..67f943195 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtcyclebox/package.json @@ -0,0 +1,7 @@ +{ + "name": "gtcyclebox", + "version": "1.0.0", + "description": "esbuild 0.25.10 per-file CommonJS output shape of a cycle whose partner stores an exported function in an object literal and calls it while that function's const binding is uninitialized", + "main": "./dist/index.js", + "types": "./dist/index.d.ts" +} diff --git a/tests/fixtures/npm/node_modules/gtcyclecall/dist/index.d.ts b/tests/fixtures/npm/node_modules/gtcyclecall/dist/index.d.ts new file mode 100644 index 000000000..5f4373e2f --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtcyclecall/dist/index.d.ts @@ -0,0 +1,3 @@ +export declare const LIMIT = 3; +export declare function show(): string; +export declare function peek(): number; diff --git a/tests/fixtures/npm/node_modules/gtcyclecall/dist/index.js b/tests/fixtures/npm/node_modules/gtcyclecall/dist/index.js new file mode 100644 index 000000000..77aca3c9b --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtcyclecall/dist/index.js @@ -0,0 +1,37 @@ +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); +var index_exports = {}; +__export(index_exports, { + LIMIT: () => LIMIT, + peek: () => peek, + show: () => show +}); +module.exports = __toCommonJS(index_exports); +var import_reader = require("./reader"); +const LIMIT = 3; +function peek() { + return LIMIT; +} +function show() { + return (0, import_reader.describe)(); +} +// Annotate the CommonJS export names for ESM import in node: +0 && (module.exports = { + LIMIT, + show +}); diff --git a/tests/fixtures/npm/node_modules/gtcyclecall/dist/reader.js b/tests/fixtures/npm/node_modules/gtcyclecall/dist/reader.js new file mode 100644 index 000000000..c98d1ae46 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtcyclecall/dist/reader.js @@ -0,0 +1,33 @@ +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); +var reader_exports = {}; +__export(reader_exports, { + captured: () => captured, + describe: () => describe +}); +module.exports = __toCommonJS(reader_exports); +var import_index = require("./index"); +const captured = (0, import_index.peek)(); +function describe() { + return "limit " + captured; +} +// Annotate the CommonJS export names for ESM import in node: +0 && (module.exports = { + captured, + describe +}); diff --git a/tests/fixtures/npm/node_modules/gtcyclecall/package.json b/tests/fixtures/npm/node_modules/gtcyclecall/package.json new file mode 100644 index 000000000..cc1069a6f --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtcyclecall/package.json @@ -0,0 +1,7 @@ +{ + "name": "gtcyclecall", + "version": "1.0.0", + "description": "esbuild 0.25.10 per-file CommonJS output shape of a cycle whose partner calls an exported function through (0, binding.fn)() while that function's const binding is uninitialized", + "main": "./dist/index.js", + "types": "./dist/index.d.ts" +} diff --git a/tests/fixtures/npm/node_modules/gtsubpath/dist/data.d.ts b/tests/fixtures/npm/node_modules/gtsubpath/dist/data.d.ts new file mode 100644 index 000000000..356c8bbc9 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtsubpath/dist/data.d.ts @@ -0,0 +1,2 @@ +import type { Manager } from "./index.js"; +export declare const managers: Manager[]; diff --git a/tests/fixtures/npm/node_modules/gtsubpath/dist/data.js b/tests/fixtures/npm/node_modules/gtsubpath/dist/data.js new file mode 100644 index 000000000..918a296a8 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtsubpath/dist/data.js @@ -0,0 +1,6 @@ +"use strict"; +exports.managers = [ + { name: "npm", lockfile: "package-lock.json" }, + { name: "pnpm", lockfile: "pnpm-lock.yaml" }, + { name: "yarn", lockfile: "yarn.lock" }, +]; diff --git a/tests/fixtures/npm/node_modules/gtsubpath/dist/detect.js b/tests/fixtures/npm/node_modules/gtsubpath/dist/detect.js new file mode 100644 index 000000000..d129041fe --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtsubpath/dist/detect.js @@ -0,0 +1,11 @@ +// A hand-rolled __toESM the recognizer cannot verify (see gtdrift). +"use strict"; +var __toESM = (mod) => { + var ns = { default: mod }; + for (var key in mod) ns[key] = mod[key]; + return ns; +}; +var import_gtcore = __toESM(require("gtcore")); +exports.detect = function detect(files) { + return import_gtcore.default.label(files.length); +}; diff --git a/tests/fixtures/npm/node_modules/gtsubpath/dist/index.d.ts b/tests/fixtures/npm/node_modules/gtsubpath/dist/index.d.ts new file mode 100644 index 000000000..24b05d86d --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtsubpath/dist/index.d.ts @@ -0,0 +1,6 @@ +export interface Manager { + name: string; + lockfile: string; +} +export declare const managers: Manager[]; +export declare function detect(files: string[]): string; diff --git a/tests/fixtures/npm/node_modules/gtsubpath/dist/index.js b/tests/fixtures/npm/node_modules/gtsubpath/dist/index.js new file mode 100644 index 000000000..a330d28c0 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtsubpath/dist/index.js @@ -0,0 +1,5 @@ +"use strict"; +const detect = require("./detect.js"); +const data = require("./data.js"); +exports.detect = detect.detect; +exports.managers = data.managers; diff --git a/tests/fixtures/npm/node_modules/gtsubpath/package.json b/tests/fixtures/npm/node_modules/gtsubpath/package.json new file mode 100644 index 000000000..f785cb154 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtsubpath/package.json @@ -0,0 +1,10 @@ +{ + "name": "gtsubpath", + "version": "1.0.0", + "description": "a package whose main entry reaches a file with bundler interop the recognizer cannot verify, beside a leaf data module the program imports by subpath", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "dependencies": { + "gtcore": "1.0.0" + } +} diff --git a/tests/harness/__snapshots__/boot-path-node-surface-fences.ts.txt b/tests/harness/__snapshots__/boot-path-node-surface-fences.ts.txt new file mode 100644 index 000000000..c287a1641 --- /dev/null +++ b/tests/harness/__snapshots__/boot-path-node-surface-fences.ts.txt @@ -0,0 +1,35 @@ +boot-path-node-surface-fences.ts:8:14 - error SC2020: 'process.send' is part of the standard library types but has no scriptc lowering yet + + 7 | + 8 | const send = process.send; + | ^~~~~~~~~~~~ + 9 | console.log(typeof send); + + hint: the type checker sees the full standard library, but only the supported surface compiles (https://scriptc.dev/limitations) + +boot-path-node-surface-fences.ts:11:1 - error SC2020: 'a '_write' method whose parameter 0 is 'number' where Node passes 'Uint8Array'' is part of the standard library types but has no scriptc lowering yet + + 10 | + 11 | class Counter extends Writable { + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 12 | _write(chunk: number, _encoding: BufferEncoding, cb: (err?: Error | null) => void): void { + + hint: annotate the parameter with the Node signature's type or a supertype of it (chunks are Buffers here) + +boot-path-node-surface-fences.ts:22:31 - error SC2020: 'util.promisify of this target' is part of the standard library types but has no scriptc lowering yet + + 21 | } + 22 | const legacyAsync = promisify(legacy); + | ^~~~~~ + 23 | legacyAsync().then((v) => console.log(v)); + + hint: child_process.exec, child_process.execFile, setTimeout, setImmediate, stream.finished, stream.pipeline, the zlib callback one-shots, the crypto callback functions (randomBytes, pbkdf2, scrypt, hkdf, randomFill, randomInt, generateKey), dns.lookup, dns.lookupService, the fs callback functions with an fs/promises counterpart, and typed callback-last functions whose callback takes (error?: Error | null, value?) are the supported targets + +boot-path-node-surface-fences.ts:23:1 - error SC2004: uses of 'legacyAsync' inherit the blocker on its declaration + + 22 | const legacyAsync = promisify(legacy); + 23 | legacyAsync().then((v) => console.log(v)); + | ^~~~~~~~~~~ + 24 | + + hint: the declaration of 'legacyAsync' did not compile — fix the diagnostic reported there and these sites clear with it \ No newline at end of file diff --git a/tests/harness/__snapshots__/class-field-accessor-descriptor.ts.txt b/tests/harness/__snapshots__/class-field-accessor-descriptor.ts.txt new file mode 100644 index 000000000..986e0d023 --- /dev/null +++ b/tests/harness/__snapshots__/class-field-accessor-descriptor.ts.txt @@ -0,0 +1,6 @@ +class-field-accessor-descriptor.ts:9:1 - error SC2020: Object.defineProperty with an accessor descriptor for the declared field 'x' is not supported yet + + 8 | const value = new Value(); + 9 | Object.defineProperty(value, "x", { + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 10 | get() { \ No newline at end of file diff --git a/tests/harness/__snapshots__/maybe-promise-fences.ts.txt b/tests/harness/__snapshots__/maybe-promise-fences.ts.txt index 58fb23517..159e88f76 100644 --- a/tests/harness/__snapshots__/maybe-promise-fences.ts.txt +++ b/tests/harness/__snapshots__/maybe-promise-fences.ts.txt @@ -55,11 +55,4 @@ maybe-promise-fences.ts:32:13 - error SC1090: 'instanceof Promise' on '{ v: numb 31 | const record = { v: 1 }; 32 | console.log(record instanceof Promise); | ^~~~~~~~~~~~~~~~~~~~~~~~~ - 33 | - -maybe-promise-fences.ts:36:26 - error SC1090: '(unknown) => Promise' values where '() => Promise' is expected is not supported yet - - 35 | type HandlerFn = (...args: unknown[]) => Promise; - 36 | const fixed: HandlerFn = async (first: unknown) => first; - | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - 37 | fixed(1, 2).then((v) => console.log(v)); \ No newline at end of file + 33 | \ No newline at end of file diff --git a/tests/harness/__snapshots__/mock-fences.ts.txt b/tests/harness/__snapshots__/mock-fences.ts.txt index 6b4defcbb..d4b8115e8 100644 --- a/tests/harness/__snapshots__/mock-fences.ts.txt +++ b/tests/harness/__snapshots__/mock-fences.ts.txt @@ -1,62 +1,62 @@ -mock-fences.ts:14:15 - error SC2020: 'mock.fn of this function type' is part of the standard library types but has no scriptc lowering yet +mock-fences.ts:18:17 - error SC2020: 'mock.fn() of a function returning 'number'' is part of the standard library types but has no scriptc lowering yet - 13 | - 14 | const empty = mock.fn(); // no original function - | ^~~~~~~~~ - 15 | empty(); + 17 | + 18 | const counted = mock.fn<() => number>(); // no original, returns number + | ^~~~~~~~~~~~~~~~~~~~~~~ + 19 | counted(); - hint: mock functions lower for functions with fixed parameters, or a single rest array parameter, and an implementation of the same type + hint: mock.fn() without an original returns undefined: pass an original, or use a function type whose return type includes undefined -mock-fences.ts:27:13 - error SC2020: 'mock.method on 'Counter'' is part of the standard library types but has no scriptc lowering yet +mock-fences.ts:31:13 - error SC2020: 'mock.method on 'Counter'' is part of the standard library types but has no scriptc lowering yet - 26 | } - 27 | mock.method(new Counter(), "count"); // class instance + 30 | } + 31 | mock.method(new Counter(), "count"); // class instance | ^~~~~~~~~~~~~ - 28 | mock.method(console, "log"); // builtin object + 32 | mock.method(console, "log"); // builtin object hint: mock.method lowers for objects whose type maps to a record (object literals and interface-typed objects) and a function-valued field named by a string literal -mock-fences.ts:28:13 - error SC2020: 'mock.method on this object' is part of the standard library types but has no scriptc lowering yet +mock-fences.ts:32:13 - error SC2020: 'mock.method on this object' is part of the standard library types but has no scriptc lowering yet - 27 | mock.method(new Counter(), "count"); // class instance - 28 | mock.method(console, "log"); // builtin object + 31 | mock.method(new Counter(), "count"); // class instance + 32 | mock.method(console, "log"); // builtin object | ^~~~~~~ - 29 | mock.method(obj, "value", { times: 1 }); // options + 33 | mock.method(obj, "value", { times: 1 }); // options hint: mock.method lowers for objects whose type maps to a record (object literals and interface-typed objects) and a function-valued field named by a string literal -mock-fences.ts:29:27 - error SC2020: 'mock.method with an options object' is part of the standard library types but has no scriptc lowering yet +mock-fences.ts:33:27 - error SC2020: 'mock.method with an options object' is part of the standard library types but has no scriptc lowering yet - 28 | mock.method(console, "log"); // builtin object - 29 | mock.method(obj, "value", { times: 1 }); // options + 32 | mock.method(console, "log"); // builtin object + 33 | mock.method(obj, "value", { times: 1 }); // options | ^~~~~~~~~~~~ - 30 | const other = mock.method(obj, "value", () => "one"); // implementation of another type + 34 | const other = mock.method(obj, "value", () => "one"); // implementation of another type hint: mock options (times, getter and setter) have no lowering -mock-fences.ts:30:41 - error SC2020: 'mock.method with an implementation of another type' is part of the standard library types but has no scriptc lowering yet +mock-fences.ts:34:41 - error SC2020: 'mock.method with an implementation of another type' is part of the standard library types but has no scriptc lowering yet - 29 | mock.method(obj, "value", { times: 1 }); // options - 30 | const other = mock.method(obj, "value", () => "one"); // implementation of another type + 33 | mock.method(obj, "value", { times: 1 }); // options + 34 | const other = mock.method(obj, "value", () => "one"); // implementation of another type | ^~~~~~~~~~~ - 31 | other(); + 35 | other(); hint: give the implementation the method's parameter and return types, or discard the result -mock-fences.ts:35:31 - error SC2020: 't.mock.method with an options object' is part of the standard library types but has no scriptc lowering yet +mock-fences.ts:39:31 - error SC2020: 't.mock.method with an options object' is part of the standard library types but has no scriptc lowering yet - 34 | t.mock.method(obj, "value"); // lowers - 35 | t.mock.method(obj, "value", { times: 2 }); // options + 38 | t.mock.method(obj, "value"); // lowers + 39 | t.mock.method(obj, "value", { times: 2 }); // options | ^~~~~~~~~~~~ - 36 | }); + 40 | }); hint: mock options (times, getter and setter) have no lowering -mock-fences.ts:38:14 - error SC2020: 'mock.fn of a function returning 'number | string'' is part of the standard library types but has no scriptc lowering yet +mock-fences.ts:42:14 - error SC2020: 'mock.fn of a function returning 'number | string'' is part of the standard library types but has no scriptc lowering yet - 37 | - 38 | const pick = mock.fn((flag: boolean): number | string => (flag ? 1 : "one")); // union without undefined + 41 | + 42 | const pick = mock.fn((flag: boolean): number | string => (flag ? 1 : "one")); // union without undefined | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - 39 | pick(true); + 43 | pick(true); hint: a union return type needs an undefined arm here (the calls log records undefined for calls that throw) \ No newline at end of file diff --git a/tests/harness/corpus-costs.json b/tests/harness/corpus-costs.json index b18e2ab28..85e4ba4aa 100644 --- a/tests/harness/corpus-costs.json +++ b/tests/harness/corpus-costs.json @@ -1886,6 +1886,7 @@ "5464-locale-compare-fractions.ts": 2833, "5465-dyn-index-read-arrays.ts": 3603, "5466-dyn-index-read-special.ts": 2823, + "5469-typed-array-loop-initializer-rebinding.ts": 1453, "5470-strip-vt-osc.cjs": 2497, "5471-strip-vt-csi.cjs": 2651, "5472-strip-vt-random.cjs": 2375, @@ -2189,9 +2190,6 @@ "5940-callback-helper-specialization.ts": 3109, "5941-callback-specialization-recursion.ts": 3112, "5942-callback-specialization-throws.ts": 3221, - "5942-local-class-observed-methods.ts": 5063, - "5943-fields-read-before-assignment.ts": 6856, - "5944-js-fields-read-before-assignment.js": 4247, "5945-local-class-observed-methods.ts": 5063, "5946-nested-record-field-optional-writes.ts": 6045, "5950-tuple-predicate-methods.ts": 5990, @@ -2233,6 +2231,8 @@ "6001-json-stringify-js-class-instances.js": 4668, "6002-inspect-class-added-properties.ts": 6044, "6003-inspect-js-class-properties.js": 2752, + "6004-class-instance-integrity.ts": 10548, + "6005-class-instance-integrity-sloppy.cjs": 7466, "601-closures-loops.ts": 1788, "6010-match-metadata.ts": 3990, "6011-match-metadata-js.js": 3660, @@ -4983,6 +4983,7 @@ "5464-locale-compare-fractions.ts": 3312, "5465-dyn-index-read-arrays.ts": 3798, "5466-dyn-index-read-special.ts": 4384, + "5469-typed-array-loop-initializer-rebinding.ts": 2058, "5470-strip-vt-osc.cjs": 2708, "5471-strip-vt-csi.cjs": 3038, "5472-strip-vt-random.cjs": 2850, @@ -5286,9 +5287,6 @@ "5940-callback-helper-specialization.ts": 4548, "5941-callback-specialization-recursion.ts": 4448, "5942-callback-specialization-throws.ts": 3443, - "5942-local-class-observed-methods.ts": 6212, - "5943-fields-read-before-assignment.ts": 72293, - "5944-js-fields-read-before-assignment.js": 40310, "5945-local-class-observed-methods.ts": 6212, "5946-nested-record-field-optional-writes.ts": 68117, "5950-tuple-predicate-methods.ts": 7790, @@ -5330,6 +5328,8 @@ "6001-json-stringify-js-class-instances.js": 7469, "6002-inspect-class-added-properties.ts": 9670, "6003-inspect-js-class-properties.js": 4403, + "6004-class-instance-integrity.ts": 16877, + "6005-class-instance-integrity-sloppy.cjs": 11946, "601-closures-loops.ts": 2297, "6010-match-metadata.ts": 5450, "6011-match-metadata-js.js": 4890, diff --git a/tests/harness/corpus-costs/5816-fixed-into-unknown-rest.ts.json b/tests/harness/corpus-costs/5816-fixed-into-unknown-rest.ts.json new file mode 100644 index 000000000..2b149eb6a --- /dev/null +++ b/tests/harness/corpus-costs/5816-fixed-into-unknown-rest.ts.json @@ -0,0 +1 @@ +{ "plain": 3028, "san": 5470 } diff --git a/tests/harness/corpus-costs/5916-dyn-error-stack-property.ts.json b/tests/harness/corpus-costs/5916-dyn-error-stack-property.ts.json new file mode 100644 index 000000000..3ed0acb36 --- /dev/null +++ b/tests/harness/corpus-costs/5916-dyn-error-stack-property.ts.json @@ -0,0 +1 @@ +{ "plain": 3232, "san": 4298 } diff --git a/tests/harness/corpus-costs/6064-protocol-subinterface-assertions.ts.json b/tests/harness/corpus-costs/6064-protocol-subinterface-assertions.ts.json deleted file mode 100644 index c05586e43..000000000 --- a/tests/harness/corpus-costs/6064-protocol-subinterface-assertions.ts.json +++ /dev/null @@ -1 +0,0 @@ -{ "plain": 4411, "san": 5159 } diff --git a/tests/harness/corpus-costs/6380-variadic-wrapper-arguments-apply.ts.json b/tests/harness/corpus-costs/6380-variadic-wrapper-arguments-apply.ts.json new file mode 100644 index 000000000..553943f5a --- /dev/null +++ b/tests/harness/corpus-costs/6380-variadic-wrapper-arguments-apply.ts.json @@ -0,0 +1 @@ +{ "plain": 3870, "san": 3660 } diff --git a/tests/harness/corpus-costs/6381-variadic-wrapper-async.ts.json b/tests/harness/corpus-costs/6381-variadic-wrapper-async.ts.json new file mode 100644 index 000000000..5cad95fef --- /dev/null +++ b/tests/harness/corpus-costs/6381-variadic-wrapper-async.ts.json @@ -0,0 +1 @@ +{ "plain": 3550, "san": 4820 } diff --git a/tests/harness/corpus-costs/6382-variadic-wrapper-methods-this.ts.json b/tests/harness/corpus-costs/6382-variadic-wrapper-methods-this.ts.json new file mode 100644 index 000000000..644fc5fea --- /dev/null +++ b/tests/harness/corpus-costs/6382-variadic-wrapper-methods-this.ts.json @@ -0,0 +1 @@ +{ "plain": 3000, "san": 6260 } diff --git a/tests/harness/corpus-costs/6383-variadic-wrapper-rest-tuple.ts.json b/tests/harness/corpus-costs/6383-variadic-wrapper-rest-tuple.ts.json new file mode 100644 index 000000000..f83a73df7 --- /dev/null +++ b/tests/harness/corpus-costs/6383-variadic-wrapper-rest-tuple.ts.json @@ -0,0 +1 @@ +{ "plain": 3140, "san": 4540 } diff --git a/tests/harness/corpus-costs/6384-variadic-wrapper-nested.ts.json b/tests/harness/corpus-costs/6384-variadic-wrapper-nested.ts.json new file mode 100644 index 000000000..21dee9274 --- /dev/null +++ b/tests/harness/corpus-costs/6384-variadic-wrapper-nested.ts.json @@ -0,0 +1 @@ +{ "plain": 3220, "san": 2960 } diff --git a/tests/harness/corpus-costs/6390-catch-binding-predicate.ts.json b/tests/harness/corpus-costs/6390-catch-binding-predicate.ts.json new file mode 100644 index 000000000..5b661172c --- /dev/null +++ b/tests/harness/corpus-costs/6390-catch-binding-predicate.ts.json @@ -0,0 +1 @@ +{ "plain": 3020, "san": 3560 } diff --git a/tests/harness/corpus-costs/6391-nullish-function-default.ts.json b/tests/harness/corpus-costs/6391-nullish-function-default.ts.json new file mode 100644 index 000000000..a3ae15ea4 --- /dev/null +++ b/tests/harness/corpus-costs/6391-nullish-function-default.ts.json @@ -0,0 +1 @@ +{ "plain": 4100, "san": 3150 } diff --git a/tests/harness/corpus-costs/6392-optional-chain-protocol-calls.ts.json b/tests/harness/corpus-costs/6392-optional-chain-protocol-calls.ts.json new file mode 100644 index 000000000..3fa72b729 --- /dev/null +++ b/tests/harness/corpus-costs/6392-optional-chain-protocol-calls.ts.json @@ -0,0 +1 @@ +{ "plain": 4400, "san": 4200 } diff --git a/tests/harness/corpus-costs/6393-function-types-any-params.ts.json b/tests/harness/corpus-costs/6393-function-types-any-params.ts.json new file mode 100644 index 000000000..73f790694 --- /dev/null +++ b/tests/harness/corpus-costs/6393-function-types-any-params.ts.json @@ -0,0 +1 @@ +{ "plain": 2790, "san": 3150 } diff --git a/tests/harness/corpus-costs/6394-in-key-error-subclasses.ts.json b/tests/harness/corpus-costs/6394-in-key-error-subclasses.ts.json new file mode 100644 index 000000000..6fe8b6765 --- /dev/null +++ b/tests/harness/corpus-costs/6394-in-key-error-subclasses.ts.json @@ -0,0 +1 @@ +{ "plain": 2900, "san": 3120 } diff --git a/tests/harness/corpus-costs/6395-generic-object-entries.ts.json b/tests/harness/corpus-costs/6395-generic-object-entries.ts.json new file mode 100644 index 000000000..d38b11826 --- /dev/null +++ b/tests/harness/corpus-costs/6395-generic-object-entries.ts.json @@ -0,0 +1 @@ +{ "plain": 2990, "san": 3030 } diff --git a/tests/harness/corpus-costs/6396-generic-any-instantiation.ts.json b/tests/harness/corpus-costs/6396-generic-any-instantiation.ts.json new file mode 100644 index 000000000..d38f92d43 --- /dev/null +++ b/tests/harness/corpus-costs/6396-generic-any-instantiation.ts.json @@ -0,0 +1 @@ +{ "plain": 2590, "san": 2850 } diff --git a/tests/harness/corpus-costs/6397-event-loop-metrics.ts.json b/tests/harness/corpus-costs/6397-event-loop-metrics.ts.json new file mode 100644 index 000000000..26c5a51a4 --- /dev/null +++ b/tests/harness/corpus-costs/6397-event-loop-metrics.ts.json @@ -0,0 +1 @@ +{ "plain": 3300, "san": 4140 } diff --git a/tests/harness/corpus-costs/6398-conditional-fixed-arity.ts.json b/tests/harness/corpus-costs/6398-conditional-fixed-arity.ts.json new file mode 100644 index 000000000..c8912b309 --- /dev/null +++ b/tests/harness/corpus-costs/6398-conditional-fixed-arity.ts.json @@ -0,0 +1 @@ +{ "plain": 3630, "san": 3870 } diff --git a/tests/harness/corpus-costs/6400-dyn-error-stack-property-js.js.json b/tests/harness/corpus-costs/6400-dyn-error-stack-property-js.js.json new file mode 100644 index 000000000..c51a3475f --- /dev/null +++ b/tests/harness/corpus-costs/6400-dyn-error-stack-property-js.js.json @@ -0,0 +1 @@ +{ "plain": 3172, "san": 4044 } diff --git a/tests/harness/corpus-costs/6401-dyn-error-own-property-names.ts.json b/tests/harness/corpus-costs/6401-dyn-error-own-property-names.ts.json new file mode 100644 index 000000000..f85c4901a --- /dev/null +++ b/tests/harness/corpus-costs/6401-dyn-error-own-property-names.ts.json @@ -0,0 +1 @@ +{ "plain": 3175, "san": 4362 } diff --git a/tests/harness/corpus-costs/6402-dyn-error-own-property-names-js.js.json b/tests/harness/corpus-costs/6402-dyn-error-own-property-names-js.js.json new file mode 100644 index 000000000..55012b442 --- /dev/null +++ b/tests/harness/corpus-costs/6402-dyn-error-own-property-names-js.js.json @@ -0,0 +1 @@ +{ "plain": 3056, "san": 4620 } diff --git a/tests/harness/corpus-costs/6414-catch-values-roundtrip.ts.json b/tests/harness/corpus-costs/6414-catch-values-roundtrip.ts.json new file mode 100644 index 000000000..502f0220d --- /dev/null +++ b/tests/harness/corpus-costs/6414-catch-values-roundtrip.ts.json @@ -0,0 +1 @@ +{"plain":4942} diff --git a/tests/harness/corpus-costs/6445-rest-function-fixed-slot.ts.json b/tests/harness/corpus-costs/6445-rest-function-fixed-slot.ts.json new file mode 100644 index 000000000..858c662a8 --- /dev/null +++ b/tests/harness/corpus-costs/6445-rest-function-fixed-slot.ts.json @@ -0,0 +1 @@ +{ "plain": 2526, "san": 3450 } diff --git a/tests/harness/corpus-costs/6446-rest-function-nested-declaration.ts.json b/tests/harness/corpus-costs/6446-rest-function-nested-declaration.ts.json new file mode 100644 index 000000000..39f698b22 --- /dev/null +++ b/tests/harness/corpus-costs/6446-rest-function-nested-declaration.ts.json @@ -0,0 +1 @@ +{ "plain": 2222, "san": 3035 } diff --git a/tests/harness/corpus-costs/6468-generic-unknown-record-entries.ts.json b/tests/harness/corpus-costs/6468-generic-unknown-record-entries.ts.json new file mode 100644 index 000000000..8489daced --- /dev/null +++ b/tests/harness/corpus-costs/6468-generic-unknown-record-entries.ts.json @@ -0,0 +1 @@ +{ "plain": 4560, "san": 4770 } diff --git a/tests/harness/esm-path-globals.test.ts b/tests/harness/esm-path-globals.test.ts new file mode 100644 index 000000000..546c924a9 --- /dev/null +++ b/tests/harness/esm-path-globals.test.ts @@ -0,0 +1,38 @@ +/* __dirname / __filename in TypeScript files that Node's strip-types rule + * loads as ES modules: scriptc compiles them to the module's source path, + * the CommonJS-bundle reading. Node itself has no such globals in an ES + * module, so the oracle runs a twin of each source with the globals spelled + * import.meta.dirname / import.meta.filename, at the same paths. */ +import { execFile } from "node:child_process"; +import { cpSync, mkdtempSync, readFileSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { promisify } from "node:util"; +import { expect, test } from "vitest"; +import { compile } from "@scriptc/compiler"; + +const execFileAsync = promisify(execFile); +const fixture = join(import.meta.dirname, "../fixtures/esm-path-globals"); + +test("__dirname and __filename in TypeScript ES modules read the module's source path", async () => { + const dir = mkdtempSync(join(tmpdir(), "scriptc-esm-path-globals-")); + cpSync(fixture, join(dir, "src"), { recursive: true }); + const entry = join(dir, "src", "main.ts"); + const result = await compile(entry, { + outPath: join(dir, "main"), + outDir: dir, + sanitize: process.env["SCRIPTC_SAN"] === "1", + }); + expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true); + if (!result.ok) return; + const native = await execFileAsync(result.binaryPath); + for (const file of [entry, join(dir, "src", "lib", "helper.ts")]) { + const text = readFileSync(file, "utf8") + .replaceAll("__dirname", "import.meta.dirname") + .replaceAll("__filename", "import.meta.filename"); + writeFileSync(file, text); + } + const node = await execFileAsync(process.execPath, [entry]); + expect(native.stdout).toBe(node.stdout); + expect(native.stdout).toContain("string string\nsrc main.ts\ntrue true\ntrue helper.ts\n"); +}); diff --git a/tests/harness/npm-cases.ts b/tests/harness/npm-cases.ts index 4dda0b9ad..0830d4162 100644 --- a/tests/harness/npm-cases.ts +++ b/tests/harness/npm-cases.ts @@ -19,12 +19,12 @@ export function npmCases(fixturesRoot: string): NpmCase[] { return [ ...globSync(join(fixturesRoot, "npm/cases/*/main.ts")) .sort() - // 2465-2469 and 2556-2557 are the --npm-static bundler-emitted-CJS + // 2465-2469, 2556-2566 and 2571 are the --npm-static bundler-emitted-CJS // cases (npm-static.test.ts drives them with the opt-in): their // consumers exercise the static rewrite's surface (lexer-visible // names the shipped .d.ts never declares, the __toESM interop // family), so they stay out of the flagless island lane by design. - .filter((entry) => !/\/(246[5-9]|255[67])-[^/]+\/main\.ts$/.test(entry)) + .filter((entry) => !/\/(246[5-9]|255[6-9]|256[0-24-6]|2571)-[^/]+\/main\.ts$/.test(entry)) .map((entry) => ({ name: entry.split("/").at(-2)!, entry, diff --git a/tests/harness/npm-static.test.ts b/tests/harness/npm-static.test.ts index 356d2fa6d..8325e2998 100644 --- a/tests/harness/npm-static.test.ts +++ b/tests/harness/npm-static.test.ts @@ -76,8 +76,8 @@ async function buildStatic(entry: string, npmStatic: string[] | "auto"): Promise ...globSync( join(fixturesRoot, "npm/node_modules/cryptozoo/**/*.{js,mjs,cjs,json,d.ts}"), ).sort(), - // the bundler-emitted-CJS mini packages (cases 2465-2469, 2556-2557) - ...globSync(join(fixturesRoot, "npm/node_modules/gt*/**/*.{js,json}")).sort(), + // the bundler-emitted-CJS mini packages (cases 2465-2469, 2556-2566, 2571) + ...globSync(join(fixturesRoot, "npm/node_modules/gt*/**/*.{js,cjs,json}")).sort(), ]; for (const f of inputs) hash.update(f).update(readFileSync(f)); const key = hash @@ -1389,6 +1389,138 @@ describe(`npm-static pilots${sanitize ? " (sanitized)" : ""}`, () => { ]); }, 120_000); + /* ── 2558-2561: esbuild's bundled CommonJS chunks and per-file CommonJS + * cycles ─ every __commonJS chunk compiles as its own CommonJS module + * (evaluated once, at its first require); a require cycle between + * esbuild outputs whose live getter tables install before their first + * require initializes exactly like Node, and a cycle that reads a + * binding in its temporal dead zone stays refused. */ + async function expectNodeParity(entry: string, packages: string[]): Promise { + const { coverage } = analyze(entry, { npmStatic: packages }); + expect(coverage.npmStatic).toEqual(packages.map((pkg) => ({ package: pkg, status: "static" }))); + expect(coverage.preflightFailed).toBe(false); + expect(coverage.diagnostics).toHaveLength(0); + const binary = await buildStatic(entry, packages); + const [nodeRes, nativeRes] = await Promise.all([ + runBinary("node", [entry]), + runBinary(binary, []), + ]); + // Node's stderr carries its type-stripping warning; the programs write + // only to stdout. + expect(nativeRes.stdout.toString("utf8")).toBe(nodeRes.stdout.toString("utf8")); + expect(comparableStderr(nativeRes.stderr).toString("utf8")).toBe(""); + expect(nativeRes.exitCode).toBe(nodeRes.exitCode); + expect(nativeRes.exitCode).toBe(0); + } + + // 2558: esbuild 0.25 (plain and whitespace/syntax minified) and 0.17 + // CommonJS bundles of one source, plus a .cjs bundle in a type-module + // package with the node-mode interop. + test("bundled CommonJS chunks 2558-bundled-chunks compile statically and byte-match Node", async () => { + await expectNodeParity(join(fixturesRoot, "npm/cases/2558-bundled-chunks/main.ts"), [ + "gtchunk", + "gtchunkws", + "gtchunk17", + "gtcjsdist", + "gtcore", + ]); + }, 180_000); + + // A whitespace/syntax-minified bundle whose helpers keep their names is + // judged by the recognizers, not refused up front as minified. + test("--npm-static=auto opts a minified bundle with named helpers in", () => { + const entry = join(fixturesRoot, "npm/cases/2558-bundled-chunks/main.ts"); + const { coverage } = analyze(entry, { npmStatic: "auto" }); + expect(coverage.npmStatic).toContainEqual({ package: "gtchunkws", status: "static" }); + }, 120_000); + + test.each(["2559-cjs-cycle", "2560-cjs-cycle-partner-first"])( + "the esbuild CommonJS cycle %s compiles statically and byte-matches Node", + async (name) => { + await expectNodeParity(join(fixturesRoot, `npm/cases/${name}/main.ts`), ["gtcycle"]); + }, + 180_000, + ); + + // 2561: Node throws a ReferenceError there; the cycle keeps SC1016. + test("a CommonJS cycle that reads a binding before its declaration stays refused", () => { + const entry = join(fixturesRoot, "npm/cases/2561-cjs-cycle-early-read/main.ts"); + const { coverage } = analyze(entry, { npmStatic: ["gtcyclebad"] }); + expect(coverage.npmStatic).toEqual([ + { + package: "gtcyclebad", + status: "refused", + detail: expect.stringMatching( + /circular imports .*can use its binding 'import_index' of a module of the cycle/, + ) as string, + }, + ]); + }, 120_000); + + // 2562: a file with unverifiable interop that no compiled code can load + // (the program imports a leaf data module and only a type from the + // main entry) leaves its package static. + test("a package is judged by the files its imported subpaths can load", async () => { + await expectNodeParity(join(fixturesRoot, "npm/cases/2562-subpath-leaf/main.ts"), [ + "gtsubpath", + ]); + }, 180_000); + + // 2566: the same early read through esbuild's `(0, binding.fn)()` call + // spelling also keeps SC1016. + test("a CommonJS cycle that calls an export during initialization stays refused", () => { + const entry = join(fixturesRoot, "npm/cases/2566-cjs-cycle-indirect-call/main.ts"); + const { coverage } = analyze(entry, { npmStatic: ["gtcyclecall"] }); + expect(coverage.npmStatic).toEqual([ + { + package: "gtcyclecall", + status: "refused", + detail: expect.stringMatching( + /circular imports .*can use its binding 'import_index' of a module of the cycle/, + ) as string, + }, + ]); + }, 120_000); + + // 2571: the partner stores the export (in a const, or in an object + // literal) and calls it while the cycle initializes; both keep SC1016. + test("a CommonJS cycle that stores an export and calls it stays refused", () => { + const entry = join(fixturesRoot, "npm/cases/2571-cjs-cycle-stored-function/main.ts"); + const { coverage } = analyze(entry, { npmStatic: ["gtcyclealias", "gtcyclebox"] }); + for (const pkg of ["gtcyclealias", "gtcyclebox"]) + expect(coverage.npmStatic).toContainEqual({ + package: pkg, + status: "refused", + detail: expect.stringMatching( + /circular imports .*can use its binding 'import_index' of a module of the cycle/, + ) as string, + }); + }, 120_000); + + // 2564: a chunk reads a top-level function whose name a nested scope of + // the chunk also declares; the function still travels with the chunk. + test("a chunk carries a top-level function its nested scopes shadow", async () => { + await expectNodeParity(join(fixturesRoot, "npm/cases/2564-chunk-helper-shadowing/main.ts"), [ + "gtchunkshadow", + ]); + }, 180_000); + + // 2565: Node shares one function between the chunks that export it; a + // per-chunk copy would not, so the package stays refused. + test("chunks that export a shared top-level function stay refused", () => { + const entry = join(fixturesRoot, "npm/cases/2565-chunk-shared-function/main.ts"); + const { coverage } = analyze(entry, { npmStatic: ["gtchunkshared"] }); + expect(coverage.npmStatic).toEqual([ + { + package: "gtchunkshared", + status: "refused", + detail: expect.stringMatching( + /uses the bundle's top-level function 'tally' as a value, which its chunks cannot share/, + ) as string, + }, + ]); + }, 120_000); + test("published type-only declarations coexist with a static runtime bundle", async () => { const entry = join(fixturesRoot, "npm/cases/2469-bundle-offender/main.ts"); const { coverage } = analyze(entry, { npmStatic: ["gtghost"] }); diff --git a/tests/harness/project-config.test.ts b/tests/harness/project-config.test.ts index d2c442517..79bf42b84 100644 --- a/tests/harness/project-config.test.ts +++ b/tests/harness/project-config.test.ts @@ -451,6 +451,42 @@ test("node-types: promisified fs callback functions lower as their fs/promises c expect(comparableStderr(native.stderr)).toBe(node.stderr); }); +test("node-types: util.promisify over typed callback-last functions settles like Node", async () => { + const outDir = outDirFor("promisify-callback-functions"); + const entry = join(nodeTypesDir, "promisify-callback-functions.ts"); + const result = await compile(entry, { + outPath: join(outDir, "promisify-callback-functions"), + outDir, + sanitize, + }); + expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true); + if (!result.ok) return; + const [native, node] = await Promise.all([ + execFileAsync(result.binaryPath), + execFileAsync(process.execPath, ["--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", entry]), + ]); + expect(native.stdout).toBe(node.stdout); + expect(comparableStderr(native.stderr)).toBe(node.stderr); +}); + +test("node-types: stream subclasses extend the @types/node stream classes", async () => { + const outDir = outDirFor("stream-subclass-hooks"); + const entry = join(nodeTypesDir, "stream-subclass-hooks.ts"); + const result = await compile(entry, { + outPath: join(outDir, "stream-subclass-hooks"), + outDir, + sanitize, + }); + expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true); + if (!result.ok) return; + const [native, node] = await Promise.all([ + execFileAsync(result.binaryPath), + execFileAsync(process.execPath, ["--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", entry]), + ]); + expect(native.stdout).toBe(node.stdout); + expect(comparableStderr(native.stderr)).toBe(node.stderr); +}); + test("node-types: promisified setTimeout and setImmediate lower as timers/promises", async () => { const outDir = outDirFor("promisify-timers"); const entry = join(nodeTypesDir, "promisify-timers.ts"); From 7d35fee6883edee81db080acbd6310086c1a890f Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Tue, 20 Oct 2026 10:21:03 -0500 Subject: [PATCH 094/122] Validate flat request header arrays and drop lookup listeners when a socket closes - http.request, https.request and get validate each name and value of a flat [name, value, ...] headers array, as Node's _storeHeader does: a name that is not an HTTP token throws ERR_INVALID_HTTP_TOKEN, a value with an invalid character throws ERR_INVALID_CHAR, and an odd length throws ERR_INVALID_ARG_VALUE with the inspected array. The [[name, value], ...] entry form throws the options fence. - A closing socket drops its 'lookup' listeners with its other listeners, so a listener that holds the socket no longer keeps it alive. - The options-record diagnostics snapshot no longer expects the socketPath refusal. --- packages/runtime/src/scr_http.c | 113 +++++++++++++++++- packages/runtime/src/scr_net.c | 2 + .../6441-http-request-options-record.ts | 15 ++- .../corpus/6443-net-lookup-http-close-text.ts | 4 +- .../__snapshots__/options-record.ts.txt | 9 -- 5 files changed, 130 insertions(+), 13 deletions(-) diff --git a/packages/runtime/src/scr_http.c b/packages/runtime/src/scr_http.c index 9a2541d24..d9876c30b 100644 --- a/packages/runtime/src/scr_http.c +++ b/packages/runtime/src/scr_http.c @@ -806,6 +806,8 @@ static bool scr_http_token_char(unsigned char ch) { (ch >= 'a' && ch <= 'z') || (ch != 0 && strchr("!#$%&'*+-.^_`|~", ch) != NULL); } +static void scr_http_throw_invalid_header_name(ScrStr *name /*borrowed*/, ScrStr *label /*borrowed*/); + /* node:http's public validators use the same token and field-vchar rules * as OutgoingMessage. Strings are UTF-8 here, so the Latin-1 obs-text * range accepted by Node's JS regex is C2/C3 plus one continuation byte. */ @@ -814,7 +816,12 @@ void scr_http_validate_header_name(ScrStr *name /*borrowed*/, ScrStr *label /*bo for (size_t i = 0; i < name->len && valid; i++) valid = scr_http_token_char((unsigned char)name->data[i]); if (valid) return; + scr_http_throw_invalid_header_name(name, label); +} +/* Node's ERR_INVALID_HTTP_TOKEN for a header name; `name` is the name's + * string form. */ +static void scr_http_throw_invalid_header_name(ScrStr *name /*borrowed*/, ScrStr *label /*borrowed*/) { ScrJsonBuf b; scr_jb_init(&b); ScrStr *shown = label->len ? label : NULL; @@ -7009,6 +7016,96 @@ static const char SCR_HTTP_REQ_SUPPORTED[] = "hostname/host, port, path, method, headers, timeout, agent, signal, socketPath, " "defaultPort, protocol, and maxHeaderSize are the supported options"; +/* validateHeaderName and validateHeaderValue over one pair of the flat + * headers array: a name that is not a string throws + * ERR_INVALID_HTTP_TOKEN with its string form, and an array value + * validates each element. */ +static void scr_http_req_validate_header_pair(const ScrDyn *name, const ScrDyn *value) { + ScrStr *label = scr_str_new("", 0); + if (name->kind == SCR_DYN_STR) { + scr_http_validate_header_name(name->v.str, label); + } else { + ScrStr *text = scr_dyn_string_coerce_js(name); + if (text != NULL) { + scr_http_throw_invalid_header_name(text, label); + scr_str_release(text); + } + } + scr_str_release(label); + if (scr_exc_pending()) return; + if (value->kind == SCR_DYN_ARR) { + for (size_t k = 0; k < value->v.arr.len && !scr_exc_pending(); k++) + scr_http_validate_header_value(name->v.str, value->v.arr.items[k]); + } else { + scr_http_validate_header_value(name->v.str, value); + } +} + +/* util.inspect's one-line rendering of a short array ("[ 'x-a', 5 ]"): up + * to six primitive entries whose rendering stays within inspect's + * 80-column break length (a start of n + 11, plus the separators and each + * entry). NULL when inspect would group or break the entries, or an entry + * is not a primitive or renders outside ASCII (inspect counts UTF-16 + * units). */ +static ScrStr *scr_http_inspect_short_array(const ScrDyn *list) { + size_t n = list->v.arr.len; + if (n == 0 || n > 6) return NULL; + ScrJsonBuf b; + scr_jb_init(&b); + scr_jb_puts(&b, "[ "); + size_t total = 2 * n + 11; + bool fits = true; + for (size_t i = 0; i < n && fits; i++) { + const ScrDyn *v = list->v.arr.items[i]; + ScrStr *part = NULL; + switch (v->kind) { + case SCR_DYN_STR: part = scr_str_inspect_quoted(v->v.str, false); break; + case SCR_DYN_NUM: + part = v->v.num == 0 && signbit(v->v.num) ? scr_str_new("-0", 2) : scr_f64_to_scrstr(v->v.num); + break; + case SCR_DYN_BOOL: part = v->v.b ? scr_str_new("true", 4) : scr_str_new("false", 5); break; + case SCR_DYN_NULL: part = scr_str_new("null", 4); break; + case SCR_DYN_UNDEF: part = scr_str_new("undefined", 9); break; + default: break; + } + if (part == NULL) { + fits = false; + break; + } + for (size_t k = 0; k < part->len && fits; k++) fits = (unsigned char)part->data[k] < 0x80; + total += part->len; + if (total > 80) fits = false; + if (i > 0) scr_jb_puts(&b, ", "); + scr_jb_write(&b, part->data, part->len); + scr_str_release(part); + } + scr_jb_puts(&b, " ]"); + ScrStr *out = scr_jb_finish(&b); + if (fits) return out; + scr_str_release(out); + return NULL; +} + +/* Node's ERR_INVALID_ARG_VALUE for a flat headers array of odd length. + * Arrays whose inspected form scr_http_inspect_short_array cannot + * reproduce fence instead. */ +static void scr_http_req_odd_headers_fail(const char *api, const ScrDyn *list) { + ScrStr *shown = scr_http_inspect_short_array(list); + if (shown == NULL) { + scr_http_req_opts_fence(api, "headers", + "the odd-length headers array error prints up to six short primitive entries here"); + return; + } + ScrJsonBuf b; + scr_jb_init(&b); + scr_jb_puts(&b, "The argument 'headers' is invalid. Received "); + scr_jb_write(&b, shown->data, shown->len); + scr_str_release(shown); + ScrStr *msg = scr_jb_finish(&b); + scr_throw_error_msg_code(SCR_ERR_TYPE, msg->data, msg->len, "ERR_INVALID_ARG_VALUE"); + scr_str_release(msg); +} + void scr_http_req_opts_fence(const char *api, const char *key, const char *hint) { ScrJsonBuf b; scr_jb_init(&b); @@ -7270,7 +7367,21 @@ bool scr_http_req_opts_read(const ScrDyn *opts, bool secure, const char *api, Sc } if (!scr_exc_pending()) o->headers = scr_http_dynh_pairs(headers); } else if (headers != NULL && headers->kind == SCR_DYN_ARR) { - o->headers = scr_http_dynh_flat_pairs(headers); + /* Node's _storeHeader checks that the flat [name, value, ...] form has + * an even length, then validates each name and value. The + * [[name, value], ...] entry form is not modeled. */ + size_t n = headers->v.arr.len; + if (n > 0 && headers->v.arr.items[0]->kind == SCR_DYN_ARR) { + scr_http_req_opts_fence( + api, "headers", + "headers is an object of string or number values or a flat [name, value, ...] array here"); + } else if (n % 2 != 0) { + scr_http_req_odd_headers_fail(api, headers); + } else { + for (size_t i = 0; i < n && !scr_exc_pending(); i += 2) + scr_http_req_validate_header_pair(headers->v.arr.items[i], headers->v.arr.items[i + 1]); + } + if (!scr_exc_pending()) o->headers = scr_http_dynh_flat_pairs(headers); } else if (headers != NULL && headers->kind != SCR_DYN_NULL) { scr_http_req_opts_fence(api, "headers", "headers is an object of string or number values here"); } diff --git a/packages/runtime/src/scr_net.c b/packages/runtime/src/scr_net.c index 978f42e3d..9902033f2 100644 --- a/packages/runtime/src/scr_net.c +++ b/packages/runtime/src/scr_net.c @@ -5172,6 +5172,7 @@ static void scr_net_sweep(void) { scr_net_ls_drop(&sock->finish_ls); scr_net_ls_drop(&sock->wcb_ls); scr_net_ls_drop(&sock->ready_ls); + scr_net_ls_drop(&sock->lookup_ls); if (sock->pipe_dst) { scr_net_sock_release(sock->pipe_dst); sock->pipe_dst = NULL; @@ -5373,6 +5374,7 @@ static void scr_net_cleanup_atexit(void) { scr_net_ls_drop(&s->conn_ls); scr_net_ls_drop(&s->timeout_ls); scr_net_ls_drop(&s->readable_ls); + scr_net_ls_drop(&s->lookup_ls); /* Tear down the protocol layer's parser NOW: an in-flight http * exchange (a handler that threw before res.end()) holds req/res, * and the res holds this socket back — a cycle the lean untraced diff --git a/tests/corpus/6441-http-request-options-record.ts b/tests/corpus/6441-http-request-options-record.ts index f2eba1a02..89a9abc29 100644 --- a/tests/corpus/6441-http-request-options-record.ts +++ b/tests/corpus/6441-http-request-options-record.ts @@ -1,8 +1,10 @@ // http.request options read at run time: a RequestOptions value chosen by // a runtime condition (a TCP endpoint with a string port, or a socket // path), spread into an options object with a timeout and an optional -// signal; agent: false; an aborted signal; and Node's validation errors -// for the method, port, host, timeout, path, protocol and agent options. +// signal; agent: false; an aborted signal; a flat [name, value, ...] +// headers array; and Node's validation errors for the method, port, host, +// timeout, path, protocol and agent options and for flat header names and +// values. import * as http from "node:http"; const server = http.createServer((req, res) => { @@ -52,11 +54,15 @@ server.listen(0, "127.0.0.1", async () => { const headers: Record = { "x-n": 1 }; const controller: AbortController | undefined = undefined; const options = { ...location, path: "/rpc", method: "post", headers, timeout: 2000, signal: controller }; + // The flat [name, value, ...] headers form; @types/node types headers as an object. + const flat = (path: string, list: unknown[]): http.RequestOptions => + ({ ...location, path, headers: list }) as unknown as http.RequestOptions; await send("spread", options); await send("agent false", { ...location, path: "/close", agent: false }); const aborted = new AbortController(); aborted.abort(); await send("aborted", { ...location, path: "/never", signal: aborted.signal }); + await send("flat headers", flat("/flat", ["host", "flat.test", "x-n", 2])); invalid("method", { ...location, method: "g t" }); invalid("port", { host: "127.0.0.1", port: "http" }); invalid("host", { hostname: 5 } as unknown as http.RequestOptions); @@ -64,5 +70,10 @@ server.listen(0, "127.0.0.1", async () => { invalid("path", { ...location, path: "/a b" }); invalid("protocol", { ...location, protocol: "https:" }); invalid("agent", { ...location, agent: 5 } as unknown as http.RequestOptions); + invalid("flat name", flat("/", ["x n", "1"])); + invalid("flat number name", flat("/", [5, "1"])); + invalid("flat crlf value", flat("/", ["x-a", "ok\r\nx-b: injected"])); + invalid("flat crlf array value", flat("/", ["x-a", ["ok", "b\r\nx-c: injected"]])); + invalid("flat odd length", flat("/", ["x-a"])); server.close(); }); diff --git a/tests/corpus/6443-net-lookup-http-close-text.ts b/tests/corpus/6443-net-lookup-http-close-text.ts index 1d50d3580..ba445b117 100644 --- a/tests/corpus/6443-net-lookup-http-close-text.ts +++ b/tests/corpus/6443-net-lookup-http-close-text.ts @@ -23,7 +23,9 @@ function lookups(): Promise { named.on("data", () => {}); named.on("close", () => { const direct = net.connect({ host: "127.0.0.1", port }); - direct.once("lookup", () => console.log("unexpected lookup")); + // A 'lookup' listener that never runs and holds its socket: closing + // the socket drops it. + direct.once("lookup", () => console.log("unexpected lookup", direct.remotePort)); direct.once("connect", () => console.log("connect without lookup")); direct.on("data", () => {}); direct.on("close", () => tcp.close(() => resolve())); diff --git a/tests/harness/__snapshots__/options-record.ts.txt b/tests/harness/__snapshots__/options-record.ts.txt index ab89613c0..26ca22db3 100644 --- a/tests/harness/__snapshots__/options-record.ts.txt +++ b/tests/harness/__snapshots__/options-record.ts.txt @@ -25,15 +25,6 @@ options-record.ts:20:22 - error SC2020: 'get with agent: false and a non-literal hint: agent: false lowers by injecting Connection: close into a LITERAL headers object — add connection: "close" to the record instead, or write the headers inline -options-record.ts:23:22 - error SC2020: 'get option 'socketPath'' is part of the standard library types but has no scriptc lowering yet - - 22 | // Documented client options with no lowering fence by name. - 23 | http.get({ port: 80, socketPath: "/tmp/sock.s" }, () => {}); - | ^~~~~~~~~~~~~~~~~~~~~~~~~ - 24 | http.get({ port: 80, localAddress: "127.0.0.1" }, () => {}); - - hint: hostname/host, port, path, method, timeout, headers, agent, signal, and maxHeaderSize are the supported options - options-record.ts:24:22 - error SC2020: 'get option 'localAddress'' is part of the standard library types but has no scriptc lowering yet 23 | http.get({ port: 80, socketPath: "/tmp/sock.s" }, () => {}); From e5b9102e0d5b3f002477900f6cf0a8f760dce50d Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Tue, 20 Oct 2026 08:32:19 -0700 Subject: [PATCH 095/122] Compile compound assignment, ++/-- and ??= through literal-union and index-signature record keys `totals[side] += n`, where `side` is a union of string literals naming declared fields of one type, now dispatches over those fields: the receiver and key evaluate once, the old value is read from the field the key names, then the right side, then the write to that same field. Every compound operator, prefix and postfix ++/--, and ??=/||=/&&= share the path. A single literal key (spelled at the site or carried by a const) reads and writes the field directly. A key smuggled outside its literal type throws the fixed-shape TypeError of a keyed write miss. A string key into a pure index-signature record (`counts[name] += 1`, `counts[name]++`) reads the entry, NaN for a missing one under numeric operators, and inserts or replaces it. Keys naming fields of different types, class instances and JavaScript sources keep their refusals. Co-Authored-By: Claude Opus 5.5 --- .../lowering/expressions/element-access.ts | 7 + .../expressions/keyed-field-targets.ts | 271 ++++++++++++++++++ .../expressions/nullish-assignment.ts | 17 ++ .../lowering/expressions/property-access.ts | 2 + .../frontend/lowering/expressions/updates.ts | 8 +- .../src/frontend/lowering/lower-exprs.ts | 15 + .../6490-literal-key-field-updates.ts.json | 6 + .../corpus/6491-index-record-updates.ts.json | 6 + .../corpus/6490-literal-key-field-updates.ts | 132 +++++++++ tests/corpus/6491-index-record-updates.ts | 44 +++ 10 files changed, 506 insertions(+), 2 deletions(-) create mode 100644 packages/compiler/src/frontend/lowering/expressions/keyed-field-targets.ts create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6490-literal-key-field-updates.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6491-index-record-updates.ts.json create mode 100644 tests/corpus/6490-literal-key-field-updates.ts create mode 100644 tests/corpus/6491-index-record-updates.ts diff --git a/packages/compiler/src/frontend/lowering/expressions/element-access.ts b/packages/compiler/src/frontend/lowering/expressions/element-access.ts index 144742257..f39b7c428 100644 --- a/packages/compiler/src/frontend/lowering/expressions/element-access.ts +++ b/packages/compiler/src/frontend/lowering/expressions/element-access.ts @@ -63,6 +63,8 @@ import { npmStaticPackageOfPath } from "../../npm-static.js"; import { isSafeToRepeat } from "./evaluation-safety.js"; import { lowerClassPrototypeComputedAssignment } from "../class-prototypes.js"; import { isClassCallback } from "../class-callbacks.js"; +import { isKeyedFieldAccess } from "./keyed-field-targets.js"; +import { lowerFieldCompoundValue } from "./updates.js"; /** `a[i]` reads. Only f64 indices into array receivers are modeled; JS * string-key element access (`a["length"]`) and string indexing (`s[0]` @@ -1417,6 +1419,11 @@ export function lowerElementCompoundParts( const target = expr.left as ts.ElementAccessExpression; fenceNodeModuleMutation(lowerer, target, "assignment"); const loc = locOf(expr); + // A record through a computed key (`totals[side] += n` with a literal + // union key, `counts[name] += 1` on an index-signature record): the + // member compound's receiver, key, old value, rhs, write sequence. + if (isKeyedFieldAccess(lowerer, target)) + return { expr: lowerFieldCompoundValue(lowerer, target, op, expr.right, loc), stmts: null }; const checked = lowerer.lowerExpr(target.expression); if (checked?.type.kind === "dyn" && isJsSourceFile(expr.getSourceFile())) { const stmts: IrStmt[] = []; diff --git a/packages/compiler/src/frontend/lowering/expressions/keyed-field-targets.ts b/packages/compiler/src/frontend/lowering/expressions/keyed-field-targets.ts new file mode 100644 index 000000000..7a4f9c557 --- /dev/null +++ b/packages/compiler/src/frontend/lowering/expressions/keyed-field-targets.ts @@ -0,0 +1,271 @@ +/* Read-modify-write targets for computed record keys: `r[k] op= v`, `r[k]++` and `r[k] ??= v`. */ +import * as ts from "../../ts7/adapter.js"; +import { + BOOL, + F64, + STRING, + typeEquals, + type IrExpr, + type IrStmt, + type SrcLoc, +} from "../../../ir/ir.js"; +import { isJsSourceFile } from "../../program.js"; +import { constituentTypes } from "../../ts7/checker.js"; +import type { FieldTarget } from "../lower-exprs.js"; +import type { Lowerer } from "../lowerer.js"; + +export type KeyedFieldTarget = FieldTarget & { container: "recordKeyed" }; + +/** The property names a computed key's type allows: one or more string + * literals (a literal union such as `"sent" | "received"`, enum string + * members, or a type parameter bound to one inside a generic instance). + * Null for any other key type. */ +export function literalFieldKeys(lowerer: Lowerer, keyNode: ts.Expression): string[] | null { + let t: ts.Type = lowerer.typeOf(keyNode); + if (t.flags & ts.TypeFlags.TypeParameter) { + const bound = lowerer.typeParamTsResolver(t); + if (!bound) return null; + t = bound; + } + const parts = t.flags & ts.TypeFlags.Union ? constituentTypes(t) : [t]; + const keys: string[] = []; + for (const part of parts) { + if (!part.isStringLiteralType()) return null; + if (!keys.includes(part.value)) keys.push(part.value); + } + return keys.length > 0 ? keys : null; +} + +/** Does `r[k]` read and write through declared record storage by key? A + * checker-only test (nothing lowers), so a caller can route before it + * evaluates the receiver: TypeScript sources only, a record whose declared + * fields include every name the key's literal type allows, or a pure + * index-signature record with a string key. */ +export function isKeyedFieldAccess(lowerer: Lowerer, access: ts.ElementAccessExpression): boolean { + if ( + access.questionDotToken || + isJsSourceFile(access.getSourceFile()) || + lowerer.isProcessEnv(access.expression) + ) + return false; + const keys = literalFieldKeys(lowerer, access.argumentExpression); + if (!keys && !(lowerer.typeOf(access.argumentExpression).flags & ts.TypeFlags.StringLike)) + return false; + const receiver = lowerer.mapTypeOf(lowerer.typeOf(access.expression)); + if (receiver?.kind !== "record") return false; + const shape = lowerer.shapes.get(receiver.shapeId); + if (!shape || shape.tuple) return false; + if (shape.fields.length === 0) return shape.indexValue !== undefined; + return keys !== null && keys.every((k) => shape.fields.some((f) => f.name === k)); +} + +/** The target of a read-modify-write through `r[k]`, or null when + * isKeyedFieldAccess declines. A literal-union key dispatches over the + * declared fields it names, which must share one type; a string key on a + * pure index-signature record reads and writes its entries. The receiver + * and key may arrive already lowered (and saved) by the caller; otherwise + * both lower here, unsaved. A caller that evaluates the target more than + * once saves `obj` and then `key`, the JavaScript evaluation order. With + * `numeric`, a missing index-signature entry reads as NaN (every numeric + * update applies ToNumber to it) instead of a value armed with undefined. */ +export function keyedFieldTarget( + lowerer: Lowerer, + access: ts.ElementAccessExpression, + numeric: boolean, + lowered?: { obj: IrExpr; key: IrExpr }, +): KeyedFieldTarget | null { + if (!isKeyedFieldAccess(lowerer, access)) return null; + const keyNode = access.argumentExpression; + const keys = literalFieldKeys(lowerer, keyNode); + const obj = lowered?.obj ?? lowerer.lowerExpr(access.expression); + if (obj.type.kind !== "record") { + lowerer.unsupported( + "SC1090", + access.expression, + `updating a computed key of a '${lowerer.fmt(obj.type)}' value (narrow the receiver to its record type first)`, + ); + } + const shapeId = obj.type.shapeId; + const shape = lowerer.shapes.get(shapeId)!; + // A single literal spelled at the site (or an identifier whose type is + // that one literal) names the field statically: identifier evaluation + // is pure, so skipping it matches JavaScript. + const indexed = shape.fields.length === 0; + const staticKey = + !indexed && keys?.length === 1 && (ts.isStringLiteralLike(keyNode) || ts.isIdentifier(keyNode)); + const key = staticKey ? null : (lowered?.key ?? lowerer.lowerExpr(keyNode)); + if (key && key.type.kind !== "string") { + lowerer.unsupported( + "SC1090", + keyNode, + `updating a record through a '${lowerer.fmt(key.type)}' key (convert the key to a string first)`, + ); + } + if (keys && !indexed) { + const fields = keys.map((k) => shape.fields.find((f) => f.name === k)); + const first = fields[0]; + if (!first || fields.some((f) => !f || !typeEquals(f.type, first.type))) { + lowerer.unsupported( + "SC1090", + access, + `updating '${lowerer.fmt(obj.type)}' through a key whose fields are not all stored with one type (${keys.map((k) => `'${k}'`).join(", ")}; switch over the key and update each field)`, + ); + } + return { + container: "recordKeyed", + obj, + shapeId, + field: keys[0]!, + keys, + key, + fieldType: first.type, + }; + } + if (!indexed || !shape.indexValue) { + lowerer.unsupported( + "SC1090", + access, + `updating '${lowerer.fmt(obj.type)}' through a runtime key (the stored record declares fields)`, + ); + } + return { + container: "recordKeyed", + obj, + shapeId, + field: "", + keys: null, + key, + fieldType: shape.indexValue, + ...(numeric && shape.indexValue.kind === "f64" ? { missNaN: true as const } : {}), + }; +} + +/** Does `key` name `field`? */ +function keyIs(key: IrExpr, field: string, loc: SrcLoc): IrExpr { + return { + kind: "strEq", + negated: false, + left: key, + right: { kind: "strLit", value: field, type: STRING, loc }, + type: BOOL, + loc, + }; +} + +/** A key outside its literal type (smuggled through a cast) names no + * declared field: a fixed-shape record cannot add the property, so the + * update throws the TypeError of a keyed write miss. */ +function keyMiss(key: IrExpr, target: KeyedFieldTarget, loc: SrcLoc): IrExpr { + const message: IrExpr = { + kind: "strConcat", + left: { + kind: "strConcat", + left: { kind: "strLit", value: "Cannot add property '", type: STRING, loc }, + right: key, + type: STRING, + loc, + }, + right: { kind: "strLit", value: "' to a fixed-shape object", type: STRING, loc }, + type: STRING, + loc, + }; + return { + kind: "libCall", + fn: "error.nodeThrow", + args: [ + { kind: "numLit", value: 1, type: F64, loc }, + { kind: "strLit", value: "", type: STRING, loc }, + message, + ], + type: target.fieldType, + loc, + }; +} + +/** The current value of a keyed target. */ +export function keyedFieldRead(lowerer: Lowerer, target: KeyedFieldTarget, loc: SrcLoc): IrExpr { + const { obj, shapeId, key, fieldType } = target; + if (!target.keys) { + const type = target.missNaN + ? fieldType + : fieldType.kind === "dyn" + ? fieldType + : lowerer.withUndefinedArmOf(fieldType); + if (!type || !key) throw new Error("keyed index-signature target without a key or read type"); + return { + kind: "recordKeyGet", + obj, + shapeId, + key, + ...(target.missNaN ? { missNaN: true as const } : {}), + type, + loc, + }; + } + const get = (field: string): IrExpr => ({ + kind: "recordGet", + obj, + shapeId, + field, + type: fieldType, + loc, + }); + if (!key) return get(target.keys[0]!); + return target.keys.reduceRight( + (otherwise, field) => ({ + kind: "ternary", + cond: keyIs(key, field, loc), + then: get(field), + else_: otherwise, + type: fieldType, + loc, + }), + keyMiss(key, target, loc), + ); +} + +/** Store `value` (already of the target's field type) through a keyed + * target. A multi-key dispatch references the value once per field, so a + * value that is not a plain local is saved first. */ +export function keyedFieldWrite( + lowerer: Lowerer, + target: KeyedFieldTarget, + value: IrExpr, + loc: SrcLoc, +): IrStmt { + const { obj, shapeId, key } = target; + if (!target.keys) { + if (!key) throw new Error("keyed index-signature target without a key"); + return { kind: "recordKeySet", obj, shapeId, key, value, loc }; + } + if (!key) return { kind: "recordSet", obj, shapeId, field: target.keys[0]!, value, loc }; + const prefix: IrStmt[] = []; + let stored = value; + if (value.kind !== "varRef") { + const local = lowerer.declareHiddenLocal("%keyedValue", value.type); + prefix.push({ kind: "varDecl", localId: local.id, init: value, loc }); + stored = { kind: "varRef", localId: local.id, type: value.type, loc }; + } + const chain = target.keys.reduceRight( + (otherwise, field) => [ + { + kind: "if", + cond: keyIs(key, field, loc), + then: [{ kind: "recordSet", obj, shapeId, field, value: stored, loc }], + else_: otherwise, + loc, + }, + ], + [{ kind: "exprStmt", expr: keyMiss(key, target, loc), loc }], + ); + return { kind: "block", body: [...prefix, ...chain], loc }; +} + +/** Save a keyed target's key after its receiver, so a multi-key dispatch + * reads and writes the field the key named when it was evaluated. */ +export function saveKeyedFieldKey( + target: FieldTarget, + save: (value: IrExpr, name: string) => IrExpr, +): void { + if (target.container === "recordKeyed" && target.key) target.key = save(target.key, "%keyedKey"); +} diff --git a/packages/compiler/src/frontend/lowering/expressions/nullish-assignment.ts b/packages/compiler/src/frontend/lowering/expressions/nullish-assignment.ts index 4366b8d6b..81b3b9d69 100644 --- a/packages/compiler/src/frontend/lowering/expressions/nullish-assignment.ts +++ b/packages/compiler/src/frontend/lowering/expressions/nullish-assignment.ts @@ -18,6 +18,11 @@ import { arrayNumberRead, arrayValueRead, arrayValueStore } from "../array-value import { fenceNodeModuleMutation } from "../lower-node-module.js"; import { globalSymbolKey } from "./global-symbols.js"; import { tryLowerExpression } from "./try-lower-expression.js"; +import { + type KeyedFieldTarget, + keyedFieldTarget, + saveKeyedFieldKey, +} from "./keyed-field-targets.js"; import { eventAccessorRead, eventAccessorWrite, @@ -147,6 +152,7 @@ export function lowerShortCircuitAssignment(lowerer: Lowerer, expr: ts.BinaryExp } else { const receiver = save(lowerer.lowerExpr(target.expression), "%shortCircuitReceiver"); let key = lowerer.lowerExpr(target.argumentExpression); + let keyed: KeyedFieldTarget | null = null; if (receiver.type.kind === "dyn") { key = save( { @@ -245,6 +251,17 @@ export function lowerShortCircuitAssignment(lowerer: Lowerer, expr: ts.BinaryExp value: lowerer.coerceToExpected(value, indexValue), loc, }); + } else if ( + receiver.type.kind === "record" && + (keyed = keyedFieldTarget(lowerer, target, false, { obj: receiver, key })) + ) { + // `totals[side] ??= v` through a literal-union key: the read and + // the write dispatch over the declared fields the key can name. + const field = keyed; + saveKeyedFieldKey(field, save); + storage = field.fieldType; + read = lowerer.fieldGetExpr(field, locOf(target), target); + write = (value) => lowerer.fieldSetStmt(field, value, loc, target); } else { lowerer.unsupported( "SC1090", diff --git a/packages/compiler/src/frontend/lowering/expressions/property-access.ts b/packages/compiler/src/frontend/lowering/expressions/property-access.ts index 6b37b7a19..1de1ec83d 100644 --- a/packages/compiler/src/frontend/lowering/expressions/property-access.ts +++ b/packages/compiler/src/frontend/lowering/expressions/property-access.ts @@ -154,6 +154,7 @@ import { errorPropertyRead } from "../error-methods.js"; import { isSafeToRepeat } from "./evaluation-safety.js"; import { isZlibStreamMember, lowerZlibStreamBytesWritten } from "../builtins/compression.js"; import { isFsStreamMember, lowerFsStreamProperty } from "../builtins/fs-streams.js"; +import { keyedFieldRead } from "./keyed-field-targets.js"; /** True when a property access names an ABSTRACT property declaration * (`abstract p: number` — a PropertyDeclaration, not an accessor). @@ -3505,6 +3506,7 @@ export function fieldGetExpr( ); if (checked) target = { ...target, obj: checked }; } + if (target.container === "recordKeyed") return keyedFieldRead(lowerer, target, loc); if (target.container === "dynamic") return lowerer.coerceInto( blame, diff --git a/packages/compiler/src/frontend/lowering/expressions/updates.ts b/packages/compiler/src/frontend/lowering/expressions/updates.ts index 2270f6043..41d8f6332 100644 --- a/packages/compiler/src/frontend/lowering/expressions/updates.ts +++ b/packages/compiler/src/frontend/lowering/expressions/updates.ts @@ -42,6 +42,7 @@ import type { CompoundOp } from "../surfaces.js"; import { tryLowerExpression } from "./try-lower-expression.js"; import { hasRuntimeStatics } from "../class-runtime-statics.js"; import { declareContextLocal } from "../function-context.js"; +import { keyedFieldTarget, keyedFieldWrite, saveKeyedFieldKey } from "./keyed-field-targets.js"; /** Read-modify-write updates read the member first: an unassigned * deferred-init receiver throws Node's member-read TypeError. */ @@ -337,10 +338,11 @@ export function lowerIncDec( } const target = ts.isPropertyAccessExpression(access) ? lowerer.fieldTarget(access) - : symbolFieldTarget(lowerer, access); + : (symbolFieldTarget(lowerer, access) ?? keyedFieldTarget(lowerer, access, true)); if (target && lowerer.dynConvertible(target.fieldType)) { checkDeferredMemberReceiver(lowerer, target, access); target.obj = save(target.obj, "%incrementReceiver"); + saveKeyedFieldKey(target, save); return finish(lowerer.fieldGetExpr(target, loc, access), (value) => lowerer.fieldSetStmt( target, @@ -617,6 +619,7 @@ export function fieldSetStmt( }; } } + if (target.container === "recordKeyed") return keyedFieldWrite(lowerer, target, value, loc); if (target.container === "dynamic") return { kind: "exprStmt", @@ -872,12 +875,13 @@ export function lowerFieldCompoundValue( const targetOf = (): FieldTarget | null => ts.isPropertyAccessExpression(access) ? lowerer.fieldTarget(access) - : symbolFieldTarget(lowerer, access); + : (symbolFieldTarget(lowerer, access) ?? keyedFieldTarget(lowerer, access, true)); const target = targetOf(); if (!target) lowerer.unsupported("SC1090", access, "compound assignment to unsupported field targets"); checkDeferredMemberReceiver(lowerer, target, access); target.obj = save(target.obj, "%compoundReceiver"); + saveKeyedFieldKey(target, save); // Accessors observe getter, RHS side effects, then setter, all through // the saved receiver. const fieldRead = lowerer.fieldGetExpr(target, locOf(access), access); diff --git a/packages/compiler/src/frontend/lowering/lower-exprs.ts b/packages/compiler/src/frontend/lowering/lower-exprs.ts index 5cbf103d5..b33700b7e 100644 --- a/packages/compiler/src/frontend/lowering/lower-exprs.ts +++ b/packages/compiler/src/frontend/lowering/lower-exprs.ts @@ -151,6 +151,21 @@ export type FieldTarget = // (the write-slot type; reads arm it with undefined under // noUncheckedIndexedAccess in fieldGetExpr). | { container: "recordOvf"; obj: IrExpr; shapeId: string; field: string; fieldType: IrType } + // A computed key of a record in a read-modify-write (`r[k] += v`, + // `r[k]++`, `r[k] ??= v`; keyed-field-targets.ts): `keys` lists the + // declared fields a literal-union key can name, which share fieldType; + // null keys mean a string key into a pure index-signature record, whose + // entries fieldType types. `key` is null when the one literal is static. + | { + container: "recordKeyed"; + obj: IrExpr; + shapeId: string; + field: string; + keys: readonly string[] | null; + key: IrExpr | null; + fieldType: IrType; + missNaN?: true; + } | { container: "accessor"; obj: IrExpr; className: string; field: string; fieldType: IrType } // A RECORD accessor property (an object-literal `get x()`/`set x(v)` — // the shape carries %get:/%set: closure slots): reads call the getter diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6490-literal-key-field-updates.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6490-literal-key-field-updates.ts.json new file mode 100644 index 000000000..1e4e1e939 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6490-literal-key-field-updates.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6490-literal-key-field-updates.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6491-index-record-updates.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6491-index-record-updates.ts.json new file mode 100644 index 000000000..babda8c3c --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6491-index-record-updates.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6491-index-record-updates.ts" + ], + "diags": [] +} diff --git a/tests/corpus/6490-literal-key-field-updates.ts b/tests/corpus/6490-literal-key-field-updates.ts new file mode 100644 index 000000000..f90739c31 --- /dev/null +++ b/tests/corpus/6490-literal-key-field-updates.ts @@ -0,0 +1,132 @@ +// Read-modify-write through a key whose type is a union of literal field +// names (`totals[side] += n`): every compound operator, ++/-- and logical +// assignment dispatch over the declared fields the key can name. The +// receiver and key evaluate once, before the right side. +type Side = "sent" | "received"; +interface Traffic { + sent: number; + received: number; + label: string; + updatedAt: number; +} + +const traffic: Traffic = { sent: 0, received: 0, label: "link", updatedAt: 0 }; + +function record(side: Side, bytes: number): void { + traffic[side] += bytes; + traffic.updatedAt++; +} +record("sent", 120); +record("received", 64); +record("sent", 8); +console.log(JSON.stringify(traffic)); + +function adjust(side: Side): string { + const parts: number[] = []; + parts.push((traffic[side] -= 3)); + parts.push((traffic[side] *= 2)); + parts.push((traffic[side] /= 4)); + parts.push((traffic[side] %= 7)); + parts.push((traffic[side] **= 2)); + parts.push((traffic[side] |= 1)); + parts.push((traffic[side] &= 0xff)); + parts.push((traffic[side] ^= 0x0f)); + parts.push((traffic[side] <<= 2)); + parts.push((traffic[side] >>= 1)); + parts.push((traffic[side] >>>= 1)); + return parts.join(","); +} +console.log(adjust("sent"), adjust("received")); + +// ++ and -- in statement and value positions, prefix and postfix. +const sides: Side[] = ["sent", "received", "sent"]; +for (const s of sides) traffic[s]++; +for (const s of sides) --traffic[s]; +const post = traffic[sides[1]!]--; +const pre = ++traffic[sides[0]!]; +console.log(post, pre, traffic.sent, traffic.received); + +// One literal key, spelled at the site or carried by a const. +traffic["sent"] += 1000; +const fixed = "received"; +traffic[fixed] -= 1; +traffic["received"]++; +console.log(traffic.sent, traffic.received); + +// Evaluation order: receiver, key, old value, right side, write. +let trace = ""; +function pick(s: Side): Side { + trace += `key:${s};`; + return s; +} +function target(): Traffic { + trace += "receiver;"; + return traffic; +} +function amount(n: number): number { + trace += `rhs:${n};`; + return n; +} +console.log((target()[pick("received")] += amount(5)), trace); +trace = ""; +console.log(target()[pick("sent")]++, trace); +trace = ""; +let current = traffic; +function swap(): number { + current = { sent: -1, received: -1, label: "other", updatedAt: 0 }; + return 1; +} +current[pick("sent")] += swap(); +console.log(traffic.sent, current.sent, trace); + +// The right side throws after the old value is read: nothing is written. +function fail(): number { + throw new Error("rhs failed"); +} +try { + traffic[pick("received")] *= fail(); +} catch (e) { + if (e instanceof Error) console.log(e.message, traffic.received); +} + +// String fields append. +type Part = "head" | "tail"; +const text: { head: string; tail: string; size: number } = { head: "a", tail: "z", size: 0 }; +function append(p: Part, s: string): string { + return (text[p] += s); +} +console.log(append("head", "bc"), append("tail", "yx"), append("head", "!")); +console.log(JSON.stringify(text)); + +// Optional fields with ??=: only a missing value takes the right side. +type Phase = "dns" | "connect" | "tls"; +const timings: { dns?: number; connect?: number; tls?: number; total: number } = { total: 0 }; +function mark(phase: Phase, at: number): number { + return (timings[phase] ??= at); +} +console.log(mark("dns", 4), mark("connect", 9), mark("dns", 12), mark("tls", 0), mark("tls", 7)); +console.log(timings.dns, timings.connect, timings.tls, timings.total); + +// ||= and &&= test truthiness and write only in the selected branch. +const flags = { quiet: false, verbose: true, color: false }; +type Flag = keyof typeof flags; +function settle(f: Flag, value: boolean): string { + const a = (flags[f] ||= value); + const b = (flags[f] &&= f !== "color"); + return `${f}:${a}:${b}`; +} +console.log(settle("quiet", true), settle("verbose", false), settle("color", true)); +console.log(JSON.stringify(flags)); + +// Nested receivers and keys from a typed lookup. +const stats = { day: { hits: 1, misses: 2 }, week: { hits: 10, misses: 20 } }; +type Window = keyof typeof stats; +type Outcome = "hits" | "misses"; +const events: [Window, Outcome][] = [ + ["day", "hits"], + ["week", "misses"], + ["day", "misses"], + ["day", "hits"], +]; +for (const [w, o] of events) stats[w][o] += 1; +console.log(JSON.stringify(stats)); diff --git a/tests/corpus/6491-index-record-updates.ts b/tests/corpus/6491-index-record-updates.ts new file mode 100644 index 000000000..108d34012 --- /dev/null +++ b/tests/corpus/6491-index-record-updates.ts @@ -0,0 +1,44 @@ +// Compound assignment and ++/-- through a string key of an +// index-signature record: a missing entry reads as undefined, which every +// numeric operator turns into NaN, and the write inserts or replaces it. +const counts: Record = {}; +const words = "the cat and the hat and the bat".split(" "); +for (const w of words) { + counts[w] ??= 0; + counts[w]++; +} +console.log(JSON.stringify(counts)); + +const sums: { [key: string]: number } = { a: 1, b: 2 }; +sums["a"] += 10; +sums.b *= 3; +const key = "a"; +sums[key] -= 1; +console.log(JSON.stringify(sums)); + +// A missing entry: undefined op number is NaN, and the value form yields it. +console.log((sums["missing"] += 1), sums["missing"], Number.isNaN(sums["missing"])); +const later = sums["gone"]++; +console.log(later, sums["gone"], Object.keys(sums).join(",")); + +// Totals keyed by a runtime string, with the key and right side evaluated once. +let trace = ""; +function name(n: string): string { + trace += `key:${n};`; + return n; +} +function size(n: number): number { + trace += `rhs:${n};`; + return n; +} +const totals: Record = { disk: 0, net: 0 }; +const samples: [string, number][] = [ + ["disk", 4], + ["net", 7], + ["disk", 2], +]; +for (const [k, v] of samples) totals[name(k)] += size(v); +console.log(JSON.stringify(totals), trace); +const before = totals["net"]--; +const after = --totals["net"]; +console.log(before, after, totals["net"]); From c7b2620e5cf6dd94dbe198352fc99a0b33e39073 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Tue, 20 Oct 2026 08:24:54 -0700 Subject: [PATCH 096/122] Compile events.once with an AbortSignal option and over stream receivers events.once(emitter, name, { signal }) lowers to events.onceSignal, served by a new runtime unit (scr_events_once_signal.c, linked only through the eventsOnceSignal feature). It follows Node 24's lib/events.js: an undefined signal is the plain form, a value that is not an AbortSignal rejects with ERR_INVALID_ARG_TYPE, an aborted signal rejects with an AbortError whose cause is signal.reason, and otherwise a once, resist-stop-propagation 'abort' listener removes the event and 'error' listeners and rejects. The event or the error removes the abort listener. Options other than an object literal with one signal stay refused. events.once now accepts stream receivers: a stream's runtime-emitted events use their fixed tuples, and its 'data' and 'keypress' events stay refused. A NodeJS stream interface slot that holds a stdio handle requests the general stream representation. Node's once is an async function that returns the executor's promise, so its result adopts that promise through a thenable job. The plain form settled its result two turns early; both forms now settle on Node's turn. Co-Authored-By: Claude Opus 5.5 --- .../src/backend/executable-features.ts | 1 + packages/compiler/src/backend/llvm/emitter.ts | 7 +- .../src/backend/llvm/expr-callbacks.ts | 12 +- .../compiler/src/backend/llvm/lib-dispatch.ts | 3 +- .../compiler/src/backend/llvm/lib-process.ts | 31 +- .../src/backend/native-link-info-core.ts | 3 + .../compiler/src/backend/native/contracts.ts | 3 + .../compiler/src/backend/native/executable.ts | 3 + .../compiler/src/backend/runtime-pack-core.ts | 2 + .../src/executable/executable-cache.ts | 1 + .../frontend/lowering/lower-event-emitter.ts | 92 ++++-- packages/compiler/src/index.ts | 1 + .../compiler/src/ir/builtin-signatures.ts | 4 + packages/compiler/src/ir/runtime-features.ts | 4 + packages/compiler/src/ir/validate.ts | 4 +- .../compiler/src/native/sanitizer.test.ts | 1 + .../runtime-pack-matrix.mjs | 2 + packages/runtime/src/scr_events_emitter.c | 33 +- packages/runtime/src/scr_events_emitter.h | 14 + packages/runtime/src/scr_events_once_signal.c | 303 ++++++++++++++++++ 20 files changed, 479 insertions(+), 45 deletions(-) create mode 100644 packages/runtime/src/scr_events_once_signal.c diff --git a/packages/compiler/src/backend/executable-features.ts b/packages/compiler/src/backend/executable-features.ts index 74d5de278..df2dc8446 100644 --- a/packages/compiler/src/backend/executable-features.ts +++ b/packages/compiler/src/backend/executable-features.ts @@ -26,6 +26,7 @@ export function executableLinkFeatures(mod: IrModule): NativeLinkFeatures { dynAsync: features.dynAsync, ...(features.dynTier ? { dynTier: true } : {}), ...(features.streamDyn ? { streamDyn: true } : {}), + ...(features.eventsOnceSignal ? { eventsOnceSignal: true } : {}), events: features.processEvents, emitter: features.emitter, symbol: features.symbol, diff --git a/packages/compiler/src/backend/llvm/emitter.ts b/packages/compiler/src/backend/llvm/emitter.ts index f7b9f2517..56ec6c1a3 100644 --- a/packages/compiler/src/backend/llvm/emitter.ts +++ b/packages/compiler/src/backend/llvm/emitter.ts @@ -5568,8 +5568,11 @@ export class LlEmitter { return emitterFixedAdapter(this, cbT); } - emitterOnceAdapter(boxerT: IrType & { kind: "func" }): { fn: string; shim: string } { - return emitterOnceAdapter(this, boxerT); + emitterOnceAdapter( + boxerT: IrType & { kind: "func" }, + fire?: string, + ): { fn: string; shim: string } { + return emitterOnceAdapter(this, boxerT, fire); } wrapEmitterListener(target: string, adapterFn: string): string { diff --git a/packages/compiler/src/backend/llvm/expr-callbacks.ts b/packages/compiler/src/backend/llvm/expr-callbacks.ts index db1dca1af..e805760d8 100644 --- a/packages/compiler/src/backend/llvm/expr-callbacks.ts +++ b/packages/compiler/src/backend/llvm/expr-callbacks.ts @@ -822,23 +822,27 @@ export function emitterFixedAdapter( * (the event's tuple to any[]): the runtime-built listener closure holds * the boxer in caps[0], so the adapter calls it with the event's typed * arguments, as emitterFixedAdapter calls a listener, and hands the array - * to scr_events_once_fire. */ + * to scr_events_once_fire (or `fire`, the signal form's entry). */ export function emitterOnceAdapter( host: LlvmEmitterContext, boxerT: IrType & { kind: "func" }, + fire = "scr_events_once_fire", ): { fn: string; shim: string } { const cursor = boxerT.params.length > 4; const shim = cursor ? "scr_ee_inv_args" : `scr_ee_inv_fixed${boxerT.params.length}`; if (cursor) host.declare("declare ptr @scr_ee_arg_next(ptr)"); host.declare(`declare void @${shim}(ptr, ptr)`); - const key = `eeOnce:${typeKey(boxerT)}`; + const key = + fire === "scr_events_once_fire" + ? `eeOnce:${typeKey(boxerT)}` + : `eeOnce:${fire}:${typeKey(boxerT)}`; let sym = host.resolveThunks.get(key); if (sym) return { fn: sym, shim }; sym = `sc_ee_once_${host.resolveThunks.size}`; host.resolveThunks.set(key, sym); host.declare(`declare ptr @scr_box_get_ref(ptr)`); host.declare(`declare void @scr_closure_release(ptr)`); - host.declare(`declare void @scr_events_once_fire(ptr, ptr)`); + host.declare(`declare void @${fire}(ptr, ptr)`); const params = cursor ? "ptr %args" : boxerT.params.map((_, i) => `ptr %a${i}`).join(", "); const d: string[] = [ `define internal void @${sym}(ptr %cb${params ? ", " + params : ""}) ${FN_ATTRS} { ; events.once adapter ${typeKey(boxerT)}`, @@ -869,7 +873,7 @@ export function emitterOnceAdapter( ` %fn = load ptr, ptr %fnp`, ` %arr = call ptr %fn(${passed.join(", ")}) ; the boxed arguments, +1`, ` call void @scr_closure_release(ptr %boxer)`, - ` call void @scr_events_once_fire(ptr %cb, ptr %arr) ; moves %arr`, + ` call void @${fire}(ptr %cb, ptr %arr) ; moves %arr`, ` ret void`, `}`, ``, diff --git a/packages/compiler/src/backend/llvm/lib-dispatch.ts b/packages/compiler/src/backend/llvm/lib-dispatch.ts index 16759a592..8dab775e5 100644 --- a/packages/compiler/src/backend/llvm/lib-dispatch.ts +++ b/packages/compiler/src/backend/llvm/lib-dispatch.ts @@ -755,7 +755,8 @@ export function emitLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlValue { } const prefix = e.fn.slice(0, e.fn.indexOf(".")) as LibCallPrefix; // events.once builds typed listener adapters beside the emitter calls. - if (e.fn === "events.once") return host.emitErrorsEventsLibCall(e); + if (e.fn === "events.once" || e.fn === "events.onceSignal") + return host.emitErrorsEventsLibCall(e); switch (prefix) { case "fetch": case "abort": diff --git a/packages/compiler/src/backend/llvm/lib-process.ts b/packages/compiler/src/backend/llvm/lib-process.ts index 4a7be1a35..097fcb4de 100644 --- a/packages/compiler/src/backend/llvm/lib-process.ts +++ b/packages/compiler/src/backend/llvm/lib-process.ts @@ -731,27 +731,34 @@ export function emitErrorsEventsLibCall(host: LlvmEmitterContext, e: LibCallExpr host.emitAlwaysPendingUnwind(); return out; } - if (e.fn === "events.once") { - // (recv, name, boxer, errorBoxer, listenForError): scr_events_once - // registers one-shot typed listeners behind adapters that box the - // event's tuple (and the 'error' tuple) into the fulfilled array. The - // runtime retains the boxers; the frame's temporaries release as usual. - // May-throw ('newListener' meta listeners run inside). + if (e.fn === "events.once" || e.fn === "events.onceSignal") { + // (recv, name, boxer, errorBoxer, listenForError[, signal]): + // scr_events_once registers one-shot typed listeners behind adapters + // that box the event's tuple (and the 'error' tuple) into the fulfilled + // array. The runtime retains the boxers; the frame's temporaries + // release as usual. May-throw ('newListener' meta listeners run + // inside). The signal form's adapters end in its own fire entry. const boxerT = e.args[2]!.type; const errorT = e.args[3]!.type; if (boxerT.kind !== "func" || errorT.kind !== "func") - throw new InternalCompilerError("llvm emitter bug: events.once boxer not a func"); + throw new InternalCompilerError(`llvm emitter bug: ${e.fn} boxer not a func`); + const signal = e.fn === "events.onceSignal"; const args = e.args.map((a) => host.emitExpr(a)); - const event = host.emitterOnceAdapter(boxerT); - const error = host.emitterOnceAdapter(errorT); - host.declare(`declare ptr @scr_events_once(ptr, ptr, ptr, ptr, ptr, ptr, ptr, ptr)`); + const fire = signal ? "scr_events_once_signal_fire" : undefined; + const event = host.emitterOnceAdapter(boxerT, fire); + const error = host.emitterOnceAdapter(errorT, fire); + const entry = signal ? "scr_events_once_signal" : "scr_events_once"; + host.declare( + `declare ptr @${entry}(ptr, ptr, ptr, ptr, ptr, ptr, ptr, ptr${signal ? ", ptr" : ""})`, + ); const errorBoxer = B.tmp(); B.line(`${errorBoxer} = select i1 ${args[4]!.name}, ptr ${args[3]!.name}, ptr null`); const t = B.tmp(); B.line( - `${t} = call ptr @scr_events_once(ptr ${args[0]!.name}, ptr ${args[1]!.name}, ` + + `${t} = call ptr @${entry}(ptr ${args[0]!.name}, ptr ${args[1]!.name}, ` + `ptr ${args[2]!.name}, ptr @${event.fn}, ptr @${event.shim}, ` + - `ptr ${errorBoxer}, ptr @${error.fn}, ptr @${error.shim})`, + `ptr ${errorBoxer}, ptr @${error.fn}, ptr @${error.shim}` + + `${signal ? `, ptr ${args[5]!.name}` : ""})`, ); const out = host.own({ name: t, type: e.type }); host.emitPendingCheck(); diff --git a/packages/compiler/src/backend/native-link-info-core.ts b/packages/compiler/src/backend/native-link-info-core.ts index df03d528a..38531267e 100644 --- a/packages/compiler/src/backend/native-link-info-core.ts +++ b/packages/compiler/src/backend/native-link-info-core.ts @@ -43,6 +43,9 @@ export interface NativeLinkFeatures { /** Checked-dynamic stream slots: the scr_stream_dyn.c unit. Absent (not * false) for programs without them. */ streamDyn?: boolean; + /** events.once with options: scr_events_once_signal.c. Absent (not + * false) for programs without it. */ + eventsOnceSignal?: boolean; events: boolean; emitter: boolean; symbol: boolean; diff --git a/packages/compiler/src/backend/native/contracts.ts b/packages/compiler/src/backend/native/contracts.ts index 3b700ea69..31b223fa6 100644 --- a/packages/compiler/src/backend/native/contracts.ts +++ b/packages/compiler/src/backend/native/contracts.ts @@ -116,6 +116,9 @@ export interface CcOptions { /** Checked-dynamic stream slots (stream.dynNative / stream.pipeDyn): * compiles scr_stream_dyn.c. */ streamDyn?: boolean; + /** events.once with options (events.onceSignal): compiles + * scr_events_once_signal.c. */ + eventsOnceSignal?: boolean; /** The program constructs EventTarget/Event/CustomEvent values or * calls events.addAbortListener (the event.* libCalls), or uses the * native fetch AbortSignal built on the same engine: compiles diff --git a/packages/compiler/src/backend/native/executable.ts b/packages/compiler/src/backend/native/executable.ts index d2616151b..f8266cc38 100644 --- a/packages/compiler/src/backend/native/executable.ts +++ b/packages/compiler/src/backend/native/executable.ts @@ -217,6 +217,9 @@ const RUNTIME_UNITS: readonly RuntimeUnit[] = [ // measures with. unit((u) => u.opts.events && u.regex, "scr_readline_tty.c"), unit((u) => u.opts.emitter || u.opts.workers, "scr_events_emitter.c"), + // events.once with options registers through the emitter and + // event-target units. + unit((u) => u.opts.eventsOnceSignal === true, "scr_events_once_signal.c"), // The checked-dynamic HANDLE support unit (listener gate + runtime // adapter closures): every referencing unit is one of the emitter or // net families (http implies net), so handle-free binaries keep diff --git a/packages/compiler/src/backend/runtime-pack-core.ts b/packages/compiler/src/backend/runtime-pack-core.ts index 41fa1442a..dc986d7d1 100644 --- a/packages/compiler/src/backend/runtime-pack-core.ts +++ b/packages/compiler/src/backend/runtime-pack-core.ts @@ -84,6 +84,7 @@ export interface RuntimeFeatureSet extends NativeLinkFeatures { eventTarget: boolean; dynTier: boolean; streamDyn: boolean; + eventsOnceSignal: boolean; warningReplace: boolean; stdioExtended: boolean; nativeFetch: boolean; @@ -275,6 +276,7 @@ export function effectiveRuntimeFeatures( eventTarget: features.eventTarget === true, dynTier: features.dynTier === true, streamDyn: features.streamDyn === true, + eventsOnceSignal: features.eventsOnceSignal === true, warningReplace: features.warningReplace === true, stdioExtended: features.stdioExtended === true, nativeFetch, diff --git a/packages/compiler/src/executable/executable-cache.ts b/packages/compiler/src/executable/executable-cache.ts index 7e3b427e5..45a186afa 100644 --- a/packages/compiler/src/executable/executable-cache.ts +++ b/packages/compiler/src/executable/executable-cache.ts @@ -66,6 +66,7 @@ export interface EarlyExecutableNativeFeatures { dynAsync: boolean; dynTier?: boolean; streamDyn?: boolean; + eventsOnceSignal?: boolean; events: boolean; emitter: boolean; symbol: boolean; diff --git a/packages/compiler/src/frontend/lowering/lower-event-emitter.ts b/packages/compiler/src/frontend/lowering/lower-event-emitter.ts index 4b8b9ea14..4f08ecc0d 100644 --- a/packages/compiler/src/frontend/lowering/lower-event-emitter.ts +++ b/packages/compiler/src/frontend/lowering/lower-event-emitter.ts @@ -72,6 +72,7 @@ import { STREAM_FORCED_EVENT_NAMES, streamForcedTuple, streamSidesOf } from "./l import { boolLit, strLit, varRef } from "../../ir/build.js"; import { tryLowerExpression } from "./expressions/try-lower-expression.js"; import { lowerAddAbortListener } from "./builtin-calls.js"; +import { requestGeneralStreamSurface } from "./general-streams.js"; /** True when the class descends from (or is) the runtime emitter. */ export function emitterRooted(lowerer: Lowerer, info: ClassInfo | undefined | null): boolean { @@ -2204,24 +2205,54 @@ function eventsOnceBoxer( return { kind: "closure", fnName: name, captures: [], type: fnType, loc }; } -/** events.once(emitter, name): a promise of the next `name` event's - * arguments as an array, rejected by an earlier 'error' event (unless the - * awaited event is 'error'). The emitter's listeners are typed by the - * program-wide event table, so the runtime registers one-shot typed - * listeners whose compiler-built boxers turn the event's tuple into the - * array. EventTarget and stream receivers, computed names and options are - * refused. */ +/** The `signal` of an events.once options object literal (a checked- + * dynamic AbortSignal or undefined), or null when the literal names none. + * Other options forms are refused. */ +function eventsOnceSignal(lowerer: Lowerer, node: ts.Expression): IrExpr | null { + if (!ts.isObjectLiteralExpression(node)) { + lowerer.noLowering( + "events.once options that are not an object literal", + node, + "pass the options as an object literal: { signal }", + ); + return null; + } + let signal: IrExpr | null = null; + for (const prop of node.properties.nodes) { + const name = prop.name && ts.isIdentifier(prop.name) ? prop.name.text : null; + if ( + name === "signal" && + signal === null && + (ts.isPropertyAssignment(prop) || ts.isShorthandPropertyAssignment(prop)) + ) { + signal = lowerer.lowerExprExpecting( + ts.isPropertyAssignment(prop) ? prop.initializer : (prop.name as ts.Identifier), + DYN, + ); + } else { + lowerer.noLowering( + "events.once options other than one signal", + prop, + "the lowered options object takes signal: AbortSignal", + ); + } + } + return signal; +} + +/** events.once(emitter, name[, { signal }]): a promise of the next `name` + * event's arguments as an array, rejected by an earlier 'error' event + * (unless the awaited event is 'error') or by the signal's abort with an + * AbortError. The emitter's listeners are typed by the program-wide event + * table (a stream's runtime-emitted events by their fixed tuples), so the + * runtime registers one-shot typed listeners whose compiler-built boxers + * turn the event's tuple into the array. EventTarget receivers, a stream's + * 'data' event, computed names and options other than a literal { signal } + * are refused. */ export function lowerEventsOnce(lowerer: Lowerer, call: ts.CallExpression, loc: SrcLoc): IrExpr { const args = call.arguments.nodes; - if (args.length < 2 || args.some(ts.isSpreadElement)) { - lowerer.noLowering("events.once without an emitter and an event name", call); - } - if (args.length > 2) { - lowerer.noLowering( - "events.once with options", - args[2]!, - "the lowered form is events.once(emitter, name); AbortSignal options are not supported yet", - ); + if (args.length < 2 || args.length > 3 || args.some(ts.isSpreadElement)) { + lowerer.noLowering("events.once without an emitter, an event name and options", call); } // Node's Promise: the arguments array of checked-dynamic values. const arrayT = arrayOf(DYN); @@ -2229,11 +2260,14 @@ export function lowerEventsOnce(lowerer: Lowerer, call: ts.CallExpression, loc: const receiver = lowerer.lowerExpr(args[0]!); const info = receiver.type.kind === "object" ? lowerer.classes.get(receiver.type.className) : undefined; - if (!emitterRooted(lowerer, info) || streamSidesOf(lowerer, info) !== null) { + if (!emitterRooted(lowerer, info)) { + // A NodeJS stream interface slot that holds a stdio handle here: the + // program lowers again with the runtime stream classes. + requestGeneralStreamSurface(lowerer, args[0]!); lowerer.noLowering( "events.once over this value", args[0]!, - "the emitter must be an EventEmitter instance; EventTarget and stream receivers are not supported yet", + "the emitter must be an EventEmitter or stream instance; EventTarget receivers are not supported yet", ); } const name = literalEventName(lowerer, args[1]!); @@ -2245,6 +2279,14 @@ export function lowerEventsOnce(lowerer: Lowerer, call: ts.CallExpression, loc: ); } const event = name ?? ""; + const sides = streamSidesOf(lowerer, info); + if ((sides === "r" || sides === "rw") && (event === "data" || event === "keypress")) { + lowerer.noLowering( + `events.once of a stream's '${event}' event`, + args[1]!, + `the chunk has no checked-dynamic boxing here; register stream.once('${event}', listener) instead`, + ); + } const table = emitterEvents(lowerer); if (event !== "error" && flexibleJsEvent(event, table.get(event))) { lowerer.noLowering( @@ -2252,14 +2294,22 @@ export function lowerEventsOnce(lowerer: Lowerer, call: ts.CallExpression, loc: args[1]!, ); } - const tuple = tupleOf(lowerer, table, event, call); + const tuple = streamForcedTuple(lowerer, info, event) ?? tupleOf(lowerer, table, event, call); const errorTuple: IrType[] = [{ kind: "object", className: "%Error" }]; const boxer = eventsOnceBoxer(lowerer, call, tuple, arrayT, loc); const errorBoxer = eventsOnceBoxer(lowerer, call, errorTuple, arrayT, loc); + const signal = args.length > 2 ? eventsOnceSignal(lowerer, args[2]!) : null; + const common = [ + receiver, + eventNameExpr(event, loc), + boxer, + errorBoxer, + boolLit(event !== "error", loc), + ]; return { kind: "libCall", - fn: "events.once", - args: [receiver, eventNameExpr(event, loc), boxer, errorBoxer, boolLit(event !== "error", loc)], + fn: signal === null ? "events.once" : "events.onceSignal", + args: signal === null ? common : [...common, signal], type: resultT, loc, }; diff --git a/packages/compiler/src/index.ts b/packages/compiler/src/index.ts index 12b84654b..4b70ec7b0 100644 --- a/packages/compiler/src/index.ts +++ b/packages/compiler/src/index.ts @@ -538,6 +538,7 @@ async function compileExecutableNative( dynAsync: features.dynAsync, ...(features.dynTier ? { dynTier: true } : {}), ...(features.streamDyn ? { streamDyn: true } : {}), + ...(features.eventsOnceSignal ? { eventsOnceSignal: true } : {}), events: features.events, emitter: features.emitter, symbol: features.symbol, diff --git a/packages/compiler/src/ir/builtin-signatures.ts b/packages/compiler/src/ir/builtin-signatures.ts index 2d70a7254..efd721557 100644 --- a/packages/compiler/src/ir/builtin-signatures.ts +++ b/packages/compiler/src/ir/builtin-signatures.ts @@ -4174,6 +4174,10 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ * errorBoxer, listenForError] → a pending promise of any[]; the boxers * turn the event's and the 'error' tuple into that array. */ "events.once": { argTypes: [null, STRING, null, null, BOOL], result: VOID }, + /** events.once(emitter, name, { signal }) (scr_events_once_signal.c): + * events.once's arguments plus the checked-dynamic signal (an + * AbortSignal or undefined). */ + "events.onceSignal": { argTypes: [null, STRING, null, null, BOOL, DYN], result: VOID }, /** The resolve-value forms: [ms?, value, ref] → a pending promise of the * value's type that copies the borrowed already-resolved `value` promise * when the timer (or immediate) fires; ref false unrefs it. The result diff --git a/packages/compiler/src/ir/runtime-features.ts b/packages/compiler/src/ir/runtime-features.ts index 9b45fad1a..7b1b759b6 100644 --- a/packages/compiler/src/ir/runtime-features.ts +++ b/packages/compiler/src/ir/runtime-features.ts @@ -129,6 +129,7 @@ const LEGACY_TEXT_DECODER_LIB_FNS: ReadonlySet = new Set([ /** Signal-taking readline, stream and timers/promises entry points, which register abort listeners. */ const ABORT_SIGNAL_LIB_FNS: ReadonlySet = new Set([ + "events.onceSignal", "http.clientSignal", "rl.createSignal", "rl.questionAsyncSignal", @@ -527,6 +528,8 @@ const RUNTIME_FEATURE_RULES = { fns: ["events.addAbortListener", ...ABORT_SIGNAL_LIB_FNS], prefixes: ["event."], }), + /** events.once(emitter, name, { signal }) (scr_events_once_signal.c). */ + eventsOnceSignal: rule({ fns: ["events.onceSignal"] }), fetch: rule({ prefixes: ["fetch."] }), fileHandle: rule({ types: ["fileHandle"] }), fsWatch: rule({ prefixes: ["fs.watch", "watcher."], types: ["fsWatcher"] }), @@ -652,6 +655,7 @@ function noRuntimeFeatures(): RuntimeFeatures { dynTier: false, emitter: false, eventTarget: false, + eventsOnceSignal: false, fetch: false, fileHandle: false, fsWatch: false, diff --git a/packages/compiler/src/ir/validate.ts b/packages/compiler/src/ir/validate.ts index 68997c295..e652eeb52 100644 --- a/packages/compiler/src/ir/validate.ts +++ b/packages/compiler/src/ir/validate.ts @@ -4154,7 +4154,7 @@ function validateFunction( if (!ok) err(`libCall ${e.fn} must be a promise of its ${cryptoPromise} value`, e.loc); return true; } - if (e.fn === "events.once") { + if (e.fn === "events.once" || e.fn === "events.onceSignal") { const [recv, , boxer, errorBoxer] = e.args; const arrayT = e.type.kind === "promise" ? e.type.inner : null; const boxes = (b: IrExpr | undefined) => @@ -4165,7 +4165,7 @@ function validateFunction( !boxes(boxer) || !boxes(errorBoxer) ) { - err(`libCall events.once must box its event tuples into the promised array`, e.loc); + err(`libCall ${e.fn} must box its event tuples into the promised array`, e.loc); } return true; } diff --git a/packages/compiler/src/native/sanitizer.test.ts b/packages/compiler/src/native/sanitizer.test.ts index d9efb7cd8..28bb80169 100644 --- a/packages/compiler/src/native/sanitizer.test.ts +++ b/packages/compiler/src/native/sanitizer.test.ts @@ -77,6 +77,7 @@ test.skipIf(process.platform !== "linux" && process.platform !== "darwin")( eventTarget: false, dynTier: false, streamDyn: false, + eventsOnceSignal: false, warningReplace: false, stdioExtended: false, regex: true, diff --git a/packages/runtime-pack-common/runtime-pack-matrix.mjs b/packages/runtime-pack-common/runtime-pack-matrix.mjs index 790864677..aa8eb880b 100644 --- a/packages/runtime-pack-common/runtime-pack-matrix.mjs +++ b/packages/runtime-pack-common/runtime-pack-matrix.mjs @@ -76,6 +76,8 @@ export const OPTIONAL_RUNTIME_SOURCES = [ ["scr_readline.c", "events"], ["scr_readline_tty.c", all("events", "regex")], ["scr_events_emitter.c", "emitter"], + // events.once with options registers through the emitter and event-target units. + ["scr_events_once_signal.c", "eventsOnceSignal"], ["scr_dyn_handle.c", any("emitter", "netEffective")], ["scr_symbol.c", "symbol"], ["scr_bigint_assert.c", all("assert", "bigint")], diff --git a/packages/runtime/src/scr_events_emitter.c b/packages/runtime/src/scr_events_emitter.c index e0e305bde..4ac7abdf1 100644 --- a/packages/runtime/src/scr_events_emitter.c +++ b/packages/runtime/src/scr_events_emitter.c @@ -518,8 +518,25 @@ static ScrClosure *scr_ee_once_closure(void *adapter, ScrClosure *boxer, ScrEeOn return c; } +static ScrBox *scr_ee_once_promise_box(ScrPromise *p) { + ScrBox *box = scr_box_new_obj(&scr_promise_retain_v, &scr_promise_release_v, &scr_promise_trace_v); + scr_box_set_ref(box, scr_promise_retain(p)); + return box; +} + +static void scr_ee_once_adopt_job(ScrClosure *job) { + ScrPromise *outer = scr_box_get_ref(job->caps[0]); + ScrPromise *inner = scr_box_get_ref(job->caps[1]); + scr_promise_race_join(outer, inner, &scr_promise_adapt_copy); + scr_promise_release(inner); + scr_promise_release(outer); +} + /* boxer and err_boxer are borrowed; the result is +1. err_boxer is NULL - * when the awaited event is 'error' itself. */ + * when the awaited event is 'error' itself. Node's once is an async + * function returning the executor's promise, so the result adopts that + * promise through a thenable job queued here, whose reaction settles the + * result one turn after the executor's promise settles. */ ScrPromise *scr_events_once(ScrEmitter *em, ScrStr *name, ScrClosure *boxer, void *adapter, ScrEeInvoke inv, ScrClosure *err_boxer, void *err_adapter, ScrEeInvoke err_inv) { @@ -558,9 +575,13 @@ ScrPromise *scr_events_once(ScrEmitter *em, ScrStr *name, ScrClosure *boxer, voi scr_caught_release(thrown); scr_promise_reject_pending(st->promise); } - ScrPromise *p = scr_promise_retain(st->promise); + ScrPromise *outer = scr_promise_new(); + ScrClosure *job = scr_closure_new((void *)&scr_ee_once_adopt_job, 2); + job->caps[0] = scr_ee_once_promise_box(outer); + job->caps[1] = scr_ee_once_promise_box(st->promise); + scr_queue_microtask(job); scr_ee_once_release(st); - return p; + return outer; } /* The adapter's tail: `cb` is the firing listener closure, `args` the @@ -1130,3 +1151,9 @@ double scr_emitter_get_default_max(void) { return scr_emitter_default_max; } static SCR_TL double scr_emitter_esm_default_max = 10; double scr_emitter_esm_default_max_get(void) { return scr_emitter_esm_default_max; } void scr_emitter_esm_sync(void) { scr_emitter_esm_default_max = scr_emitter_default_max; } + +/* events.once with a signal (scr_events_once_signal.c) undoes an add whose + * 'newListener' announcement threw. */ +void scr_emitter_unadd_quiet(ScrEmitter *em, ScrStr *name, ScrClosure *cb) { + scr_ee_unadd(em, name, cb); +} diff --git a/packages/runtime/src/scr_events_emitter.h b/packages/runtime/src/scr_events_emitter.h index a92ae1864..39a7f4b5b 100644 --- a/packages/runtime/src/scr_events_emitter.h +++ b/packages/runtime/src/scr_events_emitter.h @@ -64,6 +64,20 @@ ScrPromise *scr_events_once(ScrEmitter *em, ScrStr *name, ScrClosure *boxer, voi ScrEeInvoke inv, ScrClosure *err_boxer, void *err_adapter, ScrEeInvoke err_inv); void scr_events_once_fire(ScrClosure *cb, ScrArr *args); +/* events.once(emitter, name, { signal }) (scr_events_once_signal.c): the + * scr_events_once arguments plus the checked-dynamic signal (borrowed; + * undefined for the plain form). Never throws: a non-AbortSignal value, + * an aborted signal or a throwing 'newListener' listener rejects the +1 + * result. Its adapters end in scr_events_once_signal_fire. */ +typedef struct ScrDyn ScrDyn; /* full definition below (C11 repeat) */ +ScrPromise *scr_events_once_signal(ScrEmitter *em, ScrStr *name, ScrClosure *boxer, void *adapter, + ScrEeInvoke inv, ScrClosure *err_boxer, void *err_adapter, + ScrEeInvoke err_inv, ScrDyn *signal); +void scr_events_once_signal_fire(ScrClosure *cb, ScrArr *args); +/* Removes the listener entry whose closure is `cb` (borrowed) without a + * 'removeListener' event: the undo of an add whose 'newListener' + * announcement threw. */ +void scr_emitter_unadd_quiet(ScrEmitter *em, ScrStr *name, ScrClosure *cb); ScrEmitter *scr_emitter_on(ScrEmitter *em, ScrStr *name, ScrClosure *cb /*moves*/, ScrEeInvoke inv, bool once, bool prepend); /* The dyn-adapted registration family (JS-lane checked-dynamic diff --git a/packages/runtime/src/scr_events_once_signal.c b/packages/runtime/src/scr_events_once_signal.c new file mode 100644 index 000000000..c3c2db37d --- /dev/null +++ b/packages/runtime/src/scr_events_once_signal.c @@ -0,0 +1,303 @@ +/* events.once(emitter, name, { signal }) over typed listeners — Node 24's + * lib/events.js `once` with an options object. Linked only by programs + * that pass the options (the emitter and event-target units together). + * + * The signal is validated first: undefined is the plain form, a value that + * is not an AbortSignal rejects with ERR_INVALID_ARG_TYPE, and an aborted + * signal rejects with an AbortError whose cause is signal.reason, all + * without registering a listener. Otherwise the event's one-shot listener, + * the 'error' one (unless the event is 'error') and a once, + * resist-stop-propagation 'abort' listener on the signal share the state + * below. The event or the error removes the other emitter listener and the + * abort listener, then settles; the abort removes both emitter listeners + * and rejects with the AbortError. + * + * Ownership: the state owns the promise, the name and the signal. The + * emitter is weak in the state; the abort listener holds it strongly (the + * abort is the one settlement that does not come from the emitter itself, + * like the closure Node's abortListener keeps). The three listener closures + * reach the state through link objects; a link clears the state's pointer + * to its closure when that closure is freed, so removals never compare a + * stale identity. The cycle through the signal's listener ends when the + * promise settles or the signal aborts (or at the exit teardown of the + * signal's listeners). */ +#include "scr_runtime.h" + +#include + +typedef struct ScrEosState { + size_t rc; + ScrPromise *promise; /* owned */ + ScrEmitter *em; /* weak: alive whenever a listener fires */ + ScrStr *name; /* owned */ + ScrDyn *signal; /* owned; undefined without an abort listener */ + ScrClosure *event_cb; /* weak, NULL once freed */ + ScrClosure *error_cb; /* weak, NULL once freed or for 'error' */ + ScrDyn *listener; /* weak abort listener, NULL once freed */ + bool settled; +} ScrEosState; + +enum { SCR_EOS_EVENT, SCR_EOS_ERROR, SCR_EOS_ABORT }; + +typedef struct ScrEosLink { + size_t rc; + ScrEosState *st; /* owned */ + int slot; +} ScrEosLink; + +static void scr_eos_oom(void) { scr_trap("scriptc: out of memory in events.once\n"); } + +static void scr_eos_state_release(ScrEosState *st) { + if (--st->rc > 0) return; + scr_promise_release(st->promise); + scr_str_release(st->name); + scr_dyn_release(st->signal); + free(st); +} + +static void *scr_eos_link_retain(void *p) { + ((ScrEosLink *)p)->rc++; + return p; +} + +static void scr_eos_link_release(void *p) { + ScrEosLink *link = p; + if (--link->rc > 0) return; + ScrEosState *st = link->st; + if (link->slot == SCR_EOS_EVENT) st->event_cb = NULL; + else if (link->slot == SCR_EOS_ERROR) st->error_cb = NULL; + else st->listener = NULL; + scr_eos_state_release(st); + free(link); +} + +static ScrBox *scr_eos_link_box(ScrEosState *st, int slot) { + ScrEosLink *link = calloc(1, sizeof *link); + if (!link) scr_eos_oom(); + link->rc = 1; + link->st = st; + link->slot = slot; + st->rc++; + ScrBox *box = scr_box_new_obj(&scr_eos_link_retain, &scr_eos_link_release, NULL); + scr_box_set_ref(box, link); + return box; +} + +/* An emitter listener: caps[0] its boxer (the compiled adapter calls it), + * caps[1] its link. */ +static ScrClosure *scr_eos_closure(void *adapter, ScrClosure *boxer, ScrEosState *st, int slot) { + ScrClosure *c = scr_closure_new(adapter, 2); + ScrBox *bx = scr_box_new(SCR_BOX_FUNC); + scr_box_set_ref(bx, scr_closure_retain(boxer)); + c->caps[0] = bx; + c->caps[1] = scr_eos_link_box(st, slot); + return c; +} + +/* new AbortError(undefined, { cause: signal.reason }), thrown. */ +static void scr_eos_throw_abort(ScrDyn *signal) { + ScrStr *message = scr_str_new("The operation was aborted", 25); + ScrError *e = scr_error_new(SCR_ERR_ERROR, message); + scr_str_release(message); + scr_str_release(e->name); + e->name = scr_str_new("AbortError", 10); + /* AbortError assigns code, then name: own enumerable properties */ + scr_error_set_code(e, "ABORT_ERR"); + e->name_present = true; + e->name_enumerable = true; + ScrStr *k = scr_str_new("reason", 6); + ScrDyn *reason = scr_dyn_handle_key_get(signal, k); + scr_str_release(k); + scr_error_define_cause(e, reason ? reason : scr_dyn_undefined()); + if (reason) scr_dyn_release(reason); + scr_throw_obj(e, &scr_error_retain_v, &scr_error_release_v, scr_error_trace_arg()); +} + +/* removeEventListener('abort', listener) on the signal, when the listener + * is still registered. */ +static void scr_eos_remove_abort(ScrEosState *st) { + if (st->listener == NULL) return; + bool aborted; + ScrEventTargetCore *core = scr_et_signal_core_fn ? scr_et_signal_core_fn(st->signal, &aborted) : NULL; + if (!core) return; + ScrStr *abort = scr_str_new("abort", 5); + ScrDyn *type = scr_dyn_new_str(abort); + scr_str_release(abort); + ScrDyn *listener = scr_dyn_retain(st->listener); + ScrDyn *remove_args[2] = {type, listener}; + ScrDyn *r = scr_et_remove_listener(core, remove_args, 2); + if (r) scr_dyn_release(r); + scr_dyn_release(type); + scr_dyn_release(listener); +} + +/* emitter.removeListener(name, cb) for a listener closure still alive. */ +static bool scr_eos_off(ScrEmitter *em, ScrStr *name, ScrClosure *cb) { + if (cb == NULL) return true; + scr_emitter_release(scr_emitter_off(em, name, cb)); + return !scr_exc_pending(); +} + +/* The compiled adapter's tail: `cb` is the firing emitter listener, `args` + * the boxed event arguments (moves in). */ +void scr_events_once_signal_fire(ScrClosure *cb, ScrArr *args) { + ScrEosLink *link = scr_box_get_ref(cb->caps[1]); + ScrEosState *st = link->st; + if (!st->settled) { + st->settled = true; + bool error = link->slot == SCR_EOS_ERROR; + /* Node's errorListener removes the event's listener and resolver the + * error's, each through removeListener; then the abort listener. */ + bool ok; + if (error) { + ok = scr_eos_off(st->em, st->name, st->event_cb); + } else { + ScrStr *error_name = scr_str_new("error", 5); + ok = scr_eos_off(st->em, error_name, st->error_cb); + scr_str_release(error_name); + } + if (ok) { + scr_eos_remove_abort(st); + if (error) { + ScrDyn *reason = (ScrDyn *)scr_arr_get_ref(args, 0); + scr_dyn_throw(reason); /* moves reason */ + scr_promise_reject_pending(st->promise); + } else { + scr_promise_fulfill_ref(st->promise, scr_arr_retain(args), scr_arr_retain_v, + scr_arr_release_v, args->cyc ? scr_arr_trace_v : NULL); + } + } + } + scr_arr_release(args); + scr_eos_link_release(link); +} + +/* The signal's 'abort' listener: caps[0] its link, caps[1] the emitter. */ +static ScrDyn *scr_eos_abort_thunk(ScrClosure *clo, ScrDyn *const *args, size_t argc) { + (void)args; + (void)argc; + ScrEosLink *link = scr_box_get_ref(clo->caps[0]); + ScrEmitter *em = scr_box_get_ref(clo->caps[1]); + ScrEosState *st = link->st; + bool ok = true; + if (!st->settled) { + st->settled = true; + ok = scr_eos_off(em, st->name, st->event_cb); + if (ok) { + ScrStr *error_name = scr_str_new("error", 5); + ok = scr_eos_off(em, error_name, st->error_cb); + scr_str_release(error_name); + } + if (ok) { + scr_eos_throw_abort(st->signal); + scr_promise_reject_pending(st->promise); + } + } + scr_emitter_release(em); + scr_eos_link_release(link); + return ok ? scr_dyn_retain(scr_dyn_undefined()) : NULL; +} + +static ScrBox *scr_eos_promise_box(ScrPromise *p) { + ScrBox *box = scr_box_new_obj(&scr_promise_retain_v, &scr_promise_release_v, &scr_promise_trace_v); + scr_box_set_ref(box, scr_promise_retain(p)); + return box; +} + +static void scr_eos_adopt_job(ScrClosure *job) { + ScrPromise *outer = scr_box_get_ref(job->caps[0]); + ScrPromise *inner = scr_box_get_ref(job->caps[1]); + scr_promise_race_join(outer, inner, &scr_promise_adapt_copy); + scr_promise_release(inner); + scr_promise_release(outer); +} + +/* Node's once is an async function returning the executor's promise: its + * result adopts that promise through a thenable job queued now, whose + * reaction settles the result one turn after the inner promise settles. + * Moves `inner` in; the result is +1. */ +static ScrPromise *scr_eos_adopt(ScrPromise *inner) { + ScrPromise *outer = scr_promise_new(); + ScrClosure *job = scr_closure_new((void *)&scr_eos_adopt_job, 2); + job->caps[0] = scr_eos_promise_box(outer); + job->caps[1] = scr_eos_promise_box(inner); + scr_promise_release(inner); + scr_queue_microtask(job); + return outer; +} + +/* boxer and err_boxer are borrowed (err_boxer is NULL when the awaited + * event is 'error'); signal is borrowed. The result is +1 and never throws: + * every failure rejects it, as Node's async function does. */ +ScrPromise *scr_events_once_signal(ScrEmitter *em, ScrStr *name, ScrClosure *boxer, void *adapter, + ScrEeInvoke inv, ScrClosure *err_boxer, void *err_adapter, + ScrEeInvoke err_inv, ScrDyn *signal) { + ScrPromise *result = scr_promise_new(); + bool with_signal = signal->kind != SCR_DYN_UNDEF; + if (with_signal) { + bool aborted = false; + ScrEventTargetCore *core = + scr_et_signal_core_fn ? scr_et_signal_core_fn(signal, &aborted) : NULL; + if (!core) { + scr_dyn_prop_type_fail("options.signal", "an instance of AbortSignal", signal); + scr_promise_reject_pending(result); + return result; + } + if (aborted) { + scr_eos_throw_abort(signal); + scr_promise_reject_pending(result); + return result; + } + } + ScrEosState *st = calloc(1, sizeof *st); + if (!st) scr_eos_oom(); + st->rc = 1; + st->promise = result; + st->em = em; + st->name = scr_str_retain(name); + st->signal = scr_dyn_retain(signal); + ScrClosure *event_cb = scr_eos_closure(adapter, boxer, st, SCR_EOS_EVENT); + ScrClosure *error_cb = + err_boxer != NULL ? scr_eos_closure(err_adapter, err_boxer, st, SCR_EOS_ERROR) : NULL; + st->event_cb = event_cb; + st->error_cb = error_cb; + /* Node registers inside the promise's executor: a throwing 'newListener' + * listener rejects the promise, and the listener it announced is never + * added (the event's listener stays when only the 'error' announcement + * throws). No abort listener follows a throw. */ + ScrCaught *thrown = NULL; + scr_emitter_release(scr_emitter_on(em, name, event_cb, inv, true, false)); + if (scr_exc_pending()) { + thrown = scr_exc_take(); + scr_emitter_unadd_quiet(em, name, event_cb); + if (error_cb != NULL) scr_closure_release(error_cb); /* never registered */ + } else if (error_cb != NULL) { + ScrStr *error = scr_str_new("error", 5); + scr_emitter_release(scr_emitter_on(em, error, error_cb, err_inv, true, false)); + if (scr_exc_pending()) { + thrown = scr_exc_take(); + scr_emitter_unadd_quiet(em, error, error_cb); + } + scr_str_release(error); + } + if (thrown != NULL) { + st->settled = true; + scr_rethrow(thrown); + scr_caught_release(thrown); + scr_promise_reject_pending(st->promise); + } else if (with_signal) { + ScrClosure *clo = scr_closure_new((void *)&scr_eos_abort_thunk, 2); + clo->caps[0] = scr_eos_link_box(st, SCR_EOS_ABORT); + clo->caps[1] = scr_box_new_obj(&scr_emitter_retain_v, &scr_emitter_release_v, NULL); + scr_box_set_ref(clo->caps[1], scr_emitter_retain(em)); + ScrDyn *listener = scr_dyn_new_func(clo, &scr_eos_abort_thunk, 0, "()", "abortListener"); + st->listener = listener; + ScrDyn *dispose = scr_events_add_abort_listener(signal, listener); + if (dispose) scr_dyn_release(dispose); + /* The signal's listener entry keeps it; the state's pointer is weak. */ + scr_dyn_release(listener); + } + ScrPromise *inner = scr_promise_retain(st->promise); + scr_eos_state_release(st); + return scr_eos_adopt(inner); +} From 2429eaf5568584032c5de7082da01d0e0c068761 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Tue, 20 Oct 2026 08:25:46 -0700 Subject: [PATCH 097/122] Admit Promise race entries (the race part of main's 465abda81) The Promise.race portion of 465abda81 on main, applied unchanged so the race changes that follow build on it and merge with main cleanly: a Promise entry can only reject, so it races under any result type and its fulfillment adapter traps; a Promise executor's one-parameter resolve validates against the void inner. Includes that commit's corpus program 6450. Co-Authored-By: Claude Opus 5.5 --- .../src/backend/llvm/expr-callbacks.ts | 17 ++++++++++++++ .../frontend/lowering/builtins/promises.ts | 13 ++++++++++- packages/compiler/src/ir/validate.ts | 5 ++++- .../6450-promise-race-never-entry.ts.json | 6 +++++ tests/corpus/6450-promise-race-never-entry.ts | 22 +++++++++++++++++++ 5 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6450-promise-race-never-entry.ts.json create mode 100644 tests/corpus/6450-promise-race-never-entry.ts diff --git a/packages/compiler/src/backend/llvm/expr-callbacks.ts b/packages/compiler/src/backend/llvm/expr-callbacks.ts index e805760d8..2e62d1731 100644 --- a/packages/compiler/src/backend/llvm/expr-callbacks.ts +++ b/packages/compiler/src/backend/llvm/expr-callbacks.ts @@ -55,6 +55,23 @@ export function raceAdapterFor(host: LlvmEmitterContext, from: IrType, to: IrTyp if (sym) return sym; sym = `sc_race_${host.resolveThunks.size}`; host.resolveThunks.set(key, sym); + const toArms = to.kind === "union" ? (host.unionsById.get(to.unionId)?.arms ?? []) : []; + if (from.kind === "void" && !toArms.some((a) => a.kind === "undefinedT")) { + // A Promise entry (the frontend admits void entries without an + // undefined result arm only for those): it can only reject, so its + // fulfillment adapter never runs. + host.declare(`declare void @scr_trap(ptr)`); + const msg = host.cstr("scriptc: a Promise race entry fulfilled\n"); + host.resolveThunkDefs.push( + `define internal void @${sym}(ptr %dst, ptr %src) ${FN_ATTRS} { ; race ${key}`, + `entry:`, + ` call void @scr_trap(ptr ${msg})`, + ` unreachable`, + `}`, + ``, + ); + return sym; + } if (to.kind !== "union") throw new InternalCompilerError("llvm emitter bug: race adapter to a non-union"); const toDef = host.unionsById.get(to.unionId); diff --git a/packages/compiler/src/frontend/lowering/builtins/promises.ts b/packages/compiler/src/frontend/lowering/builtins/promises.ts index 48106d082..03e9c7497 100644 --- a/packages/compiler/src/frontend/lowering/builtins/promises.ts +++ b/packages/compiler/src/frontend/lowering/builtins/promises.ts @@ -74,6 +74,17 @@ export function lowerPromiseStaticCall( } return resultArms.some((a) => typeEquals(a, inner)); }; + // A Promise entry (the deadline idiom: `new Promise((_, + // reject) => setTimeout(reject, ms))`) can only reject, so it races + // under any result type; its inner type maps to void. + const neverEntry = (el: ts.Expression): boolean => { + const t = lowerer.typeOf(el); + const arg = + t.getSymbol()?.name === "Promise" + ? lowerer.checker.getTypeArguments(t as ts.TypeReference)[0] + : undefined; + return arg !== undefined && (arg.flags & ts.TypeFlags.Never) !== 0; + }; const entries = argNode.elements.nodes.map((el) => { const entry = lowerer.lowerExpr(el); if (entry.type.kind !== "promise") { @@ -83,7 +94,7 @@ export function lowerPromiseStaticCall( "JS would resolve plain values — wrap them: new Promise((r) => r(v))", ); } - if (!compatible(entry.type.inner)) { + if (!compatible(entry.type.inner) && !(entry.type.inner.kind === "void" && neverEntry(el))) { lowerer.unsupported( "SC1090", el, diff --git a/packages/compiler/src/ir/validate.ts b/packages/compiler/src/ir/validate.ts index e652eeb52..b46cf33de 100644 --- a/packages/compiler/src/ir/validate.ts +++ b/packages/compiler/src/ir/validate.ts @@ -6229,7 +6229,10 @@ function validateFunction( if (expectsArg && (rp.params.length !== 1 || !typeEquals(rp.params[0]!, inner))) { err(`newPromise resolve param must be (${inner.kind}) => void`, e.loc); } - if (!expectsArg && rp.params.length !== 0) { + // A Promise executor types resolve as `(value: never) => + // void` (one parameter over a void inner): the void resolve never + // reads an argument, and a never value cannot be produced. + if (!expectsArg && rp.params.length > 1) { err("newPromise resolve takes no argument", e.loc); } } diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6450-promise-race-never-entry.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6450-promise-race-never-entry.ts.json new file mode 100644 index 000000000..bb709e7f3 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6450-promise-race-never-entry.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6450-promise-race-never-entry.ts" + ], + "diags": [] +} diff --git a/tests/corpus/6450-promise-race-never-entry.ts b/tests/corpus/6450-promise-race-never-entry.ts new file mode 100644 index 000000000..412c5f817 --- /dev/null +++ b/tests/corpus/6450-promise-race-never-entry.ts @@ -0,0 +1,22 @@ +// Promise.race with a Promise deadline entry under a typed result: +// the never entry can only reject, so it races with any result type. A +// read that settles first wins; a deadline that fires first rejects. +class Deadline extends Error {} +async function readWithDeadline(read: () => Promise<{ done: boolean; value?: Uint8Array }>, ms: number): Promise<{ done: boolean; value?: Uint8Array }> { + let timer: ReturnType | undefined; + try { + return await Promise.race([ + read(), + new Promise((_, reject) => { + timer = setTimeout(() => reject(new Deadline()), ms); + }), + ]); + } catch (error) { + if (error instanceof Deadline) return { done: true }; + throw error; + } finally { + if (timer !== undefined) clearTimeout(timer); + } +} +readWithDeadline(async () => ({ done: false, value: new Uint8Array([1, 2]) }), 1000).then((r) => console.log(r.done, r.value?.length)); +readWithDeadline(() => new Promise((res) => setTimeout(() => res({ done: false }), 200)), 20).then((r) => console.log("deadline", r.done)); From 2630e8438ae738c000e4c86651003652ea4ea71a Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Tue, 20 Oct 2026 08:26:04 -0700 Subject: [PATCH 098/122] Combine Promise.race entries whose combined type has no representation When the checker's combined type has no representation (events.once's any[] arrays, or 'void | any[]'), the race result takes the one type the entries that can fulfill share, or 'unknown' when each of them widens into it; Promise entries do not take part. An entry whose payload widens into the result type converts through the checked promise payload widening, which keeps its settlement turn. Co-Authored-By: Claude Opus 5.5 --- .../frontend/lowering/builtins/promises.ts | 72 +++++++++++++------ 1 file changed, 51 insertions(+), 21 deletions(-) diff --git a/packages/compiler/src/frontend/lowering/builtins/promises.ts b/packages/compiler/src/frontend/lowering/builtins/promises.ts index 03e9c7497..aa701f53f 100644 --- a/packages/compiler/src/frontend/lowering/builtins/promises.ts +++ b/packages/compiler/src/frontend/lowering/builtins/promises.ts @@ -23,7 +23,11 @@ import { * type is the checker's combined promise, and each entry's inner type * must be that inner type, one of its union arms, or a sub-union of it * (the backend's interned adapters wrap/re-tag fulfillments; a wider - * entry would need machinery that doesn't exist and fences). + * entry would need machinery that doesn't exist and fences). A + * Promise entry fits any result, and an entry whose payload widens + * into the result's converts. When the checker's combined type has no + * representation, the result takes the type the entries that can fulfill + * share, or 'unknown'. * Promise.all/allSettled/any fence with the sequential-await hint; * resolve/reject and the rest fall to the member fence. Null for * non-Promise receivers. */ @@ -50,14 +54,47 @@ export function lowerPromiseStaticCall( "a non-empty array LITERAL of promises is the supported form: Promise.race([p, q])", ); } - const resultT = lowerer.mapTypeOf(lowerer.typeOf(call)); - if (resultT?.kind !== "promise") { - lowerer.noLowering( - "Promise.race with this combined result type", - call, - "the entries' value types must combine into a representable union", - ); + // A Promise entry (the deadline idiom: `new Promise((_, + // reject) => setTimeout(reject, ms))`) can only reject, so it races + // under any result type; its inner type maps to void. + const neverEntry = (el: ts.Expression): boolean => { + const t = lowerer.typeOf(el); + const arg = + t.getSymbol()?.name === "Promise" + ? lowerer.checker.getTypeArguments(t as ts.TypeReference)[0] + : undefined; + return arg !== undefined && (arg.flags & ts.TypeFlags.Never) !== 0; + }; + const elements = argNode.elements.nodes; + let combined = lowerer.mapTypeOf(lowerer.typeOf(call)); + let lowered: IrExpr[] | null = null; + if (combined?.kind !== "promise") { + // The checker's combined type has no representation (an 'any' + // element array such as events.once's any[], or 'void | any[]'): the + // result takes the one type the entries that can fulfill share, or + // else 'unknown' when each of them widens into it. + lowered = elements.map((el) => lowerer.lowerExpr(el)); + const fulfilling = lowered.filter((_, i) => !neverEntry(elements[i]!)); + const first = fulfilling[0]?.type; + const dynResult: IrType & { kind: "promise" } = { kind: "promise", inner: DYN }; + if (first?.kind === "promise" && fulfilling.every((e) => typeEquals(e.type, first))) { + combined = first; + } else if ( + fulfilling.length > 0 && + fulfilling.every( + (e) => e.type.kind === "promise" && lowerer.promisePayloadWidens(e.type, dynResult), + ) + ) { + combined = dynResult; + } else { + lowerer.noLowering( + "Promise.race with this combined result type", + call, + "the entries' value types must combine into a representable union", + ); + } } + const resultT = combined; const resultArms = resultT.inner.kind === "union" ? (lowerer.unions.get(resultT.inner.unionId)?.arms ?? []) @@ -74,19 +111,8 @@ export function lowerPromiseStaticCall( } return resultArms.some((a) => typeEquals(a, inner)); }; - // A Promise entry (the deadline idiom: `new Promise((_, - // reject) => setTimeout(reject, ms))`) can only reject, so it races - // under any result type; its inner type maps to void. - const neverEntry = (el: ts.Expression): boolean => { - const t = lowerer.typeOf(el); - const arg = - t.getSymbol()?.name === "Promise" - ? lowerer.checker.getTypeArguments(t as ts.TypeReference)[0] - : undefined; - return arg !== undefined && (arg.flags & ts.TypeFlags.Never) !== 0; - }; - const entries = argNode.elements.nodes.map((el) => { - const entry = lowerer.lowerExpr(el); + const entries = elements.map((el, i) => { + const entry = lowered?.[i] ?? lowerer.lowerExpr(el); if (entry.type.kind !== "promise") { lowerer.noLowering( "Promise.race over non-promise entries", @@ -95,6 +121,10 @@ export function lowerPromiseStaticCall( ); } if (!compatible(entry.type.inner) && !(entry.type.inner.kind === "void" && neverEntry(el))) { + // A payload that widens into the result's (into 'unknown', or a + // checked conversion) keeps its promise and settlement turn. + if (lowerer.promisePayloadWidens(entry.type, resultT)) + return lowerer.coerceToExpected(entry, resultT); lowerer.unsupported( "SC1090", el, From 09f427c4fe2e1e6e7401220139366d613f6f9f9c Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Tue, 20 Oct 2026 08:25:00 -0700 Subject: [PATCH 099/122] Accept Function-typed completion callbacks in stream underscore methods A stream subclass that declares `_transform(chunk, encoding, cb: Function)` (or _write, _final, _destroy, _flush with a broad `Function` callback) was rejected because 'Function' has no lowering, which also blocked every construction of the class. The callback parameter now takes the checked-dynamic callable the runtime already passes implicitly typed callbacks, and calls through it are dynamic calls. Co-Authored-By: Claude Opus 5.5 --- .../src/frontend/lowering/lower-classes.ts | 19 +++++++++++++++++++ .../src/frontend/lowering/lower-stream.ts | 10 ++++++++++ 2 files changed, 29 insertions(+) diff --git a/packages/compiler/src/frontend/lowering/lower-classes.ts b/packages/compiler/src/frontend/lowering/lower-classes.ts index a31a62e61..1d6b80535 100644 --- a/packages/compiler/src/frontend/lowering/lower-classes.ts +++ b/packages/compiler/src/frontend/lowering/lower-classes.ts @@ -112,6 +112,7 @@ import { import { STREAM_API_MEMBERS, STREAM_PROP_MEMBERS, + STREAM_COMPLETION_CALLBACK_INDEX, UNDERSCORE_METHODS, lowerStreamNew, lowerStreamSuperCall, @@ -2690,6 +2691,24 @@ export function collectClassShapeInner( ); } } + // A completion callback declared with the broad `Function` type + // (`cb: Function`) takes the checked-dynamic callable the runtime + // passes implicitly typed callbacks; calls through it are + // dynamic calls of that callable. + const callbackIndex = STREAM_COMPLETION_CALLBACK_INDEX.get(name); + const callbackParam = + ts.isMethodDeclaration(member) && callbackIndex !== undefined + ? member.parameters.nodes[callbackIndex] + : undefined; + if ( + callbackParam !== undefined && + ts.isIdentifier(callbackParam.name) && + !callbackParam.dotDotDotToken && + !callbackParam.questionToken && + !callbackParam.initializer && + lowerer.checker.typeToString(lowerer.typeOf(callbackParam.name)) === "Function" + ) + lowerer.checkedCallbackParams.add(callbackParam); } } if (ts.isPropertyDeclaration(member)) { diff --git a/packages/compiler/src/frontend/lowering/lower-stream.ts b/packages/compiler/src/frontend/lowering/lower-stream.ts index 53e2927d5..f31862f07 100644 --- a/packages/compiler/src/frontend/lowering/lower-stream.ts +++ b/packages/compiler/src/frontend/lowering/lower-stream.ts @@ -171,6 +171,16 @@ export const UNDERSCORE_METHODS: ReadonlyMap = new Map([ ["flush", "_flush"], ]); +/** The completion callback's parameter position in each underscore + * method that takes one (cbTuples' "fn" positions). */ +export const STREAM_COMPLETION_CALLBACK_INDEX: ReadonlyMap = new Map([ + ["_write", 2], + ["_final", 0], + ["_destroy", 1], + ["_transform", 2], + ["_flush", 0], +]); + /** The accepted option-callback names (canonical flag order) and the * duplex-shaped-head answer for one runtime stream class. */ export function streamCtorShape(cls: string): { From 67ace26573d8e88740b0bd608cb4518b151b2cf3 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Tue, 20 Oct 2026 08:37:28 -0700 Subject: [PATCH 100/122] Unbox native streams for events.once over checked-dynamic stream slots Under checked-dynamic NodeJS stream slots, events.once over a NodeJS.ReadableStream or NodeJS.WritableStream slot unboxes a native stream by identity and takes the stream path; any other value throws the not-supported Error that for await and pipeline stages already throw for JavaScript stream objects. Co-Authored-By: Claude Opus 5.5 --- .../frontend/lowering/lower-event-emitter.ts | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/packages/compiler/src/frontend/lowering/lower-event-emitter.ts b/packages/compiler/src/frontend/lowering/lower-event-emitter.ts index 4f08ecc0d..f2968cebd 100644 --- a/packages/compiler/src/frontend/lowering/lower-event-emitter.ts +++ b/packages/compiler/src/frontend/lowering/lower-event-emitter.ts @@ -73,6 +73,7 @@ import { boolLit, strLit, varRef } from "../../ir/build.js"; import { tryLowerExpression } from "./expressions/try-lower-expression.js"; import { lowerAddAbortListener } from "./builtin-calls.js"; import { requestGeneralStreamSurface } from "./general-streams.js"; +import { nodeStreamInterfaceSide } from "./dyn-streams.js"; /** True when the class descends from (or is) the runtime emitter. */ export function emitterRooted(lowerer: Lowerer, info: ClassInfo | undefined | null): boolean { @@ -2257,7 +2258,28 @@ export function lowerEventsOnce(lowerer: Lowerer, call: ts.CallExpression, loc: // Node's Promise: the arguments array of checked-dynamic values. const arrayT = arrayOf(DYN); const resultT: IrType = { kind: "promise", inner: arrayT }; - const receiver = lowerer.lowerExpr(args[0]!); + let receiver = lowerer.lowerExpr(args[0]!); + // A checked-dynamic NodeJS stream slot (dyn-streams.ts): a native stream + // unboxes by identity; any other value throws the not-supported Error. + if ( + receiver.type.kind === "dyn" && + lowerer.dynNodeStreams && + nodeStreamInterfaceSide(lowerer, lowerer.typeOf(args[0]!)) !== null + ) { + receiver = { + kind: "libCall", + fn: "stream.unboxDyn", + args: [ + receiver, + strLit( + "events.once over stream objects other than native streams is not supported yet", + loc, + ), + ], + type: { kind: "object", className: "%Duplex" }, + loc, + }; + } const info = receiver.type.kind === "object" ? lowerer.classes.get(receiver.type.className) : undefined; if (!emitterRooted(lowerer, info)) { From e865594089ea9da215b0b2c9f0b48d5e4957f0b0 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Tue, 20 Oct 2026 08:47:59 -0700 Subject: [PATCH 101/122] Accept NodeJS stream interfaces in the fallback events.once and document its boundary The fallback declaration of events.once takes NodeJS.ReadableStream and NodeJS.WritableStream emitters like @types/node does. The diagnostics fixture now pins the forms that stay refused (options that are not an object literal, a computed name, a stream's 'data' event), and the limitations page states the supported events.once forms. Co-Authored-By: Claude Opus 5.5 --- .../limitations/builtin-apis/eventemitter.mdx | 1 + .../ambient/scriptc-node-fallback.d.ts | 2 +- tests/diagnostics/events-once-fences.ts | 9 ++++-- .../__snapshots__/events-once-fences.ts.txt | 31 ++++++++++++------- 4 files changed, 28 insertions(+), 15 deletions(-) diff --git a/docs/content/limitations/builtin-apis/eventemitter.mdx b/docs/content/limitations/builtin-apis/eventemitter.mdx index 4f7142094..a9f03b857 100644 --- a/docs/content/limitations/builtin-apis/eventemitter.mdx +++ b/docs/content/limitations/builtin-apis/eventemitter.mdx @@ -1,4 +1,5 @@ - **EventEmitter:** Literal event names use fixed argument tuples. Computed string names require a constant prefix or suffix that excludes `error`, meta events, and stream-internal events; their arguments must match the exact arity and support checked conversion. Unrestricted names, symbols, names that may reach internal events, and typed `listeners()`/`rawListeners()` results for names with possible computed registrations are unsupported. + `events.once` accepts EventEmitter and stream instances with a literal event name and an optional object-literal `{ signal }`; a stream's `data` event, other options values, and EventTarget emitters are unsupported. Reading `defaultMaxListeners` through a CommonJS destructured or member binding (`const { defaultMaxListeners } = require("events")`) is unsupported. diff --git a/packages/compiler/ambient/scriptc-node-fallback.d.ts b/packages/compiler/ambient/scriptc-node-fallback.d.ts index 3885e0fef..2886a730a 100644 --- a/packages/compiler/ambient/scriptc-node-fallback.d.ts +++ b/packages/compiler/ambient/scriptc-node-fallback.d.ts @@ -6443,7 +6443,7 @@ declare module "events" { export { internal as EventEmitter }; // The event's arguments, as checked-dynamic values. export function once( - emitter: EventEmitter, + emitter: EventEmitter | NodeJS.ReadableStream | NodeJS.WritableStream, eventName: string, options?: { signal?: AbortSignal }, ): Promise; diff --git a/tests/diagnostics/events-once-fences.ts b/tests/diagnostics/events-once-fences.ts index 2e5613f70..97bc76254 100644 --- a/tests/diagnostics/events-once-fences.ts +++ b/tests/diagnostics/events-once-fences.ts @@ -1,11 +1,14 @@ -// events.once lowers for EventEmitter instances and literal event names; -// these forms stay refused. +// events.once lowers for EventEmitter and stream instances, literal event +// names and a literal { signal } options object; these forms stay refused. import { EventEmitter, once } from "node:events"; +import { PassThrough } from "node:stream"; const ee = new EventEmitter(); const name: string = process.argv[2] ?? "ready"; +const options = {}; async function main(): Promise { - await once(ee, "ready", { signal: AbortSignal.timeout(5) }); // options + await once(ee, "ready", options); // options that are not an object literal await once(ee, name); // computed event name + await once(new PassThrough(), "data"); // a stream's 'data' event } void main(); diff --git a/tests/harness/__snapshots__/events-once-fences.ts.txt b/tests/harness/__snapshots__/events-once-fences.ts.txt index ffab538b0..60c499cfe 100644 --- a/tests/harness/__snapshots__/events-once-fences.ts.txt +++ b/tests/harness/__snapshots__/events-once-fences.ts.txt @@ -1,17 +1,26 @@ -events-once-fences.ts:8:27 - error SC2020: 'events.once with options' is part of the standard library types but has no scriptc lowering yet +events-once-fences.ts:10:27 - error SC2020: 'events.once options that are not an object literal' is part of the standard library types but has no scriptc lowering yet - 7 | async function main(): Promise { - 8 | await once(ee, "ready", { signal: AbortSignal.timeout(5) }); // options - | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - 9 | await once(ee, name); // computed event name + 9 | async function main(): Promise { + 10 | await once(ee, "ready", options); // options that are not an object literal + | ^~~~~~~ + 11 | await once(ee, name); // computed event name - hint: the lowered form is events.once(emitter, name); AbortSignal options are not supported yet + hint: pass the options as an object literal: { signal } -events-once-fences.ts:9:18 - error SC2020: 'events.once with a computed or meta event name' is part of the standard library types but has no scriptc lowering yet +events-once-fences.ts:11:18 - error SC2020: 'events.once with a computed or meta event name' is part of the standard library types but has no scriptc lowering yet - 8 | await once(ee, "ready", { signal: AbortSignal.timeout(5) }); // options - 9 | await once(ee, name); // computed event name + 10 | await once(ee, "ready", options); // options that are not an object literal + 11 | await once(ee, name); // computed event name | ^~~~ - 10 | } + 12 | await once(new PassThrough(), "data"); // a stream's 'data' event - hint: name the awaited event with a string literal \ No newline at end of file + hint: name the awaited event with a string literal + +events-once-fences.ts:12:33 - error SC2020: 'events.once of a stream's 'data' event' is part of the standard library types but has no scriptc lowering yet + + 11 | await once(ee, name); // computed event name + 12 | await once(new PassThrough(), "data"); // a stream's 'data' event + | ^~~~~~ + 13 | } + + hint: the chunk has no checked-dynamic boxing here; register stream.once('data', listener) instead \ No newline at end of file From d883c84752e0911d0e599273630b8acd04d02f45 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Tue, 20 Oct 2026 08:48:00 -0700 Subject: [PATCH 102/122] Add corpus programs for events.once with a signal, over streams, and in races 6490 covers events.once with an AbortSignal: abort, an aborted signal, an undefined signal, abort-listener order, 'removeListener' events, a throwing 'newListener' listener and the settlement turns. 6491 covers stream receivers (file streams, Readable.from, a destroyed PassThrough, a Writable) and a NodeJS.ReadableStream part raced against its destination. 6492 covers races over events.once results with entries that only reject and entries of different value types. 6493 covers stream subclasses whose completion callbacks are typed `Function`. 6494 covers events.once over checked-dynamic stream slots. The node-types fixture runs the race shapes under @types/node, where events.once returns Promise. Co-Authored-By: Claude Opus 5.5 --- .../corpus/6490-events-once-signal.ts.json | 6 + .../corpus/6491-events-once-streams.ts.json | 6 + .../corpus/6492-promise-race-combined.ts.json | 6 + .../6493-stream-function-callbacks.ts.json | 6 + .../main.ts.json | 7 + .../events-once-signal-race.ts.json | 6 + tests/corpus/6490-events-once-signal.ts | 120 ++++++++++++++++++ tests/corpus/6491-events-once-streams.ts | 87 +++++++++++++ tests/corpus/6492-promise-race-combined.ts | 72 +++++++++++ .../corpus/6493-stream-function-callbacks.ts | 66 ++++++++++ .../6494-events-once-dyn-stream-slot/main.ts | 59 +++++++++ .../sources.js | 21 +++ .../node-types/events-once-signal-race.ts | 86 +++++++++++++ tests/harness/project-config.test.ts | 18 +++ 14 files changed, 566 insertions(+) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6490-events-once-signal.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6491-events-once-streams.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6492-promise-race-combined.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6493-stream-function-callbacks.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6494-events-once-dyn-stream-slot/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/events-once-signal-race.ts.json create mode 100644 tests/corpus/6490-events-once-signal.ts create mode 100644 tests/corpus/6491-events-once-streams.ts create mode 100644 tests/corpus/6492-promise-race-combined.ts create mode 100644 tests/corpus/6493-stream-function-callbacks.ts create mode 100644 tests/corpus/6494-events-once-dyn-stream-slot/main.ts create mode 100644 tests/corpus/6494-events-once-dyn-stream-slot/sources.js create mode 100644 tests/fixtures/node-types/events-once-signal-race.ts diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6490-events-once-signal.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6490-events-once-signal.ts.json new file mode 100644 index 000000000..9c99e2ab0 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6490-events-once-signal.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6490-events-once-signal.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6491-events-once-streams.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6491-events-once-streams.ts.json new file mode 100644 index 000000000..4fdbe7a1d --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6491-events-once-streams.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6491-events-once-streams.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6492-promise-race-combined.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6492-promise-race-combined.ts.json new file mode 100644 index 000000000..8fa2ceb9a --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6492-promise-race-combined.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6492-promise-race-combined.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6493-stream-function-callbacks.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6493-stream-function-callbacks.ts.json new file mode 100644 index 000000000..bb755b9a7 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6493-stream-function-callbacks.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6493-stream-function-callbacks.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6494-events-once-dyn-stream-slot/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6494-events-once-dyn-stream-slot/main.ts.json new file mode 100644 index 000000000..77f43443e --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6494-events-once-dyn-stream-slot/main.ts.json @@ -0,0 +1,7 @@ +{ + "order": [ + "/tests/corpus/6494-events-once-dyn-stream-slot/sources.js", + "/tests/corpus/6494-events-once-dyn-stream-slot/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/events-once-signal-race.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/events-once-signal-race.ts.json new file mode 100644 index 000000000..e5c444b78 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/events-once-signal-race.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/node-types/events-once-signal-race.ts" + ], + "diags": [] +} diff --git a/tests/corpus/6490-events-once-signal.ts b/tests/corpus/6490-events-once-signal.ts new file mode 100644 index 000000000..8535e1fec --- /dev/null +++ b/tests/corpus/6490-events-once-signal.ts @@ -0,0 +1,120 @@ +// events.once(emitter, name, { signal }) follows Node's lib/events.js: an +// abort removes the event's and the 'error' listener and rejects with an +// AbortError whose cause is signal.reason, an aborted signal rejects +// without registering anything, and a settled once removes its abort +// listener. The result settles on Node's microtask turn: once is an async +// function returning the executor's promise. +import { EventEmitter, once } from "node:events"; + +async function main(): Promise { + const ee = new EventEmitter(); + const ac = new AbortController(); + setTimeout(() => ee.emit("ready", 1, "two"), 1); + const args = await once(ee, "ready", { signal: ac.signal }); + console.log("fulfilled", JSON.stringify(args), ee.listenerCount("ready"), ee.listenerCount("error")); + + const ac2 = new AbortController(); + const pending = once(ee, "ready", { signal: ac2.signal }); + console.log("before abort", ee.listenerCount("ready"), ee.listenerCount("error")); + ac2.abort(); + console.log("after abort", ee.listenerCount("ready"), ee.listenerCount("error")); + try { + await pending; + } catch (err) { + const e = err as NodeJS.ErrnoException; + console.log("rejected", e.name, e.message, e.code, (e.cause as Error).name); + } + + const ac3 = new AbortController(); + ac3.abort(new Error("already")); + try { + await once(ee, "ready", { signal: ac3.signal }); + } catch (err) { + const e = err as Error; + console.log("pre-aborted", e.name, (e.cause as Error).message, ee.listenerCount("ready")); + } + + const ac4 = new AbortController(); + setTimeout(() => ee.emit("error", new Error("boom")), 1); + try { + await once(ee, "ready", { signal: ac4.signal }); + } catch (err) { + console.log("error event", (err as Error).message, ee.listenerCount("ready")); + } + ac4.abort(); + console.log("late abort changes nothing"); + + const errs = once(ee, "error", { signal: ac.signal }); + ee.emit("error", new Error("as value")); + const [first] = await errs; + console.log("error as value", (first as Error).message); + + // An undefined signal is the plain form. + const maybe: AbortSignal | undefined = undefined; + setTimeout(() => ee.emit("ready", 3, "four"), 1); + console.log("undefined signal", JSON.stringify(await once(ee, "ready", { signal: maybe }))); + + // Listeners registered before and after the once's abort listener. + const ac5 = new AbortController(); + const ee5 = new EventEmitter(); + ac5.signal.addEventListener("abort", () => { + console.log("first abort listener", ee5.listenerCount("z")); + }); + const p5 = once(ee5, "z", { signal: ac5.signal }); + ac5.signal.addEventListener("abort", () => { + console.log("second abort listener", ee5.listenerCount("z")); + }); + ac5.abort("why"); + await p5.catch((err: Error) => console.log("p5", err.name, String(err.cause))); + + // The abort's removals emit 'removeListener'. + const ee6 = new EventEmitter(); + ee6.on("removeListener", (name: string) => console.log("removed", name)); + const ac6 = new AbortController(); + const p6 = once(ee6, "w", { signal: ac6.signal }); + ac6.abort(); + await p6.catch(() => console.log("p6 rejected")); + + // A 'newListener' listener that throws rejects the promise. + const ee7 = new EventEmitter(); + ee7.on("newListener", (name: string) => { + if (name === "go") throw new Error("no " + name); + }); + await once(ee7, "go", { signal: new AbortController().signal }).then( + () => console.log("fulfilled"), + (err: Error) => console.log("newListener rejected", err.message, ee7.listenerCount("go")), + ); + + // Settlement turns against plain promise chains. + const ee8 = new EventEmitter(); + const ac8 = new AbortController(); + void once(ee8, "x").then(() => console.log("plain")); + void once(ee8, "x", { signal: ac8.signal }).then(() => console.log("signal")); + void Promise.resolve() + .then(() => console.log("t1")) + .then(() => console.log("t2")) + .then(() => console.log("t3")) + .then(() => console.log("t4")); + ee8.emit("x"); + const ac9 = new AbortController(); + void once(ee8, "y", { signal: ac9.signal }).then( + () => console.log("fulfilled"), + () => console.log("aborted"), + ); + ac9.abort(); + const ac10 = new AbortController(); + ac10.abort(); + void once(ee8, "y", { signal: ac10.signal }).then( + () => console.log("fulfilled"), + () => console.log("pre-aborted turn"), + ); + await new Promise((resolve) => setTimeout(resolve, 5)); + + // Pending onces whose signal never aborts keep their listeners. + const idle = new AbortController(); + void once(ee, "never", { signal: idle.signal }); + void once(new EventEmitter(), "never", { signal: idle.signal }); + console.log("pending", ee.listenerCount("never"), ee.listenerCount("error")); +} + +void main(); diff --git a/tests/corpus/6491-events-once-streams.ts b/tests/corpus/6491-events-once-streams.ts new file mode 100644 index 000000000..efe60c737 --- /dev/null +++ b/tests/corpus/6491-events-once-streams.ts @@ -0,0 +1,87 @@ +// events.once over streams: file streams, Readable.from, a PassThrough +// destroyed with an error, a Writable, and a NodeJS.ReadableStream slot +// raced against its destination's 'close' with an AbortSignal that the +// finally block aborts. +import { once } from "node:events"; +import * as fs from "node:fs"; +import * as os from "node:os"; +import * as path from "node:path"; +import { PassThrough, Readable, Writable } from "node:stream"; + +function copyPart(src: NodeJS.ReadableStream, out: PassThrough): Promise { + const ac = new AbortController(); + src.pipe(out, { end: false }); + return Promise.race([ + once(src, "end", { signal: ac.signal }), + once(src, "error", { signal: ac.signal }).then(([error]) => { + throw error; + }), + once(out, "close", { signal: ac.signal }).then(() => { + throw new Error("output closed before the part ended"); + }), + ]).finally(() => { + ac.abort(); + src.unpipe(out); + console.log("part done", out.listenerCount("close"), out.listenerCount("error")); + }); +} + +async function main(): Promise { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "scriptc-once-streams-")); + const file = path.join(dir, "a.txt"); + fs.writeFileSync(file, "part one\n"); + + const rs = fs.createReadStream(file); + rs.resume(); + console.log("file end", JSON.stringify(await once(rs, "end"))); + const ws = fs.createWriteStream(path.join(dir, "b.txt")); + ws.end("x"); + await once(ws, "finish"); + console.log("write stream finish"); + await once(ws, "close"); + console.log("write stream close"); + + const pt = new PassThrough(); + setTimeout(() => pt.destroy(new Error("pt broke")), 1); + try { + await once(pt, "end"); + } catch (err) { + console.log("passthrough error", (err as Error).message, pt.listenerCount("end")); + } + const r = Readable.from(["a", "b"]); + r.resume(); + await once(r, "close"); + console.log("readable close"); + const w = new Writable({ + write(_chunk, _encoding, callback) { + callback(); + }, + }); + w.end("z"); + await once(w, "finish"); + console.log("writable finish"); + + const out = new PassThrough(); + let text = ""; + out.on("data", (chunk: Buffer) => { + text += chunk.toString(); + }); + await copyPart(fs.createReadStream(file), out); + await copyPart(fs.createReadStream(file), out); + console.log(JSON.stringify(text)); + try { + await copyPart(fs.createReadStream(path.join(dir, "missing.txt")), out); + } catch (err) { + console.log("missing", (err as NodeJS.ErrnoException).code); + } + const closing = new PassThrough(); + setTimeout(() => closing.destroy(), 1); + try { + await copyPart(new Readable({ read() {} }), closing); + } catch (err) { + console.log("closed", (err as Error).message); + } + fs.rmSync(dir, { recursive: true }); +} + +void main(); diff --git a/tests/corpus/6492-promise-race-combined.ts b/tests/corpus/6492-promise-race-combined.ts new file mode 100644 index 000000000..70f5399af --- /dev/null +++ b/tests/corpus/6492-promise-race-combined.ts @@ -0,0 +1,72 @@ +// Promise.race over events.once results: entries that only reject (a +// handler that throws) fit any result, and entries of different value +// types combine into 'unknown'. +import { EventEmitter, once } from "node:events"; + +function waitFor(a: EventEmitter, b: EventEmitter) { + let failed = false; + return Promise.race([ + once(a, "done"), + once(a, "error").then(([error]) => { + failed = true; + throw error; + }), + once(b, "close").then(() => { + failed = true; + throw new Error("closed early"); + }), + ]).finally(() => { + console.log("settled", failed); + }); +} + +function waitVoid(a: EventEmitter): Promise { + return Promise.race([ + once(a, "done").then((): void => undefined), + once(a, "error").then(([error]) => { + throw error; + }), + ]); +} + +function waitMixed(a: EventEmitter): Promise { + return Promise.race([once(a, "done").then((): void => undefined), once(a, "other")]); +} + +async function main(): Promise { + const a = new EventEmitter(); + const b = new EventEmitter(); + setTimeout(() => a.emit("done", 7), 1); + console.log("value", JSON.stringify(await waitFor(a, b))); + setTimeout(() => b.emit("close"), 1); + try { + await waitFor(a, b); + } catch (err) { + console.log("caught", (err as Error).message); + } + setTimeout(() => a.emit("error", new Error("bad")), 1); + try { + await waitFor(a, b); + } catch (err) { + console.log("caught", (err as Error).message); + } + setTimeout(() => a.emit("done", 8), 1); + await waitVoid(a); + console.log("void done"); + setTimeout(() => a.emit("other", 1, "x"), 1); + console.log("mixed", JSON.stringify(await waitMixed(a))); + setTimeout(() => a.emit("done", 9), 1); + console.log("mixed undefined", await waitMixed(a)); + // Turn order: the race settles one turn after its first entry. + const c = new EventEmitter(); + void waitMixed(c).then(() => console.log("race settled")); + void Promise.resolve() + .then(() => console.log("u1")) + .then(() => console.log("u2")) + .then(() => console.log("u3")) + .then(() => console.log("u4")) + .then(() => console.log("u5")); + c.emit("other", 2, "y"); +} + +void main(); diff --git a/tests/corpus/6493-stream-function-callbacks.ts b/tests/corpus/6493-stream-function-callbacks.ts new file mode 100644 index 000000000..7d5ee45e1 --- /dev/null +++ b/tests/corpus/6493-stream-function-callbacks.ts @@ -0,0 +1,66 @@ +// Stream subclasses that declare their completion callbacks with the broad +// `Function` type: the runtime's callable arrives and calls through it +// complete the chunk, pass data, or fail the stream. +import { Transform, Writable } from "node:stream"; + +class Prefixer extends Transform { + _s: string; + constructor() { + super(); + this._s = ""; + } + _transform(c: Buffer, _: string, cb: Function) { + const text = c.toString(); + const lines = text.split("\n").map((line, index, all) => { + if (index === 0) return this._s === "" || this._s.endsWith("\n") ? "> " + line : line; + if (index === all.length - 1) return line !== "" ? "> " + line : line; + return "> " + line; + }); + this._s += text; + this.push(lines.join("\n")); + cb(); + } +} + +class Upper extends Transform { + _transform(chunk: Buffer, _encoding: string, cb: Function) { + if (chunk.toString() === "bad") { + cb(new Error("bad chunk")); + return; + } + cb(null, chunk.toString().toUpperCase()); + } + _flush(cb: Function) { + this.push("!"); + cb(); + } +} + +class Sink extends Writable { + seen: string[] = []; + _write(chunk: Buffer, _encoding: string, cb: Function) { + this.seen.push(chunk.toString()); + cb(); + } + _final(cb: Function) { + this.seen.push(""); + cb(); + } +} + +const prefixer = new Prefixer(); +prefixer.pipe(process.stdout); +prefixer.write("a\nb"); +prefixer.write("c\n"); +prefixer.end("d\n"); + +const sink = new Sink(); +const upper = new Upper(); +upper.pipe(sink); +sink.on("finish", () => console.log("sink", JSON.stringify(sink.seen))); +upper.write("a"); +upper.end("b"); + +const bad = new Upper(); +bad.on("error", (err: Error) => console.log("error", err.message)); +bad.write("bad"); diff --git a/tests/corpus/6494-events-once-dyn-stream-slot/main.ts b/tests/corpus/6494-events-once-dyn-stream-slot/main.ts new file mode 100644 index 000000000..501944ae3 --- /dev/null +++ b/tests/corpus/6494-events-once-dyn-stream-slot/main.ts @@ -0,0 +1,59 @@ +// events.once over a checked-dynamic NodeJS.ReadableStream slot: the +// program also moves a JavaScript stream object into such a slot, so the +// slots hold native streams through their typed capsules. +import { once } from "node:events"; +import * as fs from "node:fs"; +import * as os from "node:os"; +import * as path from "node:path"; +import { PassThrough } from "node:stream"; +import { part } from "./sources.js"; + +function collect(src: NodeJS.ReadableStream): Promise { + return new Promise((resolve) => { + let text = ""; + src.on("data", (chunk: Buffer | string) => { + text += chunk.toString(); + }); + src.on("end", () => resolve(text)); + }); +} + +function copyPart(src: NodeJS.ReadableStream, out: PassThrough) { + const ac = new AbortController(); + src.pipe(out, { end: false }); + return Promise.race([ + once(src, "end", { signal: ac.signal }), + once(src, "error", { signal: ac.signal }).then(([error]) => { + throw error; + }), + once(out, "close", { signal: ac.signal }).then(() => { + throw new Error("output closed before the part ended"); + }), + ]).finally(() => { + ac.abort(); + src.unpipe(out); + }); +} + +async function main(): Promise { + console.log("js part", await collect(part(["a", "b"]))); + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "scriptc-once-dyn-")); + const file = path.join(dir, "a.txt"); + fs.writeFileSync(file, "native part\n"); + const out = new PassThrough(); + let text = ""; + out.on("data", (chunk: Buffer) => { + text += chunk.toString(); + }); + await copyPart(fs.createReadStream(file), out); + await copyPart(fs.createReadStream(file), out); + console.log(JSON.stringify(text)); + try { + await copyPart(fs.createReadStream(path.join(dir, "missing.txt")), out); + } catch (err) { + console.log("missing", (err as NodeJS.ErrnoException).code); + } + fs.rmSync(dir, { recursive: true }); +} + +void main(); diff --git a/tests/corpus/6494-events-once-dyn-stream-slot/sources.js b/tests/corpus/6494-events-once-dyn-stream-slot/sources.js new file mode 100644 index 000000000..e76e88785 --- /dev/null +++ b/tests/corpus/6494-events-once-dyn-stream-slot/sources.js @@ -0,0 +1,21 @@ +// A constructor-function stream with its own listener table. +function Part(chunks) { + this.chunks = chunks; + this.listeners = {}; + setImmediate(() => { + for (const chunk of this.chunks) this.emit("data", chunk); + this.emit("end"); + }); +} +Part.prototype.on = function (event, listener) { + (this.listeners[event] = this.listeners[event] || []).push(listener); + return this; +}; +Part.prototype.emit = function (event, value) { + for (const listener of this.listeners[event] || []) listener(value); + return true; +}; + +export function part(chunks) { + return new Part(chunks); +} diff --git a/tests/fixtures/node-types/events-once-signal-race.ts b/tests/fixtures/node-types/events-once-signal-race.ts new file mode 100644 index 000000000..4bc1d2d36 --- /dev/null +++ b/tests/fixtures/node-types/events-once-signal-race.ts @@ -0,0 +1,86 @@ +// events.once under @types/node, which types the result Promise: +// a NodeJS.ReadableStream slot and a PassThrough raced with an AbortSignal +// that the finally block aborts (the entries that throw are +// Promise, so the race result is the any[] of the 'end' entry), an +// EventEmitter race, and a Transform whose completion callback is declared +// with the broad `Function` type. +import { EventEmitter, once } from "node:events"; +import * as fs from "node:fs"; +import * as os from "node:os"; +import * as path from "node:path"; +import { PassThrough, Transform } from "node:stream"; + +function copyPart(src: NodeJS.ReadableStream, out: PassThrough) { + const ac = new AbortController(); + let failed = false; + src.pipe(out, { end: false }); + return Promise.race([ + once(src, "end", { signal: ac.signal }), + once(src, "error", { signal: ac.signal }).then(([error]) => { + failed = true; + throw error; + }), + once(out, "error", { signal: ac.signal }).then(([error]) => { + failed = true; + throw error; + }), + once(out, "close", { signal: ac.signal }).then(() => { + failed = true; + throw new Error("output closed before the part ended"); + }), + ]).finally(() => { + ac.abort(); + src.unpipe(out); + console.log("part done", failed); + }); +} + +function waitFor(a: EventEmitter) { + const ac = new AbortController(); + return Promise.race([ + once(a, "done", { signal: ac.signal }), + once(a, "error", { signal: ac.signal }).then(([error]) => { + throw error; + }), + ]).finally(() => ac.abort()); +} + +class Prefixer extends Transform { + // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type + _transform(chunk: Buffer, _encoding: string, cb: Function) { + cb(null, "> " + chunk.toString()); + } +} + +async function main(): Promise { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "scriptc-once-race-")); + const file = path.join(dir, "a.txt"); + fs.writeFileSync(file, "part\n"); + const out = new PassThrough(); + let text = ""; + out.on("data", (chunk: Buffer) => { + text += chunk.toString(); + }); + const ended = await copyPart(fs.createReadStream(file), out); + console.log("ended", ended.length); + await copyPart(fs.createReadStream(file).pipe(new Prefixer()), out); + console.log(JSON.stringify(text)); + try { + await copyPart(fs.createReadStream(path.join(dir, "missing.txt")), out); + } catch (err) { + console.log("missing", (err as NodeJS.ErrnoException).code); + } + const a = new EventEmitter(); + setTimeout(() => a.emit("done", 1, "two"), 1); + const args = (await waitFor(a)) as unknown[]; + console.log("done", JSON.stringify(args), a.listenerCount("done"), a.listenerCount("error")); + setTimeout(() => a.emit("error", new Error("bad")), 1); + try { + await waitFor(a); + } catch (err) { + console.log("caught", (err as Error).message, a.listenerCount("done")); + } + fs.rmSync(dir, { recursive: true }); +} + +void main(); diff --git a/tests/harness/project-config.test.ts b/tests/harness/project-config.test.ts index 3635c88ed..927959440 100644 --- a/tests/harness/project-config.test.ts +++ b/tests/harness/project-config.test.ts @@ -179,6 +179,24 @@ test("node-types: events.once over typed EventEmitter listeners under @types/nod expect(comparableStderr(native.stderr)).toBe(node.stderr); }); +test("node-types: events.once with a signal over streams, raced, under @types/node", async () => { + const outDir = outDirFor("events-once-signal-race"); + const entry = join(nodeTypesDir, "events-once-signal-race.ts"); + const result = await compile(entry, { + outPath: join(outDir, "events-once-signal-race"), + outDir, + sanitize, + }); + expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true); + if (!result.ok) return; + const [native, node] = await Promise.all([ + execFileAsync(result.binaryPath), + execFileAsync(process.execPath, ["--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", entry]), + ]); + expect(native.stdout).toBe(node.stdout); + expect(comparableStderr(native.stderr)).toBe(node.stderr); +}); + test("node-types: socket.address and resetAndDestroy under @types/node", async () => { const outDir = outDirFor("net-reset-address"); const entry = join(nodeTypesDir, "net-reset-address.ts"); From 68dac76fdcd35fe3ad82d377b32d2c54c7562bae Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Tue, 20 Oct 2026 11:11:59 -0500 Subject: [PATCH 103/122] Treat setEncoding's return value as the stream it was called on A method call declared to return `this`, such as setEncoding, resolves to its receiver's access path, so a 'data' listener on the stream that `const out = child.stdout.setEncoding("utf8")` returns receives strings. A setEncoding call that a preceding statement always evaluates (its expression, a variable initializer, or the receiver of a method chain in one of those) counts as preceding the listener. Only `new` and the child_process factories (spawn, fork, exec, execFile) count as creating a separate stream; any other call result may be a stream another binding already holds. --- .../builtins/child-process-methods.ts | 98 +++++++++++++++++-- .../node-types/stream-construction.ts | 21 ++++ 2 files changed, 111 insertions(+), 8 deletions(-) diff --git a/packages/compiler/src/frontend/lowering/builtins/child-process-methods.ts b/packages/compiler/src/frontend/lowering/builtins/child-process-methods.ts index aae51a8be..e1a25d169 100644 --- a/packages/compiler/src/frontend/lowering/builtins/child-process-methods.ts +++ b/packages/compiler/src/frontend/lowering/builtins/child-process-methods.ts @@ -543,7 +543,9 @@ export function lowerChildWriterMethodCall( /** A receiver spelled as a plain access path (`child.stdout`, `out`): its * root symbol and member names, or null for other forms. A `const` alias * of an access path (`const out = child.stdout`) resolves to that path, - * so every spelling of one stream gets the same key. */ + * and so does a method call declared to return `this` + * (`child.stdout.setEncoding("utf8")`), so every spelling of one stream + * gets the same key. */ function accessPathOf( lowerer: Lowerer, expr: ts.Expression, @@ -564,9 +566,84 @@ function accessPathOf( const base = accessPathOf(lowerer, expr.expression, depth + 1); return base === null ? null : { root: base.root, path: `${base.path}.${expr.name.text}` }; } + if (returnsReceiver(lowerer, expr)) { + return accessPathOf( + lowerer, + (expr.expression as ts.PropertyAccessExpression).expression, + depth + 1, + ); + } return null; } +/** A method call (`recv.m(...)`, not optional) whose declaration returns + * `this`, so its value is the receiver itself. */ +function returnsReceiver(lowerer: Lowerer, expr: ts.Expression): expr is ts.CallExpression { + if ( + !ts.isCallExpression(expr) || + expr.questionDotToken || + !ts.isPropertyAccessExpression(expr.expression) || + expr.expression.questionDotToken + ) + return false; + const declaration = lowerer.checker.getResolvedSignature(expr)?.declaration; + return declaration?.type?.kind === ts.SyntaxKind.ThisType; +} + +/** A call of a child_process factory (spawn, fork, exec, execFile), each + * of which returns a new ChildProcess. */ +function createsChildProcess(lowerer: Lowerer, call: ts.CallExpression): boolean { + const declaration = lowerer.checker.getResolvedSignature(call)?.declaration; + if ( + declaration === undefined || + !ts.isFunctionDeclaration(declaration) || + declaration.name === undefined || + !["spawn", "fork", "exec", "execFile"].includes(declaration.name.text) + ) + return false; + for (let node: ts.Node | undefined = declaration.parent; node; node = node.parent) { + if (ts.isModuleDeclaration(node) && ts.isStringLiteral(node.name)) { + return node.name.text === "child_process" || node.name.text === "node:child_process"; + } + } + return false; +} + +/** The statement that always evaluates `call` when it runs: the call is + * the statement's expression, a variable's initializer, or the receiver of + * a method chain that is one of those. Null for calls under a condition, + * an operator, an argument list or any other construct. */ +function unconditionalStatementOf(call: ts.CallExpression): ts.Statement | null { + let node: ts.Node = call; + for (;;) { + const parent: ts.Node | undefined = node.parent; + if (parent === undefined) return null; + if ( + ts.isParenthesizedExpression(parent) || + ts.isNonNullExpression(parent) || + (ts.isPropertyAccessExpression(parent) && + parent.expression === node && + !parent.questionDotToken) || + (ts.isCallExpression(parent) && parent.expression === node && !parent.questionDotToken) + ) { + node = parent; + continue; + } + if (ts.isExpressionStatement(parent)) return parent; + if (ts.isVariableDeclaration(parent) && parent.initializer === node) { + const list = parent.parent; + const statement = list?.parent; + return list !== undefined && + ts.isVariableDeclarationList(list) && + statement !== undefined && + ts.isVariableStatement(statement) + ? statement + : null; + } + return null; + } +} + /** The initializer of a symbol declared once as a `const` variable, else null. */ function constInitializerOf(symbol: ts.Symbol): ts.Expression | null { const decls = symbol.declarations; @@ -583,18 +660,22 @@ function constInitializerOf(symbol: ts.Symbol): ts.Expression | null { } /** Whether two resolved access paths name different objects: the same root - * with different members, or two `const` roots each initialized by a call - * or `new` (such as two `spawn()` results). */ + * with different members, or two `const` roots each initialized by `new` + * or a child_process factory (such as two `spawn()` results). Any other + * call may return an object another binding already holds. */ function distinctPaths( + lowerer: Lowerer, a: { root: ts.Symbol; path: string }, b: { root: ts.Symbol; path: string }, ): boolean { if (a.root === b.root) return a.path !== b.path; const fresh = (root: ts.Symbol): boolean => { let init = constInitializerOf(root); - while (init !== null && (ts.isParenthesizedExpression(init) || ts.isAwaitExpression(init))) - init = init.expression; - return init !== null && (ts.isCallExpression(init) || ts.isNewExpression(init)); + while (init !== null && ts.isParenthesizedExpression(init)) init = init.expression; + if (init === null) return false; + return ( + ts.isNewExpression(init) || (ts.isCallExpression(init) && createsChildProcess(lowerer, init)) + ); }; return fresh(a.root) && fresh(b.root); } @@ -646,10 +727,11 @@ function childStreamEncodedAt( const target = node.expression.expression; const path = accessPathOf(lowerer, target); if (path !== null && path.root === key.root && path.path === key.path) { - if (ts.isExpressionStatement(node.parent) && preceding.has(node.parent)) before = true; + const statement = unconditionalStatementOf(node); + if (statement !== null && preceding.has(statement)) before = true; else elsewhere = true; } else if ( - (path === null || !distinctPaths(path, key)) && + (path === null || !distinctPaths(lowerer, path, key)) && lowerer.checker.isTypeAssignableTo(receiverType, lowerer.typeOf(target)) ) { // Another spelling that can hold this stream (a parameter, a `let` diff --git a/tests/fixtures/node-types/stream-construction.ts b/tests/fixtures/node-types/stream-construction.ts index 37a1b852e..a28643f7c 100644 --- a/tests/fixtures/node-types/stream-construction.ts +++ b/tests/fixtures/node-types/stream-construction.ts @@ -81,6 +81,26 @@ function run(script: string): Promise { }); } +// setEncoding returns its stream: a listener on the returned value, or on +// the stream after `const out = stream.setEncoding(...)`, receives strings. +function readEncoded(script: string): Promise { + return new Promise((resolve) => { + const child = spawn("/bin/sh", ["-c", script], { stdio: "pipe" }); + let outText = ""; + let errText = ""; + const out = child.stdout.setEncoding("utf8"); + out.on("data", (c: unknown) => { + outText += typeof c + ":" + String(c) + ";"; + }); + const err = child.stderr.setEncoding("utf8"); + child.stderr.on("data", (c: unknown) => { + errText += typeof c + ":" + String(c) + ";"; + }); + void err; + child.on("close", () => resolve(outText + " err " + errText)); + }); +} + async function main(): Promise { console.log("gunzip", await gunzipThrough(gzipSync(Buffer.from("payload")))); const file = path.join(os.tmpdir(), `scriptc-stream-construction-${process.pid}.txt`); @@ -88,6 +108,7 @@ async function main(): Promise { console.log("file", JSON.stringify(await readFileStream(file))); fs.unlinkSync(file); console.log("run", JSON.stringify(await run("echo hi; echo warn >&2"))); + console.log("encoded", JSON.stringify(await readEncoded("printf hi; printf warn >&2"))); for (const script of ["echo bad >&2; exit 3", "kill -TERM $$"]) { try { await run(script); From 1818faef10ebb0281e9eedbec51b307f049ecdb3 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Tue, 20 Oct 2026 11:20:28 -0500 Subject: [PATCH 104/122] Keep a process stream open when JavaScript pipes into it pipe() called from untyped JavaScript on a native stream went through scr_stream_pipe directly, so a helper that piped into the converted process.stdout or process.stderr ended it at the source's end, and later writes through the program's NodeJS.WritableStream slots failed. The stream view's pipe now calls scr_stream_pipe_general, which keeps end off for those Writables, as Node's pipe() does for process.stdout and process.stderr. Adds corpus 6469. --- .../main.ts.json | 7 ++++ packages/runtime/src/scr_stream.c | 9 +++-- .../6469-node-stdio-writable-js-pipe/main.ts | 36 +++++++++++++++++++ .../6469-node-stdio-writable-js-pipe/pipe.js | 4 +++ .../main.ts.json | 1 + 5 files changed, 54 insertions(+), 3 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6469-node-stdio-writable-js-pipe/main.ts.json create mode 100644 tests/corpus/6469-node-stdio-writable-js-pipe/main.ts create mode 100644 tests/corpus/6469-node-stdio-writable-js-pipe/pipe.js create mode 100644 tests/harness/corpus-costs/6469-node-stdio-writable-js-pipe/main.ts.json diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6469-node-stdio-writable-js-pipe/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6469-node-stdio-writable-js-pipe/main.ts.json new file mode 100644 index 000000000..60b1360b0 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6469-node-stdio-writable-js-pipe/main.ts.json @@ -0,0 +1,7 @@ +{ + "order": [ + "/tests/corpus/6469-node-stdio-writable-js-pipe/pipe.js", + "/tests/corpus/6469-node-stdio-writable-js-pipe/main.ts" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_stream.c b/packages/runtime/src/scr_stream.c index 7c7b2279c..56f9fd54b 100644 --- a/packages/runtime/src/scr_stream.c +++ b/packages/runtime/src/scr_stream.c @@ -6451,8 +6451,10 @@ static ScrDyn *scr_stream_view_call(ScrClosure *cb, ScrDyn *const *args, size_t end = option->kind == SCR_DYN_UNDEF || scr_dyn_truthy(option); scr_dyn_release(option); } + /* scr_stream_pipe_general: a JavaScript caller can pipe into the + * converted process.stdout / process.stderr, which a pipe never ends. */ ScrStream *returned = !strcmp(method, "pipe") - ? scr_stream_pipe(stream, destination, end) : scr_stream_unpipe(stream, destination); + ? scr_stream_pipe_general(stream, destination, end) : scr_stream_unpipe(stream, destination); scr_stream_release(returned); if (!strcmp(method, "pipe") && !scr_exc_pending()) result = scr_dyn_retain(first); } @@ -6696,8 +6698,9 @@ ScrStream *scr_stream_stdio_writable(double fd) { } /* readable.pipe() into a Writable slot under the general representation - * (the readable.pipeGeneral libCall): Node never ends process.stdout or - * process.stderr from a pipe, so their converted Writable keeps `end` off. */ + * (the readable.pipeGeneral libCall), and pipe() called from JavaScript on + * a native stream: Node never ends process.stdout or process.stderr from a + * pipe, so their converted Writable keeps `end` off. */ ScrStream *scr_stream_pipe_general(ScrStream *src, ScrStream *dst, bool end) { if (dst == scr_stream_stdio_writables[0] || dst == scr_stream_stdio_writables[1]) end = false; return scr_stream_pipe(src, dst, end); diff --git a/tests/corpus/6469-node-stdio-writable-js-pipe/main.ts b/tests/corpus/6469-node-stdio-writable-js-pipe/main.ts new file mode 100644 index 000000000..258755f83 --- /dev/null +++ b/tests/corpus/6469-node-stdio-writable-js-pipe/main.ts @@ -0,0 +1,36 @@ +// process.stdout and process.stderr reach an untyped JavaScript helper +// through NodeJS.WritableStream slots, and the helper pipes into them. As +// in Node, the pipe leaves the process stream open, so later writes +// through the same slots still work. +import { Readable, Writable } from "node:stream"; +import { pipeAll } from "./pipe.js"; + +class Sink extends Writable { + readonly parts: string[] = []; + _write(chunk: Buffer, _encoding: string, callback: (error?: Error | null) => void): void { + this.parts.push(chunk.toString("utf8")); + callback(); + } +} + +function into(destination: NodeJS.WritableStream, parts: string[]): Promise { + return new Promise((resolve) => { + pipeAll(Readable.from(parts), destination, () => resolve()); + }); +} + +function write(destination: NodeJS.WritableStream, text: string): boolean { + return destination.write(text); +} + +async function main(): Promise { + const sink = new Sink(); + await into(sink, ["to ", "a sink"]); + console.log("sink", JSON.stringify(sink.parts), sink.writableEnded); + await into(process.stdout, ["piped ", "by javascript\n"]); + console.log("write after pipe", write(process.stdout, "still open\n")); + await into(process.stderr, ["piped to stderr\n"]); + write(process.stderr, "stderr still open\n"); +} + +void main(); diff --git a/tests/corpus/6469-node-stdio-writable-js-pipe/pipe.js b/tests/corpus/6469-node-stdio-writable-js-pipe/pipe.js new file mode 100644 index 000000000..d48d592ad --- /dev/null +++ b/tests/corpus/6469-node-stdio-writable-js-pipe/pipe.js @@ -0,0 +1,4 @@ +export function pipeAll(source, destination, done) { + source.on("end", () => done()); + source.pipe(destination); +} diff --git a/tests/harness/corpus-costs/6469-node-stdio-writable-js-pipe/main.ts.json b/tests/harness/corpus-costs/6469-node-stdio-writable-js-pipe/main.ts.json new file mode 100644 index 000000000..3fafdfc4b --- /dev/null +++ b/tests/harness/corpus-costs/6469-node-stdio-writable-js-pipe/main.ts.json @@ -0,0 +1 @@ +{ "plain": 3000, "san": 3500 } From 447f56e5c37193842269a997dadeecd7bbd931da Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Tue, 20 Oct 2026 11:28:28 -0500 Subject: [PATCH 105/122] Share defaultMaxListeners with the typed EventEmitter, link instanceof alone, and clean up a pipe once - EventEmitter.defaultMaxListeners on the untyped constructor is Node's accessor over the default the typed EventEmitter reads: assignments validate as validateNumber(arg, 'defaultMaxListeners', 0) does, and getMaxListeners() of emitters without their own limit answers the new value. - dyn.builtinInstanceOf and dyn.functionInstanceOf link the constructor-function unit and the units it calls (dyn-invoke, inspect, symbol, emitter), so a program whose only use is `this instanceof F` links. - The legacy pipe's cleanup runs once: when the destination emits 'close' from end(), the cleanup still queued on the source's 'end' finds the listeners removed and emit('end') answers true. --- .../compiler/src/backend/native/executable.ts | 7 ++- packages/compiler/src/ir/runtime-features.ts | 21 ++++++-- ...7-constructor-function-instanceof.cjs.json | 6 +++ ...tructor-function-emitter-defaults.cjs.json | 6 +++ .../runtime-pack-matrix.mjs | 2 +- packages/runtime/src/scr_dyn_builtin_class.c | 49 +++++++++++++++-- .../5817-constructor-function-instanceof.cjs | 15 ++++++ ...-constructor-function-emitter-defaults.cjs | 53 +++++++++++++++++++ ...7-constructor-function-instanceof.cjs.json | 1 + ...tructor-function-emitter-defaults.cjs.json | 1 + 10 files changed, 150 insertions(+), 11 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5817-constructor-function-instanceof.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5818-constructor-function-emitter-defaults.cjs.json create mode 100644 tests/corpus/5817-constructor-function-instanceof.cjs create mode 100644 tests/corpus/5818-constructor-function-emitter-defaults.cjs create mode 100644 tests/harness/corpus-costs/5817-constructor-function-instanceof.cjs.json create mode 100644 tests/harness/corpus-costs/5818-constructor-function-emitter-defaults.cjs.json diff --git a/packages/compiler/src/backend/native/executable.ts b/packages/compiler/src/backend/native/executable.ts index bbb720cc7..740697323 100644 --- a/packages/compiler/src/backend/native/executable.ts +++ b/packages/compiler/src/backend/native/executable.ts @@ -236,7 +236,12 @@ const RUNTIME_UNITS: readonly RuntimeUnit[] = [ "scr_util_callable.c", ), unit( - (u) => u.opts.dynBuiltinClass && u.opts.inspect && u.opts.dynInvoke && u.opts.symbol, + (u) => + u.opts.dynBuiltinClass && + u.opts.inspect && + u.opts.dynInvoke && + u.opts.symbol && + (u.opts.emitter || u.opts.workers), "scr_dyn_builtin_class.c", ), unit((u) => u.opts.stream, "scr_stream.c", "scr_fs_stream.c"), diff --git a/packages/compiler/src/ir/runtime-features.ts b/packages/compiler/src/ir/runtime-features.ts index 162bf55d3..02a4dd52a 100644 --- a/packages/compiler/src/ir/runtime-features.ts +++ b/packages/compiler/src/ir/runtime-features.ts @@ -234,10 +234,21 @@ const CHILD_PROCESS_LIB_FNS: ReadonlySet = new Set([ /** libCall name prefixes that link the child-process unit. */ const CHILD_PROCESS_LIB_PREFIXES: readonly string[] = ["child.", "cp.", "spawnRes.", "writer."]; +/** The entry points scr_dyn_builtin_class.c defines. The unit calls + * listeners through the dyn-invoke unit, renders unhandled 'error' + * payloads through the inspect unit, creates EventEmitter's private + * symbols through the symbol unit and shares defaultMaxListeners with the + * emitter unit, so each of them links all five. */ +const BUILTIN_CLASS_LIB_FNS: readonly IrLibFn[] = [ + "dyn.builtinClass", + "dyn.builtinInstanceOf", + "dyn.functionInstanceOf", +]; + /** Entry points that link the symbol unit, besides the sym. and disposable. families. */ const SYMBOL_LIB_FNS: ReadonlySet = new Set([ // EventEmitter's kShapeMode and kCapture properties - "dyn.builtinClass", + ...BUILTIN_CLASS_LIB_FNS, "dyn.usingDispose", "dyn.usingMethod", "util.isDeepStrictEqual", @@ -511,11 +522,11 @@ const RUNTIME_FEATURE_RULES = { * unit, render unhandled 'error' payloads through the inspect unit and * create EventEmitter's private symbols through the symbol unit. */ dynBuiltinClass: rule({ - fns: ["dyn.builtinClass", "dyn.builtinInstanceOf", "dyn.functionInstanceOf"], + fns: [...BUILTIN_CLASS_LIB_FNS], }), dynInvoke: rule({ initial: (mod) => mod.workers === true, - fns: [...DYN_INVOKE_LIB_FNS, ...UTIL_CALLABLE_LIB_FNS, "dyn.builtinClass"], + fns: [...DYN_INVOKE_LIB_FNS, ...UTIL_CALLABLE_LIB_FNS, ...BUILTIN_CLASS_LIB_FNS], // Constant-key reads of Object.prototype member names answer the // inherited member through scr_dyn_invoke.c. expr: (node, _tables) => @@ -532,7 +543,7 @@ const RUNTIME_FEATURE_RULES = { emitter: rule({ initial: (mod) => mod.workers === true || (mod.classes ?? []).some((c) => c.name === RUNTIME_EMITTER_CLASS), - fns: [...STREAM_LIB_FNS], + fns: [...STREAM_LIB_FNS, ...BUILTIN_CLASS_LIB_FNS], prefixes: ["emitter.", ...STREAM_LIB_PREFIXES], }), /** Subclasses of the runtime EventTarget/Event bases tear down and box @@ -567,7 +578,7 @@ const RUNTIME_FEATURE_RULES = { "global.native", "util.styleText", "crypto.cipherNew", - "dyn.builtinClass", + ...BUILTIN_CLASS_LIB_FNS, ...UTIL_CALLABLE_LIB_FNS, ], prefixes: ["insp.", "console.", "test."], diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5817-constructor-function-instanceof.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5817-constructor-function-instanceof.cjs.json new file mode 100644 index 000000000..d9ad97beb --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5817-constructor-function-instanceof.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/5817-constructor-function-instanceof.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5818-constructor-function-emitter-defaults.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5818-constructor-function-emitter-defaults.cjs.json new file mode 100644 index 000000000..f22e1aaeb --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5818-constructor-function-emitter-defaults.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/5818-constructor-function-emitter-defaults.cjs" + ], + "diags": [] +} diff --git a/packages/runtime-pack-common/runtime-pack-matrix.mjs b/packages/runtime-pack-common/runtime-pack-matrix.mjs index a6b11f53e..8f9a0bc69 100644 --- a/packages/runtime-pack-common/runtime-pack-matrix.mjs +++ b/packages/runtime-pack-common/runtime-pack-matrix.mjs @@ -91,7 +91,7 @@ export const OPTIONAL_RUNTIME_SOURCES = [ // Untyped EventEmitter and stream constructor values call listeners // through the dyn-invoke unit, inspect unhandled 'error' payloads and // create EventEmitter's private symbols. - ["scr_dyn_builtin_class.c", all("dynBuiltinClass", "inspect", "dynInvoke", "symbol")], + ["scr_dyn_builtin_class.c", all("dynBuiltinClass", "inspect", "dynInvoke", "symbol", "emitter")], ["scr_stream.c", "stream"], // The zlib stream classes reference both the zlib engine and the stream unit. ["scr_zlib_stream.c", all("zlib", "stream")], diff --git a/packages/runtime/src/scr_dyn_builtin_class.c b/packages/runtime/src/scr_dyn_builtin_class.c index 76a7ab15e..6d894615b 100644 --- a/packages/runtime/src/scr_dyn_builtin_class.c +++ b/packages/runtime/src/scr_dyn_builtin_class.c @@ -263,9 +263,7 @@ static double bc_max_listeners(ScrDyn *self) { if (!max) return -1; double value; if (bc_undefined(max)) { - ScrDyn *fallback = bc_get(bc_class(BC_EMITTER), "defaultMaxListeners"); - value = fallback ? bc_number(fallback) : 10; - if (fallback) scr_dyn_release(fallback); + value = scr_emitter_get_default_max(); } else { value = bc_number(max); } @@ -1017,6 +1015,42 @@ static ScrDyn *bc_set_max(ScrClosure *closure, ScrDyn *const *args, size_t argc) return self; } +/* EventEmitter.defaultMaxListeners, Node's accessor over the one default + * the typed EventEmitter reads too (scr_events_emitter.c). The setter + * validates as Node's validateNumber(arg, 'defaultMaxListeners', 0). */ +static ScrDyn *bc_default_max_get(ScrClosure *closure, ScrDyn *const *args, size_t argc) { + (void)closure; + (void)args; + (void)argc; + return scr_dyn_new_num(scr_emitter_get_default_max()); +} + +static ScrDyn *bc_default_max_set(ScrClosure *closure, ScrDyn *const *args, size_t argc) { + (void)closure; + static const char name[] = "defaultMaxListeners"; + ScrStr *key = scr_str_new(name, sizeof name - 1); + scr_emitter_set_default_max_chk(argc ? args[0] : scr_dyn_undefined(), key); + scr_str_release(key); + if (scr_exc_pending()) return NULL; + return scr_dyn_retain(scr_dyn_undefined()); +} + +static void bc_define_default_max(ScrDyn *ctor) { + ScrDyn *descriptor = scr_dyn_new_obj(); + scr_dyn_obj_set(descriptor, "get", 3, + scr_dyn_new_func(scr_closure_new(NULL, 0), bc_default_max_get, 0, "native:events", "get")); + scr_dyn_obj_set(descriptor, "set", 3, + scr_dyn_new_func(scr_closure_new(NULL, 0), bc_default_max_set, 1, "native:events", "set")); + scr_dyn_obj_set(descriptor, "enumerable", 10, scr_dyn_new_bool(true)); + ScrStr *name = scr_str_new("defaultMaxListeners", 19); + ScrDyn *key = scr_dyn_new_str(name); + scr_str_release(name); + ScrDyn *defined = scr_dyn_define_property(ctor, key, descriptor); + if (defined) scr_dyn_release(defined); + scr_dyn_release(key); + scr_dyn_release(descriptor); +} + static ScrDyn *bc_get_max(ScrClosure *closure, ScrDyn *const *args, size_t argc) { (void)closure; (void)args; @@ -1070,6 +1104,13 @@ static bool bc_pipe_on(ScrDyn *emitter, const char *event, ScrClosure *state, in } static bool bc_pipe_cleanup(ScrClosure *state) { + /* A second run (dest emitting 'close' from end(), with cleanup still + * queued on the source's 'end') finds the listeners removed, as Node's + * repeated removeListener calls do. */ + ScrDyn *handlers = bc_cap(state, BC_PIPE_HANDLERS); + bool cleaned = handlers->kind == SCR_DYN_ARR && scr_dyn_arr_len(handlers) == 0; + scr_dyn_release(handlers); + if (cleaned) return true; ScrDyn *source = bc_cap(state, BC_PIPE_SOURCE); ScrDyn *dest = bc_cap(state, BC_PIPE_DEST); bool ok = bc_pipe_remove(source, "data", state, BC_ONDATA) && @@ -1367,7 +1408,7 @@ static ScrDyn *bc_new_class(int kind, ScrDyn *parent) { bc_method(prototype, "listenerCount", bc_listener_count, 2); bc_method(prototype, "eventNames", bc_event_names, 0); bc_set(ctor, "EventEmitter", scr_dyn_retain(ctor)); - bc_set(ctor, "defaultMaxListeners", scr_dyn_new_num(10)); + bc_define_default_max(ctor); ScrDyn *count = scr_dyn_new_func(scr_closure_new(NULL, 0), bc_static_listener_count, 2, "native:events", "listenerCount"); bc_set(ctor, "listenerCount", count); } else if (kind == BC_STREAM) { diff --git a/tests/corpus/5817-constructor-function-instanceof.cjs b/tests/corpus/5817-constructor-function-instanceof.cjs new file mode 100644 index 000000000..355f53a64 --- /dev/null +++ b/tests/corpus/5817-constructor-function-instanceof.cjs @@ -0,0 +1,15 @@ +// `this instanceof F` in a constructor function, in a program that uses no +// EventEmitter or stream constructor value: the call-without-new idiom +// returns a new instance, and instanceof follows the function's prototype. +"use strict"; +function Glob(pattern) { + if (!(this instanceof Glob)) return new Glob(pattern); + this.pattern = pattern; +} +Glob.prototype.describe = function () { + return `glob ${this.pattern}`; +}; +const called = Glob("*.ts"); +const constructed = new Glob("*.js"); +console.log(called.describe(), constructed.describe()); +console.log(called instanceof Glob, {} instanceof Glob, Object.create(Glob.prototype) instanceof Glob); diff --git a/tests/corpus/5818-constructor-function-emitter-defaults.cjs b/tests/corpus/5818-constructor-function-emitter-defaults.cjs new file mode 100644 index 000000000..d89383b3a --- /dev/null +++ b/tests/corpus/5818-constructor-function-emitter-defaults.cjs @@ -0,0 +1,53 @@ +// EventEmitter.defaultMaxListeners read and written through the untyped +// EventEmitter constructor (Node's accessor and its validation), and a +// legacy pipe whose destination emits 'close' synchronously from end(). +"use strict"; +const util = require("util"); +const EventEmitter = require("events"); +const Stream = require("stream"); + +function Bus() { + EventEmitter.call(this); +} +util.inherits(Bus, EventEmitter); + +console.log(EventEmitter.defaultMaxListeners); +console.log(new Bus().getMaxListeners()); +EventEmitter.defaultMaxListeners = 3; +console.log(EventEmitter.defaultMaxListeners, new Bus().getMaxListeners()); +for (const value of [-1, "4", Number.NaN]) { + try { + EventEmitter.defaultMaxListeners = value; + } catch (error) { + console.log(error.name, error.code, error.message); + } +} +console.log(EventEmitter.defaultMaxListeners); +EventEmitter.defaultMaxListeners = 10; + +function Source() { + Stream.call(this); + this.readable = true; +} +util.inherits(Source, Stream); + +function Sink() { + Stream.call(this); + this.writable = true; +} +util.inherits(Sink, Stream); +Sink.prototype.write = function (chunk) { + console.log("write", chunk); + return true; +}; +Sink.prototype.end = function () { + console.log("sink end"); + this.emit("close"); +}; + +const source = new Source(); +const sink = new Sink(); +source.pipe(sink); +source.emit("data", "a"); +console.log("emit end", source.emit("end")); +console.log("listeners", source.listenerCount("end"), source.listenerCount("data"), sink.listenerCount("close")); diff --git a/tests/harness/corpus-costs/5817-constructor-function-instanceof.cjs.json b/tests/harness/corpus-costs/5817-constructor-function-instanceof.cjs.json new file mode 100644 index 000000000..15eedbeaa --- /dev/null +++ b/tests/harness/corpus-costs/5817-constructor-function-instanceof.cjs.json @@ -0,0 +1 @@ +{ "plain": 2655, "san": 7944 } diff --git a/tests/harness/corpus-costs/5818-constructor-function-emitter-defaults.cjs.json b/tests/harness/corpus-costs/5818-constructor-function-emitter-defaults.cjs.json new file mode 100644 index 000000000..0f4eb2e02 --- /dev/null +++ b/tests/harness/corpus-costs/5818-constructor-function-emitter-defaults.cjs.json @@ -0,0 +1 @@ +{ "plain": 2746, "san": 51663 } From 5aa4ccc3f2e1e07c518c91747c5277d97e17bef2 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Tue, 20 Oct 2026 11:28:48 -0500 Subject: [PATCH 106/122] Update tests for the EvalError and builtin module changes --- .../compiler/test/native-callable-values.test.ts | 12 ++++++------ .../corpus/6126-punycode-dependency/main.cjs.json | 2 +- tests/harness/errors.test.ts | 8 ++++---- tests/harness/npm.test.ts | 2 +- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/compiler/test/native-callable-values.test.ts b/packages/compiler/test/native-callable-values.test.ts index e64a9319e..6e65b0c99 100644 --- a/packages/compiler/test/native-callable-values.test.ts +++ b/packages/compiler/test/native-callable-values.test.ts @@ -102,19 +102,19 @@ test.each(["llvm"] as const)( writeFileSync( join(pkg, "index.js"), ` -const inspect = (value) => eval(value); +const inspect = (value) => process.on("__proto__", value); function stopped(value) { - eval(value); + process.on("__proto__", value); return { done() { return "wrong"; } }; } -try { console.log(stopped("0").done()); } -catch (error) { console.log(String(error).includes("SC2009")); } +try { console.log(stopped(() => {}).done()); } +catch (error) { console.log(String(error).includes("SC2020")); } const first = { inspect }; const second = { inspect }; console.log(typeof first.inspect, first.inspect === second.inspect); for (const callback of [first.inspect, second.inspect]) { - try { callback({}); } - catch (error) { console.log(String(error).includes("SC2009")); } + try { callback(() => {}); } + catch (error) { console.log(String(error).includes("SC2020")); } } `, ); diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6126-punycode-dependency/main.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6126-punycode-dependency/main.cjs.json index 5de83361a..36f02246c 100644 --- a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6126-punycode-dependency/main.cjs.json +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6126-punycode-dependency/main.cjs.json @@ -12,7 +12,7 @@ "end": 376 }, "milestone": "M4", - "hint": "relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported" + "hint": "relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, dns/promises, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, stream/web, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, v8, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported" } ] } diff --git a/tests/harness/errors.test.ts b/tests/harness/errors.test.ts index 6d93675ef..c35022f51 100644 --- a/tests/harness/errors.test.ts +++ b/tests/harness/errors.test.ts @@ -1040,7 +1040,7 @@ describe("refused statements in speculatively lowered JavaScript functions", () `(function () { var root = this; console.log("before"); - console.log(root === eval("this")); + process.on("__proto__", () => root); console.log("after"); }).call(this); `, @@ -1051,7 +1051,7 @@ describe("refused statements in speculatively lowered JavaScript functions", () "speculative-apply", `(function () { console.log("before"); - console.log(typeof eval("1")); + process.on("__proto__", () => {}); }).apply(null, []); `, "before\n", @@ -1062,7 +1062,7 @@ describe("refused statements in speculatively lowered JavaScript functions", () `class Base {} class Derived extends (function () { console.log("before"); - console.log(typeof eval("1")); + process.on("__proto__", () => {}); return Base; })() {} console.log(typeof Derived); @@ -1076,7 +1076,7 @@ console.log(typeof Derived); expect(r.stdout).toBe(stdout); expect(r.stderr).toMatch( new RegExp( - `^Uncaught Error: values of type '\\(x: string\\) => any' cannot be compiled: .* \\[SC2009 at .*${name}\\.cjs:${line}\\]\\n$`, + `^Uncaught Error: 'process\\.on\\("__proto__", \\.\\.\\.\\)' is part of the standard library types but has no scriptc lowering yet \\[SC2020 at .*${name}\\.cjs:${line}\\]\\n$`, ), ); }); diff --git a/tests/harness/npm.test.ts b/tests/harness/npm.test.ts index 25baaded6..168fdb6bd 100644 --- a/tests/harness/npm.test.ts +++ b/tests/harness/npm.test.ts @@ -60,7 +60,7 @@ const KNOWN: Record = { }, "island-web-plumbing": { run: "SC2020" }, "lazy-traps": { build: "SC2013" }, - "misc-shims": { build: "SC2013" }, + "misc-shims": { run: "SC2020" }, "namespace": { build: "SC1013" }, "node24-async-events": { run: "SC2020" }, "node24-commonjs-metadata": { run: "SC2020" }, From 437dc1163f77466486a63d759c57d15b70077b8e Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Tue, 20 Oct 2026 11:39:23 -0500 Subject: [PATCH 107/122] Refuse recursive unions in comptime baking The bakeability check tracks the unions it is inside as well as the records, so a union reachable from itself through an array (`type Tree = number | Tree[]`) reports SC1090 instead of recursing without end. The message names both recursive records and recursive unions. --- .../src/frontend/lowering/lower-comptime.ts | 28 +++++++++++-------- tests/diagnostics/comptime.ts | 4 +++ tests/harness/__snapshots__/comptime.ts.txt | 11 ++++++-- 3 files changed, 29 insertions(+), 14 deletions(-) diff --git a/packages/compiler/src/frontend/lowering/lower-comptime.ts b/packages/compiler/src/frontend/lowering/lower-comptime.ts index bdd020127..f239eea82 100644 --- a/packages/compiler/src/frontend/lowering/lower-comptime.ts +++ b/packages/compiler/src/frontend/lowering/lower-comptime.ts @@ -76,7 +76,7 @@ export function lowerComptime(lowerer: Lowerer, expr: ts.CallExpression): IrExpr "SC1090", expr, `baking comptime results of type '${lowerer.fmt(target)}' into the binary ` + - `(number, string, boolean, and arrays, records and unions of those lower to literals; a record type reachable from its own fields does not)`, + `(number, string, boolean, and arrays, records and unions of those lower to literals; a record or union type reachable from itself does not)`, ); } // 5. The host executes the extracted callback under a finite budget. @@ -112,13 +112,14 @@ function comptimeFailureDetail(error: unknown): string { * expression: number/string/boolean, arrays and record shapes of those, * and unions of those with null or undefined (the mixed rows of a JSON * table: `(string | number)[][]`), nested. Closures, class instances, - * promises, dyn, void, and recursive record types (a record reachable - * from its own fields) cannot be baked. */ + * promises, dyn, void, and recursive types (a record or union reachable + * from itself, directly or through arrays) cannot be baked. */ export function comptimeBakeable(lowerer: Lowerer, t: IrType): boolean { return bakeableWithin(lowerer, t, new Set()); } -/** comptimeBakeable below the records in `open`, whose fields are being +/** comptimeBakeable below the records and unions in `open` (keyed + * `record:` and `union:`), whose members are being * checked: reaching one of them again means the type is recursive. */ function bakeableWithin(lowerer: Lowerer, t: IrType, open: Set): boolean { switch (t.kind) { @@ -130,20 +131,23 @@ function bakeableWithin(lowerer: Lowerer, t: IrType, open: Set): boolean return bakeableWithin(lowerer, t.elem, open); case "record": { const shape = lowerer.shapes.get(t.shapeId); - if (!shape || open.has(t.shapeId)) return false; - open.add(t.shapeId); + const key = `record:${t.shapeId}`; + if (!shape || open.has(key)) return false; + open.add(key); const bakeable = shape.fields.every((f) => bakeableWithin(lowerer, f.type, open)); - open.delete(t.shapeId); + open.delete(key); return bakeable; } case "union": { const arms = lowerer.unions.get(t.unionId)?.arms; - return ( - arms !== undefined && - arms.every( - (arm) => isUnitType(arm) || (arm.kind !== "union" && bakeableWithin(lowerer, arm, open)), - ) + const key = `union:${t.unionId}`; + if (arms === undefined || open.has(key)) return false; + open.add(key); + const bakeable = arms.every( + (arm) => isUnitType(arm) || (arm.kind !== "union" && bakeableWithin(lowerer, arm, open)), ); + open.delete(key); + return bakeable; } default: return false; diff --git a/tests/diagnostics/comptime.ts b/tests/diagnostics/comptime.ts index 9c8239464..645779ea7 100644 --- a/tests/diagnostics/comptime.ts +++ b/tests/diagnostics/comptime.ts @@ -27,3 +27,7 @@ interface Chain { next: Chain | null; } const chain: Chain = comptime(() => ({ value: 1, next: null }) as Chain); + +// A union reachable from itself through an array has no literal form either. +type Tree = number | Tree[]; +const tree: Tree = comptime((): Tree => [1, [2, [3]]]); diff --git a/tests/harness/__snapshots__/comptime.ts.txt b/tests/harness/__snapshots__/comptime.ts.txt index 603c297a8..757c30e6c 100644 --- a/tests/harness/__snapshots__/comptime.ts.txt +++ b/tests/harness/__snapshots__/comptime.ts.txt @@ -47,9 +47,16 @@ comptime.ts:22:23 - error SC1110: comptime evaluation failed: expected 'string' | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~ 23 | -comptime.ts:29:22 - error SC1090: baking comptime results of type '{ next: null | ...; value: number }' into the binary (number, string, boolean, and arrays, records and unions of those lower to literals; a record type reachable from its own fields does not) is not supported yet +comptime.ts:29:22 - error SC1090: baking comptime results of type '{ next: null | ...; value: number }' into the binary (number, string, boolean, and arrays, records and unions of those lower to literals; a record or union type reachable from itself does not) is not supported yet 28 | } 29 | const chain: Chain = comptime(() => ({ value: 1, next: null }) as Chain); | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - 30 | \ No newline at end of file + 30 | + +comptime.ts:33:20 - error SC1090: baking comptime results of type '(...)[] | number' into the binary (number, string, boolean, and arrays, records and unions of those lower to literals; a record or union type reachable from itself does not) is not supported yet + + 32 | type Tree = number | Tree[]; + 33 | const tree: Tree = comptime((): Tree => [1, [2, [3]]]); + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 34 | \ No newline at end of file From f7287e30ff72d988d0f49cb42a7fd3e6bbd8e4e7 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Tue, 20 Oct 2026 11:40:52 -0500 Subject: [PATCH 108/122] Keep the fromEntries assertion bridge to entries without native arrays or records Object.fromEntries(...) as T passed the entries through the checked-dynamic bridge, which copies native arrays and records: an array shared by two entries, or held elsewhere, became separate copies, and writes through the asserted object no longer reached it. The bridge now applies only when the entries' values are scalars or checked-dynamic values, which pass by value or by identity; other entries keep the exact-shape fence (SC2002). Corpus 6467 checks that entries held as unknown keep their identity; diagnostics fixture fromentries-aliased-entries.ts pins the fence. --- .../frontend/lowering/expressions/narrowing.ts | 12 +++++++++--- .../fromentries-aliased-entries.ts.json | 6 ++++++ .../corpus/6467-fromentries-record-assertion.ts | 16 +++++++++++++++- tests/diagnostics/fromentries-aliased-entries.ts | 13 +++++++++++++ .../fromentries-aliased-entries.ts.txt | 8 ++++++++ 5 files changed, 51 insertions(+), 4 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/fromentries-aliased-entries.ts.json create mode 100644 tests/diagnostics/fromentries-aliased-entries.ts create mode 100644 tests/harness/__snapshots__/fromentries-aliased-entries.ts.txt diff --git a/packages/compiler/src/frontend/lowering/expressions/narrowing.ts b/packages/compiler/src/frontend/lowering/expressions/narrowing.ts index 0397497a8..ef6a30c03 100644 --- a/packages/compiler/src/frontend/lowering/expressions/narrowing.ts +++ b/packages/compiler/src/frontend/lowering/expressions/narrowing.ts @@ -1194,9 +1194,11 @@ function classInstanceOrAbsent(lowerer: Lowerer, type: IrType): boolean { * fresh object nothing else references, so the checked-dynamic bridge, * which keeps keys and their order the same way `JSON.parse(text) as T` * does, cannot be told apart from the original object; a value outside - * T throws the catchable TypeError of every checked cast. Other sources - * may be aliased, where a rebuilt object would sever mutation, so they - * keep the exact-shape fence. */ + * T throws the catchable TypeError of every checked cast. The entries' + * values must be scalars or checked-dynamic values, which the bridge + * passes by value or by identity; native arrays and records would be + * copied. Other sources may be aliased, where a rebuilt object would sever + * mutation, so they keep the exact-shape fence. */ function freshEntriesRecordAssertion( lowerer: Lowerer, expr: ts.AsExpression | ts.TypeAssertion, @@ -1208,6 +1210,10 @@ function freshEntriesRecordAssertion( const dest = lowerer.shapes.get(target.shapeId); if (!source?.indexValue || source.tuple || source.fields.length > 0) return null; if (!dest || dest.tuple || dest.indexValue || shapeHasAccessorSlots(dest)) return null; + // Native arrays and records in the entries would be copied on the way + // through the checked-dynamic bridge, severing their identity: only + // slots of scalars or checked-dynamic values pass. + if (extractionCopiesReference(lowerer, source.indexValue)) return null; let operand: ts.Expression = expr.expression; while (ts.isParenthesizedExpression(operand)) operand = operand.expression; if ( diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/fromentries-aliased-entries.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/fromentries-aliased-entries.ts.json new file mode 100644 index 000000000..7388f2b71 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/fromentries-aliased-entries.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/diagnostics/fromentries-aliased-entries.ts" + ], + "diags": [] +} diff --git a/tests/corpus/6467-fromentries-record-assertion.ts b/tests/corpus/6467-fromentries-record-assertion.ts index 1496292b3..cf8f1c1ff 100644 --- a/tests/corpus/6467-fromentries-record-assertion.ts +++ b/tests/corpus/6467-fromentries-record-assertion.ts @@ -1,6 +1,6 @@ // `Object.fromEntries(...) as T` for a declared record T keeps exactly // the keys the entries created, in their order, as Node keeps the object -// itself. +// itself, as long as the entries hold scalars or checked-dynamic values. type Tags = { "error.type": string; "error.msg": string; "error.cause"?: string }; function scrub(input: string): string { @@ -27,3 +27,17 @@ const p = Object.fromEntries([ ["z", 3], ]) as Point; console.log(p, p.x + p.y, Object.keys(p), JSON.stringify(p)); + +// Entries held as unknown keep their identity: the object's members are +// the same array and record, and writes through either reach both. +type Holder = { list: number[]; point: { x: number } }; +const list = [1, 2]; +const point = { x: 1 }; +const entries: [string, unknown][] = [ + ["list", list], + ["point", point], +]; +const held = Object.fromEntries(entries) as Holder; +held.list.push(3); +held.point.x = 5; +console.log(list.length, point.x, held.list === list, held.point === point); diff --git a/tests/diagnostics/fromentries-aliased-entries.ts b/tests/diagnostics/fromentries-aliased-entries.ts new file mode 100644 index 000000000..ee5cc5338 --- /dev/null +++ b/tests/diagnostics/fromentries-aliased-entries.ts @@ -0,0 +1,13 @@ +// `Object.fromEntries(...) as T` keeps the object only when the entries' +// values are scalars or checked-dynamic values. Native arrays and records +// in the entries would be copied, severing their identity, so the +// assertion keeps the exact-shape fence (corpus 6467 covers the cases that +// lower). +type Scores = { a: number[]; b: number[] }; +const shared = [10]; +const scores = Object.fromEntries([ + ["a", shared], + ["b", shared], +]) as Scores; +scores.a.push(11); +console.log(shared.length, scores.b.length); diff --git a/tests/harness/__snapshots__/fromentries-aliased-entries.ts.txt b/tests/harness/__snapshots__/fromentries-aliased-entries.ts.txt new file mode 100644 index 000000000..7b0f02266 --- /dev/null +++ b/tests/harness/__snapshots__/fromentries-aliased-entries.ts.txt @@ -0,0 +1,8 @@ +fromentries-aliased-entries.ts:8:16 - error SC2002: record shapes must match exactly or width-coerce: expected '{ a: number[]; b: number[] }', got '{ [key: string]: number[] }' — 'a' is not a declared field of the source, and the source's index signature could hold it at runtime (a completed undefined would drop that value) + + 7 | const shared = [10]; + 8 | const scores = Object.fromEntries([ + | ^~~~~~~~~~~~~~~~~~~~ + 9 | ["a", shared], + + hint: width subtyping compiles as a copy (each expected field copied off the source; extra fields drop) — this pair is outside the copy's rules; build a literal with exactly the expected fields instead \ No newline at end of file From 08a3a7cf0624df62a3ed137a2f9969f2ec6e894c Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Tue, 20 Oct 2026 11:41:40 -0500 Subject: [PATCH 109/122] Read memarg flags through a function so the compiler compiles itself readMemargFlags took its reader as a `{ u32(): number }` record, and the validator (Reader) and translator (WasmCursor) pass class instances. The self-hosted compiler refuses a class instance in a record-typed parameter (SC2002), so the self-hosted LLVM emitter, the native frontend programs and the bootstrap seed builds failed. readMemargFlags now takes the reader's u32 function, and each caller passes `() => r.u32()`. --- packages/compiler/src/wasm/binary.ts | 4 ++-- packages/compiler/src/wasm/module.ts | 11 ++++++----- packages/compiler/src/wasm/translate.ts | 2 +- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/packages/compiler/src/wasm/binary.ts b/packages/compiler/src/wasm/binary.ts index ad5297739..eaedf25af 100644 --- a/packages/compiler/src/wasm/binary.ts +++ b/packages/compiler/src/wasm/binary.ts @@ -1245,7 +1245,7 @@ class FunctionValidator { /** A memarg. A memory other than 0 stops typing (multi-memory). */ memarg(naturalAlign: number): void { const r = this.r; - const { align, memory } = readMemargFlags(r); + const { align, memory } = readMemargFlags(() => r.u32()); if (align < 0) r.malformed("malformed memop flags"); if (memory !== 0) this.otherMemory(memory); if (this.m.memory64) r.u64(); @@ -1831,7 +1831,7 @@ class FunctionValidator { scanMemarg(): void { const r = this.r; - const { align, memory } = readMemargFlags(r); + const { align, memory } = readMemargFlags(() => r.u32()); if (align < 0) r.malformed("malformed memop flags"); if (memory !== 0) this.m.feature("multi-memory"); if (this.m.memory64) r.u64(); diff --git a/packages/compiler/src/wasm/module.ts b/packages/compiler/src/wasm/module.ts index d7ec38701..587a20d81 100644 --- a/packages/compiler/src/wasm/module.ts +++ b/packages/compiler/src/wasm/module.ts @@ -289,11 +289,12 @@ function decodeUtf8(bytes: Uint8Array, start: number, end: number): string { * 0; flags of 128 or more are malformed and answer align -1 without * reading further. The offset follows. Validation (binary.ts), translation * and skipImmediates all read memargs through this function, so they agree - * on where each instruction ends. */ -export function readMemargFlags(r: { u32(): number }): { align: number; memory: number } { - const flags = r.u32(); + * on where each instruction ends; `u32` reads the next u32 immediate of + * their own reader. */ +export function readMemargFlags(u32: () => number): { align: number; memory: number } { + const flags = u32(); if (flags >= 128) return { align: -1, memory: 0 }; - if (flags >= 64) return { align: flags - 64, memory: r.u32() }; + if (flags >= 64) return { align: flags - 64, memory: u32() }; return { align: flags, memory: 0 }; } @@ -386,7 +387,7 @@ export function skipImmediates(c: WasmCursor, op: number): void { } default: if (op >= 0x28 && op <= 0x3e) { - const flags = readMemargFlags(c); + const flags = readMemargFlags(() => c.u32()); if (flags.align < 0 || flags.memory !== 0) throw new Error(`wasm: memarg of memory ${flags.memory} is not supported`); c.u32(); diff --git a/packages/compiler/src/wasm/translate.ts b/packages/compiler/src/wasm/translate.ts index 813dab84a..f17c5dae3 100644 --- a/packages/compiler/src/wasm/translate.ts +++ b/packages/compiler/src/wasm/translate.ts @@ -851,7 +851,7 @@ class FunctionTranslator { /** The offset of a memarg over memory 0 (the only memory a translatable * module has). */ memargOffset(c: WasmCursor): number { - const flags = readMemargFlags(c); + const flags = readMemargFlags(() => c.u32()); if (flags.align < 0 || flags.memory !== 0) throw new WasmTranslateError(`memarg of memory ${flags.memory} is not supported`); return c.u32(); From 507610b90d1fe9761d9daf4d31ab079105f3dcd7 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Tue, 20 Oct 2026 09:46:05 -0700 Subject: [PATCH 110/122] Give constructor functions native stream state over the stream classes Bundled npm code (from2, bl, tar-stream, yazl, multistream) builds its streams as ES5 constructor functions: util.inherits(Ctor, Readable) and Readable.call(this, opts), with _read/_write/_transform on the prototype. Readable.call(this) threw SC2020. A new runtime unit, scr_dyn_builtin_stream.c (feature dynBuiltinStream), gives the untyped stream constructors their stream half. Base.call(this, opts) creates a runtime stream over the receiver and keeps it in a hidden slot; the runtime calls the receiver's underscore methods through its property lookup; the stream prototypes carry Node's methods and accessors over the hidden stream; and EventEmitter's methods register listeners with the runtime stream's emitter, so runtime events and pipes reach JavaScript listeners with the receiver as this. _readableState/_writableState are live views. Stream class values now come from dyn.builtinStreamClass, so only programs that use them link the stream half. Typed code sees these objects as their runtime streams: checked casts to the stream classes take them, readable.pipe() into an untyped destination pipes into them, and runtime streams crossing into JavaScript answer the constructor object. Co-Authored-By: Claude Opus 5.5 --- .../limitations/builtin-function-values.mdx | 6 +- .../src/backend/executable-features.ts | 1 + packages/compiler/src/backend/library-pack.ts | 1 + packages/compiler/src/backend/llvm/dyn.ts | 33 + packages/compiler/src/backend/llvm/emitter.ts | 12 +- .../src/backend/llvm/expr-stream-bridges.ts | 9 +- .../compiler/src/backend/llvm/lib-shared.ts | 5 + .../src/backend/native-link-info-core.ts | 1 + .../compiler/src/backend/native/contracts.ts | 4 + .../compiler/src/backend/native/executable.ts | 10 + .../src/backend/runtime-pack-targets.test.ts | 1 + .../compiler/src/backend/runtime-pack.test.ts | 1 + packages/compiler/src/backend/warm-cache.ts | 1 + .../src/executable/executable-cache.test.ts | 1 + .../src/executable/executable-cache.ts | 2 + .../lowering/builtins/builtin-class-dyn.ts | 12 +- .../src/frontend/lowering/lower-stream.ts | 49 + packages/compiler/src/index.ts | 1 + packages/compiler/src/ir/builtin-effects.ts | 2 + .../compiler/src/ir/builtin-signatures.ts | 8 + packages/compiler/src/ir/runtime-features.ts | 37 +- packages/compiler/src/native/cli.ts | 1 + packages/compiler/src/native/prepare.ts | 1 + .../compiler/src/native/sanitizer.test.ts | 1 + ...560-constructor-function-readable.cjs.json | 6 + ...561-constructor-function-writable.cjs.json | 6 + ...62-constructor-function-transform.cjs.json | 6 + ...onstructor-function-stream-errors.cjs.json | 6 + .../runtime-pack-matrix.mjs | 5 + packages/runtime/src/scr_dyn.h | 32 + packages/runtime/src/scr_dyn_builtin_class.c | 100 +- packages/runtime/src/scr_dyn_builtin_stream.c | 1672 +++++++++++++++++ packages/runtime/src/scr_stream.c | 2 + packages/runtime/src/scr_stream.h | 3 + .../6560-constructor-function-readable.cjs | 121 ++ .../6561-constructor-function-writable.cjs | 141 ++ .../6562-constructor-function-transform.cjs | 137 ++ ...563-constructor-function-stream-errors.cjs | 137 ++ .../self-hosting-native-toolchain.test.ts | 1 + .../self-hosting-runtime-features.test.ts | 1 + .../harness/self-hosting-runtime-pack.test.ts | 1 + 41 files changed, 2561 insertions(+), 16 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6560-constructor-function-readable.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6561-constructor-function-writable.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6562-constructor-function-transform.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6563-constructor-function-stream-errors.cjs.json create mode 100644 packages/runtime/src/scr_dyn_builtin_stream.c create mode 100644 tests/corpus/6560-constructor-function-readable.cjs create mode 100644 tests/corpus/6561-constructor-function-writable.cjs create mode 100644 tests/corpus/6562-constructor-function-transform.cjs create mode 100644 tests/corpus/6563-constructor-function-stream-errors.cjs diff --git a/docs/content/limitations/builtin-function-values.mdx b/docs/content/limitations/builtin-function-values.mdx index f3902e75b..420ac89aa 100644 --- a/docs/content/limitations/builtin-function-values.mdx +++ b/docs/content/limitations/builtin-function-values.mdx @@ -15,7 +15,11 @@ Native calls accept standard process streams; custom native streams and mutation In JavaScript, a constructor function can inherit from `EventEmitter`, the legacy `Stream` constructor, or a stream class through `util.inherits` or `Object.setPrototypeOf`. `EventEmitter.call(this)` and `Stream.call(this)` initialize the object as Node does, and its listener methods keep their state in the object's own `_events`, `_eventsCount`, and `_maxListeners` properties. The `captureRejections` option and `events.errorMonitor` listeners are not supported on such objects. -Calling `Readable`, `Writable`, `Duplex`, `Transform`, or `PassThrough` with a constructor function's receiver throws `SC2020` where the call runs. +`Readable.call(this, options)` and the same call on `Writable`, `Duplex`, `Transform`, or `PassThrough` give the object a native stream. +The stream calls the object's `_read`, `_write`, `_final`, `_transform`, `_flush`, and `_destroy` with the object as `this`, and the stream prototypes' methods and accessors work on it. +Its listeners live in the native stream rather than in `_events`, and `_readableState` and `_writableState` are read-only views that answer the commonly read fields. +Typed code that receives such an object as a stream class uses its native stream, and piping a native stream into it works. +The options the native streams refuse (`objectMode`, `decodeStrings: false`, `signal`, and others) are refused here too, as are `_writev`, `_construct`, symbol event names, `listeners()`, `rawListeners()`, `wrap()`, strings written in encodings other than UTF-8, and pipes into a destination that overrides `write()` or `end()`. Other class constructors passed to `util.inherits` or `util.deprecate` are refused. Deprecation warnings are emitted on the next tick and follow assignments to `process.noDeprecation` and `process.throwDeprecation`; compiled binaries take no Node command-line flags such as `--no-deprecation` or `--trace-deprecation`, and `process.traceDeprecation` is unsupported. A stored `util.deprecate` reports a `length` of 4, where Node reports 3. diff --git a/packages/compiler/src/backend/executable-features.ts b/packages/compiler/src/backend/executable-features.ts index a6e228b2e..9418631d0 100644 --- a/packages/compiler/src/backend/executable-features.ts +++ b/packages/compiler/src/backend/executable-features.ts @@ -32,6 +32,7 @@ export function executableLinkFeatures(mod: IrModule): NativeLinkFeatures { searchParams: features.searchParams, qs: features.qs, dynBuiltinClass: features.dynBuiltinClass, + dynBuiltinStream: features.dynBuiltinStream, parseArgs: features.parseArgs, stream: features.stream, net: features.net, diff --git a/packages/compiler/src/backend/library-pack.ts b/packages/compiler/src/backend/library-pack.ts index efabdc9d4..0a7618905 100644 --- a/packages/compiler/src/backend/library-pack.ts +++ b/packages/compiler/src/backend/library-pack.ts @@ -43,6 +43,7 @@ export async function compilePackedLibrary( searchParams: options.searchParams ?? false, qs: false, dynBuiltinClass: false, + dynBuiltinStream: false, parseArgs: false, stream: false, net: false, diff --git a/packages/compiler/src/backend/llvm/dyn.ts b/packages/compiler/src/backend/llvm/dyn.ts index 86b4f5be8..8b35c84c5 100644 --- a/packages/compiler/src/backend/llvm/dyn.ts +++ b/packages/compiler/src/backend/llvm/dyn.ts @@ -129,6 +129,8 @@ export interface DynHost extends WalkerHost { /** The shared adapter record: live array capsules read its `arrayOps`, so a * narrower return type would hand the native compiler a copy without it. */ liveDynRefAdapter(t: IrType): LlStreamTypedRefAdapter; + /** See moduleUsesDynBuiltinStream. */ + usesDynBuiltinStream?(): boolean; liveDynUnionRefAdapter(t: IrType & { kind: "union" }): string; dynPromiseAdapter(t: IrType): string; isErrorClass(className: string): boolean; @@ -139,6 +141,19 @@ export interface DynHost extends WalkerHost { const FN_ATTRS = "#0"; +/** scr_dyn_builtin_stream_check's kind for a runtime stream class (the + * numbering of scr_dyn_builtin_class.c), or null. */ +function builtinStreamKind(className: string): number | null { + const kinds: Record = { + "%Readable": 2, + "%Writable": 3, + "%Duplex": 4, + "%Transform": 5, + "%PassThrough": 6, + }; + return kinds[className] ?? null; +} + export class LlDyn { private readonly dynMatchers = new Map(); private readonly dynBuilders = new Map(); @@ -1478,6 +1493,24 @@ export class LlDyn { B.line(`${ref} = call ptr @scr_dyn_typed_ref_unbox(ptr %d)`); B.terminate(`ret ptr ${ref}`); B.startBlock(lNext); + // A constructor-function stream (scr_dyn_builtin_stream.c) carries + // its runtime stream. + const streamKind = t.kind === "object" ? builtinStreamKind(t.className) : null; + if (streamKind !== null && this.host.usesDynBuiltinStream?.() === true) { + host.declare(`declare ptr @scr_dyn_builtin_stream_check(ptr, double)`); + const own = B.tmp(), + hit = B.tmp(); + B.line( + `${own} = call ptr @scr_dyn_builtin_stream_check(ptr %d, double ${f64Lit(streamKind)})`, + ); + B.line(`${hit} = icmp ne ptr ${own}, null`); + const lOwn = B.newLabel("dc.ctor"), + lNotOwn = B.newLabel("dc.notctor"); + B.condBr(hit, lOwn, lNotOwn); + B.startBlock(lOwn); + B.terminate(`ret ptr ${own}`); + B.startBlock(lNotOwn); + } if (t.kind !== "union" && preserveRefs) { const kind = this.kindOf(B, "%d"); const capsule = B.tmp(); diff --git a/packages/compiler/src/backend/llvm/emitter.ts b/packages/compiler/src/backend/llvm/emitter.ts index 1d745b981..5b138544e 100644 --- a/packages/compiler/src/backend/llvm/emitter.ts +++ b/packages/compiler/src/backend/llvm/emitter.ts @@ -52,7 +52,11 @@ import { VOID, classJsName, } from "../../ir/ir.js"; -import { moduleRuntimeFeatures, moduleUsesMockTimers } from "../../ir/runtime-features.js"; +import { + moduleRuntimeFeatures, + moduleUsesDynBuiltinStream, + moduleUsesMockTimers, +} from "../../ir/runtime-features.js"; import { captureFreeFunctions, observesStacks } from "../stack-observations.js"; import { type DenseArrayLoop } from "./dense-array-loops.js"; import { findInitializedByteLoopBindings } from "./byte-windows.js"; @@ -890,6 +894,11 @@ export class LlEmitter { /** Identity-preserving static→dyn capsules for program classes and Web * APIs whose values remain directly observable. */ readonly liveDynRefAdapters = new Map(); + private dynBuiltinStreamUse: boolean | undefined; + /** See moduleUsesDynBuiltinStream. */ + get usesDynBuiltinStream(): boolean { + return (this.dynBuiltinStreamUse ??= moduleUsesDynBuiltinStream(this.mod)); + } /** Runtime-arm dispatchers for live union values. */ readonly liveDynUnionRefAdapters = new Map(); readonly dynPromiseAdapters = new Map(); @@ -1375,6 +1384,7 @@ export class LlEmitter { fieldAbsentTestIn: (B, value, unionId) => this.fieldAbsentTestIn(B, value, unionId), absentInstanceRef: (unionId) => this.absentInstanceRef(unionId), liveDynRefAdapter: (type) => this.liveDynRefAdapter(type), + usesDynBuiltinStream: () => this.usesDynBuiltinStream, liveDynUnionRefAdapter: (type) => this.liveDynUnionRefAdapter(type), dynPromiseAdapter: (type) => this.dynPromiseAdapter(type), isErrorClass: (name) => this.classMeta.get(name)?.root.def.name === "%Error", diff --git a/packages/compiler/src/backend/llvm/expr-stream-bridges.ts b/packages/compiler/src/backend/llvm/expr-stream-bridges.ts index 8a53b5fa8..a80974975 100644 --- a/packages/compiler/src/backend/llvm/expr-stream-bridges.ts +++ b/packages/compiler/src/backend/llvm/expr-stream-bridges.ts @@ -763,8 +763,13 @@ export function streamTypedRefMaterializeAdapter( !meta.def.instancePrototypeHelper && !meta.def.prototypeDataHelper ) { - host.declare(`declare ptr @scr_stream_dyn_view(ptr)`); - B.line(`${out} = call ptr @scr_stream_dyn_view(ptr %p)`); + // Programs with constructor-function streams view them through + // scr_dyn_builtin_stream.c, which answers their own objects. + const view = host.usesDynBuiltinStream + ? "scr_dyn_builtin_stream_view" + : "scr_stream_dyn_view"; + host.declare(`declare ptr @${view}(ptr)`); + B.line(`${out} = call ptr @${view}(ptr %p)`); } else B.line(`${out} = call ptr @scr_dyn_new_obj()`); if (meta.def.tracksOwnFields) { host.declare(`declare ptr @scr_dyn_copy_property_descriptors(ptr, ptr)`); diff --git a/packages/compiler/src/backend/llvm/lib-shared.ts b/packages/compiler/src/backend/llvm/lib-shared.ts index 2b25cc92e..09b3dd07a 100644 --- a/packages/compiler/src/backend/llvm/lib-shared.ts +++ b/packages/compiler/src/backend/llvm/lib-shared.ts @@ -1405,6 +1405,8 @@ export const LIB_FN_SYMS: Record = { "dyn.classBasePrototype": "scr_dyn_class_base_prototype", "dyn.classInherit": "scr_dyn_class_inherit", "dyn.builtinClass": "scr_dyn_builtin_class", + "dyn.builtinStreamClass": "scr_dyn_builtin_stream_class", + "dyn.builtinStreamPipe": "scr_dyn_builtin_stream_pipe", "dyn.builtinInstanceOf": "scr_dyn_builtin_instanceof", "dyn.functionInstanceOf": "scr_dyn_function_instanceof", "dyn.classSuper": "scr_dyn_class_super", @@ -1483,6 +1485,9 @@ export function mockTimerSym( export const USES_TIMERS_LIB_FNS = new Set([ "process.emitWarning", + // Constructor-function streams defer their events on the stream ticks. + "dyn.builtinStreamClass", + "dyn.builtinStreamPipe", // Decipheriv.setAuthTag reports DEP0182 on the next tick. "crypto.cipherNew", // WebCrypto results settle through the deferred crypto queue. diff --git a/packages/compiler/src/backend/native-link-info-core.ts b/packages/compiler/src/backend/native-link-info-core.ts index d7cf78327..fcb0822f9 100644 --- a/packages/compiler/src/backend/native-link-info-core.ts +++ b/packages/compiler/src/backend/native-link-info-core.ts @@ -47,6 +47,7 @@ export interface NativeLinkFeatures { searchParams: boolean; qs: boolean; dynBuiltinClass: boolean; + dynBuiltinStream: boolean; parseArgs: boolean; stream: boolean; net: boolean; diff --git a/packages/compiler/src/backend/native/contracts.ts b/packages/compiler/src/backend/native/contracts.ts index d978d4387..0a75ad9a6 100644 --- a/packages/compiler/src/backend/native/contracts.ts +++ b/packages/compiler/src/backend/native/contracts.ts @@ -155,6 +155,10 @@ export interface CcOptions { * scr_dyn_builtin_class.c, which also references the inspection, * dyn-invoke and symbol units. */ dynBuiltinClass?: boolean; + /** The program uses a stream class as an untyped constructor value: + * compiles scr_dyn_builtin_stream.c, the constructors' stream half, which + * also references the stream unit. */ + dynBuiltinStream?: boolean; /** The program uses native util parsing, comparison, or styling. Optional * utility units also require their symbol, inspection, and warning features. */ parseArgs?: boolean; diff --git a/packages/compiler/src/backend/native/executable.ts b/packages/compiler/src/backend/native/executable.ts index bbb720cc7..f904a63b1 100644 --- a/packages/compiler/src/backend/native/executable.ts +++ b/packages/compiler/src/backend/native/executable.ts @@ -239,6 +239,16 @@ const RUNTIME_UNITS: readonly RuntimeUnit[] = [ (u) => u.opts.dynBuiltinClass && u.opts.inspect && u.opts.dynInvoke && u.opts.symbol, "scr_dyn_builtin_class.c", ), + unit( + (u) => + u.opts.dynBuiltinStream && + u.opts.dynBuiltinClass && + u.opts.stream && + u.opts.inspect && + u.opts.dynInvoke && + u.opts.symbol, + "scr_dyn_builtin_stream.c", + ), unit((u) => u.opts.stream, "scr_stream.c", "scr_fs_stream.c"), // The zlib stream classes reference both the zlib engine and the // stream unit. diff --git a/packages/compiler/src/backend/runtime-pack-targets.test.ts b/packages/compiler/src/backend/runtime-pack-targets.test.ts index 8b0689c51..2253b3e94 100644 --- a/packages/compiler/src/backend/runtime-pack-targets.test.ts +++ b/packages/compiler/src/backend/runtime-pack-targets.test.ts @@ -108,6 +108,7 @@ const features = { searchParams: false, qs: false, dynBuiltinClass: false, + dynBuiltinStream: false, parseArgs: false, stream: false, net: false, diff --git a/packages/compiler/src/backend/runtime-pack.test.ts b/packages/compiler/src/backend/runtime-pack.test.ts index 5af267d96..bda9984ec 100644 --- a/packages/compiler/src/backend/runtime-pack.test.ts +++ b/packages/compiler/src/backend/runtime-pack.test.ts @@ -45,6 +45,7 @@ const BASE: NativeLinkFeatures = { searchParams: false, qs: false, dynBuiltinClass: false, + dynBuiltinStream: false, parseArgs: false, stream: false, net: false, diff --git a/packages/compiler/src/backend/warm-cache.ts b/packages/compiler/src/backend/warm-cache.ts index b0736f102..32bfb264e 100644 --- a/packages/compiler/src/backend/warm-cache.ts +++ b/packages/compiler/src/backend/warm-cache.ts @@ -51,6 +51,7 @@ export async function warmNativeCaches( searchParams: false, qs: false, dynBuiltinClass: false, + dynBuiltinStream: false, parseArgs: false, stream: false, net: false, diff --git a/packages/compiler/src/executable/executable-cache.test.ts b/packages/compiler/src/executable/executable-cache.test.ts index cdd24ff7b..b6adee539 100644 --- a/packages/compiler/src/executable/executable-cache.test.ts +++ b/packages/compiler/src/executable/executable-cache.test.ts @@ -46,6 +46,7 @@ const native: EarlyExecutableNativeFeatures = { searchParams: false, qs: false, dynBuiltinClass: false, + dynBuiltinStream: false, parseArgs: false, stream: false, net: false, diff --git a/packages/compiler/src/executable/executable-cache.ts b/packages/compiler/src/executable/executable-cache.ts index 349c07d42..ff395246c 100644 --- a/packages/compiler/src/executable/executable-cache.ts +++ b/packages/compiler/src/executable/executable-cache.ts @@ -72,6 +72,7 @@ export interface EarlyExecutableNativeFeatures { searchParams: boolean; qs: boolean; dynBuiltinClass: boolean; + dynBuiltinStream: boolean; parseArgs: boolean; stream: boolean; net: boolean; @@ -187,6 +188,7 @@ const BOOLEAN_NATIVE_KEYS = [ "searchParams", "qs", "dynBuiltinClass", + "dynBuiltinStream", "parseArgs", "stream", "net", diff --git a/packages/compiler/src/frontend/lowering/builtins/builtin-class-dyn.ts b/packages/compiler/src/frontend/lowering/builtins/builtin-class-dyn.ts index ac73924ad..9508aa651 100644 --- a/packages/compiler/src/frontend/lowering/builtins/builtin-class-dyn.ts +++ b/packages/compiler/src/frontend/lowering/builtins/builtin-class-dyn.ts @@ -111,8 +111,18 @@ export function builtinClassDynValueOf(lowerer: Lowerer, node: ts.Expression): I }; } +/** EventEmitter is dyn.builtinClass; the stream classes, which carry + * native stream state for constructor functions, are + * dyn.builtinStreamClass, so only programs that use them link the stream + * half. */ export function builtinClassValue(kind: number, loc: SrcLoc): IrExpr { - return { kind: "libCall", fn: "dyn.builtinClass", args: [numLit(kind, loc)], type: DYN, loc }; + return { + kind: "libCall", + fn: kind === 0 ? "dyn.builtinClass" : "dyn.builtinStreamClass", + args: [numLit(kind, loc)], + type: DYN, + loc, + }; } /** `Base.call(this, …)` / `Base.apply(this, args)` on one of those class diff --git a/packages/compiler/src/frontend/lowering/lower-stream.ts b/packages/compiler/src/frontend/lowering/lower-stream.ts index 64d2e60e8..1a3c93f4b 100644 --- a/packages/compiler/src/frontend/lowering/lower-stream.ts +++ b/packages/compiler/src/frontend/lowering/lower-stream.ts @@ -3276,6 +3276,54 @@ function pipeEndOption(lowerer: Lowerer, options: ts.Expression | undefined, loc return end; } +/** `readable.pipe(untyped)`: the destination is a constructor-function + * stream or a boxed runtime stream at run time (dyn.builtinStreamPipe; + * other values throw the not-supported Error there), and pipe() answers + * the destination value itself. */ +function pipeIntoUntyped( + lowerer: Lowerer, + access: ts.PropertyAccessExpression, + target: IrExpr, + options: ts.Expression | undefined, + loc: SrcLoc, +): IrExpr { + const receiverValue = lowerer.lowerExpr(access.expression); + const source = lowerer.declareHiddenLocal("%pipeSource", receiverValue.type); + const destination = lowerer.declareHiddenLocal("%pipeDestination", target.type); + const end = pipeEndOption(lowerer, options, loc); + const destinationRef: IrExpr = { + kind: "varRef", + localId: destination.id, + type: target.type, + loc, + }; + return { + kind: "seqExpr", + stmts: [ + { kind: "varDecl", localId: source.id, init: receiverValue, loc }, + { kind: "varDecl", localId: destination.id, init: target, loc }, + { + kind: "exprStmt", + expr: { + kind: "libCall", + fn: "dyn.builtinStreamPipe", + args: [ + { kind: "varRef", localId: source.id, type: receiverValue.type, loc }, + destinationRef, + end, + ], + type: VOID, + loc, + }, + loc, + }, + ], + result: destinationRef, + type: target.type, + loc, + }; +} + export function lowerStreamMethodCall( lowerer: Lowerer, call: ts.CallExpression, @@ -3526,6 +3574,7 @@ export function lowerStreamMethodCall( }; } const dst = lowerer.lowerExpr(args[0]!); + if (dst.type.kind === "dyn") return pipeIntoUntyped(lowerer, access, dst, args[1], loc); const dstInfo = dst.type.kind === "object" ? lowerer.classes.get(dst.type.className) : undefined; const dstSides = streamSidesOf(lowerer, dstInfo); diff --git a/packages/compiler/src/index.ts b/packages/compiler/src/index.ts index 05a6f7423..f34d4dc23 100644 --- a/packages/compiler/src/index.ts +++ b/packages/compiler/src/index.ts @@ -544,6 +544,7 @@ async function compileExecutableNative( searchParams: features.searchParams, qs: features.qs, dynBuiltinClass: features.dynBuiltinClass, + dynBuiltinStream: features.dynBuiltinStream, parseArgs: features.parseArgs, stream: features.stream, net: features.net, diff --git a/packages/compiler/src/ir/builtin-effects.ts b/packages/compiler/src/ir/builtin-effects.ts index 1b84c5888..514d26317 100644 --- a/packages/compiler/src/ir/builtin-effects.ts +++ b/packages/compiler/src/ir/builtin-effects.ts @@ -559,6 +559,8 @@ export const MAY_THROW_LIB_FNS: ReadonlySet = new Set([ // a proxy in the prototype chain can throw "dyn.builtinInstanceOf", "dyn.functionInstanceOf", + // a destination that is not a stream throws the not-supported Error + "dyn.builtinStreamPipe", "dyn.getOwnPropertyNames", "dyn.ownKeys", "dyn.getOwnPropertySymbols", diff --git a/packages/compiler/src/ir/builtin-signatures.ts b/packages/compiler/src/ir/builtin-signatures.ts index a3480eb2c..d67bd5c37 100644 --- a/packages/compiler/src/ir/builtin-signatures.ts +++ b/packages/compiler/src/ir/builtin-signatures.ts @@ -3413,6 +3413,14 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ * Transform (5) and PassThrough (6) as untyped constructor values, one * per thread (scr_dyn_builtin_class.c). */ "dyn.builtinClass": { argTypes: [F64], result: DYN }, + /** The stream classes (1-6 above) with their stream half: constructor + * functions over them get native stream state + * (scr_dyn_builtin_stream.c). */ + "dyn.builtinStreamClass": { argTypes: [F64], result: DYN }, + /** readable.pipe(untyped): a constructor-function stream or a boxed + * runtime stream as the destination; other values throw the not- + * supported Error (scr_dyn_builtin_stream.c). */ + "dyn.builtinStreamPipe": { argTypes: [null, DYN, BOOL], result: VOID }, /** `value instanceof C` for those constructors, over the value's * prototype chain. */ "dyn.builtinInstanceOf": { argTypes: [DYN, F64], result: BOOL }, diff --git a/packages/compiler/src/ir/runtime-features.ts b/packages/compiler/src/ir/runtime-features.ts index de91b4b78..79428a02f 100644 --- a/packages/compiler/src/ir/runtime-features.ts +++ b/packages/compiler/src/ir/runtime-features.ts @@ -224,6 +224,8 @@ const CHILD_PROCESS_LIB_PREFIXES: readonly string[] = ["child.", "cp.", "spawnRe const SYMBOL_LIB_FNS: ReadonlySet = new Set([ // EventEmitter's kShapeMode and kCapture properties "dyn.builtinClass", + "dyn.builtinStreamClass", + "dyn.builtinStreamPipe", "dyn.usingDispose", "dyn.usingMethod", "util.isDeepStrictEqual", @@ -497,11 +499,27 @@ const RUNTIME_FEATURE_RULES = { * unit, render unhandled 'error' payloads through the inspect unit and * create EventEmitter's private symbols through the symbol unit. */ dynBuiltinClass: rule({ - fns: ["dyn.builtinClass", "dyn.builtinInstanceOf", "dyn.functionInstanceOf"], + fns: [ + "dyn.builtinClass", + "dyn.builtinStreamClass", + "dyn.builtinStreamPipe", + "dyn.builtinInstanceOf", + "dyn.functionInstanceOf", + ], }), + /** The stream classes as untyped constructor values with their stream + * half (scr_dyn_builtin_stream.c): native stream state for constructor + * functions, over the stream unit. */ + dynBuiltinStream: rule({ fns: ["dyn.builtinStreamClass", "dyn.builtinStreamPipe"] }), dynInvoke: rule({ initial: (mod) => mod.workers === true, - fns: [...DYN_INVOKE_LIB_FNS, ...UTIL_CALLABLE_LIB_FNS, "dyn.builtinClass"], + fns: [ + ...DYN_INVOKE_LIB_FNS, + ...UTIL_CALLABLE_LIB_FNS, + "dyn.builtinClass", + "dyn.builtinStreamClass", + "dyn.builtinStreamPipe", + ], expr: (node, _tables) => node.kind === "dynInvoke", }), /** The program has dynamic-tier jsval operations (js.* builtins, the @@ -512,7 +530,7 @@ const RUNTIME_FEATURE_RULES = { emitter: rule({ initial: (mod) => mod.workers === true || (mod.classes ?? []).some((c) => c.name === RUNTIME_EMITTER_CLASS), - fns: [...STREAM_LIB_FNS], + fns: [...STREAM_LIB_FNS, "dyn.builtinStreamClass", "dyn.builtinStreamPipe"], prefixes: ["emitter.", ...STREAM_LIB_PREFIXES], }), /** Subclasses of the runtime EventTarget/Event bases tear down and box @@ -548,6 +566,8 @@ const RUNTIME_FEATURE_RULES = { "util.styleText", "crypto.cipherNew", "dyn.builtinClass", + "dyn.builtinStreamClass", + "dyn.builtinStreamPipe", ...UTIL_CALLABLE_LIB_FNS, ], prefixes: ["insp.", "console.", "test."], @@ -611,7 +631,7 @@ const RUNTIME_FEATURE_RULES = { }), stream: rule({ initial: (mod) => (mod.classes ?? []).some((c) => RUNTIME_STREAM_CLASSES.has(c.name)), - fns: [...STREAM_LIB_FNS], + fns: [...STREAM_LIB_FNS, "dyn.builtinStreamClass", "dyn.builtinStreamPipe"], prefixes: STREAM_LIB_PREFIXES, }), /** A Dirent row converted to dyn carries Node's Symbol(type) key @@ -653,6 +673,7 @@ function noRuntimeFeatures(): RuntimeFeatures { dgram: false, dynAsync: false, dynBuiltinClass: false, + dynBuiltinStream: false, dynInvoke: false, dynTier: false, emitter: false, @@ -815,6 +836,14 @@ export function moduleUsesStream(mod: IrModule): boolean { return scanRuntimeFeatures(mod, "stream").stream; } +/** True when the program links scr_dyn_builtin_stream.c (stream classes + * as untyped constructor values): its runtime streams box through that + * unit's view and checked casts to the stream classes accept constructor- + * function streams. Programs without it keep their exact code. */ +export function moduleUsesDynBuiltinStream(mod: IrModule): boolean { + return scanRuntimeFeatures(mod, "dynBuiltinStream").dynBuiltinStream; +} + export const NPM_COMPRESS_MIN = 1024; export function moduleUsesZlib(mod: IrModule): boolean { diff --git a/packages/compiler/src/native/cli.ts b/packages/compiler/src/native/cli.ts index ce11aadf6..d1e1a664f 100644 --- a/packages/compiler/src/native/cli.ts +++ b/packages/compiler/src/native/cli.ts @@ -90,6 +90,7 @@ async function main(): Promise { searchParams: false, qs: false, dynBuiltinClass: false, + dynBuiltinStream: false, parseArgs: false, stream: false, net: false, diff --git a/packages/compiler/src/native/prepare.ts b/packages/compiler/src/native/prepare.ts index b95b978ff..cc0f1bb9a 100644 --- a/packages/compiler/src/native/prepare.ts +++ b/packages/compiler/src/native/prepare.ts @@ -144,6 +144,7 @@ function validFeatures(value: unknown): value is NativeLinkFeatures { "searchParams", "qs", "dynBuiltinClass", + "dynBuiltinStream", "parseArgs", "stream", "net", diff --git a/packages/compiler/src/native/sanitizer.test.ts b/packages/compiler/src/native/sanitizer.test.ts index bbd07a6cd..52fe1af41 100644 --- a/packages/compiler/src/native/sanitizer.test.ts +++ b/packages/compiler/src/native/sanitizer.test.ts @@ -96,6 +96,7 @@ test.skipIf(process.platform !== "linux" && process.platform !== "darwin")( searchParams: false, qs: false, dynBuiltinClass: false, + dynBuiltinStream: false, parseArgs: false, stream: false, net: false, diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6560-constructor-function-readable.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6560-constructor-function-readable.cjs.json new file mode 100644 index 000000000..48964b0a6 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6560-constructor-function-readable.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6560-constructor-function-readable.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6561-constructor-function-writable.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6561-constructor-function-writable.cjs.json new file mode 100644 index 000000000..603b083d1 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6561-constructor-function-writable.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6561-constructor-function-writable.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6562-constructor-function-transform.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6562-constructor-function-transform.cjs.json new file mode 100644 index 000000000..c5d3d1a62 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6562-constructor-function-transform.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6562-constructor-function-transform.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6563-constructor-function-stream-errors.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6563-constructor-function-stream-errors.cjs.json new file mode 100644 index 000000000..6e7f693c1 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6563-constructor-function-stream-errors.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6563-constructor-function-stream-errors.cjs" + ], + "diags": [] +} diff --git a/packages/runtime-pack-common/runtime-pack-matrix.mjs b/packages/runtime-pack-common/runtime-pack-matrix.mjs index a6b11f53e..364369366 100644 --- a/packages/runtime-pack-common/runtime-pack-matrix.mjs +++ b/packages/runtime-pack-common/runtime-pack-matrix.mjs @@ -92,6 +92,11 @@ export const OPTIONAL_RUNTIME_SOURCES = [ // through the dyn-invoke unit, inspect unhandled 'error' payloads and // create EventEmitter's private symbols. ["scr_dyn_builtin_class.c", all("dynBuiltinClass", "inspect", "dynInvoke", "symbol")], + // Their stream half also references the stream unit. + [ + "scr_dyn_builtin_stream.c", + all("dynBuiltinStream", "dynBuiltinClass", "stream", "inspect", "dynInvoke", "symbol"), + ], ["scr_stream.c", "stream"], // The zlib stream classes reference both the zlib engine and the stream unit. ["scr_zlib_stream.c", all("zlib", "stream")], diff --git a/packages/runtime/src/scr_dyn.h b/packages/runtime/src/scr_dyn.h index 91919b641..5dacae1ef 100644 --- a/packages/runtime/src/scr_dyn.h +++ b/packages/runtime/src/scr_dyn.h @@ -1122,6 +1122,38 @@ ScrDyn *scr_dyn_new_class(ScrClassObj *cls, const char *type_key); ScrDyn *scr_dyn_builtin_class(double kind); bool scr_dyn_builtin_instanceof(ScrDyn *value, double kind); bool scr_dyn_function_instanceof(ScrDyn *value, ScrDyn *callee); +/* The stream half of those constructors (scr_dyn_builtin_stream.c, linked + * with the dynBuiltinStream feature), registered with + * scr_dyn_builtin_class_streams before the first stream class value is + * read: construct runs `Readable.call(this, opts)` and the other stream + * kinds (+1 or NULL with an exception pending), prototype installs a stream + * kind's methods, and event answers EventEmitter.prototype's methods for a + * receiver with stream state (true when it handled the call; *result +1 or + * NULL with an exception pending). */ +enum { + SCR_EE_M_ON, SCR_EE_M_PREPEND, SCR_EE_M_ONCE, SCR_EE_M_PREPEND_ONCE, SCR_EE_M_OFF, + SCR_EE_M_REMOVE_ALL, SCR_EE_M_EMIT, SCR_EE_M_LISTENERS, SCR_EE_M_RAW_LISTENERS, + SCR_EE_M_LISTENER_COUNT, SCR_EE_M_EVENT_NAMES, SCR_EE_M_SET_MAX, SCR_EE_M_GET_MAX, +}; +typedef struct ScrDynStreamOps { + ScrDyn *(*construct)(int kind, ScrDyn *self, ScrDyn *const *args, size_t argc); + void (*prototype)(int kind, ScrDyn *prototype); + bool (*event)(int method, ScrDyn *self, ScrDyn *const *args, size_t argc, ScrDyn **result); +} ScrDynStreamOps; +void scr_dyn_builtin_class_streams(const ScrDynStreamOps *ops); +/* A stream class value with its stream half (scr_dyn_builtin_stream.c), + * +1; the stream a JavaScript value stands for (a constructor-function + * stream or a boxed runtime stream), +1 or NULL; and the constructor- + * function object of a runtime stream, +1 or NULL. */ +ScrDyn *scr_dyn_builtin_stream_class(double kind); +struct ScrStream *scr_dyn_builtin_stream_of(const ScrDyn *value); +ScrDyn *scr_dyn_builtin_stream_object(struct ScrStream *s); +/* The untyped view of a runtime stream in programs that link that unit + * (+1), a checked cast to a runtime stream class kind (+1 or NULL), and + * readable.pipe() into an untyped destination (src and dest borrowed). */ +ScrDyn *scr_dyn_builtin_stream_view(struct ScrStream *s); +struct ScrStream *scr_dyn_builtin_stream_check(const ScrDyn *value, double kind); +void scr_dyn_builtin_stream_pipe(struct ScrStream *src, ScrDyn *dest, bool end); bool scr_dyn_class_is_key(const ScrDyn *value, const ScrStr *type_key); bool scr_dyn_class_is(const ScrDyn *value, const char *type_key); /* Calls a dyn value: a non-function kind throws the catchable TypeError diff --git a/packages/runtime/src/scr_dyn_builtin_class.c b/packages/runtime/src/scr_dyn_builtin_class.c index 76a7ab15e..a9b50d218 100644 --- a/packages/runtime/src/scr_dyn_builtin_class.c +++ b/packages/runtime/src/scr_dyn_builtin_class.c @@ -15,9 +15,13 @@ * Stream is Node's legacy constructor: EventEmitter.call(this, opts) and a * prototype that inherits EventEmitter's. Readable, Writable, Duplex, * Transform and PassThrough have prototypes chained as in Node, so - * util.inherits onto them and instanceof work, but their native state has - * no dyn object form: calling one with a receiver throws the "not - * supported yet" Error where the construction runs. + * util.inherits onto them and instanceof work. Their stream half (the + * prototypes' methods and `Readable.call(this, opts)`) lives in + * scr_dyn_builtin_stream.c, which registers itself through + * scr_dyn_builtin_class_streams in programs that link it; without it, + * calling one with a receiver throws the "not supported yet" Error where + * the construction runs. EventEmitter's methods hand a receiver with + * stream state to that unit, whose listeners live in the runtime stream. * * One constructor per kind and thread, so every boxing of the same class * value is the same function. All arguments are borrowed. */ @@ -59,6 +63,14 @@ static SCR_TL ScrDyn *bc_symbols[BC_SYMBOLS]; static ScrDyn *bc_class(int kind); +/* The stream half, once registered (scr_dyn_builtin_stream.c). */ +static SCR_TL const ScrDynStreamOps *bc_stream_ops; + +/* EventEmitter.prototype's method on a receiver with stream state. */ +static bool bc_stream_event(int method, ScrDyn *self, ScrDyn *const *args, size_t argc, ScrDyn **result) { + return bc_stream_ops != NULL && bc_stream_ops->event(method, self, args, argc, result); +} + /* ── property helpers ───────────────────────────────────────────────── */ static ScrDyn *bc_key(const char *name) { @@ -587,7 +599,12 @@ static ScrDyn *bc_once_wrap(ScrDyn *target, ScrDyn *type, ScrDyn *listener) { static ScrDyn *bc_on(ScrClosure *closure, ScrDyn *const *args, size_t argc) { (void)closure; ScrDyn *self = bc_this(); - ScrDyn *result = bc_require_object(self, "_events") + ScrDyn *result; + if (bc_stream_event(SCR_EE_M_ON, self, args, argc, &result)) { + scr_dyn_release(self); + return result; + } + result = bc_require_object(self, "_events") ? bc_add_listener(self, bc_arg(args, argc, 0), bc_arg(args, argc, 1), false) : NULL; scr_dyn_release(self); return result; @@ -596,7 +613,12 @@ static ScrDyn *bc_on(ScrClosure *closure, ScrDyn *const *args, size_t argc) { static ScrDyn *bc_prepend(ScrClosure *closure, ScrDyn *const *args, size_t argc) { (void)closure; ScrDyn *self = bc_this(); - ScrDyn *result = bc_require_object(self, "_events") + ScrDyn *result; + if (bc_stream_event(SCR_EE_M_PREPEND, self, args, argc, &result)) { + scr_dyn_release(self); + return result; + } + result = bc_require_object(self, "_events") ? bc_add_listener(self, bc_arg(args, argc, 0), bc_arg(args, argc, 1), true) : NULL; scr_dyn_release(self); return result; @@ -607,6 +629,10 @@ static ScrDyn *bc_once_common(ScrDyn *const *args, size_t argc, bool prepend) { ScrDyn *type = bc_arg(args, argc, 0); ScrDyn *listener = bc_arg(args, argc, 1); ScrDyn *result = NULL; + if (bc_stream_event(prepend ? SCR_EE_M_PREPEND_ONCE : SCR_EE_M_ONCE, self, args, argc, &result)) { + scr_dyn_release(self); + return result; + } if (bc_check_listener(listener) && bc_require_object(self, "_events")) { ScrDyn *wrapper = bc_once_wrap(self, type, listener); if (wrapper) { @@ -631,7 +657,12 @@ static ScrDyn *bc_prepend_once(ScrClosure *closure, ScrDyn *const *args, size_t static ScrDyn *bc_off(ScrClosure *closure, ScrDyn *const *args, size_t argc) { (void)closure; ScrDyn *self = bc_this(); - ScrDyn *result = bc_require_object(self, "_events") + ScrDyn *result; + if (bc_stream_event(SCR_EE_M_OFF, self, args, argc, &result)) { + scr_dyn_release(self); + return result; + } + result = bc_require_object(self, "_events") ? bc_remove_listener(self, bc_arg(args, argc, 0), bc_arg(args, argc, 1)) : NULL; scr_dyn_release(self); return result; @@ -643,6 +674,11 @@ static ScrDyn *bc_off(ScrClosure *closure, ScrDyn *const *args, size_t argc) { static ScrDyn *bc_remove_all(ScrClosure *closure, ScrDyn *const *args, size_t argc) { (void)closure; ScrDyn *self = bc_this(); + ScrDyn *handled; + if (bc_stream_event(SCR_EE_M_REMOVE_ALL, self, args, argc, &handled)) { + scr_dyn_release(self); + return handled; + } if (!bc_require_object(self, "_events")) { scr_dyn_release(self); return NULL; @@ -761,6 +797,11 @@ fail: static ScrDyn *bc_emit(ScrClosure *closure, ScrDyn *const *args, size_t argc) { (void)closure; ScrDyn *self = bc_this(); + ScrDyn *handled; + if (bc_stream_event(SCR_EE_M_EMIT, self, args, argc, &handled)) { + scr_dyn_release(self); + return handled; + } if (!bc_require_object(self, "_events")) { scr_dyn_release(self); return NULL; @@ -846,6 +887,11 @@ fail_self: /* _listeners(target, type, unwrap): a fresh array. */ static ScrDyn *bc_listeners_of(bool unwrap, ScrDyn *const *args, size_t argc) { ScrDyn *self = bc_this(); + ScrDyn *handled; + if (bc_stream_event(unwrap ? SCR_EE_M_LISTENERS : SCR_EE_M_RAW_LISTENERS, self, args, argc, &handled)) { + scr_dyn_release(self); + return handled; + } if (!bc_require_object(self, "_events")) { scr_dyn_release(self); return NULL; @@ -939,7 +985,12 @@ static ScrDyn *bc_count_listeners(ScrDyn *self, ScrDyn *type, ScrDyn *only) { static ScrDyn *bc_listener_count(ScrClosure *closure, ScrDyn *const *args, size_t argc) { (void)closure; ScrDyn *self = bc_this(); - ScrDyn *result = bc_require_object(self, "_events") + ScrDyn *result; + if (bc_stream_event(SCR_EE_M_LISTENER_COUNT, self, args, argc, &result)) { + scr_dyn_release(self); + return result; + } + result = bc_require_object(self, "_events") ? bc_count_listeners(self, bc_arg(args, argc, 0), argc > 1 && !bc_undefined(args[1]) && args[1]->kind != SCR_DYN_NULL ? args[1] : NULL) : NULL; @@ -966,6 +1017,11 @@ static ScrDyn *bc_event_names(ScrClosure *closure, ScrDyn *const *args, size_t a (void)args; (void)argc; ScrDyn *self = bc_this(); + ScrDyn *handled; + if (bc_stream_event(SCR_EE_M_EVENT_NAMES, self, args, argc, &handled)) { + scr_dyn_release(self); + return handled; + } if (!bc_require_object(self, "_eventsCount")) { scr_dyn_release(self); return NULL; @@ -1010,6 +1066,11 @@ static ScrDyn *bc_set_max(ScrClosure *closure, ScrDyn *const *args, size_t argc) scr_dyn_release(self); return NULL; } + ScrDyn *handled; + if (bc_stream_event(SCR_EE_M_SET_MAX, self, args, argc, &handled)) { + scr_dyn_release(self); + return handled; + } if (!bc_require_object(self, "_maxListeners") || !bc_set(self, "_maxListeners", scr_dyn_retain(n))) { scr_dyn_release(self); return NULL; @@ -1022,6 +1083,11 @@ static ScrDyn *bc_get_max(ScrClosure *closure, ScrDyn *const *args, size_t argc) (void)args; (void)argc; ScrDyn *self = bc_this(); + ScrDyn *handled; + if (bc_stream_event(SCR_EE_M_GET_MAX, self, args, argc, &handled)) { + scr_dyn_release(self); + return handled; + } double max = bc_require_object(self, "_maxListeners") ? bc_max_listeners(self) : -1; scr_dyn_release(self); if (scr_exc_pending()) return NULL; @@ -1280,6 +1346,8 @@ static ScrDyn *bc_construct(ScrClosure *closure, ScrDyn *const *args, size_t arg ScrDyn *result = NULL; if (kind == BC_EMITTER || kind == BC_STREAM) { if (bc_emitter_init(self, bc_arg(args, argc, 0))) result = scr_dyn_retain(scr_dyn_undefined()); + } else if (bc_stream_ops) { + result = bc_stream_ops->construct(kind, self, args, argc); } else { char message[160]; snprintf(message, sizeof message, @@ -1373,6 +1441,8 @@ static ScrDyn *bc_new_class(int kind, ScrDyn *parent) { } else if (kind == BC_STREAM) { ScrDyn *pipe = scr_dyn_new_func(scr_closure_new(NULL, 0), bc_pipe, 2, "native:stream", "pipe"); bc_set(prototype, "pipe", pipe); + } else if (bc_stream_ops) { + bc_stream_ops->prototype(kind, prototype); } scr_dyn_release(prototype); if (scr_exc_pending()) scr_exc_clear(); @@ -1408,6 +1478,22 @@ static ScrDyn *bc_class(int kind) { return bc_ctors[kind]; } +/* Registers the stream half; constructors that already exist get their + * stream prototypes' methods now. */ +void scr_dyn_builtin_class_streams(const ScrDynStreamOps *ops) { + if (bc_stream_ops) return; + bc_stream_ops = ops; + for (int kind = BC_READABLE; kind < BC_COUNT; kind++) { + if (!bc_ctors[kind]) continue; + ScrDyn *prototype = bc_get(bc_ctors[kind], "prototype"); + if (prototype) { + ops->prototype(kind, prototype); + scr_dyn_release(prototype); + } + if (scr_exc_pending()) scr_exc_clear(); + } +} + /* The constructor value for kind (the BC_* order), +1. */ ScrDyn *scr_dyn_builtin_class(double kind) { int index = (int)kind; diff --git a/packages/runtime/src/scr_dyn_builtin_stream.c b/packages/runtime/src/scr_dyn_builtin_stream.c new file mode 100644 index 000000000..4e42d219b --- /dev/null +++ b/packages/runtime/src/scr_dyn_builtin_stream.c @@ -0,0 +1,1672 @@ +/* Native stream state for JavaScript constructor functions that inherit + * from the node:stream classes the pre-class way: + * + * function Source(opts) { Readable.call(this, opts); } + * util.inherits(Source, Readable); + * Source.prototype._read = function (n) { this.push(...); }; + * + * scr_dyn_builtin_class.c provides Readable, Writable, Duplex, Transform + * and PassThrough as untyped constructors with Node's prototype chain. + * This unit (the dynBuiltinStream feature) gives them their stream half: + * + * - `Readable.call(this, opts)` (and the other four) creates a runtime + * stream (scr_stream.c) over the receiver. The receiver keeps it in a + * hidden slot (an entry of its symbol table that no symbol can name), + * so JavaScript sees only the properties Node's constructor adds: + * `_readableState` / `_writableState` (live read-only views of the + * runtime state), `allowHalfOpen` on the duplex kinds, and the option + * callbacks copied to `_read`, `_write`, `_final`, `_destroy`, + * `_transform` and `_flush`. + * - The runtime stream calls the receiver's `_read`, `_write`, + * `_transform`, `_flush`, `_final` and `_destroy` through its property + * lookup, with the receiver as `this`, as Node's machinery does. + * - The stream prototypes carry Node's methods and accessors (push, read, + * write, end, pipe, destroy, readable, destroyed, ...) over the hidden + * stream, and EventEmitter's methods register listeners with the runtime + * stream's own emitter (scr_dyn_builtin_class.c forwards to them), so the + * runtime's events and pipes reach JavaScript listeners with the + * receiver as `this`. + * + * Lifetime: the receiver holds its stream through a traced capsule, and + * the stream's hook closures hold the receiver through traced boxes, so + * the pair lives while either is reachable and the cycle collector + * reclaims it afterwards. The stream's native record points back at the + * receiver without a reference; the hook closures outlive the record. + * + * Not supported here (each throws the "not supported yet" Error where it + * runs): objectMode and the other options scr_stream.c's dyn option walk + * refuses, `_writev` and `_construct`, symbol event names, listeners() / + * rawListeners(), `wrap()`, and pipes into values that are neither runtime + * streams nor constructor-function streams. */ +#include "scr_runtime.h" + +#include +#include +#include + +enum { BCS_READABLE = 2, BCS_WRITABLE, BCS_DUPLEX, BCS_TRANSFORM, BCS_PASSTHROUGH }; + +static const char bcs_hidden_key[] = "scriptc:stream"; +#define BCS_HIDDEN_LEN (sizeof bcs_hidden_key - 1) + +/* The runtime stream's native record. */ +struct BcsRec; +typedef struct BcsRec { + ScrDyn *object; /* borrowed: the hook closures keep it alive */ + ScrDyn **destroy_cbs; /* destroy(err, cb) callbacks waiting for _destroy */ + size_t destroy_n, destroy_cap; + char cls[64]; /* the constructor's name, for the leak warning */ +} BcsRec; + +static void bcs_rec_release(void *p) { + BcsRec *rec = p; + for (size_t i = 0; i < rec->destroy_n; i++) scr_dyn_release(rec->destroy_cbs[i]); + free(rec->destroy_cbs); + free(rec); +} + +static BcsRec *bcs_rec(ScrStream *s) { + return scr_stream_native(s, bcs_rec_release); +} + +/* ── small helpers ──────────────────────────────────────────────────── */ + +static ScrDyn *bcs_key(const char *name) { + ScrStr *text = scr_str_new(name, strlen(name)); + ScrDyn *key = scr_dyn_new_str(text); + scr_str_release(text); + return key; +} + +/* object[name] with object as the receiver: +1, or NULL with an exception + * pending. */ +static ScrDyn *bcs_get(ScrDyn *object, const char *name) { + ScrDyn *key = bcs_key(name); + ScrDyn *value = scr_dyn_reflect_get(object, key, object); + scr_dyn_release(key); + if (!value && !scr_exc_pending()) value = scr_dyn_retain(scr_dyn_undefined()); + return value; +} + +/* object[name] = value; the value moves in. */ +static bool bcs_set(ScrDyn *object, const char *name, ScrDyn *value) { + ScrDyn *key = bcs_key(name); + scr_dyn_key_set_computed(object, key, value); + scr_dyn_release(key); + scr_dyn_release(value); + return !scr_exc_pending(); +} + +static ScrDyn *bcs_arg(ScrDyn *const *args, size_t argc, size_t index) { + return index < argc ? args[index] : scr_dyn_undefined(); +} + +static bool bcs_nullish(const ScrDyn *value) { + return value->kind == SCR_DYN_UNDEF || value->kind == SCR_DYN_NULL; +} + +static ScrDyn *bcs_undefined(void) { + return scr_dyn_retain(scr_dyn_undefined()); +} + +static ScrDyn *bcs_str(const char *text) { + return bcs_key(text); +} + +static void bcs_unsupported(const char *message) { + scr_throw_error_msg_code(SCR_ERR_ERROR, message, strlen(message), "SC2020"); +} + +/* fn.call(self, ...args): +1 or NULL. */ +static ScrDyn *bcs_apply(ScrDyn *fn, ScrDyn *self, ScrDyn *const *args, size_t argc, const char *what) { + scr_dyn_this_push_dyn(self); + ScrDyn *result = scr_dyn_call(fn, args, argc, what); + scr_dyn_this_pop(); + return result; +} + +/* An error argument of a completion callback or destroy(): NULL for a + * falsy value (Node tests `if (err)`), else the runtime error that boxes + * back to the same JavaScript value (+1). */ +static ScrError *bcs_error_of(const ScrDyn *value) { + if (!scr_dyn_truthy(value)) return NULL; + if (value->kind == SCR_DYN_STR) return scr_error_new(SCR_ERR_ERROR, value->v.str); + return scr_error_from_dyn(value); +} + +/* ── the hidden slot ────────────────────────────────────────────────── */ + +static void *bcs_capsule_retain(void *p) { return scr_stream_retain(p); } +/* Also the capsule's tag. */ +static void bcs_capsule_release(void *p) { scr_stream_release(p); } + +static bool bcs_stream_vt(const ScrVt *vt) { + return (vt->pre >= scr_readable_vt.pre && vt->pre <= scr_readable_vt.post) || + (vt->pre >= scr_writable_vt.pre && vt->pre <= scr_writable_vt.post); +} + +static void bcs_rec_release(void *p); + +/* The stream of a constructor-function stream or one of its state views, + * or of a typed capsule of such a stream (a typed stream value that + * crossed back into JavaScript), borrowed; NULL for other values. */ +static ScrStream *bcs_of(const ScrDyn *value) { + if (value != NULL && value->kind == SCR_DYN_TYPED_REF && value->v.typed_ref.type_key_len > 7 && + memcmp(value->v.typed_ref.type_key, "object:", 7) == 0) { + ScrStream *stream = value->v.typed_ref.ptr; + return bcs_stream_vt(stream->vt) && scr_stream_native(stream, bcs_rec_release) ? stream : NULL; + } + if (value == NULL || value->kind != SCR_DYN_OBJ || value->symbol_properties == NULL) return NULL; + ScrDyn *capsule = scr_dyn_obj_get(value->symbol_properties, bcs_hidden_key, BCS_HIDDEN_LEN); + if (capsule == NULL || capsule->kind != SCR_DYN_TYPED_REF || + capsule->v.typed_ref.release != bcs_capsule_release) + return NULL; + return capsule->v.typed_ref.ptr; +} + +/* Stores the stream in the object's symbol table under a storage name no + * symbol produces (symbols store under their address), outside the key + * list, so no reflection reaches it. */ +static void bcs_hide(ScrDyn *object, ScrStream *s) { + scr_dyn_region_store(object, NULL); + if (!object->symbol_properties) object->symbol_properties = scr_dyn_new_obj_null_proto(); + if (!object->symbol_keys) object->symbol_keys = scr_dyn_new_arr(); + ScrDyn *capsule = scr_dyn_new_typed_ref_traced(s, bcs_capsule_retain, bcs_capsule_release, + bcs_hidden_key, BCS_HIDDEN_LEN, + scr_dyn_class_view_unavailable, NULL); + scr_dyn_obj_set(object->symbol_properties, bcs_hidden_key, BCS_HIDDEN_LEN, capsule); +} + +/* A runtime stream boxed as a typed capsule (a typed stream value that + * crossed into JavaScript): +1, or NULL. */ +static ScrStream *bcs_native_of(const ScrDyn *value) { + if (value->kind != SCR_DYN_TYPED_REF || value->v.typed_ref.type_key_len <= 7 || + memcmp(value->v.typed_ref.type_key, "object:", 7) != 0) + return NULL; + ScrStream *stream = scr_dyn_typed_ref_unbox(value); + if (bcs_stream_vt(stream->vt)) return stream; + value->v.typed_ref.release(stream); + return NULL; +} + +/* Any stream a JavaScript value stands for (+1), or NULL. */ +ScrStream *scr_dyn_builtin_stream_of(const ScrDyn *value) { + ScrStream *own = bcs_of(value); + if (own) return scr_stream_retain(own); + return bcs_native_of(value); +} + +/* The JavaScript value of a runtime stream: the constructor-function + * object it belongs to (+1), or NULL for other streams. */ +ScrDyn *scr_dyn_builtin_stream_object(ScrStream *s) { + BcsRec *rec = bcs_rec(s); + return rec ? scr_dyn_retain(rec->object) : NULL; +} + +/* A stream as a listener argument: its constructor-function object, or a + * live view of another runtime stream. +1. */ +static ScrDyn *bcs_stream_value(ScrStream *s) { + ScrDyn *object = scr_dyn_builtin_stream_object(s); + return object ? object : scr_stream_dyn_view(s); +} + +/* The receiver's stream for a prototype method, borrowed; throws Node's + * TypeError (reading the state of a non-stream) and answers NULL. */ +static ScrStream *bcs_need(ScrDyn *self, const char *reading) { + ScrStream *s = bcs_of(self); + if (s) return s; + char message[128]; + if (bcs_nullish(self)) { + snprintf(message, sizeof message, "Cannot read properties of %s (reading '%s')", + self->kind == SCR_DYN_NULL ? "null" : "undefined", reading); + scr_throw_error_msg(SCR_ERR_TYPE, message, strlen(message)); + } else { + snprintf(message, sizeof message, + "stream method '%s' on an object without stream state is not supported yet", reading); + bcs_unsupported(message); + } + return NULL; +} + +/* ── completion callbacks ───────────────────────────────────────────── */ + +enum { BCS_DONE_WRITE, BCS_DONE_FINAL, BCS_DONE_DESTROY, BCS_DONE_TRANSFORM, BCS_DONE_FLUSH }; + +static void bcs_run_destroy_cbs(ScrStream *s, ScrError *err) { + BcsRec *rec = bcs_rec(s); + if (!rec || rec->destroy_n == 0) return; + ScrDyn **cbs = rec->destroy_cbs; + size_t n = rec->destroy_n; + rec->destroy_cbs = NULL; + rec->destroy_n = rec->destroy_cap = 0; + ScrDyn *arg = err ? scr_dyn_from_error(err) : scr_dyn_new_null(); + ScrDyn *object = rec->object; + scr_dyn_retain(object); + for (size_t i = 0; i < n; i++) { + if (!scr_exc_pending()) { + ScrDyn *result = bcs_apply(cbs[i], object, &arg, 1, "callback"); + if (result) scr_dyn_release(result); + } + scr_dyn_release(cbs[i]); + } + free(cbs); + scr_dyn_release(object); + scr_dyn_release(arg); +} + +static ScrDyn *bcs_done_call(ScrClosure *closure, ScrDyn *const *args, size_t argc) { + ScrStream *s = scr_box_get_ref(closure->caps[0]); + int which = (int)scr_box_get_f64(closure->caps[1]); + ScrError *err = bcs_error_of(bcs_arg(args, argc, 0)); + switch (which) { + case BCS_DONE_WRITE: + scr_stream_write_done(s, err); + break; + case BCS_DONE_FINAL: + scr_stream_final_done(s, err); + break; + case BCS_DONE_DESTROY: { + ScrError *kept = err ? scr_error_retain(err) : NULL; + scr_stream_destroy_done(s, err); + if (!scr_exc_pending()) bcs_run_destroy_cbs(s, kept); + if (kept) scr_error_release(kept); + break; + } + default: { + ScrDyn *data = bcs_arg(args, argc, 1); + ScrBytes *bytes = data->kind == SCR_DYN_BYTES ? scr_bytes_raw_view(data->v.bytes) : NULL; + ScrStr *text = data->kind == SCR_DYN_STR ? scr_str_retain(data->v.str) : NULL; + if (which == BCS_DONE_TRANSFORM) scr_stream_transform_done(s, err, bytes, text); + else scr_stream_flush_done(s, err, bytes, text); + break; + } + } + scr_stream_release(s); + if (scr_exc_pending()) return NULL; + return bcs_undefined(); +} + +static ScrDyn *bcs_done(ScrStream *s, int which) { + ScrClosure *closure = scr_closure_new((void *)bcs_done_call, 2); + closure->caps[0] = scr_box_new_obj(scr_stream_retain_v, scr_stream_release_v, scr_stream_trace); + scr_box_set_ref(closure->caps[0], scr_stream_retain(s)); + closure->caps[1] = scr_box_new(SCR_BOX_F64); + scr_box_set_f64(closure->caps[1], which); + uint32_t arity = which == BCS_DONE_TRANSFORM || which == BCS_DONE_FLUSH ? 2 : 1; + return scr_dyn_new_func(closure, bcs_done_call, arity, "native:stream", ""); +} + +/* ── the hooks: the runtime calls the receiver's underscore methods ─── */ + +static ScrBox *bcs_box(ScrDyn *value /* moves in */) { + ScrBox *box = scr_box_new_obj(scr_dyn_retain_v, scr_dyn_release_v, scr_dyn_trace_v); + scr_box_set_ref(box, value); + return box; +} + +static ScrClosure *bcs_hook(ScrDyn *object, void *inv) { + ScrClosure *closure = scr_closure_new(inv, 1); + closure->caps[0] = bcs_box(scr_dyn_retain(object)); + return closure; +} + +/* receiver[method](...args) with the receiver as `this`: +1 or NULL. */ +static ScrDyn *bcs_invoke(ScrDyn *object, const char *method, ScrDyn *const *args, size_t argc) { + ScrDyn *fn = bcs_get(object, method); + if (!fn) return NULL; + ScrDyn *result = NULL; + if (scr_dyn_is_callable(fn)) { + result = bcs_apply(fn, object, args, argc, method); + } else { + char message[96]; + snprintf(message, sizeof message, "this.%s is not a function", method); + scr_throw_error_msg(SCR_ERR_TYPE, message, strlen(message)); + } + scr_dyn_release(fn); + return result; +} + +/* The thrown value of a hook Node calls inside try/catch, as the runtime + * error its catch hands on (+1); clears the exception. */ +static ScrError *bcs_take_error(void) { + ScrCaught *caught = scr_exc_take(); + ScrDyn *value = scr_caught_to_dyn(caught); + scr_caught_release(caught); + ScrError *err = NULL; + if (value) { + err = scr_dyn_truthy(value) ? bcs_error_of(value) : NULL; + scr_dyn_release(value); + } + if (!err) { + ScrStr *empty = scr_str_new("", 0); + err = scr_error_new(SCR_ERR_ERROR, empty); + scr_str_release(empty); + } + return err; +} + +/* Readable.read: `try { this._read(hwm) } catch (err) { errorOrDestroy }` */ +static void bcs_read_inv(ScrClosure *cb, ScrStream *s, double size) { + ScrDyn *object = scr_box_get_ref(cb->caps[0]); + ScrDyn *n = scr_dyn_new_num(size); + ScrDyn *result = bcs_invoke(object, "_read", &n, 1); + scr_dyn_release(n); + if (result) scr_dyn_release(result); + else if (scr_exc_pending()) { + ScrError *err = bcs_take_error(); + scr_stream_fail(s, err); + scr_error_release(err); + } + scr_dyn_release(object); +} + +/* _write(chunk, 'buffer', cb) and _transform(chunk, 'buffer', cb): a + * throw propagates to the write() caller, as in Node. */ +static void bcs_chunk_call(ScrClosure *cb, ScrStream *s, ScrBytes *chunk, const char *method, int done) { + ScrDyn *object = scr_box_get_ref(cb->caps[0]); + ScrDyn *args[3]; + args[0] = scr_dyn_new_buffer(chunk); + args[1] = bcs_str("buffer"); + args[2] = bcs_done(s, done); + ScrDyn *result = bcs_invoke(object, method, args, 3); + for (int i = 0; i < 3; i++) scr_dyn_release(args[i]); + if (result) scr_dyn_release(result); + scr_dyn_release(object); +} + +static void bcs_write_inv(ScrClosure *cb, ScrStream *s, ScrBytes *chunk) { + bcs_chunk_call(cb, s, chunk, "_write", BCS_DONE_WRITE); +} + +static void bcs_transform_inv(ScrClosure *cb, ScrStream *s, ScrBytes *chunk) { + bcs_chunk_call(cb, s, chunk, "_transform", BCS_DONE_TRANSFORM); +} + +/* callFinal: `try { stream._final(onFinish) } catch (err) { onFinish(err) }`; + * a Transform's _flush runs inside the same try. */ +static void bcs_plain_call(ScrClosure *cb, ScrStream *s, const char *method, int done) { + ScrDyn *object = scr_box_get_ref(cb->caps[0]); + ScrDyn *callback = bcs_done(s, done); + ScrDyn *result = bcs_invoke(object, method, &callback, 1); + scr_dyn_release(callback); + if (result) scr_dyn_release(result); + else if (scr_exc_pending()) { + ScrError *err = bcs_take_error(); + if (done == BCS_DONE_FINAL) scr_stream_final_done(s, err); + else scr_stream_flush_done(s, err, NULL, NULL); + } + scr_dyn_release(object); +} + +static void bcs_final_inv(ScrClosure *cb, ScrStream *s) { + bcs_plain_call(cb, s, "_final", BCS_DONE_FINAL); +} + +static void bcs_flush_inv(ScrClosure *cb, ScrStream *s) { + bcs_plain_call(cb, s, "_flush", BCS_DONE_FLUSH); +} + +static ScrDyn *bcs_default_method(int kind, const char *name); + +/* _destroy(err || null, onDestroy) inside try/catch. The prototype's + * default (cb(err)) completes without a call. */ +static void bcs_destroy_inv(ScrClosure *cb, ScrStream *s, ScrError *err) { + ScrDyn *object = scr_box_get_ref(cb->caps[0]); + ScrDyn *fn = bcs_get(object, "_destroy"); + ScrDyn *fallback = bcs_default_method(BCS_READABLE, "_destroy"); + if (fn && fallback && scr_dyn_strict_eq(fn, fallback)) { + ScrError *kept = err ? scr_error_retain(err) : NULL; + scr_stream_destroy_done(s, err ? scr_error_retain(err) : NULL); + if (!scr_exc_pending()) bcs_run_destroy_cbs(s, kept); + if (kept) scr_error_release(kept); + } else if (fn) { + ScrDyn *args[2]; + args[0] = err ? scr_dyn_from_error(err) : scr_dyn_new_null(); + args[1] = bcs_done(s, BCS_DONE_DESTROY); + ScrDyn *result = NULL; + if (scr_dyn_is_callable(fn)) { + result = bcs_apply(fn, object, args, 2, "_destroy"); + } else { + static const char message[] = "this._destroy is not a function"; + scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1); + } + scr_dyn_release(args[0]); + scr_dyn_release(args[1]); + if (result) scr_dyn_release(result); + else if (scr_exc_pending()) { + ScrError *thrown = bcs_take_error(); + ScrError *kept = scr_error_retain(thrown); + scr_stream_destroy_done(s, thrown); + if (!scr_exc_pending()) bcs_run_destroy_cbs(s, kept); + scr_error_release(kept); + } + } + if (fallback) scr_dyn_release(fallback); + if (fn) scr_dyn_release(fn); + scr_dyn_release(object); +} + +/* ── listeners: the runtime emitter, the receiver as `this` ─────────── */ + +enum { BCS_L_FN, BCS_L_SELF, BCS_L_CAPS }; + +static void bcs_listener_call(ScrClosure *adapter, ScrDyn *const *args, size_t argc) { + ScrDyn *fn = scr_box_get_ref(adapter->caps[BCS_L_FN]); + ScrDyn *self = scr_box_get_ref(adapter->caps[BCS_L_SELF]); + ScrDyn *result = bcs_apply(fn, self, args, argc, "listener"); + if (result) scr_dyn_release(result); + scr_dyn_release(self); + scr_dyn_release(fn); +} + +static void bcs_listener0(ScrClosure *adapter) { + bcs_listener_call(adapter, NULL, 0); +} + +static void bcs_listener_data(ScrClosure *adapter, ScrBytes *bytes, ScrStr *text) { + ScrDyn *arg = text ? scr_dyn_new_str(text) : scr_dyn_new_buffer(bytes); + bcs_listener_call(adapter, &arg, 1); + scr_dyn_release(arg); +} + +static void bcs_listener_error(ScrClosure *adapter, ScrError *error) { + ScrDyn *arg = scr_dyn_from_error(error); + bcs_listener_call(adapter, &arg, 1); + scr_dyn_release(arg); +} + +static void bcs_listener_stream(ScrClosure *adapter, ScrStream *stream) { + ScrDyn *arg = bcs_stream_value(stream); + bcs_listener_call(adapter, &arg, 1); + scr_dyn_release(arg); +} + +static void bcs_listener_name(ScrClosure *adapter, ScrStr *name) { + ScrDyn *arg = scr_dyn_new_str(name); + bcs_listener_call(adapter, &arg, 1); + scr_dyn_release(arg); +} + +/* Computed events: the listener's checked-dynamic call, with every + * argument emit passed. */ +static ScrDyn *bcs_listener_flex(ScrClosure *adapter, ScrDyn *const *args, size_t argc) { + bcs_listener_call(adapter, args, argc); + if (scr_exc_pending()) return NULL; + return bcs_undefined(); +} + +static ScrClosure *bcs_adapter(ScrDyn *fn, ScrDyn *self, void *call) { + ScrClosure *adapter = scr_closure_new(call, BCS_L_CAPS); + adapter->caps[BCS_L_FN] = bcs_box(scr_dyn_retain(fn)); + adapter->caps[BCS_L_SELF] = bcs_box(scr_dyn_retain(self)); + scr_dyn_adopt_identity(adapter, fn); + return adapter; +} + +static bool bcs_is(const ScrStr *name, const char *text) { + size_t length = strlen(text); + return name->len == length && memcmp(name->data, text, length) == 0; +} + +/* The runtime's fixed event tuples: 0 none, 1 data, 2 error, 3 a stream + * (pipe/unpipe), 4 an event name (the meta events); -1 computed. */ +static int bcs_event_shape(const ScrStr *name) { + if (bcs_is(name, "data")) return 1; + if (bcs_is(name, "error")) return 2; + if (bcs_is(name, "pipe") || bcs_is(name, "unpipe")) return 3; + if (bcs_is(name, "newListener") || bcs_is(name, "removeListener")) return 4; + static const char *const empty[] = { + "end", "close", "finish", "drain", "readable", "resume", "pause", "prefinish", + }; + for (size_t i = 0; i < sizeof empty / sizeof *empty; i++) + if (bcs_is(name, empty[i])) return 0; + return -1; +} + +/* The event name argument as a string, or NULL with an exception + * pending. */ +static ScrStr *bcs_event_name(const ScrDyn *type) { + if (type->kind == SCR_DYN_STR) return scr_str_retain(type->v.str); + if (type->kind == SCR_DYN_SYMBOL) { + bcs_unsupported("symbol event names on a constructor-function stream are not supported yet"); + return NULL; + } + return scr_dyn_string_coerce_js(type); +} + +static bool bcs_check_listener(const ScrDyn *listener) { + if (listener->kind == SCR_DYN_FUNC) return true; + if (scr_dyn_is_callable(listener)) { + bcs_unsupported("listeners other than functions on a constructor-function stream are not supported yet"); + return false; + } + scr_dyn_arg_type_fail("listener", "of type function", listener); + return false; +} + +static bool bcs_add_listener(ScrStream *s, ScrDyn *self, ScrDyn *type, ScrDyn *listener, bool once, + bool prepend) { + if (!bcs_check_listener(listener)) return false; + ScrStr *name = bcs_event_name(type); + if (!name) return false; + ScrEmitter *em = (ScrEmitter *)s; + ScrEmitter *chained = NULL; + switch (bcs_event_shape(name)) { + case 0: + chained = scr_emitter_on_dyn(em, name, listener, bcs_adapter(listener, self, (void *)bcs_listener0), + scr_ee_inv_fixed0, once, prepend); + break; + case 1: + chained = scr_emitter_on_dyn(em, name, listener, bcs_adapter(listener, self, (void *)bcs_listener_data), + scr_ee_inv_fixed2, once, prepend); + break; + case 2: + chained = scr_emitter_on_dyn(em, name, listener, bcs_adapter(listener, self, (void *)bcs_listener_error), + scr_ee_inv_fixed1, once, prepend); + break; + case 3: + chained = scr_emitter_on_dyn(em, name, listener, bcs_adapter(listener, self, (void *)bcs_listener_stream), + scr_ee_inv_fixed1, once, prepend); + break; + case 4: + chained = scr_emitter_on_dyn(em, name, listener, bcs_adapter(listener, self, (void *)bcs_listener_name), + scr_ee_inv_fixed1, once, prepend); + break; + default: { + ScrClosure *adapter = bcs_adapter(listener, self, (void *)bcs_listener_flex); + ScrDyn *wrapper = scr_dyn_new_func(adapter, bcs_listener_flex, 0, "native:events", "listener"); + chained = scr_emitter_on_flex(em, name, wrapper, once, prepend); + scr_dyn_release(wrapper); + break; + } + } + if (chained) scr_emitter_release(chained); + scr_str_release(name); + return !scr_exc_pending(); +} + +/* this.emit(type, ...args) on the runtime emitter: the fixed tuples the + * runtime's own listeners read, computed events with every argument. */ +static ScrDyn *bcs_emit(ScrStream *s, ScrDyn *const *args, size_t argc) { + ScrStr *name = bcs_event_name(bcs_arg(args, argc, 0)); + if (!name) return NULL; + ScrEmitter *em = (ScrEmitter *)s; + ScrDyn *const *rest = argc > 1 ? args + 1 : NULL; + size_t restc = argc > 1 ? argc - 1 : 0; + ScrDyn *first = restc ? rest[0] : scr_dyn_undefined(); + bool had = false; + switch (bcs_event_shape(name)) { + case 0: + had = scr_emitter_emit(em, name); + break; + case 1: + if (first->kind == SCR_DYN_BYTES) { + ScrBytes *bytes = scr_bytes_raw_view(first->v.bytes); + had = scr_emitter_emit(em, name, bytes, (ScrStr *)NULL); + scr_bytes_release(bytes); + } else if (first->kind == SCR_DYN_STR) { + had = scr_emitter_emit(em, name, (ScrBytes *)NULL, first->v.str); + } else { + bcs_unsupported("emit('data') with a chunk other than a Buffer or string on a constructor-function stream is not supported yet"); + } + break; + case 2: + if (!scr_emitter_has(em, "error")) { + /* Node's unhandled 'error': an Error throws itself, anything else + * ERR_UNHANDLED_ERROR. */ + if (scr_dyn_err_instanceof(first, SCR_ERR_ERROR)) { + scr_dyn_throw(scr_dyn_retain(first)); + } else { + ScrStr *shown = scr_insp_dyn(first, 0, 2); + ScrJsonBuf text; + scr_jb_init(&text); + scr_jb_puts(&text, "Unhandled error. ("); + scr_jb_put_str(&text, shown); + scr_jb_putc(&text, ')'); + scr_str_release(shown); + ScrStr *message = scr_jb_finish(&text); + scr_throw_error_msg_code(SCR_ERR_ERROR, message->data, message->len, "ERR_UNHANDLED_ERROR"); + scr_str_release(message); + } + } else { + ScrError *err = bcs_error_of(first); + if (!err) { + ScrStr *empty = scr_str_new("", 0); + err = scr_error_new(SCR_ERR_ERROR, empty); + scr_str_release(empty); + } + had = scr_emitter_emit_error(em, name, err); + scr_error_release(err); + } + break; + case 3: { + ScrStream *stream = scr_dyn_builtin_stream_of(first); + if (stream) { + had = scr_emitter_emit(em, name, stream); + scr_stream_release(stream); + } else { + bcs_unsupported("emit('pipe') or emit('unpipe') with a value other than a stream on a constructor-function stream is not supported yet"); + } + break; + } + case 4: + if (first->kind == SCR_DYN_STR) had = scr_emitter_emit(em, name, first->v.str); + else bcs_unsupported("emit('newListener') or emit('removeListener') with a name other than a string on a constructor-function stream is not supported yet"); + break; + default: + had = scr_emitter_emit_flex(em, name, rest, restc); + break; + } + scr_str_release(name); + if (scr_exc_pending()) return NULL; + return scr_dyn_new_bool(had); +} + +static ScrDyn *bcs_event_names(ScrStream *s) { + ScrArr *names = scr_emitter_event_names((ScrEmitter *)s); + ScrDyn *result = scr_dyn_new_arr(); + double length = scr_arr_len(names); + for (double i = 0; i < length; i++) { + ScrStr *name = scr_arr_get_ref(names, i); + scr_dyn_arr_push(result, scr_dyn_new_str(name)); + scr_str_release(name); + } + scr_arr_release(names); + return result; +} + +/* EventEmitter.prototype's methods on a constructor-function stream + * (called by scr_dyn_builtin_class.c): true when self has a stream. */ +static bool bcs_event(int method, ScrDyn *self, ScrDyn *const *args, size_t argc, ScrDyn **result) { + ScrStream *s = bcs_of(self); + if (!s) return false; + ScrDyn *type = bcs_arg(args, argc, 0); + ScrEmitter *em = (ScrEmitter *)s; + ScrDyn *out = NULL; + switch (method) { + case SCR_EE_M_ON: + case SCR_EE_M_PREPEND: + case SCR_EE_M_ONCE: + case SCR_EE_M_PREPEND_ONCE: + if (bcs_add_listener(s, self, type, bcs_arg(args, argc, 1), + method == SCR_EE_M_ONCE || method == SCR_EE_M_PREPEND_ONCE, + method == SCR_EE_M_PREPEND || method == SCR_EE_M_PREPEND_ONCE)) + out = scr_dyn_retain(self); + break; + case SCR_EE_M_OFF: { + ScrDyn *listener = bcs_arg(args, argc, 1); + if (!bcs_check_listener(listener)) break; + ScrStr *name = bcs_event_name(type); + if (!name) break; + scr_emitter_release(scr_emitter_off_dyn(em, name, listener)); + scr_str_release(name); + if (!scr_exc_pending()) out = scr_dyn_retain(self); + break; + } + case SCR_EE_M_REMOVE_ALL: { + ScrStr *name = argc == 0 || type->kind == SCR_DYN_UNDEF ? NULL : bcs_event_name(type); + if (argc > 0 && type->kind != SCR_DYN_UNDEF && !name) break; + if (!name) name = scr_str_new("", 0); + scr_emitter_release(scr_emitter_remove_all(em, name, argc == 0 || type->kind == SCR_DYN_UNDEF)); + scr_str_release(name); + if (!scr_exc_pending()) out = scr_dyn_retain(self); + break; + } + case SCR_EE_M_EMIT: + out = bcs_emit(s, args, argc); + break; + case SCR_EE_M_LISTENER_COUNT: { + ScrStr *name = bcs_event_name(type); + if (!name) break; + ScrDyn *only = bcs_arg(args, argc, 1); + double count = bcs_nullish(only) ? scr_emitter_listener_count(em, name) + : only->kind == SCR_DYN_FUNC ? scr_emitter_listener_count_dyn(em, name, only) : 0; + scr_str_release(name); + if (!scr_exc_pending()) out = scr_dyn_new_num(count); + break; + } + case SCR_EE_M_EVENT_NAMES: + out = bcs_event_names(s); + break; + case SCR_EE_M_SET_MAX: + scr_emitter_release(scr_emitter_set_max(em, bcs_arg(args, argc, 0)->v.num)); + out = scr_dyn_retain(self); + break; + case SCR_EE_M_GET_MAX: + out = scr_dyn_new_num(scr_emitter_get_max(em)); + break; + default: + bcs_unsupported("listeners() and rawListeners() on a constructor-function stream are not supported yet"); + break; + } + *result = scr_exc_pending() ? (out ? (scr_dyn_release(out), NULL) : NULL) : out; + return true; +} + +/* ── construction ───────────────────────────────────────────────────── */ + +static bool bcs_is_function(ScrDyn *object, const char *name, bool *failed) { + ScrDyn *value = bcs_get(object, name); + if (!value) { + *failed = true; + return false; + } + bool callable = scr_dyn_is_callable(value); + scr_dyn_release(value); + return callable; +} + +/* Node's constructors copy these options onto the instance. */ +static bool bcs_copy_option(ScrDyn *self, ScrDyn *opts, const char *option, const char *property) { + ScrDyn *value = bcs_get(opts, option); + if (!value) return false; + if (scr_dyn_is_callable(value)) return bcs_set(self, property, value); + scr_dyn_release(value); + return true; +} + +static void bcs_class_name(ScrDyn *self, char *out, size_t size) { + snprintf(out, size, "%s", "Object"); + ScrDyn *ctor = bcs_get(self, "constructor"); + if (!ctor) { + scr_exc_clear(); + return; + } + if (scr_dyn_is_callable(ctor)) { + ScrDyn *name = bcs_get(ctor, "name"); + if (name && name->kind == SCR_DYN_STR && name->v.str->len > 0) + snprintf(out, size, "%.*s", (int)name->v.str->len, name->v.str->data); + if (name) scr_dyn_release(name); + else scr_exc_clear(); + } + scr_dyn_release(ctor); +} + +static ScrDyn *bcs_state_proto(bool readable); + +/* Listeners added before the constructor ran (bl registers its 'pipe' + * listener first) sit in the instance's own _events, where Node's + * constructor keeps them: they move to the runtime emitter, in order. */ +static bool bcs_adopt_listeners(ScrStream *s, ScrDyn *self) { + ScrDyn *events = bcs_get(self, "_events"); + if (!events) return false; + if (events->kind != SCR_DYN_OBJ) { + scr_dyn_release(events); + return true; + } + ScrDyn *keys = scr_dyn_own_keys(events); + bool ok = keys != NULL; + size_t count = ok ? (size_t)scr_dyn_arr_len(keys) : 0; + for (size_t i = 0; ok && i < count; i++) { + ScrDyn *key = scr_dyn_index_get(keys, (double)i); + ScrDyn *list = key ? scr_dyn_reflect_get(events, key, events) : NULL; + if (!list) { + if (key) scr_dyn_release(key); + ok = false; + break; + } + if (list->kind == SCR_DYN_ARR) { + size_t length = (size_t)scr_dyn_arr_len(list); + for (size_t j = 0; ok && j < length; j++) { + ScrDyn *listener = scr_dyn_index_get(list, (double)j); + ok = listener && bcs_add_listener(s, self, key, listener, false, false); + if (listener) scr_dyn_release(listener); + } + } else if (scr_dyn_is_callable(list)) { + ok = bcs_add_listener(s, self, key, list, false, false); + } + scr_dyn_release(list); + scr_dyn_release(key); + } + if (keys) scr_dyn_release(keys); + scr_dyn_release(events); + if (ok && count > 0) + ok = bcs_set(self, "_events", scr_dyn_new_obj_null_proto()) && bcs_set(self, "_eventsCount", scr_dyn_new_num(0)); + return ok; +} + +static bool bcs_state_view(ScrDyn *self, ScrStream *s, bool readable) { + ScrDyn *view = scr_dyn_obj_create(bcs_state_proto(readable)); + bcs_hide(view, s); + return bcs_set(self, readable ? "_readableState" : "_writableState", view); +} + +static ScrDyn *bcs_construct(int kind, ScrDyn *self, ScrDyn *const *args, size_t argc) { + static const char *const names[] = { "", "", "Readable", "Writable", "Duplex", "Transform", "PassThrough" }; + char message[192]; + if (self->kind != SCR_DYN_OBJ || !scr_dyn_builtin_instanceof(self, kind)) { + if (scr_exc_pending()) return NULL; + snprintf(message, sizeof message, + "%s.call() on a receiver that does not inherit %s.prototype is not supported yet", + names[kind], names[kind]); + bcs_unsupported(message); + return NULL; + } + if (bcs_of(self)) { + snprintf(message, sizeof message, + "%s.call() on a receiver that already has stream state is not supported yet", names[kind]); + bcs_unsupported(message); + return NULL; + } + bool failed = false; + bool writes = kind != BCS_READABLE; + if (writes && bcs_is_function(self, "_writev", &failed)) { + bcs_unsupported("_writev on a constructor-function stream is not supported yet"); + return NULL; + } + if (!failed && bcs_is_function(self, "_construct", &failed)) { + bcs_unsupported("_construct on a constructor-function stream is not supported yet"); + return NULL; + } + if (failed) return NULL; + ScrDyn *opts = bcs_arg(args, argc, 0); + ScrStream *s = kind == BCS_READABLE ? scr_stream_new_readable_dyn(opts) + : kind == BCS_WRITABLE ? scr_stream_new_writable_dyn(opts) + : kind == BCS_DUPLEX ? scr_stream_new_duplex_dyn(opts) + : kind == BCS_TRANSFORM ? scr_stream_new_transform_dyn(opts) + : scr_stream_new_passthrough_dyn(opts); + if (!s) return NULL; + bool ok = true; + if (opts->kind == SCR_DYN_OBJ) { + bool transformish = kind == BCS_TRANSFORM || kind == BCS_PASSTHROUGH; + if (!transformish && kind != BCS_WRITABLE) ok = ok && bcs_copy_option(self, opts, "read", "_read"); + if (!transformish && kind != BCS_READABLE) { + ok = ok && bcs_copy_option(self, opts, "write", "_write"); + ok = ok && bcs_copy_option(self, opts, "final", "_final"); + } + ok = ok && bcs_copy_option(self, opts, "destroy", "_destroy"); + if (transformish) { + ok = ok && bcs_copy_option(self, opts, "transform", "_transform"); + ok = ok && bcs_copy_option(self, opts, "flush", "_flush"); + } + } + if (ok && kind >= BCS_DUPLEX) + ok = bcs_set(self, "allowHalfOpen", scr_dyn_new_bool(scr_stream_prop(s, "allowHalfOpen") != 0)); + if (!ok) { + scr_stream_release(s); + return NULL; + } + BcsRec *rec = calloc(1, sizeof *rec); + if (!rec) scr_trap("out of memory"); + rec->object = self; + bcs_class_name(self, rec->cls, sizeof rec->cls); + scr_stream_set_native(s, rec, bcs_rec_release); + s->cls = rec->cls; + /* The runtime calls the instance's underscore methods. */ + if (kind == BCS_READABLE || kind == BCS_DUPLEX) + scr_stream_set_read(s, bcs_hook(self, (void *)bcs_read_inv), bcs_read_inv); + if (kind == BCS_WRITABLE || kind == BCS_DUPLEX) { + scr_stream_set_write(s, bcs_hook(self, (void *)bcs_write_inv), bcs_write_inv); + if (bcs_is_function(self, "_final", &failed)) scr_stream_set_final(s, bcs_hook(self, (void *)bcs_final_inv), bcs_final_inv); + else scr_stream_set_final(s, NULL, NULL); + } + if (kind == BCS_TRANSFORM || kind == BCS_PASSTHROUGH) { + bool identity = false; + if (kind == BCS_PASSTHROUGH) { + ScrDyn *own = bcs_get(self, "_transform"); + ScrDyn *fallback = bcs_default_method(BCS_PASSTHROUGH, "_transform"); + identity = own && fallback && scr_dyn_strict_eq(own, fallback); + if (own) scr_dyn_release(own); + else failed = true; + if (fallback) scr_dyn_release(fallback); + } + if (identity) scr_stream_set_transform(s, NULL, NULL); + else scr_stream_set_transform(s, bcs_hook(self, (void *)bcs_transform_inv), bcs_transform_inv); + if (bcs_is_function(self, "_flush", &failed)) scr_stream_set_flush(s, bcs_hook(self, (void *)bcs_flush_inv), bcs_flush_inv); + else scr_stream_set_flush(s, NULL, NULL); + if (!failed && bcs_is_function(self, "_final", &failed)) { + bcs_unsupported("_final on a constructor-function Transform is not supported yet"); + failed = true; + } + } + scr_stream_set_destroy(s, bcs_hook(self, (void *)bcs_destroy_inv), bcs_destroy_inv); + bcs_hide(self, s); + if (!failed) failed = !bcs_adopt_listeners(s, self); + if (!failed && kind != BCS_WRITABLE) failed = !bcs_state_view(self, s, true); + if (!failed && kind != BCS_READABLE) failed = !bcs_state_view(self, s, false); + scr_stream_release(s); /* the capsule holds it */ + if (failed || scr_exc_pending()) return NULL; + return bcs_undefined(); +} + +/* ── prototype methods ──────────────────────────────────────────────── */ + +/* Node's encoding names, canonical; NULL with ERR_UNKNOWN_ENCODING. */ +static ScrStr *bcs_encoding(const ScrDyn *value) { + static const struct { const char *from, *to; } map[] = { + {"utf8", "utf8"}, {"utf-8", "utf8"}, {"hex", "hex"}, {"base64", "base64"}, + {"base64url", "base64url"}, {"latin1", "latin1"}, {"binary", "latin1"}, + {"ascii", "ascii"}, {"utf16le", "utf16le"}, {"utf-16le", "utf16le"}, + {"ucs2", "utf16le"}, {"ucs-2", "utf16le"}, + }; + ScrStr *raw = value->kind == SCR_DYN_STR ? scr_str_retain(value->v.str) : scr_dyn_string_coerce_js(value); + if (!raw) return NULL; + for (size_t i = 0; i < sizeof map / sizeof *map; i++) { + if (bcs_is(raw, map[i].from)) { + scr_str_release(raw); + return scr_str_new(map[i].to, strlen(map[i].to)); + } + } + char message[128]; + snprintf(message, sizeof message, "Unknown encoding: %.*s", (int)(raw->len < 64 ? raw->len : 64), raw->data); + scr_str_release(raw); + scr_throw_error_msg_code(SCR_ERR_TYPE, message, strlen(message), "ERR_UNKNOWN_ENCODING"); + return NULL; +} + +enum { + BCS_M_PUSH, BCS_M_UNSHIFT, BCS_M_READ, BCS_M_SET_ENCODING, BCS_M_PAUSE, BCS_M_RESUME, + BCS_M_IS_PAUSED, BCS_M_PIPE, BCS_M_UNPIPE, BCS_M_DESTROY, BCS_M_WRITE, BCS_M_END, + BCS_M_CORK, BCS_M_UNCORK, BCS_M_SET_DEFAULT_ENCODING, BCS_M_WRITABLE_PIPE, BCS_M_WRAP, + BCS_M_READ_DEFAULT, BCS_M_WRITE_DEFAULT, BCS_M_TRANSFORM_DEFAULT, BCS_M_DESTROY_DEFAULT, + BCS_M_PASSTHROUGH_TRANSFORM, BCS_M_ON, BCS_M_OFF, BCS_M_REMOVE_ALL, +}; + +enum { BCS_CB_STREAM = BCS_L_CAPS, BCS_CB_CAPS }; + +/* A write or end callback: null after a successful write and at 'finish'; + * once the stream failed, its error, or Node's ERR_STREAM_DESTROYED for a + * write a destroy cut off. */ +static void bcs_callback_fire(ScrClosure *closure) { + ScrDyn *fn = scr_box_get_ref(closure->caps[BCS_L_FN]); + ScrDyn *self = scr_box_get_ref(closure->caps[BCS_L_SELF]); + ScrStream *s = scr_box_get_ref(closure->caps[BCS_CB_STREAM]); + ScrError *err = scr_stream_errored(s); + ScrDyn *arg; + if (err) { + arg = scr_dyn_from_error(err); + scr_error_release(err); + } else if (scr_stream_prop(s, "destroyed") != 0) { + ScrStr *message = scr_str_new("Cannot call write after a stream was destroyed", 46); + ScrError *destroyed = scr_error_new(SCR_ERR_ERROR, message); + scr_str_release(message); + scr_error_set_code(destroyed, "ERR_STREAM_DESTROYED"); + arg = scr_dyn_from_error(destroyed); + scr_error_release(destroyed); + } else { + arg = scr_dyn_new_null(); + } + scr_stream_release(s); + ScrDyn *result = bcs_apply(fn, self, &arg, 1, "callback"); + if (result) scr_dyn_release(result); + scr_dyn_release(arg); + scr_dyn_release(self); + scr_dyn_release(fn); +} + +static ScrClosure *bcs_callback(ScrDyn *fn, ScrDyn *self, ScrStream *s) { + if (!scr_dyn_is_callable(fn)) return NULL; + ScrClosure *closure = scr_closure_new((void *)bcs_callback_fire, BCS_CB_CAPS); + closure->caps[BCS_L_FN] = bcs_box(scr_dyn_retain(fn)); + closure->caps[BCS_L_SELF] = bcs_box(scr_dyn_retain(self)); + closure->caps[BCS_CB_STREAM] = scr_box_new_obj(scr_stream_retain_v, scr_stream_release_v, scr_stream_trace); + scr_box_set_ref(closure->caps[BCS_CB_STREAM], scr_stream_retain(s)); + return closure; +} + +/* Strings in another encoding than utf8 have no write path here. */ +static bool bcs_utf8_only(const ScrDyn *encoding) { + if (encoding->kind != SCR_DYN_STR) return true; + ScrStr *canonical = bcs_encoding(encoding); + if (!canonical) return false; + bool utf8 = bcs_is(canonical, "utf8"); + scr_str_release(canonical); + if (!utf8) bcs_unsupported("writing strings in an encoding other than utf8 to a constructor-function stream is not supported yet"); + return utf8; +} + +static ScrDyn *bcs_write(ScrStream *s, ScrDyn *self, ScrDyn *const *args, size_t argc) { + ScrDyn *chunk = bcs_arg(args, argc, 0); + ScrDyn *encoding = bcs_arg(args, argc, 1); + ScrDyn *cb = bcs_arg(args, argc, 2); + if (scr_dyn_is_callable(encoding)) { + cb = encoding; + encoding = scr_dyn_undefined(); + } + if (chunk->kind == SCR_DYN_STR && !bcs_utf8_only(encoding)) return NULL; + bool below = scr_stream_write_dyn(s, chunk, bcs_callback(cb, self, s)); + if (scr_exc_pending()) return NULL; + return scr_dyn_new_bool(below); +} + +static ScrDyn *bcs_end(ScrStream *s, ScrDyn *self, ScrDyn *const *args, size_t argc) { + ScrDyn *chunk = bcs_arg(args, argc, 0); + ScrDyn *encoding = bcs_arg(args, argc, 1); + ScrDyn *cb = bcs_arg(args, argc, 2); + if (scr_dyn_is_callable(chunk)) { + cb = chunk; + chunk = scr_dyn_undefined(); + encoding = scr_dyn_undefined(); + } else if (scr_dyn_is_callable(encoding)) { + cb = encoding; + encoding = scr_dyn_undefined(); + } + if (chunk->kind == SCR_DYN_STR && !bcs_utf8_only(encoding)) return NULL; + ScrStream *chained = NULL; + if (bcs_nullish(chunk)) { + chained = scr_stream_end(s, NULL, NULL, bcs_callback(cb, self, s)); + } else if (chunk->kind == SCR_DYN_STR) { + chained = scr_stream_end(s, NULL, chunk->v.str, bcs_callback(cb, self, s)); + } else if (chunk->kind == SCR_DYN_BYTES) { + ScrBytes *bytes = scr_bytes_raw_view(chunk->v.bytes); + chained = scr_stream_end(s, bytes, NULL, bcs_callback(cb, self, s)); + scr_bytes_release(bytes); + } else { + scr_dyn_arg_type_fail("chunk", "of type string or an instance of Buffer, TypedArray, or DataView", chunk); + } + if (chained) scr_stream_release(chained); + if (scr_exc_pending()) return NULL; + return scr_dyn_retain(self); +} + +static ScrDyn *bcs_destroy(ScrStream *s, ScrDyn *self, ScrDyn *const *args, size_t argc) { + ScrDyn *errv = bcs_arg(args, argc, 0); + ScrDyn *cb = bcs_arg(args, argc, 1); + if (scr_stream_prop(s, "destroyed") != 0) { + if (scr_dyn_is_callable(cb)) { + ScrDyn *result = bcs_apply(cb, self, NULL, 0, "callback"); + if (!result) return NULL; + scr_dyn_release(result); + } + return scr_dyn_retain(self); + } + if (scr_dyn_is_callable(cb)) { + BcsRec *rec = bcs_rec(s); + if (rec->destroy_n == rec->destroy_cap) { + rec->destroy_cap = rec->destroy_cap ? rec->destroy_cap * 2 : 2; + rec->destroy_cbs = realloc(rec->destroy_cbs, rec->destroy_cap * sizeof *rec->destroy_cbs); + if (!rec->destroy_cbs) scr_trap("out of memory"); + } + rec->destroy_cbs[rec->destroy_n++] = scr_dyn_retain(cb); + } + ScrError *err = bcs_error_of(errv); + ScrStream *chained = scr_stream_destroy(s, err); + if (err) scr_error_release(err); + scr_stream_release(chained); + if (scr_exc_pending()) return NULL; + return scr_dyn_retain(self); +} + +/* pipe(dest, { end }): runtime streams and constructor-function streams + * pipe through the runtime's pipe. */ +static bool bcs_overrides(ScrDyn *dest, const char *name); + +static ScrDyn *bcs_pipe(ScrStream *s, ScrDyn *const *args, size_t argc) { + ScrDyn *dest = bcs_arg(args, argc, 0); + ScrDyn *options = bcs_arg(args, argc, 1); + ScrStream *d = scr_dyn_builtin_stream_of(dest); + if (!d) { + if (!scr_exc_pending()) bcs_unsupported("pipe destinations other than streams are not supported yet"); + return NULL; + } + /* The runtime's pipe writes and ends the destination's stream itself; + * Node calls the destination's write() and end(). */ + if (bcs_of(dest) && (bcs_overrides(dest, "write") || bcs_overrides(dest, "end"))) { + scr_stream_release(d); + if (!scr_exc_pending()) + bcs_unsupported("pipe into a constructor-function stream with its own write() or end() is not supported yet"); + return NULL; + } + bool end = true; + if (options->kind == SCR_DYN_OBJ) { + ScrDyn *option = bcs_get(options, "end"); + if (!option) { + scr_stream_release(d); + return NULL; + } + end = !(option->kind == SCR_DYN_BOOL && !option->v.b); + scr_dyn_release(option); + } + ScrStream *chained = scr_stream_pipe(s, d, end); + if (chained) scr_stream_release(chained); + scr_stream_release(d); + if (scr_exc_pending()) return NULL; + return scr_dyn_retain(dest); +} + +static ScrDyn *bcs_not_implemented(const char *method) { + char message[64]; + snprintf(message, sizeof message, "The %s method is not implemented", method); + scr_throw_error_msg_code(SCR_ERR_ERROR, message, strlen(message), "ERR_METHOD_NOT_IMPLEMENTED"); + return NULL; +} + +static ScrDyn *bcs_method_call(ScrClosure *closure, ScrDyn *const *args, size_t argc) { + int method = (int)scr_box_get_f64(closure->caps[0]); + switch (method) { + case BCS_M_READ_DEFAULT: + return bcs_not_implemented("_read()"); + case BCS_M_WRITE_DEFAULT: + return bcs_not_implemented("_write()"); + case BCS_M_TRANSFORM_DEFAULT: + return bcs_not_implemented("_transform()"); + case BCS_M_DESTROY_DEFAULT: + case BCS_M_PASSTHROUGH_TRANSFORM: { + /* _destroy(err, cb) => cb(err); _transform(chunk, enc, cb) => cb(null, chunk) */ + ScrDyn *cb = bcs_arg(args, argc, method == BCS_M_DESTROY_DEFAULT ? 1 : 2); + ScrDyn *pass[2]; + size_t passc = 1; + if (method == BCS_M_DESTROY_DEFAULT) { + pass[0] = bcs_arg(args, argc, 0); + } else { + pass[0] = scr_dyn_new_null(); + pass[1] = bcs_arg(args, argc, 0); + passc = 2; + } + ScrDyn *answer = scr_dyn_call(cb, pass, passc, "callback"); + if (method != BCS_M_DESTROY_DEFAULT) scr_dyn_release(pass[0]); + return answer; + } + default: + break; + } + ScrDyn *self = scr_dyn_this_get(); + static const char *const reading[] = { + "push", "unshift", "read", "setEncoding", "pause", "resume", "isPaused", "pipe", "unpipe", + "destroy", "write", "end", "cork", "uncork", "setDefaultEncoding", "pipe", "wrap", + "", "", "", "", "", "on", "removeListener", "removeAllListeners", + }; + ScrDyn *result = NULL; + if (method == BCS_M_WRITABLE_PIPE) { + ScrStream *s = bcs_need(self, "pipe"); + if (s) { + ScrStr *message = scr_str_new("Cannot pipe, not readable", 25); + ScrError *err = scr_error_new(SCR_ERR_ERROR, message); + scr_str_release(message); + scr_error_set_code(err, "ERR_STREAM_CANNOT_PIPE"); + scr_stream_fail(s, err); + scr_error_release(err); + if (!scr_exc_pending()) result = scr_dyn_retain(bcs_arg(args, argc, 0)); + } + scr_dyn_release(self); + return result; + } + if (method == BCS_M_ON || method == BCS_M_OFF || method == BCS_M_REMOVE_ALL) { + int ee = method == BCS_M_ON ? SCR_EE_M_ON : method == BCS_M_OFF ? SCR_EE_M_OFF : SCR_EE_M_REMOVE_ALL; + if (!bcs_event(ee, self, args, argc, &result)) { + /* Before the constructor ran (bl adds listeners first), Node's + * Readable.prototype.on is Stream.prototype.on over _events. */ + ScrDyn *emitter = scr_dyn_builtin_class(0); + ScrDyn *prototype = bcs_get(emitter, "prototype"); + scr_dyn_release(emitter); + ScrDyn *fn = prototype ? bcs_get(prototype, reading[method]) : NULL; + result = fn ? bcs_apply(fn, self, args, argc, reading[method]) : NULL; + if (fn) scr_dyn_release(fn); + if (prototype) scr_dyn_release(prototype); + } + scr_dyn_release(self); + return result; + } + ScrStream *s = bcs_need(self, reading[method]); + if (!s) { + scr_dyn_release(self); + return NULL; + } + ScrDyn *first = bcs_arg(args, argc, 0); + switch (method) { + case BCS_M_PUSH: + case BCS_M_UNSHIFT: { + ScrDyn *encoding = bcs_arg(args, argc, 1); + if (method == BCS_M_PUSH && first->kind == SCR_DYN_STR && encoding->kind == SCR_DYN_STR) { + ScrStr *canonical = bcs_encoding(encoding); + if (canonical) { + result = scr_dyn_new_bool(scr_stream_push_str_enc(s, first->v.str, canonical)); + scr_str_release(canonical); + } + } else if (method == BCS_M_PUSH) { + if (first->kind == SCR_DYN_UNDEF) { + ScrStr *empty = scr_str_new("", 0); + result = scr_dyn_new_bool(scr_stream_push_str(s, empty)); + scr_str_release(empty); + } else { + result = scr_dyn_new_bool(scr_stream_push_dyn(s, first)); + } + } else if (first->kind == SCR_DYN_STR) { + scr_stream_unshift_str(s, first->v.str); + result = bcs_undefined(); + } else if (first->kind == SCR_DYN_BYTES) { + ScrBytes *bytes = scr_bytes_raw_view(first->v.bytes); + scr_stream_unshift(s, bytes); + scr_bytes_release(bytes); + result = bcs_undefined(); + } else if (first->kind == SCR_DYN_NULL) { + scr_stream_push_null(s); + result = bcs_undefined(); + } else { + scr_dyn_arg_type_fail("chunk", "of type string or an instance of Buffer or Uint8Array", first); + } + break; + } + case BCS_M_READ: + result = scr_stream_read_dyn(s, first); + break; + case BCS_M_SET_ENCODING: { + ScrStr *canonical = bcs_encoding(first->kind == SCR_DYN_UNDEF ? scr_dyn_undefined() : first); + if (canonical) { + scr_stream_release(scr_stream_set_encoding(s, canonical)); + scr_str_release(canonical); + result = scr_dyn_retain(self); + } + break; + } + case BCS_M_PAUSE: + scr_stream_release(scr_stream_pause(s)); + result = scr_dyn_retain(self); + break; + case BCS_M_RESUME: + scr_stream_release(scr_stream_resume(s)); + result = scr_dyn_retain(self); + break; + case BCS_M_IS_PAUSED: + result = scr_dyn_new_bool(scr_stream_is_paused(s)); + break; + case BCS_M_PIPE: + result = bcs_pipe(s, args, argc); + break; + case BCS_M_UNPIPE: { + ScrStream *d = first->kind == SCR_DYN_UNDEF ? NULL : scr_dyn_builtin_stream_of(first); + if (first->kind == SCR_DYN_UNDEF || d) { + scr_stream_release(scr_stream_unpipe(s, d)); + if (d) scr_stream_release(d); + } + if (!scr_exc_pending()) result = scr_dyn_retain(self); + break; + } + case BCS_M_DESTROY: + result = bcs_destroy(s, self, args, argc); + break; + case BCS_M_WRITE: + result = bcs_write(s, self, args, argc); + break; + case BCS_M_END: + result = bcs_end(s, self, args, argc); + break; + case BCS_M_CORK: + scr_stream_cork(s); + result = bcs_undefined(); + break; + case BCS_M_UNCORK: + scr_stream_uncork(s); + result = bcs_undefined(); + break; + case BCS_M_SET_DEFAULT_ENCODING: + if (bcs_utf8_only(first)) result = scr_dyn_retain(self); + break; + default: + bcs_unsupported("Readable.prototype.wrap on a constructor-function stream is not supported yet"); + break; + } + scr_dyn_release(self); + if (scr_exc_pending()) { + if (result) scr_dyn_release(result); + return NULL; + } + return result; +} + +static ScrDyn *bcs_function(int method, const char *name, uint32_t arity) { + ScrClosure *closure = scr_closure_new(NULL, 1); + closure->caps[0] = scr_box_new(SCR_BOX_F64); + scr_box_set_f64(closure->caps[0], method); + return scr_dyn_new_func(closure, bcs_method_call, arity, "native:stream", name); +} + +static void bcs_method(ScrDyn *prototype, const char *name, int method, uint32_t arity) { + bcs_set(prototype, name, bcs_function(method, name, arity)); +} + +/* ── accessors ──────────────────────────────────────────────────────── */ + +enum { + BCS_P_READABLE, BCS_P_READABLE_ENDED, BCS_P_READABLE_LENGTH, BCS_P_READABLE_HWM, + BCS_P_READABLE_OBJECT_MODE, BCS_P_READABLE_FLOWING, BCS_P_WRITABLE, BCS_P_WRITABLE_ENDED, + BCS_P_WRITABLE_FINISHED, BCS_P_WRITABLE_LENGTH, BCS_P_WRITABLE_HWM, BCS_P_WRITABLE_NEED_DRAIN, + BCS_P_WRITABLE_CORKED, BCS_P_WRITABLE_OBJECT_MODE, BCS_P_DESTROYED, BCS_P_CLOSED, BCS_P_ERRORED, + BCS_P_COUNT +}; + +static const char *const bcs_property_names[BCS_P_COUNT] = { + "readable", "readableEnded", "readableLength", "readableHighWaterMark", "readableObjectMode", + "readableFlowing", "writable", "writableEnded", "writableFinished", "writableLength", + "writableHighWaterMark", "writableNeedDrain", "writableCorked", "writableObjectMode", + "destroyed", "closed", "errored", +}; + +static ScrDyn *bcs_flowing(ScrStream *s) { + double flowing = scr_stream_flowing(s); + return flowing < 0 ? scr_dyn_new_null() : scr_dyn_new_bool(flowing != 0); +} + +static ScrDyn *bcs_errored(ScrStream *s) { + ScrError *err = scr_stream_errored(s); + if (!err) return scr_dyn_new_null(); + ScrDyn *value = scr_dyn_from_error(err); + scr_error_release(err); + return value; +} + +static ScrDyn *bcs_getter_call(ScrClosure *closure, ScrDyn *const *args, size_t argc) { + (void)args; + (void)argc; + int property = (int)scr_box_get_f64(closure->caps[0]); + ScrDyn *self = scr_dyn_this_get(); + ScrStream *s = bcs_of(self); + scr_dyn_release(self); + /* Node's getters answer undefined (or false) before the constructor ran. */ + if (!s) return property == BCS_P_DESTROYED ? scr_dyn_new_bool(false) : bcs_undefined(); + const char *name = bcs_property_names[property]; + switch (property) { + case BCS_P_READABLE_FLOWING: + return bcs_flowing(s); + case BCS_P_ERRORED: + return bcs_errored(s); + case BCS_P_WRITABLE_OBJECT_MODE: + return scr_dyn_new_bool(false); + case BCS_P_READABLE_LENGTH: + case BCS_P_READABLE_HWM: + case BCS_P_WRITABLE_LENGTH: + case BCS_P_WRITABLE_HWM: + case BCS_P_WRITABLE_CORKED: + return scr_dyn_new_num(scr_stream_prop(s, name)); + default: + return scr_dyn_new_bool(scr_stream_prop(s, name) != 0); + } +} + +/* The destroyed setter: Node sets the states' flag without destroying. + * The readable and writable setters store a flag the runtime state does + * not keep (legacy code assigns `this.readable = true`), so they are + * accepted and have no effect. */ +static ScrDyn *bcs_setter_call(ScrClosure *closure, ScrDyn *const *args, size_t argc) { + int property = (int)scr_box_get_f64(closure->caps[0]); + ScrDyn *self = scr_dyn_this_get(); + ScrStream *s = bcs_of(self); + scr_dyn_release(self); + if (s && property == BCS_P_DESTROYED) scr_stream_set_destroyed(s, scr_dyn_truthy(bcs_arg(args, argc, 0))); + return bcs_undefined(); +} + +static void bcs_define(ScrDyn *target, const char *name, ScrDyn *getter, ScrDyn *setter) { + ScrDyn *descriptor = scr_dyn_new_obj(); + bcs_set(descriptor, "get", getter); + if (setter) bcs_set(descriptor, "set", setter); + bcs_set(descriptor, "enumerable", scr_dyn_new_bool(false)); + bcs_set(descriptor, "configurable", scr_dyn_new_bool(true)); + ScrDyn *key = bcs_key(name); + ScrDyn *defined = scr_dyn_define_property(target, key, descriptor); + if (defined) scr_dyn_release(defined); + scr_dyn_release(key); + scr_dyn_release(descriptor); +} + +static void bcs_accessor(ScrDyn *prototype, int property) { + ScrClosure *closure = scr_closure_new(NULL, 1); + closure->caps[0] = scr_box_new(SCR_BOX_F64); + scr_box_set_f64(closure->caps[0], property); + const char *name = bcs_property_names[property]; + ScrDyn *getter = scr_dyn_new_func(closure, bcs_getter_call, 0, "native:stream", name); + ScrDyn *setter = NULL; + if (property == BCS_P_DESTROYED || property == BCS_P_READABLE || property == BCS_P_WRITABLE) { + ScrClosure *set = scr_closure_new(NULL, 1); + set->caps[0] = scr_box_new(SCR_BOX_F64); + scr_box_set_f64(set->caps[0], property); + setter = scr_dyn_new_func(set, bcs_setter_call, 1, "native:stream", name); + } + bcs_define(prototype, name, getter, setter); +} + +/* ── _readableState / _writableState views ──────────────────────────── */ + +static const char *const bcs_rs_names[] = { + "highWaterMark", "length", "ended", "endEmitted", "reading", "destroyed", "objectMode", + "needReadable", "emittedReadable", "readableListening", "resumeScheduled", "errorEmitted", + "closed", "constructed", "flowing", "errored", +}; +static const char *const bcs_ws_names[] = { + "highWaterMark", "length", "ended", "ending", "finished", "prefinished", "destroyed", + "objectMode", "needDrain", "corked", "closed", "constructed", "errorEmitted", + "bufferedRequestCount", "errored", +}; +#define BCS_RS_COUNT (sizeof bcs_rs_names / sizeof *bcs_rs_names) +#define BCS_WS_COUNT (sizeof bcs_ws_names / sizeof *bcs_ws_names) + +static SCR_TL ScrDyn *bcs_state_protos[2]; +static SCR_TL bool bcs_teardown_registered; + +static ScrDyn *bcs_state_get(ScrClosure *closure, ScrDyn *const *args, size_t argc) { + (void)args; + (void)argc; + bool readable = scr_box_get_f64(closure->caps[0]) != 0; + size_t index = (size_t)scr_box_get_f64(closure->caps[1]); + const char *name = readable ? bcs_rs_names[index] : bcs_ws_names[index]; + ScrDyn *self = scr_dyn_this_get(); + ScrStream *s = bcs_of(self); + scr_dyn_release(self); + if (!s) return bcs_undefined(); + if (!strcmp(name, "flowing")) return bcs_flowing(s); + if (!strcmp(name, "errored")) return bcs_errored(s); + char key[48]; + snprintf(key, sizeof key, "%s:%s", readable ? "rs" : "ws", name); + double value = scr_stream_prop(s, key); + bool number = !strcmp(name, "highWaterMark") || !strcmp(name, "length") || !strcmp(name, "corked") || + !strcmp(name, "bufferedRequestCount"); + return number ? scr_dyn_new_num(value) : scr_dyn_new_bool(value != 0); +} + +static void bcs_teardown(void) { + for (int i = 0; i < 2; i++) { + if (bcs_state_protos[i]) scr_dyn_release(bcs_state_protos[i]); + bcs_state_protos[i] = NULL; + } + if (scr_exc_pending()) scr_exc_clear(); +} + +static ScrDyn *bcs_state_proto(bool readable) { + ScrDyn **slot = &bcs_state_protos[readable ? 0 : 1]; + if (!*slot) { + if (!bcs_teardown_registered) { + bcs_teardown_registered = true; + scr_atexit(bcs_teardown); + } + ScrDyn *proto = scr_dyn_new_obj(); + size_t count = readable ? BCS_RS_COUNT : BCS_WS_COUNT; + for (size_t i = 0; i < count; i++) { + ScrClosure *closure = scr_closure_new(NULL, 2); + closure->caps[0] = scr_box_new(SCR_BOX_F64); + scr_box_set_f64(closure->caps[0], readable ? 1 : 0); + closure->caps[1] = scr_box_new(SCR_BOX_F64); + scr_box_set_f64(closure->caps[1], (double)i); + const char *name = readable ? bcs_rs_names[i] : bcs_ws_names[i]; + bcs_define(proto, name, scr_dyn_new_func(closure, bcs_state_get, 0, "native:stream", name), NULL); + } + *slot = proto; + } + return *slot; +} + +/* ── the prototypes ─────────────────────────────────────────────────── */ + +static ScrDyn *bcs_default_method(int kind, const char *name) { + ScrDyn *ctor = scr_dyn_builtin_class(kind); + ScrDyn *prototype = bcs_get(ctor, "prototype"); + scr_dyn_release(ctor); + if (!prototype) return NULL; + ScrDyn *method = bcs_get(prototype, name); + scr_dyn_release(prototype); + return method; +} + +static void bcs_writable_members(ScrDyn *prototype) { + bcs_method(prototype, "write", BCS_M_WRITE, 3); + bcs_method(prototype, "cork", BCS_M_CORK, 0); + bcs_method(prototype, "uncork", BCS_M_UNCORK, 0); + bcs_method(prototype, "setDefaultEncoding", BCS_M_SET_DEFAULT_ENCODING, 1); + bcs_method(prototype, "_write", BCS_M_WRITE_DEFAULT, 3); + bcs_set(prototype, "_writev", scr_dyn_new_null()); + bcs_method(prototype, "end", BCS_M_END, 3); + for (int p = BCS_P_WRITABLE; p <= BCS_P_WRITABLE_OBJECT_MODE; p++) bcs_accessor(prototype, p); +} + +/* Node's stream prototypes: Readable and Writable carry their methods, + * Duplex adds Writable's, Transform and PassThrough their _transform. */ +static void bcs_prototype(int kind, ScrDyn *prototype) { + switch (kind) { + case BCS_READABLE: + bcs_method(prototype, "destroy", BCS_M_DESTROY, 2); + bcs_method(prototype, "_destroy", BCS_M_DESTROY_DEFAULT, 2); + bcs_method(prototype, "push", BCS_M_PUSH, 2); + bcs_method(prototype, "unshift", BCS_M_UNSHIFT, 2); + bcs_method(prototype, "isPaused", BCS_M_IS_PAUSED, 0); + bcs_method(prototype, "setEncoding", BCS_M_SET_ENCODING, 1); + bcs_method(prototype, "read", BCS_M_READ, 1); + bcs_method(prototype, "_read", BCS_M_READ_DEFAULT, 1); + bcs_method(prototype, "pipe", BCS_M_PIPE, 2); + bcs_method(prototype, "on", BCS_M_ON, 2); + { + ScrDyn *on = bcs_get(prototype, "on"); + if (on) bcs_set(prototype, "addListener", on); + } + bcs_method(prototype, "removeListener", BCS_M_OFF, 2); + { + ScrDyn *off = bcs_get(prototype, "removeListener"); + if (off) bcs_set(prototype, "off", off); + } + bcs_method(prototype, "removeAllListeners", BCS_M_REMOVE_ALL, 1); + bcs_method(prototype, "resume", BCS_M_RESUME, 0); + bcs_method(prototype, "pause", BCS_M_PAUSE, 0); + bcs_method(prototype, "unpipe", BCS_M_UNPIPE, 1); + bcs_method(prototype, "wrap", BCS_M_WRAP, 1); + for (int p = BCS_P_READABLE; p <= BCS_P_READABLE_FLOWING; p++) bcs_accessor(prototype, p); + bcs_accessor(prototype, BCS_P_DESTROYED); + bcs_accessor(prototype, BCS_P_CLOSED); + bcs_accessor(prototype, BCS_P_ERRORED); + break; + case BCS_WRITABLE: + bcs_method(prototype, "pipe", BCS_M_WRITABLE_PIPE, 0); + bcs_writable_members(prototype); + bcs_method(prototype, "destroy", BCS_M_DESTROY, 2); + bcs_method(prototype, "_destroy", BCS_M_DESTROY_DEFAULT, 2); + bcs_accessor(prototype, BCS_P_DESTROYED); + bcs_accessor(prototype, BCS_P_CLOSED); + bcs_accessor(prototype, BCS_P_ERRORED); + break; + case BCS_DUPLEX: + bcs_writable_members(prototype); + bcs_accessor(prototype, BCS_P_DESTROYED); + break; + case BCS_TRANSFORM: + bcs_method(prototype, "_transform", BCS_M_TRANSFORM_DEFAULT, 3); + break; + case BCS_PASSTHROUGH: + bcs_method(prototype, "_transform", BCS_M_PASSTHROUGH_TRANSFORM, 3); + break; + default: + break; + } + if (scr_exc_pending()) scr_exc_clear(); +} + +static const ScrDynStreamOps bcs_ops = { bcs_construct, bcs_prototype, bcs_event }; + +/* A stream class value (the BC_* kinds of scr_dyn_builtin_class.c) with + * its stream half attached, +1. */ +ScrDyn *scr_dyn_builtin_stream_class(double kind) { + scr_dyn_builtin_class_streams(&bcs_ops); + return scr_dyn_builtin_class(kind); +} + +/* Whether dest's method is its own rather than the stream prototypes'. */ +static bool bcs_overrides(ScrDyn *dest, const char *name) { + ScrDyn *own = bcs_get(dest, name); + if (!own) return true; + ScrDyn *fallback = bcs_default_method(BCS_WRITABLE, name); + ScrDyn *duplex = bcs_default_method(BCS_DUPLEX, name); + bool overridden = !(fallback && scr_dyn_strict_eq(own, fallback)) && !(duplex && scr_dyn_strict_eq(own, duplex)); + if (fallback) scr_dyn_release(fallback); + if (duplex) scr_dyn_release(duplex); + scr_dyn_release(own); + return overridden; +} + +/* pipe and unpipe on a runtime stream's untyped view, which also take + * constructor-function streams. */ +static ScrDyn *bcs_view_call(ScrClosure *closure, ScrDyn *const *args, size_t argc) { + ScrStream *s = scr_box_get_ref(closure->caps[0]); + bool pipe = scr_box_get_f64(closure->caps[1]) != 0; + ScrDyn *result = NULL; + if (pipe) { + result = bcs_pipe(s, args, argc); + } else { + ScrDyn *first = bcs_arg(args, argc, 0); + ScrStream *d = first->kind == SCR_DYN_UNDEF ? NULL : scr_dyn_builtin_stream_of(first); + if (first->kind == SCR_DYN_UNDEF || d) { + scr_stream_release(scr_stream_unpipe(s, d)); + if (d) scr_stream_release(d); + } else { + scr_dyn_arg_type_fail("destination", "an instance of Stream", first); + } + if (!scr_exc_pending()) result = scr_dyn_this_get(); + } + scr_stream_release(s); + return result; +} + +/* The untyped view of a runtime stream crossing into JavaScript, used in + * place of scr_stream_dyn_view by programs that link this unit: a + * constructor-function stream's own object, or the runtime view with pipe + * and unpipe that take those objects. +1. */ +ScrDyn *scr_dyn_builtin_stream_view(ScrStream *s) { + ScrDyn *object = scr_dyn_builtin_stream_object(s); + if (object) return object; + ScrDyn *view = scr_stream_dyn_view(s); + ScrDyn *prototype = scr_dyn_get_prototype(view); + if (prototype && prototype->kind == SCR_DYN_OBJ) { + for (int pipe = 0; pipe < 2; pipe++) { + ScrClosure *closure = scr_closure_new((void *)bcs_view_call, 2); + closure->caps[0] = scr_box_new_obj(scr_stream_retain_v, scr_stream_release_v, scr_stream_trace); + scr_box_set_ref(closure->caps[0], scr_stream_retain(s)); + closure->caps[1] = scr_box_new(SCR_BOX_F64); + scr_box_set_f64(closure->caps[1], pipe); + const char *name = pipe ? "pipe" : "unpipe"; + bcs_set(prototype, name, scr_dyn_new_func(closure, bcs_view_call, 0, "native stream method", name)); + } + } + if (prototype) scr_dyn_release(prototype); + if (scr_exc_pending()) scr_exc_clear(); + return view; +} + +/* A checked cast of an untyped value to a runtime stream class (kind: + * the BC_* numbering): the stream of a constructor-function stream of a + * matching class, +1, or NULL. */ +ScrStream *scr_dyn_builtin_stream_check(const ScrDyn *value, double kind) { + if (value->kind != SCR_DYN_OBJ) return NULL; + ScrStream *s = bcs_of(value); + if (!s) return NULL; + const ScrVt *vt = s->vt; + bool readable = vt->pre >= scr_readable_vt.pre && vt->pre <= scr_readable_vt.post; + bool writable = vt->pre >= scr_writable_vt.pre && vt->pre <= scr_writable_vt.post; + bool duplex = vt->pre >= scr_duplex_vt.pre && vt->pre <= scr_duplex_vt.post; + bool ok = false; + switch ((int)kind) { + case BCS_READABLE: ok = readable; break; + case BCS_WRITABLE: ok = writable || duplex; break; + case BCS_DUPLEX: ok = duplex; break; + case BCS_TRANSFORM: ok = vt->pre >= scr_transform_vt.pre && vt->pre <= scr_transform_vt.post; break; + case BCS_PASSTHROUGH: ok = vt->pre >= scr_passthrough_vt.pre && vt->pre <= scr_passthrough_vt.post; break; + default: break; + } + return ok ? scr_stream_retain(s) : NULL; +} + +/* readable.pipe(untyped) from typed code: a constructor-function stream + * or a boxed runtime stream as the destination. src borrowed; answers + * nothing (the expression's value is the destination). */ +void scr_dyn_builtin_stream_pipe(ScrStream *src, ScrDyn *dest, bool end) { + ScrDyn *options = scr_dyn_new_obj(); + bcs_set(options, "end", scr_dyn_new_bool(end)); + ScrDyn *args[2] = { dest, options }; + ScrDyn *result = bcs_pipe(src, args, 2); + if (result) scr_dyn_release(result); + scr_dyn_release(options); +} diff --git a/packages/runtime/src/scr_stream.c b/packages/runtime/src/scr_stream.c index 5f0bb493d..9fbf7a970 100644 --- a/packages/runtime/src/scr_stream.c +++ b/packages/runtime/src/scr_stream.c @@ -2254,6 +2254,8 @@ void scr_stream_set_flush(ScrStream *s, ScrClosure *cb /*moves*/, ScrStreamPlain void scr_stream_set_flush_on_prefinish(ScrStream *s) { s->st->flush_on_prefinish = true; } +void scr_stream_set_destroyed(ScrStream *s, bool destroyed) { s->st->destroyed = destroyed; } + void scr_stream_set_native(ScrStream *s, void *state /*moves*/, void (*release)(void *)) { ScrStreamState *st = s->st; if (st->native) st->native_release(st->native); diff --git a/packages/runtime/src/scr_stream.h b/packages/runtime/src/scr_stream.h index 82727c1dc..61993dba4 100644 --- a/packages/runtime/src/scr_stream.h +++ b/packages/runtime/src/scr_stream.h @@ -431,6 +431,9 @@ void scr_stream_set_flush(ScrStream *s, ScrClosure *cb, ScrStreamPlainInv inv); * released when the stream is freed; scr_stream_native answers it * (borrowed) when it was set with `release`, else NULL. */ void scr_stream_set_flush_on_prefinish(ScrStream *s); +/* `stream.destroyed = value` (scr_dyn_builtin_stream.c): Node's setter + * stores the states' flag without destroying the stream. */ +void scr_stream_set_destroyed(ScrStream *s, bool destroyed); void scr_stream_set_native(ScrStream *s, void *state, void (*release)(void *)); void *scr_stream_native(ScrStream *s, void (*release)(void *)); diff --git a/tests/corpus/6560-constructor-function-readable.cjs b/tests/corpus/6560-constructor-function-readable.cjs new file mode 100644 index 000000000..5319aada1 --- /dev/null +++ b/tests/corpus/6560-constructor-function-readable.cjs @@ -0,0 +1,121 @@ +// Readable streams built by constructor functions, the from2 pattern: +// util.inherits(Ctor, Readable), Readable.call(this, opts) and _read on +// the prototype. +const { Readable } = require("stream"); +const util = require("util"); + +function Source(list, opts) { + if (!(this instanceof Source)) return new Source(list, opts); + this._list = list.slice(); + // Before Readable.call there is no state: Node's setter ignores this. + this.destroyed = false; + Readable.call(this, opts || {}); + this._reads = 0; + console.log("hwm", this._readableState.highWaterMark, "objectMode", this._readableState.objectMode); +} +util.inherits(Source, Readable); +Source.prototype._read = function (size) { + this._reads++; + const item = this._list.shift(); + this.push(item === undefined ? null : Buffer.from(item)); +}; + +const s = Source(["alpha", "beta", "gamma"]); +console.log("instanceof", s instanceof Source, s instanceof Readable, Source.super_ === Readable); +console.log("readable", s.readable, "ended", s.readableEnded, "destroyed", s.destroyed); +console.log("flowing", s.readableFlowing, s._readableState.flowing); +s.on("data", function (chunk) { + console.log("data", this === s, Buffer.isBuffer(chunk), chunk.toString()); +}); +s.on("end", function () { + console.log("end", this === s, s.readableEnded, s._readableState.ended, s._reads); +}); +s.on("close", () => { + console.log("close", s.destroyed, s.closed); + paused(); +}); +console.log("sync after on('data')", s.readableFlowing); + +// from2's callback-driven _read, asynchronous pushes and a small +// highWaterMark. +function From(read, opts) { + if (!(this instanceof From)) return new From(read, opts); + this._reading = false; + this._callback = check; + this.destroyed = false; + Readable.call(this, opts); + const self = this; + const hwm = this._readableState.highWaterMark; + function check(err, data) { + if (self.destroyed) return; + if (err) return self.destroy(err); + if (data === null) return self.push(null); + self._reading = false; + if (self.push(data)) self._read(hwm); + } + this._from = read; +} +util.inherits(From, Readable); +From.prototype._read = function (size) { + if (this._reading || this.destroyed) return; + this._reading = true; + this._from(size, this._callback); +}; +From.prototype.destroy = function (err) { + if (this.destroyed) return; + this.destroyed = true; + const self = this; + process.nextTick(function () { + if (err) self.emit("error", err); + self.emit("close"); + }); +}; + +function paused() { + const parts = ["one ", "two ", "three"]; + const r = new From(function (size, next) { + setImmediate(() => next(null, parts.length ? parts.shift() : null)); + }, { highWaterMark: 4 }); + console.log("from hwm", r._readableState.highWaterMark, r.readableHighWaterMark); + r.setEncoding("utf8"); + let text = ""; + r.on("readable", () => { + let chunk; + while ((chunk = r.read()) !== null) text += chunk; + }); + r.on("end", () => { + console.log("paused text", JSON.stringify(text), typeof text); + // From() without new runs outside the listener, where `this` is unset. + setImmediate(failing); + }); +} + +function failing() { + let calls = 0; + const r = From(function (size, next) { + calls++; + if (calls === 2) return next(new Error("source failed")); + next(null, "chunk" + calls); + }); + r.on("data", (d) => console.log("failing data", String(d))); + r.on("error", (e) => console.log("failing error", e.message, r.destroyed)); + r.on("close", () => { + console.log("failing close", calls); + unshifted(); + }); +} + +function unshifted() { + const r = new Source(["xyz"]); + r.once("readable", () => { + const first = r.read(1); + console.log("read(1)", String(first), r.readableLength); + r.unshift(first); + console.log("after unshift", r.readableLength); + r.pause(); + console.log("isPaused", r.isPaused()); + r.on("data", (d) => console.log("rest", String(d))); + r.resume(); + }); + r.on("end", () => console.log("unshifted end")); +} diff --git a/tests/corpus/6561-constructor-function-writable.cjs b/tests/corpus/6561-constructor-function-writable.cjs new file mode 100644 index 000000000..ee25b7595 --- /dev/null +++ b/tests/corpus/6561-constructor-function-writable.cjs @@ -0,0 +1,141 @@ +// Writable and Duplex streams built by constructor functions, the bl and +// tar-stream patterns: Writable.call(this, opts), Duplex.call(this), +// _write/_final/_read on the prototype, listeners added before the base +// constructor runs, and an end() override that calls the base's end. +const stream = require("stream"); +const util = require("util"); + +function Sink(opts) { + if (!(this instanceof Sink)) return new Sink(opts); + stream.Writable.call(this, opts); + this.chunks = []; + this.finalized = false; +} +util.inherits(Sink, stream.Writable); +Sink.prototype._write = function (chunk, enc, cb) { + console.log("_write", this instanceof Sink, enc, Buffer.isBuffer(chunk), chunk.toString()); + this.chunks.push(chunk); + if (this.chunks.length === 2) setImmediate(cb); + else cb(); +}; +Sink.prototype._final = function (cb) { + console.log("_final", this.chunks.length); + this.finalized = true; + cb(); +}; + +const sink = new Sink({ highWaterMark: 8 }); +console.log("writable", sink.writable, sink.writableHighWaterMark, sink._writableState.highWaterMark); +console.log("write a", sink.write("first")); +console.log("write b", sink.write(Buffer.from("second"), () => console.log("write b callback"))); +console.log("needDrain", sink.writableNeedDrain, sink.writableLength); +sink.on("drain", () => console.log("drain", sink.writableLength)); +sink.on("finish", function () { + console.log("finish", this === sink, sink.finalized, sink.writableFinished, Buffer.concat(sink.chunks).toString()); +}); +sink.on("close", () => { + console.log("close", sink.destroyed, sink.closed); + duplex(); +}); +sink.end("third", (err) => console.log("end callback", err, sink.writableEnded, sink._writableState.ended)); +console.log("after end", sink.writableEnded, sink.writable); + +// A bl-style list: a Duplex that collects writes and reads them back. +function BufferListStream(callback) { + if (!(this instanceof BufferListStream)) return new BufferListStream(callback); + if (typeof callback === "function") { + this._callback = callback; + const piper = function piper(err) { + if (this._callback) { + this._callback(err); + this._callback = null; + } + }.bind(this); + this.on("pipe", function onPipe(src) { + console.log("pipe event", src instanceof stream.Readable); + src.on("error", piper); + }); + this.on("unpipe", function onUnpipe(src) { + console.log("unpipe event"); + src.removeListener("error", piper); + }); + callback = null; + } + this._bufs = []; + this.length = 0; + stream.Duplex.call(this); +} +util.inherits(BufferListStream, stream.Duplex); +BufferListStream.prototype._write = function (buf, encoding, callback) { + this._bufs.push(buf); + this.length += buf.length; + callback(); +}; +BufferListStream.prototype._read = function (size) { + if (!this.length) return this.push(null); + const all = Buffer.concat(this._bufs); + size = Math.min(size, this.length, 4); + this.push(all.subarray(0, size)); + this._bufs = [all.subarray(size)]; + this.length -= size; +}; +BufferListStream.prototype.end = function end(chunk) { + stream.Duplex.prototype.end.call(this, chunk); + if (this._callback) { + this._callback(null, Buffer.concat(this._bufs)); + this._callback = null; + } +}; +BufferListStream.prototype.slice = function () { + return Buffer.concat(this._bufs); +}; + +function Letters(text) { + stream.Readable.call(this, { highWaterMark: 2 }); + this._text = text; +} +util.inherits(Letters, stream.Readable); +Letters.prototype._read = function () { + const next = this._text.slice(0, 3); + this._text = this._text.slice(3); + this.push(next.length ? next : null); +}; + +function duplex() { + const list = new BufferListStream((err, data) => { + console.log("bl callback", err, data.toString(), data.length); + }); + console.log("duplex", list.readable, list.writable, list.allowHalfOpen, list instanceof stream.Duplex); + console.log("pre-constructor listeners", list.listenerCount("pipe"), list.listenerCount("unpipe")); + list.write("hello "); + list.write("constructor "); + list.end("functions"); + list.on("finish", () => { + const out = []; + list.on("data", (d) => out.push(d.toString())); + list.on("end", () => { + console.log("read back", JSON.stringify(out)); + piping(); + }); + }); +} + +function piping() { + const src = new Letters("abcdefghij"); + const dst = new Sink({ highWaterMark: 1 }); + dst.on("pipe", (from) => console.log("dst pipe", from === src)); + const returned = src.pipe(dst); + console.log("pipe returns dest", returned === dst); + dst.on("finish", () => { + console.log("piped", Buffer.concat(dst.chunks).toString(), src.readableEnded); + const keep = new Sink(); + const from = new Letters("xyz"); + from.pipe(keep, { end: false }); + from.on("end", () => { + setImmediate(() => { + console.log("end:false keeps the destination open", keep.writable, keep.writableEnded); + keep.end(); + }); + }); + }); +} diff --git a/tests/corpus/6562-constructor-function-transform.cjs b/tests/corpus/6562-constructor-function-transform.cjs new file mode 100644 index 000000000..c112d8ccc --- /dev/null +++ b/tests/corpus/6562-constructor-function-transform.cjs @@ -0,0 +1,137 @@ +// Transform and PassThrough streams built by constructor functions (the +// yazl and tar-stream patterns), piped together with runtime streams: +// zlib and fs. +const { Readable, Writable, Transform, PassThrough } = require("stream"); +const util = require("util"); +const zlib = require("zlib"); +const fs = require("fs"); +const os = require("os"); +const path = require("path"); + +// yazl's ByteCounter +function ByteCounter(options) { + Transform.call(this, options); + this.byteCount = 0; +} +util.inherits(ByteCounter, Transform); +ByteCounter.prototype._transform = function (chunk, encoding, cb) { + this.byteCount += chunk.length; + cb(null, chunk); +}; + +// A transform with a flush that appends a trailer. +function Upper() { + if (!(this instanceof Upper)) return new Upper(); + Transform.call(this); + this.seen = 0; +} +util.inherits(Upper, Transform); +Upper.prototype._transform = function (chunk, encoding, cb) { + this.seen++; + this.push(chunk.toString().toUpperCase()); + cb(); +}; +Upper.prototype._flush = function (cb) { + cb(null, "[" + this.seen + " chunks]"); +}; + +// tar-stream's entry stream: a PassThrough with its own destroy. +function Source(parent, offset) { + this._parent = parent; + this.offset = offset; + PassThrough.call(this, { autoDestroy: false }); +} +util.inherits(Source, PassThrough); +Source.prototype.destroy = function (err) { + console.log("Source.destroy forwarded", err && err.message); + this._parent.destroyed = true; +}; + +// A PassThrough without its own _transform passes chunks through. +function Plain() { + PassThrough.call(this); +} +util.inherits(Plain, PassThrough); + +function Words(words) { + Readable.call(this); + this._words = words.slice(); +} +util.inherits(Words, Readable); +Words.prototype._read = function () { + this.push(this._words.length ? this._words.shift() : null); +}; + +function Collect() { + Writable.call(this); + this.parts = []; +} +util.inherits(Collect, Writable); +Collect.prototype._write = function (chunk, enc, cb) { + this.parts.push(chunk); + cb(); +}; +Collect.prototype.text = function () { + return Buffer.concat(this.parts).toString(); +}; + +const upper = Upper(); +const words = []; +upper.on("data", (d) => words.push(d.toString())); +upper.on("end", () => { + console.log("upper", JSON.stringify(words), upper.seen, upper._readableState.ended, upper._writableState.finished); + counter(); +}); +upper.write("one "); +upper.write(Buffer.from("two ")); +upper.end("three"); + +function counter() { + const counterStream = new ByteCounter(); + const sink = new Collect(); + new Words(["hello ", "byte ", "counter"]).pipe(counterStream).pipe(sink); + sink.on("finish", () => { + console.log("counted", counterStream.byteCount, JSON.stringify(sink.text())); + entry(); + }); +} + +function entry() { + const parent = { destroyed: false }; + const source = new Source(parent, 512); + const plain = new Plain(); + console.log("source", source.offset, source instanceof PassThrough, source instanceof Transform); + let body = ""; + source.pipe(plain); + plain.on("data", (d) => (body += d)); + plain.on("end", () => { + console.log("entry body", JSON.stringify(body), source.destroyed); + source.destroy(new Error("stop")); + console.log("parent destroyed", parent.destroyed); + gzip(); + }); + source.write("file "); + source.end("contents"); +} + +function gzip() { + const counted = new ByteCounter(); + const out = new Collect(); + new Words(["compressed ", "through ", "zlib"]).pipe(counted).pipe(zlib.createGzip()).pipe(out); + out.on("finish", () => { + const back = zlib.gunzipSync(Buffer.concat(out.parts)).toString(); + console.log("gzip roundtrip", JSON.stringify(back), counted.byteCount); + toFile(); + }); +} + +function toFile() { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "ctor-stream-")); + const file = path.join(dir, "out.txt"); + const ws = fs.createWriteStream(file); + new Words(["written ", "by ", "a ", "constructor"]).pipe(Upper()).pipe(ws); + ws.on("finish", () => { + console.log("file", JSON.stringify(fs.readFileSync(file, "utf8"))); + fs.rmSync(dir, { recursive: true }); + }); +} diff --git a/tests/corpus/6563-constructor-function-stream-errors.cjs b/tests/corpus/6563-constructor-function-stream-errors.cjs new file mode 100644 index 000000000..cf2fb3e7c --- /dev/null +++ b/tests/corpus/6563-constructor-function-stream-errors.cjs @@ -0,0 +1,137 @@ +// Errors, destroy and custom events on constructor-function streams. +const { Readable, Writable, Duplex } = require("stream"); +const util = require("util"); + +function Failing() { + Writable.call(this); +} +util.inherits(Failing, Writable); +Failing.prototype._write = function (chunk, enc, cb) { + const err = new Error("cannot store " + chunk); + err.code = "E_STORE"; + this.lastError = err; + cb(err); +}; + +const failing = new Failing(); +failing.on("error", function (err) { + console.log("write error", this === failing, err === failing.lastError, err.message, err.code); + console.log("errored", failing.errored === err, failing.destroyed, failing.writable); +}); +failing.on("close", () => { + console.log("write close"); + destroying(); +}); +failing.write("x", (err) => console.log("write callback", err && err.code)); + +function Ticker() { + Readable.call(this); + this.n = 0; +} +util.inherits(Ticker, Readable); +Ticker.prototype._read = function () { + this.push(String(this.n++)); +}; +Ticker.prototype._destroy = function (err, cb) { + console.log("_destroy", err && err.message, this.n > 0); + setImmediate(() => cb(err && new Error("wrapped: " + err.message))); +}; + +function destroying() { + const t = new Ticker(); + const order = []; + t.on("data", (d) => { + if (d.toString() === "3") t.destroy(new Error("enough"), (e) => order.push("destroy callback " + (e && e.message))); + }); + t.on("error", (e) => order.push("error " + e.message)); + t.on("close", () => { + order.push("close"); + console.log(order.join(" | "), t.destroyed, t.readable); + t.destroy(null, () => console.log("destroy after close calls back")); + notImplemented(); + }); +} + +function Bare() { + Readable.call(this); +} +util.inherits(Bare, Readable); + +function Throws() { + Readable.call(this); +} +util.inherits(Throws, Readable); +Throws.prototype._read = function () { + throw new TypeError("read threw"); +}; + +function notImplemented() { + const bare = new Bare(); + bare.on("error", (e) => { + console.log("bare", e.code, e.message); + const thrower = new Throws(); + thrower.on("error", (err) => { + console.log("throws", err instanceof TypeError, err.message, thrower.destroyed); + afterEnd(); + }); + thrower.resume(); + }); + bare.resume(); +} + +function Quiet() { + Writable.call(this); +} +util.inherits(Quiet, Writable); +Quiet.prototype._write = function (chunk, enc, cb) { + cb(); +}; + +function afterEnd() { + const q = new Quiet(); + q.end("done"); + q.on("error", (e) => { + console.log("after end", e.code, e.message); + customEvents(); + }); + q.write("late"); +} + +// tar-stream's Extract emits 'entry' with three arguments. +function Extract() { + if (!(this instanceof Extract)) return new Extract(); + Writable.call(this); +} +util.inherits(Extract, Writable); +Extract.prototype._write = function (data, enc, cb) { + const header = { name: data.toString(), size: data.length }; + this.emit("entry", header, "stream-for-" + header.name, cb); +}; + +function customEvents() { + const ex = Extract(); + const seen = []; + function onEntry(header, s, next) { + seen.push(header.name + ":" + header.size + ":" + s + ":" + (this === ex)); + next(); + } + ex.on("entry", onEntry); + ex.once("entry", (header) => seen.push("once " + header.name)); + ex.prependListener("entry", () => seen.push("first")); + console.log("listenerCount", ex.listenerCount("entry"), ex.listenerCount("entry", onEntry)); + ex.write("a.txt"); + ex.removeListener("entry", onEntry); + ex.write("b.txt"); + console.log(seen.join(" | ")); + console.log("emit returns", ex.emit("nothing"), ex.emit("entry", { name: "c", size: 0 }, "", () => {})); + try { + ex.emit("error", new RangeError("nobody listens")); + } catch (e) { + console.log("unhandled error rethrown", e instanceof RangeError, e.message); + } + ex.setMaxListeners(1); + console.log("max listeners", ex.getMaxListeners()); + ex.removeAllListeners("entry"); + console.log("after removeAll", ex.listenerCount("entry")); + console.log("Duplex prototype", typeof Duplex.prototype.write, typeof Duplex.prototype.push); +} diff --git a/tests/harness/self-hosting-native-toolchain.test.ts b/tests/harness/self-hosting-native-toolchain.test.ts index 1de960368..1a172ca0c 100644 --- a/tests/harness/self-hosting-native-toolchain.test.ts +++ b/tests/harness/self-hosting-native-toolchain.test.ts @@ -31,6 +31,7 @@ const features: NativeLinkFeatures = { searchParams: false, qs: false, dynBuiltinClass: false, + dynBuiltinStream: false, parseArgs: false, stream: false, net: false, diff --git a/tests/harness/self-hosting-runtime-features.test.ts b/tests/harness/self-hosting-runtime-features.test.ts index 8d65d72cd..b2f7e3062 100644 --- a/tests/harness/self-hosting-runtime-features.test.ts +++ b/tests/harness/self-hosting-runtime-features.test.ts @@ -51,6 +51,7 @@ const featureCalls: readonly [string, IrLibFn][] = [ ["searchParams", "sp.new"], ["qs", "qs.parse"], ["dynBuiltinClass", "dyn.builtinClass"], + ["dynBuiltinStream", "dyn.builtinStreamClass"], ["parseArgs", "util.parseArgs"], ["parseArgs", "util.isDeepStrictEqual"], ["parseArgs", "util.styleText"], diff --git a/tests/harness/self-hosting-runtime-pack.test.ts b/tests/harness/self-hosting-runtime-pack.test.ts index 42cf8a729..a0de981b6 100644 --- a/tests/harness/self-hosting-runtime-pack.test.ts +++ b/tests/harness/self-hosting-runtime-pack.test.ts @@ -32,6 +32,7 @@ const base: NativeLinkFeatures = { searchParams: false, qs: false, dynBuiltinClass: false, + dynBuiltinStream: false, parseArgs: false, stream: false, net: false, From 2a9328cc5c80972cfa871388197040a54e7d17fc Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Tue, 20 Oct 2026 09:54:08 -0700 Subject: [PATCH 111/122] Pipe a runtime stream into constructor-function streams in the transform corpus program Co-Authored-By: Claude Opus 5.5 --- tests/corpus/6562-constructor-function-transform.cjs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/corpus/6562-constructor-function-transform.cjs b/tests/corpus/6562-constructor-function-transform.cjs index c112d8ccc..144a746bc 100644 --- a/tests/corpus/6562-constructor-function-transform.cjs +++ b/tests/corpus/6562-constructor-function-transform.cjs @@ -132,6 +132,12 @@ function toFile() { new Words(["written ", "by ", "a ", "constructor"]).pipe(Upper()).pipe(ws); ws.on("finish", () => { console.log("file", JSON.stringify(fs.readFileSync(file, "utf8"))); - fs.rmSync(dir, { recursive: true }); + // A runtime stream piped into a constructor-function stream. + const back = new Collect(); + fs.createReadStream(file).pipe(new ByteCounter()).pipe(back); + back.on("finish", () => { + console.log("read back", JSON.stringify(back.text())); + fs.rmSync(dir, { recursive: true }); + }); }); } From cf003870eef41776060fd1a16b9b062726ecfe63 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Tue, 20 Oct 2026 12:18:55 -0500 Subject: [PATCH 112/122] Write invisible characters in the WebAssembly spec data as escapes --- tests/corpus/webassembly-spec-imports/data.mjs | 2 +- tests/wasm-spec/gen.mjs | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/corpus/webassembly-spec-imports/data.mjs b/tests/corpus/webassembly-spec-imports/data.mjs index c082547b8..5ecccab26 100644 --- a/tests/corpus/webassembly-spec-imports/data.mjs +++ b/tests/corpus/webassembly-spec-imports/data.mjs @@ -134,4 +134,4 @@ export const modules = [ "AGFzbQEAAAABBQFgAAF/A+ED3wMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAerId8DAAAAATAAAQItMAACAV8AAwEkAAQBQAAFIX4hQCMkJV4mKigpXytgLT17fXxbXVw6IjsnPD4/LC4vIAAGA05hTgAHCEluZmluaXR5AAgCaWYACQZtYWxsb2MACgdfbWFsbG9jAAsIX19tYWxsb2MADAFhAA0BQQAOA++7vwAPAsOFABADQcyKABED4oSrABID76yDABMEZu+sgQAUA2ZmaQAVEAABAgMEBQYHCAkKCwwNDg8AFhAQERITFBUWFxgZGhscHR4fABcCIH8AGCDCgMKBwoLCg8KEwoXChsKHwojCicKKwovCjMKNwo7CjwAZIMKQwpHCksKTwpTClcKWwpfCmMKZwprCm8Kcwp3CnsKfABoY77+w77+x77+y77+z77+077+177+277+3ABsY77+477+577+677+777+877+977++77+/ABww4pCA4pCB4pCC4pCD4pCE4pCF4pCG4pCH4pCI4pCJ4pCK4pCL4pCM4pCN4pCO4pCPAB0w4pCQ4pCR4pCS4pCT4pCU4pCV4pCW4pCX4pCY4pCZ4pCa4pCb4pCc4pCd4pCe4pCfAB4G4pCg4pChAB8q77+w77+x77+y77+z77+077+177+277+377+477+577+677+777+877+9ACAD4oCNACED4oCMACICzY8AIwPigaAAJAPitb8AJQTwkYG/ACYD4aCOACcW77+v4oCLwqDCreKBoOGagOKAruKArQAoJ+KAjuKAj+KAkeKAqOKAqeKAquKAq+KArOKAr+KBpuKBp+KBqOKBqQApEuKBquKBq+KBrOKBreKBruKBrwAqDOKBoeKBouKBo+KBpAArDPCQgIDzn7+/9I+/vwAsgQJazI/NhsyGzZvNjMy0zZjNns2HzKvMpcyqzZPNiM2UzY7Ml8yezLrMr8yxzJ7MmcyxzJzMlsygYc2XzajMjsyEzIbNl8y/zaHNn82AzLbNgcylzLDMs8ytzZnMssyxzLnMnc2OzLxszYTNisyazZfNps2EzavMh82BzLbMt82JzKnMucyrzJ3Nls2FzJnMssy8zYfNms2NzK7NjsylzYXMnmfNg8yQzIXNrsyUzJDMjsyCzI/Mvs2KzI3Ni82KzafMgcyGzabNnsy2zZXNlM2azKlvzYvMlM2QzarNqcyhzY/MosynzYHMq8yZzKTMrs2WzZnNk8y6zJzMqcy8zJjMoAAtDOGFn+GFoOOFpO++oAAuA++4gAAvA++4hAAwBPOghIAAMQTzoIevADICzIgAMwEKADQD4pCkADUD4oCoADYBDQA3Ag0KADgCCg0AOQEeADoBCwA7AQwAPALChQA9A+KAqQA+A+KApgA/A+KPjgBAAsKLAEECwowAQgLCjQBDA+KGtQBEA+KGqQBFA+KMpABGA+KktgBHA+KGsgBIA+KuqABJA+KusABKA++/vQBLA++3kABMA++3kQBNA++3kgBOA++3kwBPA++3lABQA++3lQBRA++3lgBSA++3lwBTA++3mABUA++3mQBVA++3mgBWA++3mwBXA++3nABYA++3nQBZA++3ngBaA++3nwBbA++3oABcA++3oQBdA++3ogBeA++3owBfA++3pABgA++3pQBhA++3pgBiA++3pwBjA++3qABkA++3qQBlA++3qgBmA++3qwBnA++3rABoA++3rQBpA++3rgBqA++3rwBrA++/vgBsA++/vwBtBPCfv74AbgTwn7+/AG8E8K+/vgBwBPCvv78AcQTwv7++AHIE8L+/vwBzBPGPv74AdATxj7+/AHUE8Z+/vgB2BPGfv78AdwTxr7++AHgE8a+/vwB5BPG/v74AegTxv7+/AHsE8o+/vgB8BPKPv78AfQTyn7++AH4E8p+/vwB/BPKvv74AgAEE8q+/vwCBAQTyv7++AIIBBPK/v78AgwEE84+/vgCEAQTzj7+/AIUBBPOfv74AhgEE85+/vwCHAQTzr7++AIgBBPOvv78AiQEE87+/vgCKAQTzv7+/AIsBBPSPv74AjAEE9I+/vwCNAQnMiOKAvcyIzIkAjgEDYWJjAI8BBuKArWFiYwCQAQbigK5jYmEAkQEJ4oCtYWJj4oCuAJIBCeKArmNiYeKArQCTAQTwnZGoAJQBBPCdkLQAlQEE8J2YiACWAQTwnZi8AJcBBPCdkIAAmAEE8J2TkACZAQTwnZWsAJoBBPCdl5QAmwEE8J2SnACcAQTwnZSEAJ0BBPCdlLgAngEE8J2WoACfAQTwnZmwAKABA+G0gAChAQPhtKwAogED4pK2AKMBA++8oQCkAQTwn4SQAKUBBPCfhLAApgEE86CBgQCnAQZVKzAwNDEAqAEEQeKAiwCpAQLQkACqAQPqmZYAqwED4re8AKwBA+K3tgCtAQPisa8ArgEE8J+FkACvAQTwn4WwALABA+KwrQCxAQTwkJCCALIBBPCQkIgAswEE8JCSsAC0AQLDgAC1AQLDgQC2AQLDggC3AQLDgwC4AQLDhAC5AQLEgAC6AQLEggC7AQLEhAC8AQLHjQC9AQLHngC+AQLHoAC/AQLHugDAAQLIgADBAQLIggDCAQLIpgDDAQLIugDEAQLTkADFAQLTkgDGAQLfigDHAQPgoKEAyAED4KCiAMkBA+CgowDKAQPgoKQAywED4KClAMwBA+CkhADNAQPgpIUAzgED4KWyAM8BA+CmhQDQAQPgqIUA0QED4KqFANIBA+CshQDTAQPgroUA1AED4LCFANUBA+CyhQDWAQPgtIUA1wED4LiwANgBA+C6sADZAQPgvIEA2gED4L2oANsBA+C+uADcAQPhgKEA3QED4YCiAN4BA+GCnADfAQPhhaEA4AED4YqgAOEBA+GLkADiAQPhjqAA4wED4ZCKAOQBA+GWswDlAQPhmqgA5gED4ZqqAOcBA+GbhgDoAQPhnIAA6QED4ZygAOoBA+GdgADrAQPhnaAA7AED4aCgAO0BA+GihwDuAQPhpKAA7wED4aWjAPABA+GolQDxAQPhqYsA8gED4amhAPMBA+GugwD0AQPhr4AA9QED4a+BAPYBA+GwowD3AQPhuIAA+AED4bqgAPkBA+G6ogD6AQPhuqQA+wED4bqmAPwBA+G6qAD9AQPhuqoA/gED4bqsAP8BA+G6rgCAAgPhurAAgQID4bqyAIICA+G6tACDAgPhurYAhAID44GCAIUCA+OCogCGAgPjhJoAhwID44WPAIgCA+OIjgCJAgPjiI8AigID44iQAIsCA+OIkQCMAgPjiJIAjQID44iTAI4CA+OIlACPAgPjiJUAkAID44iWAJECA+OIlwCSAgPjiJgAkwID44iZAJQCA+OImgCVAgPjiJsAlgID44muAJcCA+OJrwCYAgPjibAAmQID44mxAJoCA+OJsgCbAgPjibMAnAID44m0AJ0CA+OJtQCeAgPjibYAnwID44m3AKACA+OJuAChAgPjibkAogID44m6AKMCA+OJuwCkAgPji5AApQID6oCKAKYCA+qTrgCnAgPqlYkAqAID6pqgAKkCA+qggACqAgPqoKMAqwID6qGdAKwCA+qiggCtAgPqo6oArgID6qSiAK8CA+qlhgCwAgPqpoQAsQID6qiAALICA++9sQCzAgPvv4IAtAIE8JCAgAC1AgTwkIqAALYCBPCQiqAAtwIE8JCMgAC4AgTwkI6gALkCBPCQkpYAugIE8JCUgAC7AgTwkJ2AALwCBPCQoIAAvQIE8JCkoAC+AgTwkKaAAL8CBPCQpqAAwAIE8JCogADBAgTwkKyAAMICBPCQsIAAwwIE8JCwgQDEAgTwkLKAAMUCBPCRgIUAxgIE8JGCgwDHAgTwkYSnAMgCBPCRhZAAyQIE8JGGgwDKAgTwkYiAAMsCBPCRioAAzAIE8JGKsADNAgTwkYyFAM4CBPCRjbAAzwIE8JGQgADQAgTwkZKBANECBPCRloAA0gIE8JGYgADTAgTwkZqAANQCBPCRnJIA1QIE8JGcoADWAgTwkaKhANcCBPCRq5UA2AIE8JGwgADZAgTwkbKPANoCBPCRsq8A2wIE8JKAgADcAgTwlqeVAN0CBPCWqYYA3gIE8JarpwDfAgTwlr2UAOACBPCbsYEA4QIE8JuxpADiAgTwnqCjAOMCBPCfh6YA5AID4rGtAOUCAs6bAOYCA+KxsADnAgLCqgDoAgPiiIAA6QID4oKzAOoCBPCQpIAA6wID4rKAAOwCBPCQjLAA7QICzoYA7gICzpEA7wID4byIAPACA+G8iQDxAgPhvIoA8gID4byLAPMCA+G8jAD0AgPhvI0A9QID4byOAPYCA+G8jwD3AgPhvogA+AID4b6JAPkCA+G+igD6AgPhvosA+wID4b6MAPwCA+G+jQD9AgPhvo4A/gID4b6PAP8CA+G+uACAAwPhvrkAgQMD4b66AIIDA+G+uwCDAwPhvrwAhAME8J2aqACFAwTwnZuiAIYDBPCdnJwAhwME8J2dlgCIAwTwnZ6QAIkDA+KNtgCKAwPijboAiwMD4qmcAIwDA+GXhQCNAwPhjqoAjgOcASnLusu88JSXj/CdhbTwnYW28J2FuPCdhbrigb7igo7inaninavin6/vtL/vuLbvuZrvvInvvaDzoICp4p2z4p214p+n4p+p4p+r4p+t4qaI4qaK4qaW4rij4ril77iY77i477i677i877i+77mA77mC77mE77mI77mc77me77y9772d772j86CBnfOggb3Cu+KAmeKAneKAuuKdrwCPA5wBKMu5y7vwlJeO8J2Fs/CdhbXwnYW38J2FueKBveKCjeKdqOKdquKfru+0vu+4te+5me+8iO+9n/OggKjinbLinbTin6bin6jin6rin6zipofiponippXiuKLiuKTvuJfvuLfvuLnvuLvvuL3vuL/vuYHvuYPvuYfvuZvvuZ3vvLvvvZvvvaLzoIGb86CBu8Kr4oCY4oCc4oC54p2uAJADCPCdqovwnaqkAJEDBPCdqosAkgMCwr0AkwMFMeKBhDIAlAMDMS8yAJUDA+CtswCWAwPgtbQAlwMD4rO9AJgDA+qgsQCZAwTwkIWBAJoDBPCQhbUAmwME8JCFtgCcAwTwkKa9AJ0DBPCQubsAngMD77yCAJ8DAX8AoAMBCAChAwPijKsAogMD4oymAKMDA+KQiACkAwPikKEApQMD4be7AKYDAQ8ApwMD4oaQAKgDA+KMpwCpAwPijZIAqgMD4o2UAKsDA+KNogCsAwPijasArQMBGgCuAwPikKYArwMD4pCaALADA++/vACxAwE/ALIDAsK/ALMDA+GlhQC0AwLNvgC1AwLVngC2AwLYnwC3AwPhjacAuAMD4oGHALkDA+KNsAC6AwPinZMAuwMD4p2UALwDA+KzugC9AwPis7sAvgMD4riuAL8DA+OJhADAAwPqmI8AwQMD6pu3AMIDA++4lgDDAwPvuZYAxAMD77yfAMUDBPCRhYMAxgME8J6lnwDHAwTzoIC/AMgDBPCWoYQAyQMD4q+RAMoDAsK2AMsDA+KBiwDMAwLcgADNAwPhg7sAzgMD4Y2oAM8DA+OAtwDQAwPinaEA0QMD4riPANIDA+K4kADTAwPiuJEA1AMD4riOANUDARQA1gMD4piZANcDA+K4vwDYAwPjgIcA2QMD4LmbANoDA+qZrgDbAwLPkwDcAwLPlADdAwPhupsA3gMK/BXfAwQAQQALBABBAQsEAEECCwQAQQMLBABBBAsEAEEFCwQAQQYLBABBBwsEAEEICwQAQQkLBABBCgsEAEELCwQAQQwLBABBDQsEAEEOCwQAQQ8LBABBEAsEAEERCwQAQRILBABBEwsEAEEUCwQAQRULBABBFgsEAEEXCwQAQRgLBABBGQsEAEEaCwQAQRsLBABBHAsEAEEdCwQAQR4LBABBHwsEAEEgCwQAQSELBABBIgsEAEEjCwQAQSQLBABBJQsEAEEmCwQAQScLBABBKAsEAEEpCwQAQSoLBABBKwsEAEEsCwQAQS0LBABBLgsEAEEvCwQAQTALBABBMQsEAEEyCwQAQTMLBABBNAsEAEE1CwQAQTYLBABBNwsEAEE4CwQAQTkLBABBOgsEAEE7CwQAQTwLBABBPQsEAEE+CwQAQT8LBQBBwAALBQBBwQALBQBBwgALBQBBwwALBQBBxAALBQBBxQALBQBBxgALBQBBxwALBQBByAALBQBByQALBQBBygALBQBBywALBQBBzAALBQBBzQALBQBBzgALBQBBzwALBQBB0AALBQBB0QALBQBB0gALBQBB0wALBQBB1AALBQBB1QALBQBB1gALBQBB1wALBQBB2AALBQBB2QALBQBB2gALBQBB2wALBQBB3AALBQBB3QALBQBB3gALBQBB3wALBQBB4AALBQBB4QALBQBB4gALBQBB4wALBQBB5AALBQBB5QALBQBB5gALBQBB5wALBQBB6AALBQBB6QALBQBB6gALBQBB6wALBQBB7AALBQBB7QALBQBB7gALBQBB7wALBQBB8AALBQBB8QALBQBB8gALBQBB8wALBQBB9AALBQBB9QALBQBB9gALBQBB9wALBQBB+AALBQBB+QALBQBB+gALBQBB+wALBQBB/AALBQBB/QALBQBB/gALBQBB/wALBQBBgAELBQBBgQELBQBBggELBQBBgwELBQBBhAELBQBBhQELBQBBhgELBQBBhwELBQBBiAELBQBBiQELBQBBigELBQBBiwELBQBBjAELBQBBjQELBQBBjgELBQBBjwELBQBBkAELBQBBkQELBQBBkgELBQBBkwELBQBBlAELBQBBlQELBQBBlgELBQBBlwELBQBBmAELBQBBmQELBQBBmgELBQBBmwELBQBBnAELBQBBnQELBQBBngELBQBBnwELBQBBoAELBQBBoQELBQBBogELBQBBowELBQBBpAELBQBBpQELBQBBpgELBQBBpwELBQBBqAELBQBBqQELBQBBqgELBQBBqwELBQBBrAELBQBBrQELBQBBrgELBQBBrwELBQBBsAELBQBBsQELBQBBsgELBQBBswELBQBBtAELBQBBtQELBQBBtgELBQBBtwELBQBBuAELBQBBuQELBQBBugELBQBBuwELBQBBvAELBQBBvQELBQBBvgELBQBBvwELBQBBwAELBQBBwQELBQBBwgELBQBBwwELBQBBxAELBQBBxQELBQBBxgELBQBBxwELBQBByAELBQBByQELBQBBygELBQBBywELBQBBzAELBQBBzQELBQBBzgELBQBBzwELBQBB0AELBQBB0QELBQBB0gELBQBB0wELBQBB1AELBQBB1QELBQBB1gELBQBB1wELBQBB2AELBQBB2QELBQBB2gELBQBB2wELBQBB3AELBQBB3QELBQBB3gELBQBB3wELBQBB4AELBQBB4QELBQBB4gELBQBB4wELBQBB5AELBQBB5QELBQBB5gELBQBB5wELBQBB6AELBQBB6QELBQBB6gELBQBB6wELBQBB7AELBQBB7QELBQBB7gELBQBB7wELBQBB8AELBQBB8QELBQBB8gELBQBB8wELBQBB9AELBQBB9QELBQBB9gELBQBB9wELBQBB+AELBQBB+QELBQBB+gELBQBB+wELBQBB/AELBQBB/QELBQBB/gELBQBB/wELBQBBgAILBQBBgQILBQBBggILBQBBgwILBQBBhAILBQBBhQILBQBBhgILBQBBhwILBQBBiAILBQBBiQILBQBBigILBQBBiwILBQBBjAILBQBBjQILBQBBjgILBQBBjwILBQBBkAILBQBBkQILBQBBkgILBQBBkwILBQBBlAILBQBBlQILBQBBlgILBQBBlwILBQBBmAILBQBBmQILBQBBmgILBQBBmwILBQBBnAILBQBBnQILBQBBngILBQBBnwILBQBBoAILBQBBoQILBQBBogILBQBBowILBQBBpAILBQBBpQILBQBBpgILBQBBpwILBQBBqAILBQBBqQILBQBBqgILBQBBqwILBQBBrAILBQBBrQILBQBBrgILBQBBrwILBQBBsAILBQBBsQILBQBBsgILBQBBswILBQBBtAILBQBBtQILBQBBtgILBQBBtwILBQBBuAILBQBBuQILBQBBugILBQBBuwILBQBBvAILBQBBvQILBQBBvgILBQBBvwILBQBBwAILBQBBwQILBQBBwgILBQBBwwILBQBBxAILBQBBxQILBQBBxgILBQBBxwILBQBByAILBQBByQILBQBBygILBQBBywILBQBBzAILBQBBzQILBQBBzgILBQBBzwILBQBB0AILBQBB0QILBQBB0gILBQBB0wILBQBB1AILBQBB1QILBQBB1gILBQBB1wILBQBB2AILBQBB2QILBQBB2gILBQBB2wILBQBB3AILBQBB3QILBQBB3gILBQBB3wILBQBB4AILBQBB4QILBQBB4gILBQBB4wILBQBB5AILBQBB5QILBQBB5gILBQBB5wILBQBB6AILBQBB6QILBQBB6gILBQBB6wILBQBB7AILBQBB7QILBQBB7gILBQBB7wILBQBB8AILBQBB8QILBQBB8gILBQBB8wILBQBB9AILBQBB9QILBQBB9gILBQBB9wILBQBB+AILBQBB+QILBQBB+gILBQBB+wILBQBB/AILBQBB/QILBQBB/gILBQBB/wILBQBBgAMLBQBBgQMLBQBBggMLBQBBgwMLBQBBhAMLBQBBhQMLBQBBhgMLBQBBhwMLBQBBiAMLBQBBiQMLBQBBigMLBQBBiwMLBQBBjAMLBQBBjQMLBQBBjgMLBQBBjwMLBQBBkAMLBQBBkQMLBQBBkgMLBQBBkwMLBQBBlAMLBQBBlQMLBQBBlgMLBQBBlwMLBQBBmAMLBQBBmQMLBQBBmgMLBQBBmwMLBQBBnAMLBQBBnQMLBQBBngMLBQBBnwMLBQBBoAMLBQBBoQMLBQBBogMLBQBBowMLBQBBpAMLBQBBpQMLBQBBpgMLBQBBpwMLBQBBqAMLBQBBqQMLBQBBqgMLBQBBqwMLBQBBrAMLBQBBrQMLBQBBrgMLBQBBrwMLBQBBsAMLBQBBsQMLBQBBsgMLBQBBswMLBQBBtAMLBQBBtQMLBQBBtgMLBQBBtwMLBQBBuAMLBQBBuQMLBQBBugMLBQBBuwMLBQBBvAMLBQBBvQMLBQBBvgMLBQBBvwMLBQBBwAMLBQBBwQMLBQBBwgMLBQBBwwMLBQBBxAMLBQBBxQMLBQBBxgMLBQBBxwMLBQBByAMLBQBByQMLBQBBygMLBQBBywMLBQBBzAMLBQBBzQMLBQBBzgMLBQBBzwMLBQBB0AMLBQBB0QMLBQBB0gMLBQBB0wMLBQBB1AMLBQBB1QMLBQBB1gMLBQBB1wMLBQBB2AMLBQBB2QMLBQBB2gMLBQBB2wMLBQBB3AMLBQBB3QMLBQBB3gML", "AGFzbQEAAAABCgJgAX8AYAJ/fwACKwIIc3BlY3Rlc3QJcHJpbnRfaTMyAAAIc3BlY3Rlc3QJcHJpbnRfaTMyAAADAgEBBwsBB3ByaW50MzIAAgoMAQoAIAAQACABEAEL", ]; -export const script = "[[\"file\",\"imports.wast\"],[\"module\",3,0],[\"register\",\"test\"],[\"module\",26,1],[\"invoke\",85,0,\"print32\",[\"i32:13\"]],[\"invoke\",86,0,\"print64\",[\"i64:24\"]],[\"module\",97,2],[\"invoke\",104,0,\"print_i32\",[\"i32:13\"]],[\"module\",107,3],[\"invoke\",114,0,\"print_i32\",[\"i32:5\",\"i32:11\"]],[\"module\",116,4],[\"module\",117,5],[\"module\",118,6],[\"module\",119,7],[\"module\",120,8],[\"module\",121,9],[\"module\",122,10],[\"instantiate\",125,11],[\"instantiate\",129,12],[\"instantiate\",134,13],[\"instantiate\",138,14],[\"instantiate\",142,15],[\"instantiate\",146,16],[\"instantiate\",150,17],[\"instantiate\",154,18],[\"instantiate\",158,19],[\"instantiate\",162,20],[\"instantiate\",166,21],[\"instantiate\",170,22],[\"instantiate\",174,23],[\"instantiate\",178,24],[\"instantiate\",182,25],[\"instantiate\",186,26],[\"instantiate\",190,27],[\"instantiate\",194,28],[\"instantiate\",199,29],[\"instantiate\",203,30],[\"instantiate\",207,31],[\"instantiate\",211,32],[\"instantiate\",215,33],[\"instantiate\",219,34],[\"module\",226,35],[\"invoke\",246,0,\"get-0\",[]],[\"invoke\",247,0,\"get-1\",[]],[\"invoke\",248,0,\"get-x\",[]],[\"invoke\",249,0,\"get-y\",[]],[\"invoke\",250,0,\"get-4\",[]],[\"invoke\",251,0,\"get-5\",[]],[\"invoke\",252,0,\"get-6\",[]],[\"module\",254,36],[\"module\",255,37],[\"module\",256,38],[\"instantiate\",259,39],[\"instantiate\",263,40],[\"instantiate\",268,41],[\"instantiate\",272,42],[\"instantiate\",276,43],[\"instantiate\",280,44],[\"instantiate\",284,45],[\"instantiate\",288,46],[\"instantiate\",292,47],[\"instantiate\",296,48],[\"instantiate\",300,49],[\"instantiate\",304,50],[\"instantiate\",308,51],[\"instantiate\",312,52],[\"instantiate\",317,53],[\"instantiate\",321,54],[\"instantiate\",325,55],[\"instantiate\",329,56],[\"instantiate\",333,57],[\"instantiate\",337,58],[\"module\",344,59],[\"invoke\",356,0,\"call\",[\"i32:0\"]],[\"invoke\",357,0,\"call\",[\"i32:1\"]],[\"invoke\",358,0,\"call\",[\"i32:2\"]],[\"invoke\",359,0,\"call\",[\"i32:3\"]],[\"invoke\",360,0,\"call\",[\"i32:100\"]],[\"module\",363,59],[\"invoke\",375,0,\"call\",[\"i32:0\"]],[\"invoke\",376,0,\"call\",[\"i32:1\"]],[\"invoke\",377,0,\"call\",[\"i32:2\"]],[\"invoke\",378,0,\"call\",[\"i32:3\"]],[\"invoke\",379,0,\"call\",[\"i32:100\"]],[\"module\",381,60],[\"module\",388,61],[\"module\",389,62],[\"module\",390,63],[\"module\",391,64],[\"module\",392,65],[\"module\",393,66],[\"module\",394,67],[\"module\",395,68],[\"module\",396,69],[\"module\",397,70],[\"module\",398,71],[\"module\",399,72],[\"module\",400,73],[\"module\",401,74],[\"module\",402,75],[\"module\",403,76],[\"module\",404,77],[\"module\",405,78],[\"module\",406,79],[\"module\",407,80],[\"instantiate\",410,81],[\"instantiate\",414,82],[\"instantiate\",419,83],[\"instantiate\",423,84],[\"instantiate\",427,85],[\"instantiate\",431,86],[\"instantiate\",435,87],[\"instantiate\",439,88],[\"instantiate\",444,89],[\"instantiate\",448,90],[\"instantiate\",452,91],[\"instantiate\",456,92],[\"module\",464,93],[\"invoke\",471,0,\"load\",[\"i32:0\"]],[\"invoke\",472,0,\"load\",[\"i32:10\"]],[\"invoke\",473,0,\"load\",[\"i32:8\"]],[\"invoke\",474,0,\"load\",[\"i32:1000000\"]],[\"module\",476,93],[\"invoke\",482,0,\"load\",[\"i32:0\"]],[\"invoke\",483,0,\"load\",[\"i32:10\"]],[\"invoke\",484,0,\"load\",[\"i32:8\"]],[\"invoke\",485,0,\"load\",[\"i32:1000000\"]],[\"module\",500,94],[\"module\",501,95],[\"module\",502,96],[\"module\",503,97],[\"module\",504,98],[\"module\",505,99],[\"module\",506,100],[\"module\",507,101],[\"module\",508,102],[\"instantiate\",511,103],[\"instantiate\",515,104],[\"instantiate\",520,105],[\"instantiate\",524,106],[\"instantiate\",528,107],[\"instantiate\",532,108],[\"instantiate\",537,109],[\"instantiate\",541,110],[\"instantiate\",545,111],[\"instantiate\",549,112],[\"instantiate\",553,113],[\"instantiate\",557,114],[\"instantiate\",562,107],[\"instantiate\",566,108],[\"module\",570,115],[\"invoke\",574,0,\"grow\",[\"i32:0\"]],[\"invoke\",575,0,\"grow\",[\"i32:1\"]],[\"invoke\",576,0,\"grow\",[\"i32:0\"]],[\"invoke\",577,0,\"grow\",[\"i32:1\"]],[\"invoke\",578,0,\"grow\",[\"i32:0\"]],[\"module\",654,116],[\"register\",\"not wasm\"],[\"instantiate\",657,117],[\"file\",\"table.wast\"],[\"module\",3,118],[\"module\",4,119],[\"module\",5,120],[\"module\",6,121],[\"module\",7,122],[\"module\",8,123],[\"module\",9,124],[\"module\",11,125],[\"module\",12,126],[\"file\",\"names.wast\"],[\"module\",4,127],[\"invoke\",8,0,\"foo\",[]],[\"module\",12,128],[\"module\",19,129],[\"invoke\",615,0,\"\",[]],[\"invoke\",616,0,\"0\",[]],[\"invoke\",617,0,\"-0\",[]],[\"invoke\",618,0,\"_\",[]],[\"invoke\",620,0,\"@\",[]],[\"invoke\",621,0,\"~!@#$%^&*()_+`-={}|[]\\\\:\\\";'<>?,./ \",[]],[\"invoke\",622,0,\"NaN\",[]],[\"invoke\",623,0,\"Infinity\",[]],[\"invoke\",624,0,\"if\",[]],[\"invoke\",626,0,\"_malloc\",[]],[\"invoke\",627,0,\"__malloc\",[]],[\"invoke\",628,0,\"a\",[]],[\"invoke\",629,0,\"A\",[]],[\"invoke\",630,0,\"\",[]],[\"invoke\",632,0,\"Å\",[]],[\"invoke\",633,0,\"Å\",[]],[\"invoke\",634,0,\"ffi\",[]],[\"invoke\",635,0,\"ffi\",[]],[\"invoke\",636,0,\"ffi\",[]],[\"invoke\",638,0,\"\\u0010\\u0011\\u0012\\u0013\\u0014\\u0015\\u0016\\u0017\\u0018\\u0019\\u001a\\u001b\\u001c\\u001d\\u001e\\u001f\",[]],[\"invoke\",639,0,\" \",[]],[\"invoke\",640,0,\"€‚ƒ„…†‡ˆ‰Š‹ŒŽ\",[]],[\"invoke\",641,0,\"‘’“”•–—˜™š›œžŸ\",[]],[\"invoke\",642,0,\"￰￱￲￳￴￵￶￷\",[]],[\"invoke\",644,0,\"␀␁␂␃␄␅␆␇␈␉␊␋␌␍␎␏\",[]],[\"invoke\",645,0,\"␐␑␒␓␔␕␖␗␘␙␚␛␜␝␞␟\",[]],[\"invoke\",646,0,\"␠␡\",[]],[\"invoke\",647,0,\"￰￱￲￳￴￵￶￷￸�\",[]],[\"invoke\",648,0,\"‍\",[]],[\"invoke\",650,0,\"͏\",[]],[\"invoke\",651,0,\"⁠\",[]],[\"invoke\",652,0,\"⵿\",[]],[\"invoke\",653,0,\"𑁿\",[]],[\"invoke\",654,0,\"᠎\",[]],[\"invoke\",656,0,\"‎‏‑

‪‫‬ ⁦⁧⁨⁩\",[]],[\"invoke\",657,0,\"\",[]],[\"invoke\",658,0,\"⁡⁢⁣⁤\",[]],[\"invoke\",659,0,\"𐀀󟿿􏿿\",[]],[\"invoke\",660,0,\"Z̴͇̫̥̪͓͈͔͎̗̞̺̯̱̞̙̱̜̖̠̏͆̆͛͌͘͞ḁ̶̰̳̭͙̲̱̹̝͎̼͗ͨ̎̄̆͗̿̀́͟͡l̶̷͉̩̹̫̝͖̙̲̼͇͚͍̮͎̥̞̈́͊͗ͦ̈́ͫ̇́̚ͅͅg̶͕͔͚̩̓̐̅ͮ̔̐̎̂̏̾͊̍͋͊ͧ́̆ͦ͞o̡͋̔͐ͪͩ͏̢̧̫̙̤̮͖͙͓̺̜̩̼̘̠́\",[]],[\"invoke\",662,0,\"︀\",[]],[\"invoke\",663,0,\"︄\",[]],[\"invoke\",664,0,\"󠄀\",[]],[\"invoke\",665,0,\"󠇯\",[]],[\"invoke\",667,0,\"\\n\",[]],[\"invoke\",668,0,\"␤\",[]],[\"invoke\",669,0,\"
\",[]],[\"invoke\",670,0,\"\\r\",[]],[\"invoke\",671,0,\"\\r\\n\",[]],[\"invoke\",673,0,\"\\u001e\",[]],[\"invoke\",674,0,\"\\u000b\",[]],[\"invoke\",675,0,\"\\f\",[]],[\"invoke\",676,0,\"…\",[]],[\"invoke\",677,0,\"
\",[]],[\"invoke\",679,0,\"⏎\",[]],[\"invoke\",680,0,\"‹\",[]],[\"invoke\",681,0,\"Œ\",[]],[\"invoke\",682,0,\"\",[]],[\"invoke\",683,0,\"↵\",[]],[\"invoke\",685,0,\"⌤\",[]],[\"invoke\",686,0,\"⤶\",[]],[\"invoke\",687,0,\"↲\",[]],[\"invoke\",688,0,\"⮨\",[]],[\"invoke\",689,0,\"⮰\",[]],[\"invoke\",691,0,\"﷐\",[]],[\"invoke\",692,0,\"﷑\",[]],[\"invoke\",693,0,\"﷒\",[]],[\"invoke\",694,0,\"﷓\",[]],[\"invoke\",695,0,\"﷔\",[]],[\"invoke\",697,0,\"﷖\",[]],[\"invoke\",698,0,\"﷗\",[]],[\"invoke\",699,0,\"﷘\",[]],[\"invoke\",700,0,\"﷙\",[]],[\"invoke\",701,0,\"﷚\",[]],[\"invoke\",703,0,\"﷜\",[]],[\"invoke\",704,0,\"﷝\",[]],[\"invoke\",705,0,\"﷞\",[]],[\"invoke\",706,0,\"﷟\",[]],[\"invoke\",707,0,\"﷠\",[]],[\"invoke\",709,0,\"﷢\",[]],[\"invoke\",710,0,\"﷣\",[]],[\"invoke\",711,0,\"﷤\",[]],[\"invoke\",712,0,\"﷥\",[]],[\"invoke\",714,0,\"﷧\",[]],[\"invoke\",715,0,\"﷨\",[]],[\"invoke\",716,0,\"﷩\",[]],[\"invoke\",717,0,\"﷪\",[]],[\"invoke\",718,0,\"﷫\",[]],[\"invoke\",720,0,\"﷭\",[]],[\"invoke\",721,0,\"﷮\",[]],[\"invoke\",722,0,\"﷯\",[]],[\"invoke\",723,0,\"￾\",[]],[\"invoke\",724,0,\"￿\",[]],[\"invoke\",726,0,\"🿿\",[]],[\"invoke\",727,0,\"𯿾\",[]],[\"invoke\",728,0,\"𯿿\",[]],[\"invoke\",729,0,\"𿿾\",[]],[\"invoke\",730,0,\"𿿿\",[]],[\"invoke\",732,0,\"񏿿\",[]],[\"invoke\",733,0,\"񟿾\",[]],[\"invoke\",734,0,\"񟿿\",[]],[\"invoke\",735,0,\"񯿾\",[]],[\"invoke\",736,0,\"񯿿\",[]],[\"invoke\",738,0,\"񿿿\",[]],[\"invoke\",739,0,\"򏿾\",[]],[\"invoke\",740,0,\"򏿿\",[]],[\"invoke\",741,0,\"򟿾\",[]],[\"invoke\",742,0,\"򟿿\",[]],[\"invoke\",744,0,\"򯿿\",[]],[\"invoke\",745,0,\"򿿾\",[]],[\"invoke\",746,0,\"򿿿\",[]],[\"invoke\",747,0,\"󏿾\",[]],[\"invoke\",748,0,\"󏿿\",[]],[\"invoke\",750,0,\"󟿿\",[]],[\"invoke\",751,0,\"󯿾\",[]],[\"invoke\",752,0,\"󯿿\",[]],[\"invoke\",753,0,\"󿿾\",[]],[\"invoke\",754,0,\"󿿿\",[]],[\"invoke\",756,0,\"􏿿\",[]],[\"invoke\",757,0,\"̈‽̈̉\",[]],[\"invoke\",758,0,\"abc\",[]],[\"invoke\",759,0,\"‭abc\",[]],[\"invoke\",761,0,\"‭abc‮\",[]],[\"invoke\",762,0,\"‮cba‭\",[]],[\"invoke\",763,0,\"𝑨\",[]],[\"invoke\",764,0,\"𝐴\",[]],[\"invoke\",765,0,\"𝘈\",[]],[\"invoke\",767,0,\"𝐀\",[]],[\"invoke\",768,0,\"𝓐\",[]],[\"invoke\",769,0,\"𝕬\",[]],[\"invoke\",770,0,\"𝗔\",[]],[\"invoke\",771,0,\"𝒜\",[]],[\"invoke\",773,0,\"𝔸\",[]],[\"invoke\",774,0,\"𝖠\",[]],[\"invoke\",775,0,\"𝙰\",[]],[\"invoke\",776,0,\"ᴀ\",[]],[\"invoke\",777,0,\"ᴬ\",[]],[\"invoke\",779,0,\"A\",[]],[\"invoke\",780,0,\"🄐\",[]],[\"invoke\",781,0,\"🄰\",[]],[\"invoke\",782,0,\"󠁁\",[]],[\"invoke\",783,0,\"U+0041\",[]],[\"invoke\",785,0,\"А\",[]],[\"invoke\",786,0,\"Ꙗ\",[]],[\"invoke\",787,0,\"ⷼ\",[]],[\"invoke\",788,0,\"ⷶ\",[]],[\"invoke\",789,0,\"Ɐ\",[]],[\"invoke\",791,0,\"🅰\",[]],[\"invoke\",792,0,\"Ⱝ\",[]],[\"invoke\",793,0,\"𐐂\",[]],[\"invoke\",794,0,\"𐐈\",[]],[\"invoke\",795,0,\"𐒰\",[]],[\"invoke\",797,0,\"Á\",[]],[\"invoke\",798,0,\"Â\",[]],[\"invoke\",799,0,\"Ã\",[]],[\"invoke\",800,0,\"Ä\",[]],[\"invoke\",801,0,\"Ā\",[]],[\"invoke\",803,0,\"Ą\",[]],[\"invoke\",804,0,\"Ǎ\",[]],[\"invoke\",805,0,\"Ǟ\",[]],[\"invoke\",806,0,\"Ǡ\",[]],[\"invoke\",808,0,\"Ȁ\",[]],[\"invoke\",809,0,\"Ȃ\",[]],[\"invoke\",810,0,\"Ȧ\",[]],[\"invoke\",811,0,\"Ⱥ\",[]],[\"invoke\",812,0,\"Ӑ\",[]],[\"invoke\",814,0,\"ߊ\",[]],[\"invoke\",815,0,\"ࠡ\",[]],[\"invoke\",816,0,\"ࠢ\",[]],[\"invoke\",817,0,\"ࠣ\",[]],[\"invoke\",818,0,\"ࠤ\",[]],[\"invoke\",820,0,\"ऄ\",[]],[\"invoke\",821,0,\"अ\",[]],[\"invoke\",822,0,\"ॲ\",[]],[\"invoke\",823,0,\"অ\",[]],[\"invoke\",824,0,\"ਅ\",[]],[\"invoke\",826,0,\"ଅ\",[]],[\"invoke\",827,0,\"அ\",[]],[\"invoke\",828,0,\"అ\",[]],[\"invoke\",829,0,\"ಅ\",[]],[\"invoke\",830,0,\"അ\",[]],[\"invoke\",832,0,\"ະ\",[]],[\"invoke\",833,0,\"༁\",[]],[\"invoke\",834,0,\"ཨ\",[]],[\"invoke\",835,0,\"ྸ\",[]],[\"invoke\",836,0,\"အ\",[]],[\"invoke\",838,0,\"ႜ\",[]],[\"invoke\",839,0,\"ᅡ\",[]],[\"invoke\",840,0,\"አ\",[]],[\"invoke\",841,0,\"ዐ\",[]],[\"invoke\",842,0,\"Ꭰ\",[]],[\"invoke\",844,0,\"ᖳ\",[]],[\"invoke\",845,0,\"ᚨ\",[]],[\"invoke\",846,0,\"ᚪ\",[]],[\"invoke\",847,0,\"ᛆ\",[]],[\"invoke\",848,0,\"ᜀ\",[]],[\"invoke\",850,0,\"ᝀ\",[]],[\"invoke\",851,0,\"ᝠ\",[]],[\"invoke\",852,0,\"ᠠ\",[]],[\"invoke\",853,0,\"ᢇ\",[]],[\"invoke\",855,0,\"ᥣ\",[]],[\"invoke\",856,0,\"ᨕ\",[]],[\"invoke\",857,0,\"ᩋ\",[]],[\"invoke\",858,0,\"ᩡ\",[]],[\"invoke\",859,0,\"ᮃ\",[]],[\"invoke\",861,0,\"ᯁ\",[]],[\"invoke\",862,0,\"ᰣ\",[]],[\"invoke\",863,0,\"Ḁ\",[]],[\"invoke\",864,0,\"Ạ\",[]],[\"invoke\",865,0,\"Ả\",[]],[\"invoke\",867,0,\"Ầ\",[]],[\"invoke\",868,0,\"Ẩ\",[]],[\"invoke\",869,0,\"Ẫ\",[]],[\"invoke\",870,0,\"Ậ\",[]],[\"invoke\",871,0,\"Ắ\",[]],[\"invoke\",873,0,\"Ẳ\",[]],[\"invoke\",874,0,\"Ẵ\",[]],[\"invoke\",875,0,\"Ặ\",[]],[\"invoke\",876,0,\"あ\",[]],[\"invoke\",877,0,\"ア\",[]],[\"invoke\",879,0,\"ㅏ\",[]],[\"invoke\",880,0,\"㈎\",[]],[\"invoke\",881,0,\"㈏\",[]],[\"invoke\",882,0,\"㈐\",[]],[\"invoke\",883,0,\"㈑\",[]],[\"invoke\",885,0,\"㈓\",[]],[\"invoke\",886,0,\"㈔\",[]],[\"invoke\",887,0,\"㈕\",[]],[\"invoke\",888,0,\"㈖\",[]],[\"invoke\",889,0,\"㈗\",[]],[\"invoke\",891,0,\"㈙\",[]],[\"invoke\",892,0,\"㈚\",[]],[\"invoke\",893,0,\"㈛\",[]],[\"invoke\",894,0,\"㉮\",[]],[\"invoke\",895,0,\"㉯\",[]],[\"invoke\",897,0,\"㉱\",[]],[\"invoke\",898,0,\"㉲\",[]],[\"invoke\",899,0,\"㉳\",[]],[\"invoke\",900,0,\"㉴\",[]],[\"invoke\",901,0,\"㉵\",[]],[\"invoke\",903,0,\"㉷\",[]],[\"invoke\",904,0,\"㉸\",[]],[\"invoke\",905,0,\"㉹\",[]],[\"invoke\",906,0,\"㉺\",[]],[\"invoke\",908,0,\"㋐\",[]],[\"invoke\",909,0,\"ꀊ\",[]],[\"invoke\",910,0,\"ꓮ\",[]],[\"invoke\",911,0,\"ꕉ\",[]],[\"invoke\",912,0,\"ꚠ\",[]],[\"invoke\",914,0,\"ꠣ\",[]],[\"invoke\",915,0,\"ꡝ\",[]],[\"invoke\",916,0,\"ꢂ\",[]],[\"invoke\",917,0,\"꣪\",[]],[\"invoke\",918,0,\"ꤢ\",[]],[\"invoke\",920,0,\"ꦄ\",[]],[\"invoke\",921,0,\"ꨀ\",[]],[\"invoke\",922,0,\"ア\",[]],[\"invoke\",923,0,\"ᅡ\",[]],[\"invoke\",924,0,\"𐀀\",[]],[\"invoke\",926,0,\"𐊠\",[]],[\"invoke\",927,0,\"𐌀\",[]],[\"invoke\",928,0,\"𐎠\",[]],[\"invoke\",929,0,\"𐒖\",[]],[\"invoke\",930,0,\"𐔀\",[]],[\"invoke\",932,0,\"𐠀\",[]],[\"invoke\",933,0,\"𐤠\",[]],[\"invoke\",934,0,\"𐦀\",[]],[\"invoke\",935,0,\"𐦠\",[]],[\"invoke\",936,0,\"𐨀\",[]],[\"invoke\",938,0,\"𐰀\",[]],[\"invoke\",939,0,\"𐰁\",[]],[\"invoke\",940,0,\"𐲀\",[]],[\"invoke\",941,0,\"𑀅\",[]],[\"invoke\",942,0,\"𑂃\",[]],[\"invoke\",944,0,\"𑅐\",[]],[\"invoke\",945,0,\"𑆃\",[]],[\"invoke\",946,0,\"𑈀\",[]],[\"invoke\",947,0,\"𑊀\",[]],[\"invoke\",948,0,\"𑊰\",[]],[\"invoke\",950,0,\"𑍰\",[]],[\"invoke\",951,0,\"𑐀\",[]],[\"invoke\",952,0,\"𑒁\",[]],[\"invoke\",953,0,\"𑖀\",[]],[\"invoke\",955,0,\"𑚀\",[]],[\"invoke\",956,0,\"𑜒\",[]],[\"invoke\",957,0,\"𑜠\",[]],[\"invoke\",958,0,\"𑢡\",[]],[\"invoke\",959,0,\"𑫕\",[]],[\"invoke\",961,0,\"𑲏\",[]],[\"invoke\",962,0,\"𑲯\",[]],[\"invoke\",963,0,\"𒀀\",[]],[\"invoke\",964,0,\"𖧕\",[]],[\"invoke\",965,0,\"𖩆\",[]],[\"invoke\",967,0,\"𖽔\",[]],[\"invoke\",968,0,\"𛱁\",[]],[\"invoke\",969,0,\"𛱤\",[]],[\"invoke\",970,0,\"𞠣\",[]],[\"invoke\",971,0,\"🇦\",[]],[\"invoke\",973,0,\"Λ\",[]],[\"invoke\",974,0,\"Ɒ\",[]],[\"invoke\",975,0,\"ª\",[]],[\"invoke\",976,0,\"∀\",[]],[\"invoke\",977,0,\"₳\",[]],[\"invoke\",979,0,\"Ⲁ\",[]],[\"invoke\",980,0,\"𐌰\",[]],[\"invoke\",981,0,\"Ά\",[]],[\"invoke\",982,0,\"Α\",[]],[\"invoke\",983,0,\"Ἀ\",[]],[\"invoke\",985,0,\"Ἂ\",[]],[\"invoke\",986,0,\"Ἃ\",[]],[\"invoke\",987,0,\"Ἄ\",[]],[\"invoke\",988,0,\"Ἅ\",[]],[\"invoke\",989,0,\"Ἆ\",[]],[\"invoke\",991,0,\"ᾈ\",[]],[\"invoke\",992,0,\"ᾉ\",[]],[\"invoke\",993,0,\"ᾊ\",[]],[\"invoke\",994,0,\"ᾋ\",[]],[\"invoke\",995,0,\"ᾌ\",[]],[\"invoke\",997,0,\"ᾎ\",[]],[\"invoke\",998,0,\"ᾏ\",[]],[\"invoke\",999,0,\"Ᾰ\",[]],[\"invoke\",1000,0,\"Ᾱ\",[]],[\"invoke\",1002,0,\"Ά\",[]],[\"invoke\",1003,0,\"ᾼ\",[]],[\"invoke\",1004,0,\"𝚨\",[]],[\"invoke\",1005,0,\"𝛢\",[]],[\"invoke\",1006,0,\"𝜜\",[]],[\"invoke\",1008,0,\"𝞐\",[]],[\"invoke\",1009,0,\"⍶\",[]],[\"invoke\",1010,0,\"⍺\",[]],[\"invoke\",1011,0,\"⩜\",[]],[\"invoke\",1012,0,\"ᗅ\",[]],[\"invoke\",1014,0,\")˺˼𔗏𝅴𝅶𝅸𝅺⁾₎❩❫⟯﴿︶﹚)⦆󠀩❳❵⟧⟩⟫⟭⦈⦊⦖⸣⸥︘︸︺︼︾﹀﹂﹄﹈﹜﹞]}」󠁝󠁽»’”›❯\",[]],[\"invoke\",1015,0,\"(˹˻𔗎𝅳𝅵𝅷𝅹⁽₍❨❪⟮﴾︵﹙(⦅󠀨❲❴⟦⟨⟪⟬⦇⦉⦕⸢⸤︗︷︹︻︽︿﹁﹃﹇﹛﹝[{「󠁛󠁻«‘“‹❮\",[]],[\"invoke\",1016,0,\"𝪋𝪤\",[]],[\"invoke\",1017,0,\"𝪋\",[]],[\"invoke\",1018,0,\"½\",[]],[\"invoke\",1020,0,\"1/2\",[]],[\"invoke\",1021,0,\"୳\",[]],[\"invoke\",1022,0,\"൴\",[]],[\"invoke\",1023,0,\"⳽\",[]],[\"invoke\",1024,0,\"꠱\",[]],[\"invoke\",1026,0,\"𐅵\",[]],[\"invoke\",1027,0,\"𐅶\",[]],[\"invoke\",1028,0,\"𐦽\",[]],[\"invoke\",1029,0,\"𐹻\",[]],[\"invoke\",1030,0,\""\",[]],[\"invoke\",1032,0,\"\\b\",[]],[\"invoke\",1033,0,\"⌫\",[]],[\"invoke\",1034,0,\"⌦\",[]],[\"invoke\",1035,0,\"␈\",[]],[\"invoke\",1036,0,\"␡\",[]],[\"invoke\",1038,0,\"\\u000f\",[]],[\"invoke\",1039,0,\"←\",[]],[\"invoke\",1040,0,\"⌧\",[]],[\"invoke\",1041,0,\"⍒\",[]],[\"invoke\",1042,0,\"⍔\",[]],[\"invoke\",1044,0,\"⍫\",[]],[\"invoke\",1045,0,\"\\u001a\",[]],[\"invoke\",1046,0,\"␦\",[]],[\"invoke\",1047,0,\"␚\",[]],[\"invoke\",1049,0,\"?\",[]],[\"invoke\",1050,0,\"¿\",[]],[\"invoke\",1051,0,\"᥅\",[]],[\"invoke\",1052,0,\";\",[]],[\"invoke\",1053,0,\"՞\",[]],[\"invoke\",1055,0,\"፧\",[]],[\"invoke\",1056,0,\"⁇\",[]],[\"invoke\",1057,0,\"⍰\",[]],[\"invoke\",1058,0,\"❓\",[]],[\"invoke\",1059,0,\"❔\",[]],[\"invoke\",1061,0,\"⳻\",[]],[\"invoke\",1062,0,\"⸮\",[]],[\"invoke\",1063,0,\"㉄\",[]],[\"invoke\",1064,0,\"꘏\",[]],[\"invoke\",1065,0,\"꛷\",[]],[\"invoke\",1067,0,\"﹖\",[]],[\"invoke\",1068,0,\"?\",[]],[\"invoke\",1069,0,\"𑅃\",[]],[\"invoke\",1070,0,\"𞥟\",[]],[\"invoke\",1071,0,\"󠀿\",[]],[\"invoke\",1073,0,\"⯑\",[]],[\"invoke\",1074,0,\"¶\",[]],[\"invoke\",1075,0,\"⁋\",[]],[\"invoke\",1076,0,\"܀\",[]],[\"invoke\",1077,0,\"჻\",[]],[\"invoke\",1079,0,\"〷\",[]],[\"invoke\",1080,0,\"❡\",[]],[\"invoke\",1081,0,\"⸏\",[]],[\"invoke\",1082,0,\"⸐\",[]],[\"invoke\",1083,0,\"⸑\",[]],[\"invoke\",1085,0,\"\\u0014\",[]],[\"invoke\",1086,0,\"☙\",[]],[\"invoke\",1087,0,\"⸿\",[]],[\"invoke\",1088,0,\"〇\",[]],[\"invoke\",1089,0,\"๛\",[]],[\"invoke\",1091,0,\"ϓ\",[]],[\"invoke\",1092,0,\"ϔ\",[]],[\"invoke\",1093,0,\"ẛ\",[]],[\"module\",1095,130],[\"invoke\",1107,0,\"print32\",[\"i32:42\",\"i32:123\"]]]"; +export const script = "[[\"file\",\"imports.wast\"],[\"module\",3,0],[\"register\",\"test\"],[\"module\",26,1],[\"invoke\",85,0,\"print32\",[\"i32:13\"]],[\"invoke\",86,0,\"print64\",[\"i64:24\"]],[\"module\",97,2],[\"invoke\",104,0,\"print_i32\",[\"i32:13\"]],[\"module\",107,3],[\"invoke\",114,0,\"print_i32\",[\"i32:5\",\"i32:11\"]],[\"module\",116,4],[\"module\",117,5],[\"module\",118,6],[\"module\",119,7],[\"module\",120,8],[\"module\",121,9],[\"module\",122,10],[\"instantiate\",125,11],[\"instantiate\",129,12],[\"instantiate\",134,13],[\"instantiate\",138,14],[\"instantiate\",142,15],[\"instantiate\",146,16],[\"instantiate\",150,17],[\"instantiate\",154,18],[\"instantiate\",158,19],[\"instantiate\",162,20],[\"instantiate\",166,21],[\"instantiate\",170,22],[\"instantiate\",174,23],[\"instantiate\",178,24],[\"instantiate\",182,25],[\"instantiate\",186,26],[\"instantiate\",190,27],[\"instantiate\",194,28],[\"instantiate\",199,29],[\"instantiate\",203,30],[\"instantiate\",207,31],[\"instantiate\",211,32],[\"instantiate\",215,33],[\"instantiate\",219,34],[\"module\",226,35],[\"invoke\",246,0,\"get-0\",[]],[\"invoke\",247,0,\"get-1\",[]],[\"invoke\",248,0,\"get-x\",[]],[\"invoke\",249,0,\"get-y\",[]],[\"invoke\",250,0,\"get-4\",[]],[\"invoke\",251,0,\"get-5\",[]],[\"invoke\",252,0,\"get-6\",[]],[\"module\",254,36],[\"module\",255,37],[\"module\",256,38],[\"instantiate\",259,39],[\"instantiate\",263,40],[\"instantiate\",268,41],[\"instantiate\",272,42],[\"instantiate\",276,43],[\"instantiate\",280,44],[\"instantiate\",284,45],[\"instantiate\",288,46],[\"instantiate\",292,47],[\"instantiate\",296,48],[\"instantiate\",300,49],[\"instantiate\",304,50],[\"instantiate\",308,51],[\"instantiate\",312,52],[\"instantiate\",317,53],[\"instantiate\",321,54],[\"instantiate\",325,55],[\"instantiate\",329,56],[\"instantiate\",333,57],[\"instantiate\",337,58],[\"module\",344,59],[\"invoke\",356,0,\"call\",[\"i32:0\"]],[\"invoke\",357,0,\"call\",[\"i32:1\"]],[\"invoke\",358,0,\"call\",[\"i32:2\"]],[\"invoke\",359,0,\"call\",[\"i32:3\"]],[\"invoke\",360,0,\"call\",[\"i32:100\"]],[\"module\",363,59],[\"invoke\",375,0,\"call\",[\"i32:0\"]],[\"invoke\",376,0,\"call\",[\"i32:1\"]],[\"invoke\",377,0,\"call\",[\"i32:2\"]],[\"invoke\",378,0,\"call\",[\"i32:3\"]],[\"invoke\",379,0,\"call\",[\"i32:100\"]],[\"module\",381,60],[\"module\",388,61],[\"module\",389,62],[\"module\",390,63],[\"module\",391,64],[\"module\",392,65],[\"module\",393,66],[\"module\",394,67],[\"module\",395,68],[\"module\",396,69],[\"module\",397,70],[\"module\",398,71],[\"module\",399,72],[\"module\",400,73],[\"module\",401,74],[\"module\",402,75],[\"module\",403,76],[\"module\",404,77],[\"module\",405,78],[\"module\",406,79],[\"module\",407,80],[\"instantiate\",410,81],[\"instantiate\",414,82],[\"instantiate\",419,83],[\"instantiate\",423,84],[\"instantiate\",427,85],[\"instantiate\",431,86],[\"instantiate\",435,87],[\"instantiate\",439,88],[\"instantiate\",444,89],[\"instantiate\",448,90],[\"instantiate\",452,91],[\"instantiate\",456,92],[\"module\",464,93],[\"invoke\",471,0,\"load\",[\"i32:0\"]],[\"invoke\",472,0,\"load\",[\"i32:10\"]],[\"invoke\",473,0,\"load\",[\"i32:8\"]],[\"invoke\",474,0,\"load\",[\"i32:1000000\"]],[\"module\",476,93],[\"invoke\",482,0,\"load\",[\"i32:0\"]],[\"invoke\",483,0,\"load\",[\"i32:10\"]],[\"invoke\",484,0,\"load\",[\"i32:8\"]],[\"invoke\",485,0,\"load\",[\"i32:1000000\"]],[\"module\",500,94],[\"module\",501,95],[\"module\",502,96],[\"module\",503,97],[\"module\",504,98],[\"module\",505,99],[\"module\",506,100],[\"module\",507,101],[\"module\",508,102],[\"instantiate\",511,103],[\"instantiate\",515,104],[\"instantiate\",520,105],[\"instantiate\",524,106],[\"instantiate\",528,107],[\"instantiate\",532,108],[\"instantiate\",537,109],[\"instantiate\",541,110],[\"instantiate\",545,111],[\"instantiate\",549,112],[\"instantiate\",553,113],[\"instantiate\",557,114],[\"instantiate\",562,107],[\"instantiate\",566,108],[\"module\",570,115],[\"invoke\",574,0,\"grow\",[\"i32:0\"]],[\"invoke\",575,0,\"grow\",[\"i32:1\"]],[\"invoke\",576,0,\"grow\",[\"i32:0\"]],[\"invoke\",577,0,\"grow\",[\"i32:1\"]],[\"invoke\",578,0,\"grow\",[\"i32:0\"]],[\"module\",654,116],[\"register\",\"not wasm\"],[\"instantiate\",657,117],[\"file\",\"table.wast\"],[\"module\",3,118],[\"module\",4,119],[\"module\",5,120],[\"module\",6,121],[\"module\",7,122],[\"module\",8,123],[\"module\",9,124],[\"module\",11,125],[\"module\",12,126],[\"file\",\"names.wast\"],[\"module\",4,127],[\"invoke\",8,0,\"foo\",[]],[\"module\",12,128],[\"module\",19,129],[\"invoke\",615,0,\"\",[]],[\"invoke\",616,0,\"0\",[]],[\"invoke\",617,0,\"-0\",[]],[\"invoke\",618,0,\"_\",[]],[\"invoke\",620,0,\"@\",[]],[\"invoke\",621,0,\"~!@#$%^&*()_+`-={}|[]\\\\:\\\";'<>?,./ \",[]],[\"invoke\",622,0,\"NaN\",[]],[\"invoke\",623,0,\"Infinity\",[]],[\"invoke\",624,0,\"if\",[]],[\"invoke\",626,0,\"_malloc\",[]],[\"invoke\",627,0,\"__malloc\",[]],[\"invoke\",628,0,\"a\",[]],[\"invoke\",629,0,\"A\",[]],[\"invoke\",630,0,\"\u{feff}\",[]],[\"invoke\",632,0,\"Å\",[]],[\"invoke\",633,0,\"Å\",[]],[\"invoke\",634,0,\"ffi\",[]],[\"invoke\",635,0,\"ffi\",[]],[\"invoke\",636,0,\"ffi\",[]],[\"invoke\",638,0,\"\\u0010\\u0011\\u0012\\u0013\\u0014\\u0015\\u0016\\u0017\\u0018\\u0019\\u001a\\u001b\\u001c\\u001d\\u001e\\u001f\",[]],[\"invoke\",639,0,\" \u{7f}\",[]],[\"invoke\",640,0,\"\u{80}\u{81}\u{82}\u{83}\u{84}\u{85}\u{86}\u{87}\u{88}\u{89}\u{8a}\u{8b}\u{8c}\u{8d}\u{8e}\u{8f}\",[]],[\"invoke\",641,0,\"\u{90}\u{91}\u{92}\u{93}\u{94}\u{95}\u{96}\u{97}\u{98}\u{99}\u{9a}\u{9b}\u{9c}\u{9d}\u{9e}\u{9f}\",[]],[\"invoke\",642,0,\"￰￱￲￳￴￵￶￷\",[]],[\"invoke\",644,0,\"␀␁␂␃␄␅␆␇␈␉␊␋␌␍␎␏\",[]],[\"invoke\",645,0,\"␐␑␒␓␔␕␖␗␘␙␚␛␜␝␞␟\",[]],[\"invoke\",646,0,\"␠␡\",[]],[\"invoke\",647,0,\"￰￱￲￳￴￵￶￷￸\u{fff9}\u{fffa}\u{fffb}�\",[]],[\"invoke\",648,0,\"\u{200d}\",[]],[\"invoke\",650,0,\"͏\",[]],[\"invoke\",651,0,\"\u{2060}\",[]],[\"invoke\",652,0,\"⵿\",[]],[\"invoke\",653,0,\"𑁿\",[]],[\"invoke\",654,0,\"\u{180e}\",[]],[\"invoke\",656,0,\"\u{200e}\u{200f}‑\u{2028}\u{2029}\u{202a}\u{202b}\u{202c} \u{2066}\u{2067}\u{2068}\u{2069}\",[]],[\"invoke\",657,0,\"\u{206a}\u{206b}\u{206c}\u{206d}\u{206e}\u{206f}\",[]],[\"invoke\",658,0,\"\u{2061}\u{2062}\u{2063}\u{2064}\",[]],[\"invoke\",659,0,\"𐀀󟿿􏿿\",[]],[\"invoke\",660,0,\"Z̴͇̫̥̪͓͈͔͎̗̞̺̯̱̞̙̱̜̖̠̏͆̆͛͌͘͞ḁ̶̰̳̭͙̲̱̹̝͎̼͗ͨ̎̄̆͗̿̀́͟͡l̶̷͉̩̹̫̝͖̙̲̼͇͚͍̮͎̥̞̈́͊͗ͦ̈́ͫ̇́̚ͅͅg̶͕͔͚̩̓̐̅ͮ̔̐̎̂̏̾͊̍͋͊ͧ́̆ͦ͞o̡͋̔͐ͪͩ͏̢̧̫̙̤̮͖͙͓̺̜̩̼̘̠́\",[]],[\"invoke\",662,0,\"︀\",[]],[\"invoke\",663,0,\"︄\",[]],[\"invoke\",664,0,\"󠄀\",[]],[\"invoke\",665,0,\"󠇯\",[]],[\"invoke\",667,0,\"\\n\",[]],[\"invoke\",668,0,\"␤\",[]],[\"invoke\",669,0,\"\u{2028}\",[]],[\"invoke\",670,0,\"\\r\",[]],[\"invoke\",671,0,\"\\r\\n\",[]],[\"invoke\",673,0,\"\\u001e\",[]],[\"invoke\",674,0,\"\\u000b\",[]],[\"invoke\",675,0,\"\\f\",[]],[\"invoke\",676,0,\"\u{85}\",[]],[\"invoke\",677,0,\"\u{2029}\",[]],[\"invoke\",679,0,\"⏎\",[]],[\"invoke\",680,0,\"\u{8b}\",[]],[\"invoke\",681,0,\"\u{8c}\",[]],[\"invoke\",682,0,\"\u{8d}\",[]],[\"invoke\",683,0,\"↵\",[]],[\"invoke\",685,0,\"⌤\",[]],[\"invoke\",686,0,\"⤶\",[]],[\"invoke\",687,0,\"↲\",[]],[\"invoke\",688,0,\"⮨\",[]],[\"invoke\",689,0,\"⮰\",[]],[\"invoke\",691,0,\"﷐\",[]],[\"invoke\",692,0,\"﷑\",[]],[\"invoke\",693,0,\"﷒\",[]],[\"invoke\",694,0,\"﷓\",[]],[\"invoke\",695,0,\"﷔\",[]],[\"invoke\",697,0,\"﷖\",[]],[\"invoke\",698,0,\"﷗\",[]],[\"invoke\",699,0,\"﷘\",[]],[\"invoke\",700,0,\"﷙\",[]],[\"invoke\",701,0,\"﷚\",[]],[\"invoke\",703,0,\"﷜\",[]],[\"invoke\",704,0,\"﷝\",[]],[\"invoke\",705,0,\"﷞\",[]],[\"invoke\",706,0,\"﷟\",[]],[\"invoke\",707,0,\"﷠\",[]],[\"invoke\",709,0,\"﷢\",[]],[\"invoke\",710,0,\"﷣\",[]],[\"invoke\",711,0,\"﷤\",[]],[\"invoke\",712,0,\"﷥\",[]],[\"invoke\",714,0,\"﷧\",[]],[\"invoke\",715,0,\"﷨\",[]],[\"invoke\",716,0,\"﷩\",[]],[\"invoke\",717,0,\"﷪\",[]],[\"invoke\",718,0,\"﷫\",[]],[\"invoke\",720,0,\"﷭\",[]],[\"invoke\",721,0,\"﷮\",[]],[\"invoke\",722,0,\"﷯\",[]],[\"invoke\",723,0,\"￾\",[]],[\"invoke\",724,0,\"￿\",[]],[\"invoke\",726,0,\"🿿\",[]],[\"invoke\",727,0,\"𯿾\",[]],[\"invoke\",728,0,\"𯿿\",[]],[\"invoke\",729,0,\"𿿾\",[]],[\"invoke\",730,0,\"𿿿\",[]],[\"invoke\",732,0,\"񏿿\",[]],[\"invoke\",733,0,\"񟿾\",[]],[\"invoke\",734,0,\"񟿿\",[]],[\"invoke\",735,0,\"񯿾\",[]],[\"invoke\",736,0,\"񯿿\",[]],[\"invoke\",738,0,\"񿿿\",[]],[\"invoke\",739,0,\"򏿾\",[]],[\"invoke\",740,0,\"򏿿\",[]],[\"invoke\",741,0,\"򟿾\",[]],[\"invoke\",742,0,\"򟿿\",[]],[\"invoke\",744,0,\"򯿿\",[]],[\"invoke\",745,0,\"򿿾\",[]],[\"invoke\",746,0,\"򿿿\",[]],[\"invoke\",747,0,\"󏿾\",[]],[\"invoke\",748,0,\"󏿿\",[]],[\"invoke\",750,0,\"󟿿\",[]],[\"invoke\",751,0,\"󯿾\",[]],[\"invoke\",752,0,\"󯿿\",[]],[\"invoke\",753,0,\"󿿾\",[]],[\"invoke\",754,0,\"󿿿\",[]],[\"invoke\",756,0,\"􏿿\",[]],[\"invoke\",757,0,\"̈‽̈̉\",[]],[\"invoke\",758,0,\"abc\",[]],[\"invoke\",759,0,\"\u{202d}abc\",[]],[\"invoke\",761,0,\"\u{202d}abc\u{202e}\",[]],[\"invoke\",762,0,\"\u{202e}cba\u{202d}\",[]],[\"invoke\",763,0,\"𝑨\",[]],[\"invoke\",764,0,\"𝐴\",[]],[\"invoke\",765,0,\"𝘈\",[]],[\"invoke\",767,0,\"𝐀\",[]],[\"invoke\",768,0,\"𝓐\",[]],[\"invoke\",769,0,\"𝕬\",[]],[\"invoke\",770,0,\"𝗔\",[]],[\"invoke\",771,0,\"𝒜\",[]],[\"invoke\",773,0,\"𝔸\",[]],[\"invoke\",774,0,\"𝖠\",[]],[\"invoke\",775,0,\"𝙰\",[]],[\"invoke\",776,0,\"ᴀ\",[]],[\"invoke\",777,0,\"ᴬ\",[]],[\"invoke\",779,0,\"A\",[]],[\"invoke\",780,0,\"🄐\",[]],[\"invoke\",781,0,\"🄰\",[]],[\"invoke\",782,0,\"\u{e0041}\",[]],[\"invoke\",783,0,\"U+0041\",[]],[\"invoke\",785,0,\"А\",[]],[\"invoke\",786,0,\"Ꙗ\",[]],[\"invoke\",787,0,\"ⷼ\",[]],[\"invoke\",788,0,\"ⷶ\",[]],[\"invoke\",789,0,\"Ɐ\",[]],[\"invoke\",791,0,\"🅰\",[]],[\"invoke\",792,0,\"Ⱝ\",[]],[\"invoke\",793,0,\"𐐂\",[]],[\"invoke\",794,0,\"𐐈\",[]],[\"invoke\",795,0,\"𐒰\",[]],[\"invoke\",797,0,\"Á\",[]],[\"invoke\",798,0,\"Â\",[]],[\"invoke\",799,0,\"Ã\",[]],[\"invoke\",800,0,\"Ä\",[]],[\"invoke\",801,0,\"Ā\",[]],[\"invoke\",803,0,\"Ą\",[]],[\"invoke\",804,0,\"Ǎ\",[]],[\"invoke\",805,0,\"Ǟ\",[]],[\"invoke\",806,0,\"Ǡ\",[]],[\"invoke\",808,0,\"Ȁ\",[]],[\"invoke\",809,0,\"Ȃ\",[]],[\"invoke\",810,0,\"Ȧ\",[]],[\"invoke\",811,0,\"Ⱥ\",[]],[\"invoke\",812,0,\"Ӑ\",[]],[\"invoke\",814,0,\"ߊ\",[]],[\"invoke\",815,0,\"ࠡ\",[]],[\"invoke\",816,0,\"ࠢ\",[]],[\"invoke\",817,0,\"ࠣ\",[]],[\"invoke\",818,0,\"ࠤ\",[]],[\"invoke\",820,0,\"ऄ\",[]],[\"invoke\",821,0,\"अ\",[]],[\"invoke\",822,0,\"ॲ\",[]],[\"invoke\",823,0,\"অ\",[]],[\"invoke\",824,0,\"ਅ\",[]],[\"invoke\",826,0,\"ଅ\",[]],[\"invoke\",827,0,\"அ\",[]],[\"invoke\",828,0,\"అ\",[]],[\"invoke\",829,0,\"ಅ\",[]],[\"invoke\",830,0,\"അ\",[]],[\"invoke\",832,0,\"ະ\",[]],[\"invoke\",833,0,\"༁\",[]],[\"invoke\",834,0,\"ཨ\",[]],[\"invoke\",835,0,\"ྸ\",[]],[\"invoke\",836,0,\"အ\",[]],[\"invoke\",838,0,\"ႜ\",[]],[\"invoke\",839,0,\"ᅡ\",[]],[\"invoke\",840,0,\"አ\",[]],[\"invoke\",841,0,\"ዐ\",[]],[\"invoke\",842,0,\"Ꭰ\",[]],[\"invoke\",844,0,\"ᖳ\",[]],[\"invoke\",845,0,\"ᚨ\",[]],[\"invoke\",846,0,\"ᚪ\",[]],[\"invoke\",847,0,\"ᛆ\",[]],[\"invoke\",848,0,\"ᜀ\",[]],[\"invoke\",850,0,\"ᝀ\",[]],[\"invoke\",851,0,\"ᝠ\",[]],[\"invoke\",852,0,\"ᠠ\",[]],[\"invoke\",853,0,\"ᢇ\",[]],[\"invoke\",855,0,\"ᥣ\",[]],[\"invoke\",856,0,\"ᨕ\",[]],[\"invoke\",857,0,\"ᩋ\",[]],[\"invoke\",858,0,\"ᩡ\",[]],[\"invoke\",859,0,\"ᮃ\",[]],[\"invoke\",861,0,\"ᯁ\",[]],[\"invoke\",862,0,\"ᰣ\",[]],[\"invoke\",863,0,\"Ḁ\",[]],[\"invoke\",864,0,\"Ạ\",[]],[\"invoke\",865,0,\"Ả\",[]],[\"invoke\",867,0,\"Ầ\",[]],[\"invoke\",868,0,\"Ẩ\",[]],[\"invoke\",869,0,\"Ẫ\",[]],[\"invoke\",870,0,\"Ậ\",[]],[\"invoke\",871,0,\"Ắ\",[]],[\"invoke\",873,0,\"Ẳ\",[]],[\"invoke\",874,0,\"Ẵ\",[]],[\"invoke\",875,0,\"Ặ\",[]],[\"invoke\",876,0,\"あ\",[]],[\"invoke\",877,0,\"ア\",[]],[\"invoke\",879,0,\"ㅏ\",[]],[\"invoke\",880,0,\"㈎\",[]],[\"invoke\",881,0,\"㈏\",[]],[\"invoke\",882,0,\"㈐\",[]],[\"invoke\",883,0,\"㈑\",[]],[\"invoke\",885,0,\"㈓\",[]],[\"invoke\",886,0,\"㈔\",[]],[\"invoke\",887,0,\"㈕\",[]],[\"invoke\",888,0,\"㈖\",[]],[\"invoke\",889,0,\"㈗\",[]],[\"invoke\",891,0,\"㈙\",[]],[\"invoke\",892,0,\"㈚\",[]],[\"invoke\",893,0,\"㈛\",[]],[\"invoke\",894,0,\"㉮\",[]],[\"invoke\",895,0,\"㉯\",[]],[\"invoke\",897,0,\"㉱\",[]],[\"invoke\",898,0,\"㉲\",[]],[\"invoke\",899,0,\"㉳\",[]],[\"invoke\",900,0,\"㉴\",[]],[\"invoke\",901,0,\"㉵\",[]],[\"invoke\",903,0,\"㉷\",[]],[\"invoke\",904,0,\"㉸\",[]],[\"invoke\",905,0,\"㉹\",[]],[\"invoke\",906,0,\"㉺\",[]],[\"invoke\",908,0,\"㋐\",[]],[\"invoke\",909,0,\"ꀊ\",[]],[\"invoke\",910,0,\"ꓮ\",[]],[\"invoke\",911,0,\"ꕉ\",[]],[\"invoke\",912,0,\"ꚠ\",[]],[\"invoke\",914,0,\"ꠣ\",[]],[\"invoke\",915,0,\"ꡝ\",[]],[\"invoke\",916,0,\"ꢂ\",[]],[\"invoke\",917,0,\"꣪\",[]],[\"invoke\",918,0,\"ꤢ\",[]],[\"invoke\",920,0,\"ꦄ\",[]],[\"invoke\",921,0,\"ꨀ\",[]],[\"invoke\",922,0,\"ア\",[]],[\"invoke\",923,0,\"ᅡ\",[]],[\"invoke\",924,0,\"𐀀\",[]],[\"invoke\",926,0,\"𐊠\",[]],[\"invoke\",927,0,\"𐌀\",[]],[\"invoke\",928,0,\"𐎠\",[]],[\"invoke\",929,0,\"𐒖\",[]],[\"invoke\",930,0,\"𐔀\",[]],[\"invoke\",932,0,\"𐠀\",[]],[\"invoke\",933,0,\"𐤠\",[]],[\"invoke\",934,0,\"𐦀\",[]],[\"invoke\",935,0,\"𐦠\",[]],[\"invoke\",936,0,\"𐨀\",[]],[\"invoke\",938,0,\"𐰀\",[]],[\"invoke\",939,0,\"𐰁\",[]],[\"invoke\",940,0,\"𐲀\",[]],[\"invoke\",941,0,\"𑀅\",[]],[\"invoke\",942,0,\"𑂃\",[]],[\"invoke\",944,0,\"𑅐\",[]],[\"invoke\",945,0,\"𑆃\",[]],[\"invoke\",946,0,\"𑈀\",[]],[\"invoke\",947,0,\"𑊀\",[]],[\"invoke\",948,0,\"𑊰\",[]],[\"invoke\",950,0,\"𑍰\",[]],[\"invoke\",951,0,\"𑐀\",[]],[\"invoke\",952,0,\"𑒁\",[]],[\"invoke\",953,0,\"𑖀\",[]],[\"invoke\",955,0,\"𑚀\",[]],[\"invoke\",956,0,\"𑜒\",[]],[\"invoke\",957,0,\"𑜠\",[]],[\"invoke\",958,0,\"𑢡\",[]],[\"invoke\",959,0,\"𑫕\",[]],[\"invoke\",961,0,\"𑲏\",[]],[\"invoke\",962,0,\"𑲯\",[]],[\"invoke\",963,0,\"𒀀\",[]],[\"invoke\",964,0,\"𖧕\",[]],[\"invoke\",965,0,\"𖩆\",[]],[\"invoke\",967,0,\"𖽔\",[]],[\"invoke\",968,0,\"𛱁\",[]],[\"invoke\",969,0,\"𛱤\",[]],[\"invoke\",970,0,\"𞠣\",[]],[\"invoke\",971,0,\"🇦\",[]],[\"invoke\",973,0,\"Λ\",[]],[\"invoke\",974,0,\"Ɒ\",[]],[\"invoke\",975,0,\"ª\",[]],[\"invoke\",976,0,\"∀\",[]],[\"invoke\",977,0,\"₳\",[]],[\"invoke\",979,0,\"Ⲁ\",[]],[\"invoke\",980,0,\"𐌰\",[]],[\"invoke\",981,0,\"Ά\",[]],[\"invoke\",982,0,\"Α\",[]],[\"invoke\",983,0,\"Ἀ\",[]],[\"invoke\",985,0,\"Ἂ\",[]],[\"invoke\",986,0,\"Ἃ\",[]],[\"invoke\",987,0,\"Ἄ\",[]],[\"invoke\",988,0,\"Ἅ\",[]],[\"invoke\",989,0,\"Ἆ\",[]],[\"invoke\",991,0,\"ᾈ\",[]],[\"invoke\",992,0,\"ᾉ\",[]],[\"invoke\",993,0,\"ᾊ\",[]],[\"invoke\",994,0,\"ᾋ\",[]],[\"invoke\",995,0,\"ᾌ\",[]],[\"invoke\",997,0,\"ᾎ\",[]],[\"invoke\",998,0,\"ᾏ\",[]],[\"invoke\",999,0,\"Ᾰ\",[]],[\"invoke\",1000,0,\"Ᾱ\",[]],[\"invoke\",1002,0,\"Ά\",[]],[\"invoke\",1003,0,\"ᾼ\",[]],[\"invoke\",1004,0,\"𝚨\",[]],[\"invoke\",1005,0,\"𝛢\",[]],[\"invoke\",1006,0,\"𝜜\",[]],[\"invoke\",1008,0,\"𝞐\",[]],[\"invoke\",1009,0,\"⍶\",[]],[\"invoke\",1010,0,\"⍺\",[]],[\"invoke\",1011,0,\"⩜\",[]],[\"invoke\",1012,0,\"ᗅ\",[]],[\"invoke\",1014,0,\")˺˼𔗏\u{1d174}\u{1d176}\u{1d178}\u{1d17a}⁾₎❩❫⟯﴿︶﹚)⦆\u{e0029}❳❵⟧⟩⟫⟭⦈⦊⦖⸣⸥︘︸︺︼︾﹀﹂﹄﹈﹜﹞]}」\u{e005d}\u{e007d}»’”›❯\",[]],[\"invoke\",1015,0,\"(˹˻𔗎\u{1d173}\u{1d175}\u{1d177}\u{1d179}⁽₍❨❪⟮﴾︵﹙(⦅\u{e0028}❲❴⟦⟨⟪⟬⦇⦉⦕⸢⸤︗︷︹︻︽︿﹁﹃﹇﹛﹝[{「\u{e005b}\u{e007b}«‘“‹❮\",[]],[\"invoke\",1016,0,\"𝪋𝪤\",[]],[\"invoke\",1017,0,\"𝪋\",[]],[\"invoke\",1018,0,\"½\",[]],[\"invoke\",1020,0,\"1/2\",[]],[\"invoke\",1021,0,\"୳\",[]],[\"invoke\",1022,0,\"൴\",[]],[\"invoke\",1023,0,\"⳽\",[]],[\"invoke\",1024,0,\"꠱\",[]],[\"invoke\",1026,0,\"𐅵\",[]],[\"invoke\",1027,0,\"𐅶\",[]],[\"invoke\",1028,0,\"𐦽\",[]],[\"invoke\",1029,0,\"𐹻\",[]],[\"invoke\",1030,0,\""\",[]],[\"invoke\",1032,0,\"\\b\",[]],[\"invoke\",1033,0,\"⌫\",[]],[\"invoke\",1034,0,\"⌦\",[]],[\"invoke\",1035,0,\"␈\",[]],[\"invoke\",1036,0,\"␡\",[]],[\"invoke\",1038,0,\"\\u000f\",[]],[\"invoke\",1039,0,\"←\",[]],[\"invoke\",1040,0,\"⌧\",[]],[\"invoke\",1041,0,\"⍒\",[]],[\"invoke\",1042,0,\"⍔\",[]],[\"invoke\",1044,0,\"⍫\",[]],[\"invoke\",1045,0,\"\\u001a\",[]],[\"invoke\",1046,0,\"␦\",[]],[\"invoke\",1047,0,\"␚\",[]],[\"invoke\",1049,0,\"?\",[]],[\"invoke\",1050,0,\"¿\",[]],[\"invoke\",1051,0,\"᥅\",[]],[\"invoke\",1052,0,\";\",[]],[\"invoke\",1053,0,\"՞\",[]],[\"invoke\",1055,0,\"፧\",[]],[\"invoke\",1056,0,\"⁇\",[]],[\"invoke\",1057,0,\"⍰\",[]],[\"invoke\",1058,0,\"❓\",[]],[\"invoke\",1059,0,\"❔\",[]],[\"invoke\",1061,0,\"⳻\",[]],[\"invoke\",1062,0,\"⸮\",[]],[\"invoke\",1063,0,\"㉄\",[]],[\"invoke\",1064,0,\"꘏\",[]],[\"invoke\",1065,0,\"꛷\",[]],[\"invoke\",1067,0,\"﹖\",[]],[\"invoke\",1068,0,\"?\",[]],[\"invoke\",1069,0,\"𑅃\",[]],[\"invoke\",1070,0,\"𞥟\",[]],[\"invoke\",1071,0,\"\u{e003f}\",[]],[\"invoke\",1073,0,\"⯑\",[]],[\"invoke\",1074,0,\"¶\",[]],[\"invoke\",1075,0,\"⁋\",[]],[\"invoke\",1076,0,\"܀\",[]],[\"invoke\",1077,0,\"჻\",[]],[\"invoke\",1079,0,\"〷\",[]],[\"invoke\",1080,0,\"❡\",[]],[\"invoke\",1081,0,\"⸏\",[]],[\"invoke\",1082,0,\"⸐\",[]],[\"invoke\",1083,0,\"⸑\",[]],[\"invoke\",1085,0,\"\\u0014\",[]],[\"invoke\",1086,0,\"☙\",[]],[\"invoke\",1087,0,\"⸿\",[]],[\"invoke\",1088,0,\"〇\",[]],[\"invoke\",1089,0,\"๛\",[]],[\"invoke\",1091,0,\"ϓ\",[]],[\"invoke\",1092,0,\"ϔ\",[]],[\"invoke\",1093,0,\"ẛ\",[]],[\"module\",1095,130],[\"invoke\",1107,0,\"print32\",[\"i32:42\",\"i32:123\"]]]"; diff --git a/tests/wasm-spec/gen.mjs b/tests/wasm-spec/gen.mjs index bfb6dd9ee..2b4e5c9fb 100644 --- a/tests/wasm-spec/gen.mjs +++ b/tests/wasm-spec/gen.mjs @@ -123,6 +123,12 @@ const { values } = parseArgs({ }); const repo = join(dirname(new URL(import.meta.url).pathname), "../.."); +// Writes control and format characters (names.wast has several) as escapes, so the +// generated file holds the same strings without invisible characters in its source. +function escapeInvisible(literal) { + return literal.replace(/[\u007f-\u009f\u2028\u2029\p{Cf}]/gu, (c) => `\\u{${c.codePointAt(0).toString(16)}}`); +} + function value(v) { if (v.type === "v128") return null; return `${v.type}:${v.value}`; @@ -257,7 +263,7 @@ for (const [group, names] of Object.entries(GROUPS)) { * ${names.map((n) => `${n}.wast`).join(", ")}. Do not edit. */\n`; const body = `export const modules = [\n${modules.map((m) => ` "${m}",`).join("\n")}\n];\n` + - `export const script = ${JSON.stringify(JSON.stringify(commands))};\n`; + `export const script = ${escapeInvisible(JSON.stringify(JSON.stringify(commands)))};\n`; writeFileSync(join(out, "data.mjs"), header + body); writeFileSync( join(out, "main.mjs"), From 28f16464610bf579f757f6a60cd018393ddcb5c5 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Tue, 20 Oct 2026 12:29:12 -0500 Subject: [PATCH 113/122] Unpipe JavaScript destinations on unpipe() without a destination With checked-dynamic stream slots, readable.unpipe() also unpipes every JavaScript destination stream.pipeDyn registered from the source, oldest pipe first. Each destination hears 'unpipe' and the source pauses, as in Node's unpipe(), which empties every pipe. Before, those pipes stayed attached and later chunks still reached them. --- .../src/frontend/lowering/lower-stream.ts | 38 +++++++++++++++++++ packages/runtime/src/scr_stream_dyn.c | 22 ++++++++--- .../5819-node-stream-dyn-direct-pipe/main.ts | 13 ++++++- 3 files changed, 66 insertions(+), 7 deletions(-) diff --git a/packages/compiler/src/frontend/lowering/lower-stream.ts b/packages/compiler/src/frontend/lowering/lower-stream.ts index 4d729f639..44027f65c 100644 --- a/packages/compiler/src/frontend/lowering/lower-stream.ts +++ b/packages/compiler/src/frontend/lowering/lower-stream.ts @@ -3876,6 +3876,44 @@ export function lowerStreamMethodCall( loc, }; } + // Checked-dynamic slots: unpipe() also unpipes the JavaScript + // destinations stream.pipeDyn registered (stream.unpipeDyn with an + // undefined destination), as Node's unpipe() empties every pipe. + if (lowerer.dynNodeStreams) { + const source = lowerer.declareHiddenLocal("%unpipeSource", receiver.type); + const sourceRef = varRef(source.id, receiver.type, loc); + return { + kind: "seqExpr", + stmts: [ + { kind: "varDecl", localId: source.id, init: receiver, loc }, + { + kind: "exprStmt", + expr: { + kind: "libCall", + fn: "readable.unpipe", + args: [sourceRef], + type: receiver.type, + loc, + }, + loc, + }, + { + kind: "exprStmt", + expr: { + kind: "libCall", + fn: "stream.unpipeDyn", + args: [sourceRef, dynUndefinedExpr(loc)], + type: VOID, + loc, + }, + loc, + }, + ], + result: sourceRef, + type: receiver.type, + loc, + }; + } return { kind: "libCall", fn: "readable.unpipe", args: [receiver], type: receiver.type, loc }; } diff --git a/packages/runtime/src/scr_stream_dyn.c b/packages/runtime/src/scr_stream_dyn.c index d230bf22e..0859c4e07 100644 --- a/packages/runtime/src/scr_stream_dyn.c +++ b/packages/runtime/src/scr_stream_dyn.c @@ -365,13 +365,25 @@ out: /* readable.unpipe(destination) for a destination that is not a native * stream: the pipe scr_stream_pipe_dyn registered from src into it * unpipes, as Node's unpipe does for a piped destination; nothing happens - * when there is none. Borrowed. */ + * when there is none. An undefined destination (unpipe() without one) + * unpipes every JavaScript destination of src, oldest pipe first. + * Borrowed. */ void scr_stream_unpipe_dyn(ScrStream *src, ScrDyn *dest) { - for (ScrDynPipe *p = scr_dyn_pipe_live; p; p = p->live_next) { - if (p->src != src || p->unpiped || p->cleaned || !p->dest || !scr_dyn_strict_eq(p->dest, dest)) continue; - ScrDynPipe *held = scr_dyn_pipe_retain_v(p); + bool all = dest == NULL || dest->kind == SCR_DYN_UNDEF; + for (;;) { + /* The live list is newest first; the oldest matching pipe goes next. + * Unpiping runs JavaScript ('unpipe' listeners), so each round walks + * the list again. */ + ScrDynPipe *match = NULL; + for (ScrDynPipe *p = scr_dyn_pipe_live; p; p = p->live_next) { + if (p->src != src || p->unpiped || p->cleaned || !p->dest) continue; + if (!all && !scr_dyn_strict_eq(p->dest, dest)) continue; + match = p; + } + if (!match) return; + ScrDynPipe *held = scr_dyn_pipe_retain_v(match); scr_dyn_pipe_unpipe(held); scr_dyn_pipe_release_v(held); - return; + if (!all || scr_exc_pending()) return; } } diff --git a/tests/corpus/5819-node-stream-dyn-direct-pipe/main.ts b/tests/corpus/5819-node-stream-dyn-direct-pipe/main.ts index c1feb8eb5..a72328d3f 100644 --- a/tests/corpus/5819-node-stream-dyn-direct-pipe/main.ts +++ b/tests/corpus/5819-node-stream-dyn-direct-pipe/main.ts @@ -1,7 +1,8 @@ // A native Readable piped straight into a JavaScript sink, the only // untyped stream flow in the program; unpipe of a NodeJS.WritableStream -// that holds a native Writable or a JavaScript sink; and JavaScript -// destinations whose prependListener or writableNeedDrain getter throws. +// that holds a native Writable or a JavaScript sink; unpipe() of every +// JavaScript sink; and JavaScript destinations whose prependListener or +// writableNeedDrain getter throws. import { Readable, Writable } from "node:stream"; import { plainSink, throwingSink } from "./sources.js"; @@ -37,6 +38,14 @@ async function main(): Promise { await new Promise((resolve) => setTimeout(resolve, 10)); console.log("second paused", second.isPaused()); + const third = new Readable({ read() {} }); + third.pipe(plainSink("first of two")); + third.pipe(plainSink("second of two")); + third.unpipe(); + third.push("after unpipe()"); + await new Promise((resolve) => setTimeout(resolve, 10)); + console.log("third paused", third.isPaused(), third.listenerCount("data")); + for (const member of ["prependListener", "writableNeedDrain"]) { try { new Readable({ read() {} }).pipe(throwingSink(member)); From b89594c276658571eadf72b0032e73372588df70 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Tue, 20 Oct 2026 10:31:10 -0700 Subject: [PATCH 114/122] Keep the legacy Stream constructor on the EventEmitter unit Programs whose only stream value is the legacy Stream do not need the stream half. Co-Authored-By: Claude Opus 5.5 --- .../src/frontend/lowering/builtins/builtin-class-dyn.ts | 6 +++--- packages/compiler/src/ir/builtin-signatures.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/compiler/src/frontend/lowering/builtins/builtin-class-dyn.ts b/packages/compiler/src/frontend/lowering/builtins/builtin-class-dyn.ts index 9508aa651..07113eee7 100644 --- a/packages/compiler/src/frontend/lowering/builtins/builtin-class-dyn.ts +++ b/packages/compiler/src/frontend/lowering/builtins/builtin-class-dyn.ts @@ -111,14 +111,14 @@ export function builtinClassDynValueOf(lowerer: Lowerer, node: ts.Expression): I }; } -/** EventEmitter is dyn.builtinClass; the stream classes, which carry - * native stream state for constructor functions, are +/** EventEmitter and the legacy Stream are dyn.builtinClass; the stream + * classes, which carry native stream state for constructor functions, are * dyn.builtinStreamClass, so only programs that use them link the stream * half. */ export function builtinClassValue(kind: number, loc: SrcLoc): IrExpr { return { kind: "libCall", - fn: kind === 0 ? "dyn.builtinClass" : "dyn.builtinStreamClass", + fn: kind <= 1 ? "dyn.builtinClass" : "dyn.builtinStreamClass", args: [numLit(kind, loc)], type: DYN, loc, diff --git a/packages/compiler/src/ir/builtin-signatures.ts b/packages/compiler/src/ir/builtin-signatures.ts index c3d4eeba7..e645738a9 100644 --- a/packages/compiler/src/ir/builtin-signatures.ts +++ b/packages/compiler/src/ir/builtin-signatures.ts @@ -3540,7 +3540,7 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ * Transform (5) and PassThrough (6) as untyped constructor values, one * per thread (scr_dyn_builtin_class.c). */ "dyn.builtinClass": { argTypes: [F64], result: DYN }, - /** The stream classes (1-6 above) with their stream half: constructor + /** The stream classes (2-6 above) with their stream half: constructor * functions over them get native stream state * (scr_dyn_builtin_stream.c). */ "dyn.builtinStreamClass": { argTypes: [F64], result: DYN }, From 5f3efe314c10e1ac0b555e0e1ec5b0e4bd9c26a7 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Tue, 20 Oct 2026 10:35:49 -0700 Subject: [PATCH 115/122] Format the runtime-pack matrix entry Co-Authored-By: Claude Opus 5.5 --- packages/runtime-pack-common/runtime-pack-matrix.mjs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/runtime-pack-common/runtime-pack-matrix.mjs b/packages/runtime-pack-common/runtime-pack-matrix.mjs index 8baef6698..945192de6 100644 --- a/packages/runtime-pack-common/runtime-pack-matrix.mjs +++ b/packages/runtime-pack-common/runtime-pack-matrix.mjs @@ -95,7 +95,15 @@ export const OPTIONAL_RUNTIME_SOURCES = [ // Their stream half also references the stream unit. [ "scr_dyn_builtin_stream.c", - all("dynBuiltinStream", "dynBuiltinClass", "stream", "inspect", "dynInvoke", "symbol", "emitter"), + all( + "dynBuiltinStream", + "dynBuiltinClass", + "stream", + "inspect", + "dynInvoke", + "symbol", + "emitter", + ), ], ["scr_stream.c", "stream"], // The zlib stream classes reference both the zlib engine and the stream unit. From 97bf98cd892ba7cf37a7f08577c8680746dd9896 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Tue, 20 Oct 2026 12:50:19 -0500 Subject: [PATCH 116/122] Declare the WebAssembly W5 and stream-slot entry points in the runtime headers The backend can emit scr_wasm_memory_construct, scr_wasm_table_construct, scr_wasm_global_construct, scr_wasm_construct_without_new, scr_wasm_module_imports, scr_wasm_module_exports, scr_wasm_module_custom_sections and scr_wasm_value_of (#1410), and scr_stream_dyn_native, scr_stream_pipe_dyn and scr_stream_unpipe_dyn (#1524). Their prototypes were in scr_wasm.c or nowhere, so the runtime ABI guard failed. They are now in scr_wasm.h and scr_stream.h, and scr_wasm.c no longer repeats the header's declarations. The speculative-hoisting test relied on `v instanceof Num` in a JavaScript function failing, which constructor-function instanceof now compiles (#1512). Its hoisted function reads the prototype through Reflect.getPrototypeOf instead, which still fences. --- .../speculative-hoisted-functions.test.ts | 8 +++++--- packages/runtime/src/scr_stream.h | 7 +++++++ packages/runtime/src/scr_wasm.c | 19 ------------------- packages/runtime/src/scr_wasm.h | 8 ++++++++ 4 files changed, 20 insertions(+), 22 deletions(-) diff --git a/packages/compiler/test/speculative-hoisted-functions.test.ts b/packages/compiler/test/speculative-hoisted-functions.test.ts index a58310b2a..79a56dcde 100644 --- a/packages/compiler/test/speculative-hoisted-functions.test.ts +++ b/packages/compiler/test/speculative-hoisted-functions.test.ts @@ -9,7 +9,9 @@ import type { IrModule } from "../src/ir/ir.js"; // list above the constructor. The initializer lowers speculatively first, // which hoists the constructor and the helper it calls. Those bodies are // kept, so their failing statements must keep runtime fences; dropping -// them left `isNum` without its return (an SC9002 trap at load). +// them left `isNum` without its return (an SC9002 trap at load). isNum's +// return reads the prototype through Reflect.getPrototypeOf, which has no +// lowering, so it fences. const source = ` function clone(config) { var P = (Num.prototype = { constructor: Num, toString: null }), @@ -20,7 +22,7 @@ function clone(config) { x.v = isNum(v) ? 0 : v * unit; } function isNum(v) { - return v instanceof Num || (!!v && v._isNum === true); + return Reflect.getPrototypeOf(v) === P || (!!v && v._isNum === true); } P._isNum = true; return Num; @@ -59,6 +61,6 @@ test("speculatively hoisted JavaScript functions keep their statement fences", a const mod = await lower(); const isNum = mod.functions.find((fn) => fn.name.endsWith("_isNum")); expect(isNum).toBeDefined(); - expect(fenceCodes(isNum!.body)).toEqual(["SC1090"]); + expect(fenceCodes(isNum!.body)).toEqual(["SC2020"]); expect(mod.functions.flatMap((fn) => fenceCodes(fn.body))).not.toContain("SC9002"); }); diff --git a/packages/runtime/src/scr_stream.h b/packages/runtime/src/scr_stream.h index 69da3dc0e..5587b58d7 100644 --- a/packages/runtime/src/scr_stream.h +++ b/packages/runtime/src/scr_stream.h @@ -136,6 +136,13 @@ ScrStream *scr_stream_pipe_general(ScrStream *src, ScrStream *dst, bool end); /* A boxed native stream out of a checked-dynamic pipeline stage (+1), or * NULL with `message` thrown as the not-supported Error. */ ScrStream *scr_stream_unbox_dyn(const struct ScrDyn *value, const ScrStr *message); +/* Checked-dynamic stream slots (scr_stream_dyn.c): the native stream a + * value boxes with the requested side (1 readable, 2 writable; +1, or + * NULL), and readable.pipe()/unpipe() for a destination that is not a + * native stream. */ +ScrStream *scr_stream_dyn_native(const struct ScrDyn *value, double side); +void scr_stream_pipe_dyn(ScrStream *src, struct ScrDyn *src_box, struct ScrDyn *dest, bool end); +void scr_stream_unpipe_dyn(ScrStream *src, struct ScrDyn *dest); ScrStream *scr_stream_new_duplex_dyn(struct ScrDyn *opts); ScrStream *scr_stream_new_transform_dyn(struct ScrDyn *opts); ScrStream *scr_stream_new_passthrough_dyn(struct ScrDyn *opts); diff --git a/packages/runtime/src/scr_wasm.c b/packages/runtime/src/scr_wasm.c index 8f9244c28..92d556272 100644 --- a/packages/runtime/src/scr_wasm.c +++ b/packages/runtime/src/scr_wasm.c @@ -90,25 +90,6 @@ #define SCR_WASM_ASAN 1 #endif -ScrPromise *scr_wasm_compile(ScrDyn *bytes); -bool scr_wasm_validate(ScrDyn *bytes); -ScrPromise *scr_wasm_instantiate(ScrDyn *source, ScrDyn *imports); -ScrDyn *scr_wasm_module_new(ScrDyn *bytes); -ScrDyn *scr_wasm_instance_new(ScrDyn *module, ScrDyn *imports); -bool scr_wasm_is(const ScrDyn *value, double kind); -ScrDyn *scr_wasm_error_new(double kind, ScrDyn *message, ScrDyn *options); -ScrDyn *scr_wasm_function(double kind); -ScrDyn *scr_wasm_namespace(void); -ScrDyn *scr_wasm_global_view(void); -ScrDyn *scr_wasm_memory_construct(ScrDyn *descriptor); -ScrDyn *scr_wasm_table_construct(ScrDyn *descriptor, ScrDyn *value); -ScrDyn *scr_wasm_global_construct(ScrDyn *descriptor, ScrDyn *value); -ScrDyn *scr_wasm_construct_without_new(double kind, ScrDyn *arg0, ScrDyn *arg1); -ScrDyn *scr_wasm_module_imports(ScrDyn *module); -ScrDyn *scr_wasm_module_exports(ScrDyn *module); -ScrDyn *scr_wasm_module_custom_sections(ScrDyn *module, ScrDyn *name); -ScrDyn *scr_wasm_value_of(ScrDyn *value); - /* Entry points of translated code. */ /* Sanitized builds do not declare it noreturn: AddressSanitizer would * call __asan_handle_no_return before every call, which warns on the diff --git a/packages/runtime/src/scr_wasm.h b/packages/runtime/src/scr_wasm.h index 7b0a29689..f7a791a26 100644 --- a/packages/runtime/src/scr_wasm.h +++ b/packages/runtime/src/scr_wasm.h @@ -19,5 +19,13 @@ ScrDyn *scr_wasm_error_new(double kind, ScrDyn *message, ScrDyn *options); ScrDyn *scr_wasm_function(double kind); ScrDyn *scr_wasm_namespace(void); ScrDyn *scr_wasm_global_view(void); +ScrDyn *scr_wasm_memory_construct(ScrDyn *descriptor); +ScrDyn *scr_wasm_table_construct(ScrDyn *descriptor, ScrDyn *value); +ScrDyn *scr_wasm_global_construct(ScrDyn *descriptor, ScrDyn *value); +ScrDyn *scr_wasm_construct_without_new(double kind, ScrDyn *arg0, ScrDyn *arg1); +ScrDyn *scr_wasm_module_imports(ScrDyn *module); +ScrDyn *scr_wasm_module_exports(ScrDyn *module); +ScrDyn *scr_wasm_module_custom_sections(ScrDyn *module, ScrDyn *name); +ScrDyn *scr_wasm_value_of(ScrDyn *value); #endif /* SCR_WASM_H */ From 53443672194673c36446afd481114332e1650430 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Tue, 20 Oct 2026 13:16:29 -0500 Subject: [PATCH 117/122] Read constructor-function stream hooks when they run A constructor-function stream now reads `_final` and `_flush` when its writable side finishes, and `_transform` for each chunk, as Node does. A hook assigned after `Writable.call(this)` or `Transform.call(this)` runs, and an override of a PassThrough's `_transform` replaces the pass-through. Without `_final` or `_flush` the finish continues as Node's does. A runtime stream passed to a constructor-function stream's listeners, such as the source of a 'pipe' event, is the view whose pipe and unpipe accept constructor-function streams, so `src.unpipe(this)` in a 'pipe' listener unpipes instead of throwing. --- ...ructor-function-stream-late-hooks.cjs.json | 6 + packages/runtime/src/scr_dyn_builtin_stream.c | 54 +++++--- ...constructor-function-stream-late-hooks.cjs | 117 ++++++++++++++++++ ...ructor-function-stream-late-hooks.cjs.json | 1 + 4 files changed, 158 insertions(+), 20 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6564-constructor-function-stream-late-hooks.cjs.json create mode 100644 tests/corpus/6564-constructor-function-stream-late-hooks.cjs create mode 100644 tests/harness/corpus-costs/6564-constructor-function-stream-late-hooks.cjs.json diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6564-constructor-function-stream-late-hooks.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6564-constructor-function-stream-late-hooks.cjs.json new file mode 100644 index 000000000..52d8e55ff --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6564-constructor-function-stream-late-hooks.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6564-constructor-function-stream-late-hooks.cjs" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_dyn_builtin_stream.c b/packages/runtime/src/scr_dyn_builtin_stream.c index d9ac6bc50..1926b0622 100644 --- a/packages/runtime/src/scr_dyn_builtin_stream.c +++ b/packages/runtime/src/scr_dyn_builtin_stream.c @@ -204,10 +204,10 @@ ScrDyn *scr_dyn_builtin_stream_object(ScrStream *s) { } /* A stream as a listener argument: its constructor-function object, or a - * live view of another runtime stream. +1. */ + * live view of another runtime stream whose pipe and unpipe take those + * objects (scr_dyn_builtin_stream_view). +1. */ static ScrDyn *bcs_stream_value(ScrStream *s) { - ScrDyn *object = scr_dyn_builtin_stream_object(s); - return object ? object : scr_stream_dyn_view(s); + return scr_dyn_builtin_stream_view(s); } /* The receiver's stream for a prototype method, borrowed; throws Node's @@ -398,12 +398,35 @@ static void bcs_plain_call(ScrClosure *cb, ScrStream *s, const char *method, int scr_dyn_release(object); } +static bool bcs_is_function(ScrDyn *object, const char *name, bool *failed); + +/* prefinish reads `stream._final` when the writable side finishes, so a + * method assigned after the constructor runs; without one the finish goes + * on as Node's does when `typeof stream._final !== 'function'`. */ static void bcs_final_inv(ScrClosure *cb, ScrStream *s) { - bcs_plain_call(cb, s, "_final", BCS_DONE_FINAL); + ScrDyn *object = scr_box_get_ref(cb->caps[0]); + bool failed = false; + bool present = bcs_is_function(object, "_final", &failed); + scr_dyn_release(object); + if (failed) return; + if (present) bcs_plain_call(cb, s, "_final", BCS_DONE_FINAL); + else scr_stream_final_done(s, NULL); } +/* Transform's final reads `this._flush` the same way: without one it ends + * the readable side. */ static void bcs_flush_inv(ScrClosure *cb, ScrStream *s) { - bcs_plain_call(cb, s, "_flush", BCS_DONE_FLUSH); + ScrDyn *object = scr_box_get_ref(cb->caps[0]); + bool failed = false; + bool present = bcs_is_function(object, "_flush", &failed); + if (!failed && bcs_is_function(object, "_final", &failed)) { + bcs_unsupported("_final on a constructor-function Transform is not supported yet"); + failed = true; + } + scr_dyn_release(object); + if (failed) return; + if (present) bcs_plain_call(cb, s, "_flush", BCS_DONE_FLUSH); + else scr_stream_flush_done(s, NULL, NULL, NULL); } static ScrDyn *bcs_default_method(int kind, const char *name); @@ -897,23 +920,14 @@ static ScrDyn *bcs_construct(int kind, ScrDyn *self, ScrDyn *const *args, size_t scr_stream_set_read(s, bcs_hook(self, (void *)bcs_read_inv), bcs_read_inv); if (kind == BCS_WRITABLE || kind == BCS_DUPLEX) { scr_stream_set_write(s, bcs_hook(self, (void *)bcs_write_inv), bcs_write_inv); - if (bcs_is_function(self, "_final", &failed)) scr_stream_set_final(s, bcs_hook(self, (void *)bcs_final_inv), bcs_final_inv); - else scr_stream_set_final(s, NULL, NULL); + /* _final is read when the writable side finishes (bcs_final_inv). */ + scr_stream_set_final(s, bcs_hook(self, (void *)bcs_final_inv), bcs_final_inv); } if (kind == BCS_TRANSFORM || kind == BCS_PASSTHROUGH) { - bool identity = false; - if (kind == BCS_PASSTHROUGH) { - ScrDyn *own = bcs_get(self, "_transform"); - ScrDyn *fallback = bcs_default_method(BCS_PASSTHROUGH, "_transform"); - identity = own && fallback && scr_dyn_strict_eq(own, fallback); - if (own) scr_dyn_release(own); - else failed = true; - if (fallback) scr_dyn_release(fallback); - } - if (identity) scr_stream_set_transform(s, NULL, NULL); - else scr_stream_set_transform(s, bcs_hook(self, (void *)bcs_transform_inv), bcs_transform_inv); - if (bcs_is_function(self, "_flush", &failed)) scr_stream_set_flush(s, bcs_hook(self, (void *)bcs_flush_inv), bcs_flush_inv); - else scr_stream_set_flush(s, NULL, NULL); + /* _transform is read per chunk (a PassThrough's default passes the + * chunk on) and _flush when the writable side finishes. */ + scr_stream_set_transform(s, bcs_hook(self, (void *)bcs_transform_inv), bcs_transform_inv); + scr_stream_set_flush(s, bcs_hook(self, (void *)bcs_flush_inv), bcs_flush_inv); if (!failed && bcs_is_function(self, "_final", &failed)) { bcs_unsupported("_final on a constructor-function Transform is not supported yet"); failed = true; diff --git a/tests/corpus/6564-constructor-function-stream-late-hooks.cjs b/tests/corpus/6564-constructor-function-stream-late-hooks.cjs new file mode 100644 index 000000000..22a8ec2f4 --- /dev/null +++ b/tests/corpus/6564-constructor-function-stream-late-hooks.cjs @@ -0,0 +1,117 @@ +// Stream hooks a constructor function assigns after the base constructor +// runs, and unpipe from the destination's 'pipe' listener. +const { Writable, Transform, PassThrough } = require("stream"); +const util = require("util"); + +function Sink() { + Writable.call(this); + this.chunks = []; + this._final = function (cb) { + console.log("_final", this instanceof Sink, this.chunks.join(",")); + setImmediate(cb); + }; +} +util.inherits(Sink, Writable); +Sink.prototype._write = function (chunk, enc, cb) { + this.chunks.push(String(chunk)); + cb(); +}; + +const sink = new Sink(); +sink.on("prefinish", () => console.log("sink prefinish")); +sink.on("finish", () => { + console.log("sink finish"); + flushing(); +}); +sink.write("a"); +sink.end("b"); + +function Summer() { + Transform.call(this); + this.total = 0; + this._flush = function (cb) { + console.log("_flush", this.total); + cb(null, "total " + this.total); + }; +} +util.inherits(Summer, Transform); +Summer.prototype._transform = function (chunk, enc, cb) { + this.total += Number(String(chunk)); + cb(null, "seen " + chunk + "\n"); +}; + +function flushing() { + const summer = new Summer(); + const out = []; + summer.on("data", (chunk) => out.push(String(chunk))); + summer.on("end", () => { + console.log("summer end", JSON.stringify(out)); + passing(); + }); + summer.write("2"); + summer.write("3"); + summer.end(); +} + +function Upper() { + PassThrough.call(this); + this._transform = function (chunk, enc, cb) { + cb(null, String(chunk).toUpperCase()); + }; +} +util.inherits(Upper, PassThrough); + +function passing() { + const upper = new Upper(); + const out = []; + upper.on("data", (chunk) => out.push(String(chunk))); + upper.on("end", () => { + console.log("upper end", JSON.stringify(out)); + plain(); + }); + upper.write("x"); + upper.end("y"); +} + +// Without late hooks the defaults still apply. +function Plain() { + PassThrough.call(this); +} +util.inherits(Plain, PassThrough); + +function plain() { + const p = new Plain(); + const out = []; + p.on("data", (chunk) => out.push(String(chunk))); + p.on("finish", () => console.log("plain finish")); + p.on("end", () => { + console.log("plain end", JSON.stringify(out)); + unpiping(); + }); + p.end("same"); +} + +function Collector() { + Writable.call(this); + this.chunks = []; +} +util.inherits(Collector, Writable); +Collector.prototype._write = function (chunk, enc, cb) { + this.chunks.push(String(chunk)); + cb(); +}; + +function unpiping() { + const source = new PassThrough(); + const collector = new Collector(); + collector.on("pipe", function (src) { + console.log("pipe", typeof src.unpipe, this === collector); + src.unpipe(this); + }); + collector.on("unpipe", (src) => { + console.log("unpipe", typeof src.pipe); + source.write("after"); + setImmediate(() => console.log("collected", JSON.stringify(collector.chunks), source.readableFlowing)); + }); + source.pipe(collector); +} diff --git a/tests/harness/corpus-costs/6564-constructor-function-stream-late-hooks.cjs.json b/tests/harness/corpus-costs/6564-constructor-function-stream-late-hooks.cjs.json new file mode 100644 index 000000000..e873baf78 --- /dev/null +++ b/tests/harness/corpus-costs/6564-constructor-function-stream-late-hooks.cjs.json @@ -0,0 +1 @@ +{ "plain": 2750, "san": 3200 } From bb1452ca5aa935030e3b8876ec581a3fe5d4fc35 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Tue, 20 Oct 2026 13:28:32 -0500 Subject: [PATCH 118/122] Expect SC2020 from three web-stream npm programs The fixture's launch() helper calls run().then, which used to be the first statement these programs fenced at (SC1090). It compiles since #1503, so node24-web-stream-pipe-abort and node24-web-stream-readable-lifecycle now stop at 'new ReadableStream' and node24-web-stream-tee at 'ReadableStream.tee', which have no lowering (SC2020). --- tests/harness/npm.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/harness/npm.test.ts b/tests/harness/npm.test.ts index 168fdb6bd..f5f3fdeeb 100644 --- a/tests/harness/npm.test.ts +++ b/tests/harness/npm.test.ts @@ -91,13 +91,13 @@ const KNOWN: Record = { "node24-web-stream-encoding": { run: "SC1090" }, "node24-web-stream-errors": { run: "SC1090" }, "node24-web-stream-lifecycle": { run: "SC1090" }, - "node24-web-stream-pipe-abort": { run: "SC1090" }, + "node24-web-stream-pipe-abort": { run: "SC2020" }, "node24-web-stream-pipe-errors": { run: "SC1090" }, "node24-web-stream-pipes": { run: "SC1090" }, "node24-web-stream-queues": { run: "SC1090" }, - "node24-web-stream-readable-lifecycle": { run: "SC1090" }, + "node24-web-stream-readable-lifecycle": { run: "SC2020" }, "node24-web-stream-readers": { run: "SC1090" }, - "node24-web-stream-tee": { run: "SC1090" }, + "node24-web-stream-tee": { run: "SC2020" }, "node24-web-stream-transforms": { run: "SC1090" }, "package-imports": { build: "SC2013" }, "promise-bridge": { build: "SC1090" }, From cad421b35add1b409801c9883cbdbfd81babd206 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Tue, 20 Oct 2026 13:39:27 -0500 Subject: [PATCH 119/122] Search each stack-key local once per question A key forwarded through functions with several call sites made the search visit every call path. Each search now visits a local once, and a local keeps its answer: true once a search reached a key that can be "stack" from it, false once a search that found none passed it. --- .../src/backend/stack-observations.test.ts | 42 +++++++++++++++++++ .../src/backend/stack-observations.ts | 37 ++++++++++------ 2 files changed, 67 insertions(+), 12 deletions(-) diff --git a/packages/compiler/src/backend/stack-observations.test.ts b/packages/compiler/src/backend/stack-observations.test.ts index 4a5a5de13..d381c5307 100644 --- a/packages/compiler/src/backend/stack-observations.test.ts +++ b/packages/compiler/src/backend/stack-observations.test.ts @@ -133,6 +133,48 @@ test('a computed key that cannot be "stack" reads no stack', () => { expect(observesStacks(stack)).toBe(true); }); +test("a key forwarded through functions with several call sites is searched once", () => { + // f0 calls f1 twice, f1 calls f2 twice, and so on, each passing its + // parameter on: 2^40 call paths reach the read in f40. + const depth = 40; + const value: IrExpr = { loc, kind: "varRef", localId: "d", type: DYN }; + const key: IrExpr = { loc, kind: "varRef", localId: "key", type: STRING }; + const forward = (callee: string): IrStmt => ({ + loc, + kind: "exprStmt", + expr: { loc, kind: "call", callee, args: [key], type: VOID }, + }); + const chain = (root: IrExpr): IrModule => { + const functions: IrFunction[] = []; + for (let i = 0; i <= depth; i++) { + const body: IrStmt[] = + i < depth + ? [forward(`f${i + 1}`), forward(`f${i + 1}`)] + : [{ loc, kind: "exprStmt", expr: { loc, kind: "dynKeyGet", value, key, type: DYN } }]; + functions.push({ + ...fn(`f${i}`, body), + params: [{ localId: "key", name: "key", type: STRING }], + locals: [{ id: "key", name: "key", type: STRING, mutable: false }], + }); + } + const spelled: IrStmt = { + loc, + kind: "exprStmt", + expr: { loc, kind: "strLit", value: "stack", type: STRING }, + }; + const start: IrStmt = { + loc, + kind: "exprStmt", + expr: { loc, kind: "call", callee: "f0", args: [root], type: VOID }, + }; + return mod([fn("%main", [start, spelled]), ...functions]); + }; + expect(observesStacks(chain({ loc, kind: "strLit", value: "message", type: STRING }))).toBe( + false, + ); + expect(observesStacks(chain({ loc, kind: "strLit", value: "stack", type: STRING }))).toBe(true); +}); + test("programs without stack reads are not", () => { expect(observesStacks(mod([fn("%main", [call("helper")]), fn("helper", [ret])]))).toBe(false); }); diff --git a/packages/compiler/src/backend/stack-observations.ts b/packages/compiler/src/backend/stack-observations.ts index fbb1c23cf..fa67c071f 100644 --- a/packages/compiler/src/backend/stack-observations.ts +++ b/packages/compiler/src/backend/stack-observations.ts @@ -33,7 +33,9 @@ class StackKeys { private readonly calls = new Map(); private readonly named: Set; private readonly inits = new Map>(); - private readonly visiting = new Set(); + /** Settled answers by function and local: true where a search reached a + * key that can be "stack", false where a search that found none passed. */ + private readonly answers = new Map(); constructor(mod: IrModule) { this.spelled = spellsStack(mod); @@ -59,6 +61,15 @@ class StackKeys { } mayBeStack(fn: IrFunction, key: IrExpr): boolean { + const visited = new Set(); + const answer = this.keyMayBeStack(fn, key, visited); + // A search that found no way to "stack" explored everything it visited + // to the end, so none of it can be "stack" either. + if (!answer) for (const visit of visited) this.answers.set(visit, false); + return answer; + } + + private keyMayBeStack(fn: IrFunction, key: IrExpr, visited: Set): boolean { if (key.kind === "strLit") return key.value === "stack"; if (!this.spelled) return false; if (NON_STACK_KEY_TYPES.has(key.type.kind)) return false; @@ -66,17 +77,18 @@ class StackKeys { if (key.kind === "dynFrom" && NON_STACK_KEY_TYPES.has(key.value.type.kind)) return false; if (key.kind !== "varRef") return true; const visit = `${fn.name}\0${key.localId}`; - // A cycle adds no value of its own: the answer comes from its entries. - if (this.visiting.has(visit)) return false; - this.visiting.add(visit); - try { - return this.localMayBeStack(fn, key.localId); - } finally { - this.visiting.delete(visit); - } + const known = this.answers.get(visit); + if (known !== undefined) return known; + // Each local is searched once per question: a cycle, or a second path + // to a local already searched, adds no value of its own. + if (visited.has(visit)) return false; + visited.add(visit); + const answer = this.localMayBeStack(fn, key.localId, visited); + if (answer) this.answers.set(visit, true); + return answer; } - private localMayBeStack(fn: IrFunction, localId: string): boolean { + private localMayBeStack(fn: IrFunction, localId: string, visited: Set): boolean { const local = fn.locals.find((candidate) => candidate.id === localId); if (local === undefined || local.mutable) return true; const index = fn.params.findIndex((param) => param.localId === localId); @@ -85,11 +97,12 @@ class StackKeys { if (this.named.has(fn.name) || sites === undefined) return true; return sites.some( (site) => - site.args.length !== fn.params.length || this.mayBeStack(site.caller, site.args[index]!), + site.args.length !== fn.params.length || + this.keyMayBeStack(site.caller, site.args[index]!, visited), ); } const init = this.initializerOf(fn, localId); - return init === null || this.mayBeStack(fn, init); + return init === null || this.keyMayBeStack(fn, init, visited); } /** The one initializer of an immutable local, or null when it has none From 5765d8922784bac7a859ac8ca124ef89fe64e537 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Tue, 20 Oct 2026 13:41:39 -0500 Subject: [PATCH 120/122] Check the frontend's types against tsc-ts/api The frontend calls tokenStartOfNode, getTextOfNode, getSourceFileOfNode and forEachChild instead of AstNode's position and traversal methods. Node lists answer hasTrailingComma(), and a declaration's modifiers are a modifier list with their flags. Symbol, Type, Signature, IndexInfo, TypePredicate, Program, TypeChecker and Diagnostic have tsc-ts/api's members and types. Compiler code reads symbol names with symbolName(symbol). tsc-ts goes to 0.0.5. api-conformance.ts asserts that AstNode, AstNodeList, SourceFile and each node interface are assignable to tsc-ts/api's interface of the same name, and names the members where the wire format differs. --- packages/compiler/package.json | 2 +- packages/compiler/src/frontend/cjs-syntax.ts | 43 +- .../compiler/src/frontend/contract-types.ts | 2 +- .../src/frontend/experimental-import.ts | 6 +- .../compiler/src/frontend/fork-target.test.ts | 3 +- packages/compiler/src/frontend/fork-target.ts | 9 +- .../src/frontend/inferred-js-diagnostics.ts | 14 +- .../compiler/src/frontend/lib-contract.ts | 20 +- packages/compiler/src/frontend/lib-exports.ts | 2 +- .../src/frontend/lowering/assert-instances.ts | 2 +- .../frontend/lowering/assertion-members.ts | 2 +- .../src/frontend/lowering/binding-analysis.ts | 34 +- .../lowering/builtin-function-fences.ts | 2 +- .../lowering/builtins/async-context.ts | 2 +- .../src/frontend/lowering/builtins/blob.ts | 8 +- .../lowering/builtins/builtin-class-dyn.ts | 2 +- .../lowering/builtins/callable-aliases.ts | 2 +- .../builtins/child-process-methods.ts | 12 +- .../lowering/builtins/class-values.ts | 7 +- .../frontend/lowering/builtins/compression.ts | 4 +- .../frontend/lowering/builtins/constants.ts | 2 +- .../src/frontend/lowering/builtins/crypto.ts | 12 +- .../lowering/builtins/disposable-stack.ts | 7 +- .../src/frontend/lowering/builtins/errors.ts | 5 +- .../frontend/lowering/builtins/filesystem.ts | 4 +- .../frontend/lowering/builtins/fs-streams.ts | 8 +- .../src/frontend/lowering/builtins/json.ts | 2 +- .../frontend/lowering/builtins/key-object.ts | 4 +- .../lowering/builtins/module-bindings.ts | 8 +- .../lowering/builtins/module-calls.ts | 2 +- .../lowering/builtins/module-resolution.ts | 6 +- .../frontend/lowering/builtins/navigator.ts | 3 +- .../frontend/lowering/builtins/net-address.ts | 8 +- .../frontend/lowering/builtins/performance.ts | 8 +- .../src/frontend/lowering/builtins/process.ts | 25 +- .../frontend/lowering/builtins/promises.ts | 12 +- .../frontend/lowering/builtins/readline.ts | 10 +- .../lowering/builtins/receiver-types.ts | 4 +- .../lowering/builtins/runtime-constructor.ts | 2 +- .../frontend/lowering/builtins/text-codecs.ts | 13 +- .../src/frontend/lowering/builtins/timers.ts | 4 +- .../src/frontend/lowering/builtins/url.ts | 2 +- .../frontend/lowering/builtins/webcrypto.ts | 6 +- .../src/frontend/lowering/builtins/workers.ts | 4 +- .../src/frontend/lowering/call-arguments.ts | 12 +- .../src/frontend/lowering/call-signatures.ts | 61 +- .../frontend/lowering/cjs-exports-object.ts | 18 +- .../frontend/lowering/cjs-inline-require.ts | 6 +- .../src/frontend/lowering/cjs-tsc-interop.ts | 8 +- .../src/frontend/lowering/class-callbacks.ts | 6 +- .../lowering/class-dynamic-dispatch.ts | 4 +- .../lowering/class-method-mutations.ts | 7 +- .../frontend/lowering/class-method-values.ts | 5 +- .../lowering/class-protocol-dispatch.ts | 18 +- .../src/frontend/lowering/class-protocols.ts | 22 +- .../frontend/lowering/class-static-data.ts | 2 +- .../src/frontend/lowering/code-generation.ts | 8 +- .../frontend/lowering/collection-ingestion.ts | 2 +- .../frontend/lowering/collection-narrowing.ts | 7 +- .../lowering/construction-escapes.test.ts | 4 +- .../frontend/lowering/construction-escapes.ts | 8 +- .../frontend/lowering/constructor-returns.ts | 19 +- .../lowering/containers/array-callbacks.ts | 12 +- .../lowering/containers/array-construction.ts | 6 +- .../lowering/containers/array-methods.ts | 6 +- .../src/frontend/lowering/containers/bytes.ts | 26 +- .../lowering/containers/callback-arguments.ts | 2 +- .../containers/collection-grouping.ts | 2 +- .../lowering/containers/collection-methods.ts | 10 +- .../lowering/containers/dynamic-receivers.ts | 2 +- .../lowering/containers/exec-result-uses.ts | 2 +- .../lowering/containers/iterator-helpers.ts | 2 +- .../lowering/containers/match-row-uses.ts | 2 +- .../lowering/deferred-module-initializers.ts | 53 +- .../src/frontend/lowering/divergences.ts | 24 +- .../src/frontend/lowering/dyn-streams.ts | 12 +- .../lowering/expressions/array-literals.ts | 14 +- .../frontend/lowering/expressions/binary.ts | 14 +- .../lowering/expressions/element-access.ts | 28 +- .../lowering/expressions/identifiers.ts | 20 +- .../expressions/keyed-field-targets.ts | 2 +- .../lowering/expressions/narrowing.ts | 28 +- .../lowering/expressions/native-proxy.ts | 2 +- .../lowering/expressions/object-literals.ts | 51 +- .../lowering/expressions/optional-chains.ts | 6 +- .../lowering/expressions/property-access.ts | 89 +- .../lowering/expressions/relational.ts | 12 +- .../frontend/lowering/expressions/typeof.ts | 4 +- .../lowering/expressions/union-fields.ts | 2 +- .../frontend/lowering/expressions/updates.ts | 19 +- .../src/frontend/lowering/function-returns.ts | 40 +- .../frontend/lowering/generic-functions.ts | 53 +- .../src/frontend/lowering/import-meta.ts | 2 +- .../src/frontend/lowering/intern-tables.ts | 2 +- .../frontend/lowering/js-dynamic-objects.ts | 14 +- .../src/frontend/lowering/json-require.ts | 2 +- .../src/frontend/lowering/lower-assert.ts | 6 +- .../src/frontend/lowering/lower-calls.ts | 167 ++-- .../src/frontend/lowering/lower-classes.ts | 294 ++++--- .../src/frontend/lowering/lower-comptime.ts | 6 +- .../src/frontend/lowering/lower-console.ts | 2 +- .../src/frontend/lowering/lower-dgram.ts | 4 +- .../src/frontend/lowering/lower-enums.ts | 10 +- .../frontend/lowering/lower-event-emitter.ts | 12 +- .../src/frontend/lowering/lower-expando.ts | 24 +- .../src/frontend/lowering/lower-exprs.ts | 23 +- .../src/frontend/lowering/lower-functions.ts | 18 +- .../src/frontend/lowering/lower-generators.ts | 10 +- .../frontend/lowering/lower-global-value.ts | 2 +- .../src/frontend/lowering/lower-inspect.ts | 8 +- .../src/frontend/lowering/lower-mixins.ts | 11 +- .../src/frontend/lowering/lower-mock.ts | 2 +- .../src/frontend/lowering/lower-modules.ts | 29 +- .../src/frontend/lowering/lower-namespaces.ts | 42 +- .../frontend/lowering/lower-node-module.ts | 20 +- .../src/frontend/lowering/lower-server.ts | 22 +- .../src/frontend/lowering/lower-stdlib.ts | 53 +- .../src/frontend/lowering/lower-stmts.ts | 161 ++-- .../src/frontend/lowering/lower-stream.ts | 8 +- .../src/frontend/lowering/lower-test.ts | 4 +- .../src/frontend/lowering/lower-timers.ts | 2 +- .../compiler/src/frontend/lowering/lowerer.ts | 158 ++-- .../frontend/lowering/module-member-this.ts | 10 +- .../src/frontend/lowering/nan-coded-reads.ts | 30 +- .../frontend/lowering/narrowing-stability.ts | 21 +- .../src/frontend/lowering/native-ffi.ts | 6 +- .../frontend/lowering/object-factory-new.ts | 2 +- .../src/frontend/lowering/private-brands.ts | 2 +- .../lowering/require-initialization.ts | 4 +- .../lowering/runtime-optional-bounds.ts | 6 +- .../lowering/runtime-optional-elements.ts | 2 +- .../lowering/runtime-optional-guards.ts | 2 +- .../lowering/runtime-optional-ownership.ts | 4 +- .../lowering/runtime-optional-provenance.ts | 4 +- .../lowering/runtime-optional-records.test.ts | 2 +- .../lowering/runtime-optional-records.ts | 14 +- .../src/frontend/lowering/split-consumers.ts | 2 +- .../lowering/static-function-names.ts | 2 +- .../src/frontend/lowering/static-this.ts | 12 +- .../frontend/lowering/string-index-bounds.ts | 16 +- .../src/frontend/lowering/surfaces.ts | 14 +- .../src/frontend/lowering/symbol-fields.ts | 14 +- .../src/frontend/lowering/typed-arguments.ts | 6 +- .../src/frontend/lowering/var-hoisting.ts | 4 +- .../frontend/module-source-candidates.test.ts | 3 +- .../src/frontend/npm-static-bundled-chunks.ts | 29 +- .../src/frontend/npm-static-bundled-cjs.ts | 19 +- .../src/frontend/npm-static-bundled-esm.ts | 53 +- .../frontend/npm-static-cjs-exports-alias.ts | 13 +- .../frontend/npm-static-cjs-exports-var.ts | 5 +- .../frontend/npm-static-declaration-syntax.ts | 70 +- .../src/frontend/npm-static-rewrite-syntax.ts | 59 +- .../src/frontend/npm-static-subtyping.ts | 8 +- packages/compiler/src/frontend/program.ts | 105 +-- .../compiler/src/frontend/services.test.ts | 3 +- .../compiler/src/frontend/tier-analysis.ts | 25 +- .../src/frontend/ts7/api-conformance.ts | 297 +++++++ .../src/frontend/ts7/ast-guards.generated.ts | 3 +- .../src/frontend/ts7/ast-node.test.ts | 30 +- .../compiler/src/frontend/ts7/ast-node.ts | 35 +- .../src/frontend/ts7/ast-types.generated.ts | 76 +- .../compiler/src/frontend/ts7/ast-types.ts | 7 +- packages/compiler/src/frontend/ts7/ast.ts | 1 + .../compiler/src/frontend/ts7/checker.test.ts | 10 +- packages/compiler/src/frontend/ts7/checker.ts | 53 +- .../compiler/src/frontend/ts7/diagnostic.ts | 38 + .../src/frontend/ts7/node-functions.ts | 23 + .../compiler/src/frontend/ts7/program-host.ts | 18 +- .../src/frontend/ts7/semantic-checker.test.ts | 8 +- .../src/frontend/ts7/semantic-checker.ts | 48 +- .../src/frontend/ts7/semantic-model.test.ts | 14 +- .../src/frontend/ts7/semantic-model.ts | 97 ++- .../src/frontend/ts7/semantic-types.ts | 29 +- .../compiler/src/frontend/ts7/session.test.ts | 11 +- packages/compiler/src/frontend/ts7/session.ts | 25 +- .../src/frontend/ts7/source-parser.test.ts | 21 +- packages/compiler/src/frontend/type-mapper.ts | 796 ++++++++++-------- .../compiler/src/frontend/worker-target.ts | 3 +- packages/compiler/src/ir/traverse.test.ts | 27 +- packages/compiler/src/wasm/discover.ts | 2 +- packages/compiler/test/ts7/parity.test.ts | 7 +- packages/compiler/test/ts7/rpc-api.test.ts | 28 +- pnpm-lock.yaml | 75 +- pnpm-workspace.yaml | 16 +- scripts/generate-ts7-ast-schema.mjs | 7 +- scripts/generate-ts7-ast-views.mjs | 15 +- .../self-hosting/lowering-context-cases.ts | 2 +- .../self-hosting/ts7-checker-cases.ts | 8 +- .../fixtures/self-hosting/ts7-client-cases.ts | 3 +- .../self-hosting/ts7-semantic-cases.ts | 19 +- 190 files changed, 2781 insertions(+), 1861 deletions(-) create mode 100644 packages/compiler/src/frontend/ts7/api-conformance.ts create mode 100644 packages/compiler/src/frontend/ts7/diagnostic.ts create mode 100644 packages/compiler/src/frontend/ts7/node-functions.ts diff --git a/packages/compiler/package.json b/packages/compiler/package.json index b6a18db06..97c7688f5 100644 --- a/packages/compiler/package.json +++ b/packages/compiler/package.json @@ -39,7 +39,7 @@ }, "dependencies": { "@scriptc/runtime": "workspace:*", - "tsc-ts": "0.0.3", + "tsc-ts": "0.0.5", "typescript": "7.0.2", "typescript5": "npm:typescript@5.9.3" }, diff --git a/packages/compiler/src/frontend/cjs-syntax.ts b/packages/compiler/src/frontend/cjs-syntax.ts index 41191432a..99195d994 100644 --- a/packages/compiler/src/frontend/cjs-syntax.ts +++ b/packages/compiler/src/frontend/cjs-syntax.ts @@ -1,4 +1,5 @@ import * as ts from "./ts7/syntax.js"; +import { tokenStartOfNode } from "./ts7/node-functions.js"; export interface CjsLexedExports { /** Names Node's lexer detects in THIS file (no reexport resolution). */ @@ -30,7 +31,7 @@ function identRunEnd(text: string, pos: number): number { * a `a`-escaped name parses to the same AST as its plain spelling, * but merve reads bytes and stops at the backslash. */ function sourceSpellsIdentifier(node: ts.Node, sf: ts.SourceFile): boolean { - const ch = sf.text[node.getStart(sf)]; + const ch = sf.text[tokenStartOfNode(node)]; return ch !== undefined && isIdentStartChar(ch); } @@ -39,16 +40,16 @@ function sourceSpellsIdentifier(node: ts.Node, sf: ts.SourceFile): boolean { * defineProperty getter's `return IDENT.IDENT` because `this` lexes as an * identifier run — probed), escapes and private names do not. */ function isIdentTokenNode(node: ts.Node, sf: ts.SourceFile): boolean { - const start = node.getStart(sf); + const start = tokenStartOfNode(node); const ch = sf.text[start]; - return ch !== undefined && isIdentStartChar(ch) && identRunEnd(sf.text, start) === node.getEnd(); + return ch !== undefined && isIdentStartChar(ch) && identRunEnd(sf.text, start) === node.end; } /** The identifier run at the node's START — the whole name when its * spelling is plain, the byte prefix merve consumed when a unicode escape * interrupts the run mid-name. */ function identPrefixOf(node: ts.Node, sf: ts.SourceFile): string { - const start = node.getStart(sf); + const start = tokenStartOfNode(node); return sf.text.slice(start, identRunEnd(sf.text, start)); } @@ -84,20 +85,17 @@ export function isModuleExports(e: ts.Expression): boolean { * follows — so the recognizer descends the leftmost-child chain while it * still starts where `e` starts. A ParenthesizedExpression breaks the * chain naturally (its child starts after the `(`), matching merve. */ -function leadingRequireOf( - e: ts.Expression, - sf: ts.SourceFile, -): { spec: string; end: number } | null { - const start = e.getStart(sf); +function leadingRequireOf(e: ts.Expression): { spec: string; end: number } | null { + const start = tokenStartOfNode(e); let cur: ts.Node = e; for (;;) { const spec = bareRequireSpecOf(cur); - if (spec !== null && cur.getStart(sf) === start) return { spec, end: cur.getEnd() }; + if (spec !== null && tokenStartOfNode(cur) === start) return { spec, end: cur.end }; let first: ts.Node | undefined; ts.forEachChild(cur, (c) => { first ??= c; }); - if (first === undefined || first.getStart(sf) !== start) return null; + if (first === undefined || tokenStartOfNode(first) !== start) return null; cur = first; } } @@ -179,13 +177,13 @@ function scanTableLiteral( continue; // whitespace before `,` is skipped on the shorthand path } if (ts.isSpreadAssignment(prop)) { - const req = leadingRequireOf(prop.expression, sf); + const req = leadingRequireOf(prop.expression); let consumedEnd: number; if (req !== null) { reexports.push(req.spec); consumedEnd = req.end; } else if (sourceSpellsIdentifier(prop.expression, sf)) { - consumedEnd = identRunEnd(text, prop.expression.getStart(sf)); + consumedEnd = identRunEnd(text, tokenStartOfNode(prop.expression)); } else { return; // `...{}` and friends stop the scan } @@ -213,7 +211,7 @@ function scanTableLiteral( } if (key === null) return; // numeric / computed / template keys stop the scan const value = prop.initializer; - const req = leadingRequireOf(value, sf); + const req = leadingRequireOf(value); if (req !== null) { // A require VALUE adds the key, records the reexport, and stops // the scan UNCONDITIONALLY — probed: `{ a: require('x'), b: v }` @@ -223,7 +221,7 @@ function scanTableLiteral( reexports.push(req.spec); return; } - const start = value.getStart(sf); + const start = tokenStartOfNode(value); const ch0 = text[start]; if (ch0 === undefined || !isIdentStartChar(ch0)) return; // literal values stop WITHOUT adding the key const consumedEnd = identRunEnd(text, start); @@ -351,8 +349,8 @@ function starExportSpecOf(call: ts.CallExpression, sf: ts.SourceFile): string | if (!sourceSpellsIdentifier(nameNode, sf)) return null; const arg0 = call.arguments.nodes[0]; if (arg0 === undefined) return null; - if (!byteAdjacentParen(sf, nameNode.getEnd(), arg0.getStart(sf))) return null; - return leadingRequireOf(arg0, sf)?.spec ?? null; + if (!byteAdjacentParen(sf, nameNode.end, tokenStartOfNode(arg0))) return null; + return leadingRequireOf(arg0)?.spec ?? null; } /** True when the bytes between a callee name ending at `nameEnd` and its @@ -384,7 +382,7 @@ function starAssignOf(stmt: ts.VariableStatement, sf: ts.SourceFile): [string, s init.arguments.nodes.length >= 1 ) { const arg0 = init.arguments.nodes[0]!; - if (!byteAdjacentParen(sf, init.expression.getEnd(), arg0.getStart(sf))) return null; + if (!byteAdjacentParen(sf, init.expression.end, tokenStartOfNode(arg0))) return null; const spec = bareRequireSpecOf(arg0); if (spec !== null) return [decl.name.text, spec]; } @@ -717,15 +715,16 @@ export function cjsLexedExportsOfFile(sf: ts.SourceFile): CjsLexedExports { ) { exports.add(lhs.argumentExpression.text); } else if (isModuleExports(lhs)) { - events.push({ pos: n.getStart(sf), kind: "assign", rhs: n.right }); + events.push({ pos: tokenStartOfNode(n), kind: "assign", rhs: n.right }); } } else if (ts.isCallExpression(n)) { scanDefineProperty(n, sf, exports); if (braceDepth === 0) { const starSpec = starExportSpecOf(n, sf); - if (starSpec !== null) events.push({ pos: n.getStart(sf), kind: "spec", spec: starSpec }); + if (starSpec !== null) + events.push({ pos: tokenStartOfNode(n), kind: "spec", spec: starSpec }); const loopId = starLoopIdOf(n, sf); - if (loopId !== null) events.push({ pos: n.getStart(sf), kind: "loop", id1: loopId }); + if (loopId !== null) events.push({ pos: tokenStartOfNode(n), kind: "loop", id1: loopId }); } } else if (ts.isVariableStatement(n) && braceDepth === 0) { const assign = starAssignOf(n, sf); @@ -745,7 +744,7 @@ export function cjsLexedExportsOfFile(sf: ts.SourceFile): CjsLexedExports { for (const spec of starAssigns.get(ev.id1) ?? []) reexports.push(spec); } else { reexports = []; // every `module.exports =` clears — stars included (probed) - const req = leadingRequireOf(ev.rhs, sf); + const req = leadingRequireOf(ev.rhs); if (req !== null) { reexports.push(req.spec); } else if (ts.isObjectLiteralExpression(ev.rhs)) { diff --git a/packages/compiler/src/frontend/contract-types.ts b/packages/compiler/src/frontend/contract-types.ts index c30788fa1..4ded2a1a0 100644 --- a/packages/compiler/src/frontend/contract-types.ts +++ b/packages/compiler/src/frontend/contract-types.ts @@ -130,7 +130,7 @@ function markerNamed( function nameText(node: ts.Node | undefined): string { if (node === undefined) return ""; if (ts.isIdentifier(node) || ts.isPrivateIdentifier(node)) return node.text; - return node.getText(); + return ts.getTextOfNode(node); } function contractDiag( diff --git a/packages/compiler/src/frontend/experimental-import.ts b/packages/compiler/src/frontend/experimental-import.ts index 8fc2238bf..3b35a196a 100644 --- a/packages/compiler/src/frontend/experimental-import.ts +++ b/packages/compiler/src/frontend/experimental-import.ts @@ -73,7 +73,7 @@ export function lifetimeRegionMemberOf( if ( !ts.isClassDeclaration(owner) || owner.name?.text !== "LifetimeRegion" || - !isExperimentalDeclarationFile(owner.getSourceFile(), known) + !isExperimentalDeclarationFile(ts.getSourceFileOfNode(owner), known) ) return null; const name = access.name.text; @@ -105,7 +105,7 @@ export function internTableMemberOf( if ( !ts.isClassDeclaration(owner) || owner.name?.text !== "InternTable" || - !isExperimentalDeclarationFile(owner.getSourceFile(), known) + !isExperimentalDeclarationFile(ts.getSourceFileOfNode(owner), known) ) return null; const name = access.name.text; @@ -135,7 +135,7 @@ export function threadSlotsMemberOf( if ( !ts.isClassDeclaration(owner) || owner.name?.text !== "ThreadSlots" || - !isExperimentalDeclarationFile(owner.getSourceFile(), known) + !isExperimentalDeclarationFile(ts.getSourceFileOfNode(owner), known) ) return null; const name = access.name.text; diff --git a/packages/compiler/src/frontend/fork-target.test.ts b/packages/compiler/src/frontend/fork-target.test.ts index 7a3e23c9b..5e56cea5d 100644 --- a/packages/compiler/src/frontend/fork-target.test.ts +++ b/packages/compiler/src/frontend/fork-target.test.ts @@ -7,6 +7,7 @@ import { fallbackDtsPath } from "./dts-paths.js"; import { Ts7Host } from "./ts7/program-host.js"; import { Ts7Api } from "./ts7/rpc-api.js"; import { ModuleKind, ModuleResolutionKind } from "./ts7/enums.js"; +import { getTextOfNode } from "./ts7/node-functions.js"; test("fork discovery batches only candidate bindings and preserves aliases and shadowing", () => { const directory = mkdtempSync(join(tmpdir(), "scriptc-fork-discovery-")); @@ -49,7 +50,7 @@ test("fork discovery batches only candidate bindings and preserves aliases and s ["first.ts", "second.ts", "third.ts"].map((name) => join(directory, name)), ); expect(batch).toHaveBeenCalledTimes(1); - expect(batch.mock.calls[0]![0].map((node) => node.getText())).toEqual([ + expect(batch.mock.calls[0]![0].map((node) => getTextOfNode(node))).toEqual([ "start", "child", "launch", diff --git a/packages/compiler/src/frontend/fork-target.ts b/packages/compiler/src/frontend/fork-target.ts index 592bbb2d6..38c8f5f07 100644 --- a/packages/compiler/src/frontend/fork-target.ts +++ b/packages/compiler/src/frontend/fork-target.ts @@ -12,7 +12,7 @@ export function isNodeGlobal(program: ts.Program, expression: ts.Identifier): bo return ( declarations.length > 0 && declarations.every((declaration) => { - const source = declaration.getSourceFile(); + const source = ts.getSourceFileOfNode(declaration); return ( source.isDeclarationFile && (tsgoPath(source.fileName) === fallbackDtsPath() || isNodeTypesPath(source.fileName)) @@ -61,7 +61,7 @@ export function isBuiltinMemberImport( if (symbol !== undefined && (symbol.flags & ts.SymbolFlags.Alias) !== 0) { const target = checker.getAliasedSymbol(symbol); const targetDecl = target.declarations[0]; - if (targetDecl !== undefined && targetDecl.getSourceFile().isDeclarationFile) { + if (targetDecl !== undefined && ts.getSourceFileOfNode(targetDecl).isDeclarationFile) { for ( let parent: ts.Node | undefined = targetDecl.parent; parent !== undefined && !ts.isSourceFile(parent); @@ -69,7 +69,8 @@ export function isBuiltinMemberImport( ) { if (ts.isModuleDeclaration(parent) && ts.isStringLiteral(parent.name)) { return ( - canonicalBuiltinModule(parent.name.text) === moduleName && target.name === memberName + canonicalBuiltinModule(parent.name.text) === moduleName && + ts.symbolName(target) === memberName ); } } @@ -188,7 +189,7 @@ function staticString( ): string | null { const current = strip(expr); if (ts.isStringLiteralLike(current)) return current.text; - if (importMetaUrl(current)) return pathToFileURL(current.getSourceFile().fileName).href; + if (importMetaUrl(current)) return pathToFileURL(ts.getSourceFileOfNode(current).fileName).href; if (ts.isTemplateExpression(current)) { let value = current.head.text; for (const span of current.templateSpans.nodes) { diff --git a/packages/compiler/src/frontend/inferred-js-diagnostics.ts b/packages/compiler/src/frontend/inferred-js-diagnostics.ts index 7095cea5d..b21d19cb0 100644 --- a/packages/compiler/src/frontend/inferred-js-diagnostics.ts +++ b/packages/compiler/src/frontend/inferred-js-diagnostics.ts @@ -16,7 +16,7 @@ export function inferredJsDiagnosticSuppressed( const checker = program.getTypeChecker(); let suppressed = false; ts.walkPreorder(file, (node) => { - if (node.getEnd() < diagnostic.pos || node.getStart(file) > diagnostic.end) return "skip"; + if (node.end < diagnostic.pos || ts.tokenStartOfNode(node) > diagnostic.end) return "skip"; if ( diagnostic.code === 2565 && isJsSourceFileName(file.fileName) && @@ -30,7 +30,7 @@ export function inferredJsDiagnosticSuppressed( ts.isPropertyDeclaration(decl) && !decl.type && !decl.initializer && - !/@type\b/.test(file.text.slice(decl.pos, decl.getStart(file))), + !/@type\b/.test(file.text.slice(decl.pos, ts.tokenStartOfNode(decl))), ) ) { // Bare JS fields exist with value undefined before the first write; @@ -44,14 +44,14 @@ export function inferredJsDiagnosticSuppressed( const declaration = signature && signature.declaration; if ( declaration && - isJsSourceFileName(declaration.getSourceFile().fileName) && + isJsSourceFileName(ts.getSourceFileOfNode(declaration).fileName) && (ts.isFunctionDeclaration(declaration) || ts.isFunctionExpression(declaration) || ts.isMethodDeclaration(declaration)) && node.arguments.nodes.length > declaration.parameters.nodes.length && - node.arguments.nodes[declaration.parameters.nodes.length]?.getStart(file) === + ts.tokenStartOfNode(node.arguments.nodes[declaration.parameters.nodes.length]!) === diagnostic.pos && - node.arguments.nodes[node.arguments.nodes.length - 1]?.getEnd() === diagnostic.end && + node.arguments.nodes[node.arguments.nodes.length - 1]!.end === diagnostic.end && bodyReadsArguments(declaration) ) { suppressed = true; @@ -68,7 +68,7 @@ export function inferredJsDiagnosticSuppressed( const iterable = checker.getTypeAtLocation(node.expression); const iterator = checker .getPropertiesOfType(iterable) - .find((property) => property.name.startsWith("__@iterator@")); + .find((property) => ts.symbolName(property).startsWith("__@iterator@")); if (!iterator) return undefined; const signatures = checker.getCallSignatures( checker.getTypeOfSymbolAtLocation(iterator, node.expression), @@ -88,7 +88,7 @@ export function inferredJsDiagnosticSuppressed( declaration && ts.isMethodDeclaration(declaration) && declaration.type === undefined && - isJsSourceFileName(declaration.getSourceFile().fileName) && + isJsSourceFileName(ts.getSourceFileOfNode(declaration).fileName) && declaration.body?.statements?.nodes.length === 1 && ts.isThrowStatement(declaration.body.statements.nodes[0]!) ) { diff --git a/packages/compiler/src/frontend/lib-contract.ts b/packages/compiler/src/frontend/lib-contract.ts index 0432d06e2..28532ecd8 100644 --- a/packages/compiler/src/frontend/lib-contract.ts +++ b/packages/compiler/src/frontend/lib-contract.ts @@ -115,7 +115,7 @@ const CONTRACT_GLOBAL_TYPES = new Set(["Array", "ReadonlyArray", "Uint8Array"]); type TypeBindings = Map>; function locOf(file: ts.SourceFile, node: ts.Node): SrcLoc { - return { file: file.fileName, start: node.getStart(), end: node.end }; + return { file: file.fileName, start: ts.tokenStartOfNode(node), end: node.end }; } function isExported(stmt: ts.Node): boolean { @@ -248,7 +248,7 @@ function typeShape( const lit = node.literal; if (ts.isStringLiteral(lit)) return { k: "stringLit", text: lit.text }; if (lit.kind === ts.SyntaxKind.NullKeyword) return { k: "absent", unit: "null" }; - return { k: "unsupported", text: node.getText(file) }; + return { k: "unsupported", text: ts.getTextOfNode(node) }; } if (ts.isArrayTypeNode(node)) return { k: "array", elem: typeShape(file, node.elementType, bindings) }; @@ -280,15 +280,19 @@ function typeShape( if (ts.isConditionalTypeNode(node)) { return { k: "unsupported", - text: `a conditional type (${node.getText(file)})`, + text: `a conditional type (${ts.getTextOfNode(node)})`, computed: "conditional", }; } if (ts.isMappedTypeNode(node)) { - return { k: "unsupported", text: `a mapped type (${node.getText(file)})`, computed: "mapped" }; + return { + k: "unsupported", + text: `a mapped type (${ts.getTextOfNode(node)})`, + computed: "mapped", + }; } if (ts.isTypeReferenceNode(node)) { - if (!ts.isIdentifier(node.typeName)) return { k: "unsupported", text: node.getText(file) }; + if (!ts.isIdentifier(node.typeName)) return { k: "unsupported", text: ts.getTextOfNode(node) }; const name = node.typeName.text; const global = CONTRACT_GLOBAL_TYPES.has(name) && isUnshadowedGlobalType(file, node, name, bindings); @@ -302,10 +306,10 @@ function typeShape( return { k: "array", elem: typeShape(file, node.typeArguments.nodes[0]!, bindings) }; } if ((node.typeArguments?.nodes.length ?? 0) > 0) - return { k: "unsupported", text: node.getText(file) }; + return { k: "unsupported", text: ts.getTextOfNode(node) }; return { k: "ref", name }; } - return { k: "unsupported", text: node.getText(file) }; + return { k: "unsupported", text: ts.getTextOfNode(node) }; } /** Unwrap `expr as const` / parenthesized initializers. */ @@ -362,7 +366,7 @@ function envMsgArray(expr: ts.Expression): { env: string; msg: string }[] | null /** One mergeable declaration site, `file:line` (1-based line). */ function siteOf(file: ts.SourceFile, node: ts.Node): string { - return `${file.fileName}:${ts.getLineAndCharacterOfPosition(file, node.getStart()).line + 1}`; + return `${file.fileName}:${ts.getLineAndCharacterOfPosition(file, ts.tokenStartOfNode(node)).line + 1}`; } /** Everything the sidecar projection needs from the program's syntax diff --git a/packages/compiler/src/frontend/lib-exports.ts b/packages/compiler/src/frontend/lib-exports.ts index 738dd2f19..6a42074bd 100644 --- a/packages/compiler/src/frontend/lib-exports.ts +++ b/packages/compiler/src/frontend/lib-exports.ts @@ -35,7 +35,7 @@ export function entryFunctionExports(entry: ts.SourceFile): Map 0, async: mods?.some((m) => m.kind === ts.SyntaxKind.AsyncKeyword) === true, generator: stmt.asteriskToken !== undefined, diff --git a/packages/compiler/src/frontend/lowering/assert-instances.ts b/packages/compiler/src/frontend/lowering/assert-instances.ts index ee8a34ea2..1166c6afa 100644 --- a/packages/compiler/src/frontend/lowering/assert-instances.ts +++ b/packages/compiler/src/frontend/lowering/assert-instances.ts @@ -194,7 +194,7 @@ function onlyMethodCallReferences(lowerer: Lowerer, name: ts.Identifier): boolea } ts.forEachChild(node, visit); }; - visit(name.getSourceFile()); + visit(ts.getSourceFileOfNode(name)); return ok; } diff --git a/packages/compiler/src/frontend/lowering/assertion-members.ts b/packages/compiler/src/frontend/lowering/assertion-members.ts index d61c379d6..484963fa8 100644 --- a/packages/compiler/src/frontend/lowering/assertion-members.ts +++ b/packages/compiler/src/frontend/lowering/assertion-members.ts @@ -79,7 +79,7 @@ export function extendAssertedRecordMembers( symbol.declarations.length > 0 && symbol.declarations.every( (d) => - !lowerer.isStdlibFile(d.getSourceFile()) && + !lowerer.isStdlibFile(ts.getSourceFileOfNode(d)) && (ts.isTypeLiteralNode(d) || ts.isInterfaceDeclaration(d) || ts.isTypeAliasDeclaration(d)), diff --git a/packages/compiler/src/frontend/lowering/binding-analysis.ts b/packages/compiler/src/frontend/lowering/binding-analysis.ts index 88b0f91dc..83b9aa6f9 100644 --- a/packages/compiler/src/frontend/lowering/binding-analysis.ts +++ b/packages/compiler/src/frontend/lowering/binding-analysis.ts @@ -16,7 +16,7 @@ export function bindingNeverReassigned(lowerer: Lowerer, sym: ts.Symbol, decl: t let written = false; // Text pre-check keeps the file walk cheap: only same-named // identifiers pay a symbol resolution. - const symText = sym.name; + const symText = ts.symbolName(sym); const namesSym = (e: ts.Node): boolean => ts.isIdentifier(e) && e.text === symText && lowerer.resolveValueSymbol(e) === sym; const scanTarget = (t: ts.Expression): void => { @@ -31,7 +31,7 @@ export function bindingNeverReassigned(lowerer: Lowerer, sym: ts.Symbol, decl: t if (ts.isArrayLiteralExpression(e) || ts.isObjectLiteralExpression(e)) { const walk = (n: ts.Node): void => { if (namesSym(n)) written = true; - else n.forEachChild(walk); + else ts.forEachChild(n, walk); }; walk(e); } @@ -54,9 +54,9 @@ export function bindingNeverReassigned(lowerer: Lowerer, sym: ts.Symbol, decl: t ) { scanTarget(n.initializer as ts.Expression); } - n.forEachChild(visit); + ts.forEachChild(n, visit); }; - decl.getSourceFile().forEachChild(visit); + ts.forEachChild(ts.getSourceFileOfNode(decl), visit); lowerer.neverReassignedCache.set(sym, !written); return !written; } @@ -123,7 +123,7 @@ export function nullishValueUnitOf( !decl || !ts.isVariableDeclaration(decl) || !ts.isIdentifier(decl.name) || - decl.getSourceFile().isDeclarationFile || + ts.getSourceFileOfNode(decl).isDeclarationFile || decl.initializer === undefined || !ts.isVariableStatement(decl.parent?.parent) ) { @@ -154,7 +154,7 @@ export function nullishValueUnitOf( /** True when any identifier resolving to `sym` sits inside a class * heritage clause anywhere in the program. */ function usedInHeritageClause(lowerer: Lowerer, sym: ts.Symbol): boolean { - const symText = sym.name; + const symText = ts.symbolName(sym); let found = false; const visit = (n: ts.Node): void => { if (found) return; @@ -171,12 +171,12 @@ function usedInHeritageClause(lowerer: Lowerer, sym: ts.Symbol): boolean { } return; } - n.forEachChild(visit); + ts.forEachChild(n, visit); }; for (const file of lowerer.program.getImplementationSourceFiles()) { if (found) break; if (file.isDeclarationFile) continue; - file.forEachChild(visit); + ts.forEachChild(file, visit); } return found; } @@ -236,7 +236,7 @@ export function nullishGenericBindingUnitOf( * value knowable. Compound assignments, ++/--, for-in/of cursors, and * destructuring targets all disqualify. */ function allWritesNullish(lowerer: Lowerer, sym: ts.Symbol, decl: ts.Node): boolean { - const symText = sym.name; + const symText = ts.symbolName(sym); const namesSym = (e: ts.Node): boolean => ts.isIdentifier(e) && e.text === symText && lowerer.resolveValueSymbol(e) === sym; let ok = true; @@ -253,7 +253,7 @@ function allWritesNullish(lowerer: Lowerer, sym: ts.Symbol, decl: ts.Node): bool } else if (ts.isArrayLiteralExpression(lhs) || ts.isObjectLiteralExpression(lhs)) { const walk = (m: ts.Node): void => { if (namesSym(m)) ok = false; - else m.forEachChild(walk); + else ts.forEachChild(m, walk); }; walk(lhs); } @@ -274,9 +274,9 @@ function allWritesNullish(lowerer: Lowerer, sym: ts.Symbol, decl: ts.Node): bool t = (t as ts.ParenthesizedExpression).expression; if (namesSym(t)) ok = false; } - n.forEachChild(visit); + ts.forEachChild(n, visit); }; - decl.getSourceFile().forEachChild(visit); + ts.forEachChild(ts.getSourceFileOfNode(decl), visit); return ok; } @@ -332,7 +332,7 @@ export function deadUnmappableBinding( // the no-storage family never claims it (catch bindings sit outside a // variable statement too and stay out the same way). if (!ts.isVariableStatement(decl.parent?.parent)) return false; - const sf = decl.getSourceFile(); + const sf = ts.getSourceFileOfNode(decl); if (sf.isDeclarationFile || isJsSourceFile(sf)) return false; if (decl.initializer !== undefined && !sideEffectFreeValueExpr(lowerer, decl.initializer)) return false; @@ -344,7 +344,7 @@ export function deadUnmappableBinding( // bug), so only survivors of the syntactic filters pay it. Mappable // types keep their real storage. if (lowerer.mapTypeOf(lowerer.checker.getTypeOfSymbol(sym)) !== null) return false; - const symText = sym.name; + const symText = ts.symbolName(sym); const namesSym = (e: ts.Node): boolean => ts.isIdentifier(e) && e.text === symText && lowerer.resolveValueSymbol(e) === sym; let dead = true; @@ -353,7 +353,7 @@ export function deadUnmappableBinding( if (ts.isIdentifier(n) && n.text === symText) { // Declaration-name occurrences are not reads. if (n.parent !== undefined && ts.isVariableDeclaration(n.parent) && n.parent.name === n) { - n.forEachChild(visit); + ts.forEachChild(n, visit); return; } // A plain-assignment LHS is a WRITE — dead only when the RHS @@ -375,12 +375,12 @@ export function deadUnmappableBinding( if (namesSym(n)) dead = false; return; } - n.forEachChild(visit); + ts.forEachChild(n, visit); }; for (const file of lowerer.program.getImplementationSourceFiles()) { if (!dead) break; if (file.isDeclarationFile) continue; - file.forEachChild(visit); + ts.forEachChild(file, visit); } if (dead) lowerer.deadBindings.add(sym); return dead; diff --git a/packages/compiler/src/frontend/lowering/builtin-function-fences.ts b/packages/compiler/src/frontend/lowering/builtin-function-fences.ts index e6221384b..312596c1f 100644 --- a/packages/compiler/src/frontend/lowering/builtin-function-fences.ts +++ b/packages/compiler/src/frontend/lowering/builtin-function-fences.ts @@ -25,7 +25,7 @@ export function builtinFunctionFenceValue( `library functions as values ('${display}' has no function-value lowering; call it directly)`, ); lowerer.runtimeFences.push(diag); - const line = ts.getLineAndCharacterOfPosition(node.getSourceFile(), loc.start).line + 1; + const line = ts.getLineAndCharacterOfPosition(ts.getSourceFileOfNode(node), loc.start).line + 1; lowerer.liftedFns.push({ name, params: [], diff --git a/packages/compiler/src/frontend/lowering/builtins/async-context.ts b/packages/compiler/src/frontend/lowering/builtins/async-context.ts index e6de99b09..18bd93a86 100644 --- a/packages/compiler/src/frontend/lowering/builtins/async-context.ts +++ b/packages/compiler/src/frontend/lowering/builtins/async-context.ts @@ -35,7 +35,7 @@ export function lowerDiagnosticsSubscriber(lowerer: Lowerer, node: ts.Expression if (ts.isIdentifier(node) && node.text === "setImmediate") { const sym = lowerer.checker.getSymbolAtLocation(node); const decls = sym ? sym.declarations : []; - if (decls.length > 0 && decls.every((d) => lowerer.isStdlibFile(d.getSourceFile()))) { + if (decls.length > 0 && decls.every((d) => lowerer.isStdlibFile(ts.getSourceFileOfNode(d)))) { return { kind: "libCall", fn: "timers.setImmediateFnValue", diff --git a/packages/compiler/src/frontend/lowering/builtins/blob.ts b/packages/compiler/src/frontend/lowering/builtins/blob.ts index 3fc4ec173..94c8c7ffe 100644 --- a/packages/compiler/src/frontend/lowering/builtins/blob.ts +++ b/packages/compiler/src/frontend/lowering/builtins/blob.ts @@ -52,10 +52,10 @@ export function blobClassOf(lowerer: Lowerer, node: ts.Expression): BlobClass | function blobReceiverClass(lowerer: Lowerer, node: ts.Expression): BlobClass | null { const type = lowerer.checker.getBaseTypeOfLiteralType(lowerer.typeOf(node)); const sym = type.getAliasSymbol() ?? type.getSymbol(); - if (!sym || (sym.name !== "Blob" && sym.name !== "File") || !lowerer.isStdlibSymbol(sym)) { - return null; - } - return sym.name; + if (!sym) return null; + const name = ts.symbolName(sym); + if ((name !== "Blob" && name !== "File") || !lowerer.isStdlibSymbol(sym)) return null; + return name; } function memberNameOf(access: ts.PropertyAccessExpression | ts.ElementAccessExpression) { diff --git a/packages/compiler/src/frontend/lowering/builtins/builtin-class-dyn.ts b/packages/compiler/src/frontend/lowering/builtins/builtin-class-dyn.ts index 07113eee7..eaa08c9dd 100644 --- a/packages/compiler/src/frontend/lowering/builtins/builtin-class-dyn.ts +++ b/packages/compiler/src/frontend/lowering/builtins/builtin-class-dyn.ts @@ -140,7 +140,7 @@ export function lowerBuiltinClassCall(lowerer: Lowerer, call: ts.CallExpression) kind: "dynInvoke", recv: receiver, method, - calleeName: callee.getText(), + calleeName: ts.getTextOfNode(callee), args: call.arguments.nodes.map((arg) => lowerer.lowerExprExpecting(arg, DYN)), type: DYN, loc, diff --git a/packages/compiler/src/frontend/lowering/builtins/callable-aliases.ts b/packages/compiler/src/frontend/lowering/builtins/callable-aliases.ts index afb221d04..661fec528 100644 --- a/packages/compiler/src/frontend/lowering/builtins/callable-aliases.ts +++ b/packages/compiler/src/frontend/lowering/builtins/callable-aliases.ts @@ -50,7 +50,7 @@ export function registerBuiltinCallableAlias( // These checked native functions retain a runtime slot in JavaScript so // aliases can escape without narrowing their arguments to string. if ( - isJsSourceFile(nameNode.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(nameNode)) && target.module === "util" && ["stripVTControlCharacters", "toUSVString", "isDeepStrictEqual", "styleText"].includes( target.member, diff --git a/packages/compiler/src/frontend/lowering/builtins/child-process-methods.ts b/packages/compiler/src/frontend/lowering/builtins/child-process-methods.ts index e1a25d169..af7270ea9 100644 --- a/packages/compiler/src/frontend/lowering/builtins/child-process-methods.ts +++ b/packages/compiler/src/frontend/lowering/builtins/child-process-methods.ts @@ -122,12 +122,14 @@ export function lowerChildMethodCall( } const listenerNode = stripTypeCasts(call.arguments.nodes[1]!); if ( - isJsSourceFile(listenerNode.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(listenerNode)) && (ts.isArrowFunction(listenerNode) || ts.isFunctionExpression(listenerNode)) && listenerNode.parameters.nodes.some((param) => param.dotDotDotToken) && listenerNode.parameters.nodes.every((param) => !param.type) && !/@(?:param|type)\b/.test( - listenerNode.getSourceFile().text.slice(listenerNode.pos, listenerNode.getStart()), + ts + .getSourceFileOfNode(listenerNode) + .text.slice(listenerNode.pos, ts.tokenStartOfNode(listenerNode)), ) ) { for (const param of listenerNode.parameters.nodes) lowerer.checkedCallbackParams.add(param); @@ -142,10 +144,10 @@ export function lowerChildMethodCall( : lowerer.lowerExpr(call.arguments.nodes[1]!); if ( narrowed.length > 0 || - (isJsSourceFile(listenerNode.getSourceFile()) && cb.type.kind === "dyn") || + (isJsSourceFile(ts.getSourceFileOfNode(listenerNode)) && cb.type.kind === "dyn") || (cb.type.kind === "func" && (cb.type.rest || - (isJsSourceFile(listenerNode.getSourceFile()) && + (isJsSourceFile(ts.getSourceFileOfNode(listenerNode)) && cb.type.params.some((param) => param.kind === "dyn")))) ) { if ( @@ -756,7 +758,7 @@ function childStreamEncodedAt( * admit null. */ function narrowedExitListenerParams(lowerer: Lowerer, listener: ts.Expression): ts.Symbol[] { if (!ts.isArrowFunction(listener) && !ts.isFunctionExpression(listener)) return []; - if (isJsSourceFile(listener.getSourceFile())) return []; + if (isJsSourceFile(ts.getSourceFileOfNode(listener))) return []; const symbols: ts.Symbol[] = []; const wanted: readonly string[] = ["f64", "string"]; listener.parameters.nodes.slice(0, 2).forEach((param, i) => { diff --git a/packages/compiler/src/frontend/lowering/builtins/class-values.ts b/packages/compiler/src/frontend/lowering/builtins/class-values.ts index 416217716..4d073c264 100644 --- a/packages/compiler/src/frontend/lowering/builtins/class-values.ts +++ b/packages/compiler/src/frontend/lowering/builtins/class-values.ts @@ -97,7 +97,7 @@ export function builtinClassAliasOwnsNoSlot(lowerer: Lowerer, symbol: ts.Symbol) if (decl === undefined || !ts.isVariableDeclaration(decl)) return false; if (builtinMemberRequireDecl(decl.name, decl.initializer)) return false; if (streamClassAliasDecl(lowerer, decl.name, decl.initializer)) return false; - if (!isJsSourceFile(decl.getSourceFile())) return true; + if (!isJsSourceFile(ts.getSourceFileOfNode(decl))) return true; let init = decl.initializer; while (init !== undefined && ts.isParenthesizedExpression(init)) init = init.expression; if (init === undefined) return true; @@ -157,9 +157,10 @@ export function runtimeClassValueOfType(lowerer: Lowerer, type: ts.Type): string const symbol = type.getSymbol(); if (!symbol || !lowerer.isStdlibSymbol(symbol)) return null; let info = lowerer.builtinEmitterInfoOf(symbol) ?? lowerer.builtinStreamInfoOf(symbol); - if (!info && symbol.name.endsWith("Constructor")) { + if (!info && ts.symbolName(symbol).endsWith("Constructor")) { for (const [irName, rec] of RUNTIME_ERROR_CLASSES) { - if (`${rec.lib}Constructor` === symbol.name) info = lowerer.classes.get(irName) ?? null; + if (`${rec.lib}Constructor` === ts.symbolName(symbol)) + info = lowerer.classes.get(irName) ?? null; } } return info?.valueCtorParams !== undefined ? info.def.name : null; diff --git a/packages/compiler/src/frontend/lowering/builtins/compression.ts b/packages/compiler/src/frontend/lowering/builtins/compression.ts index e69011522..717e09933 100644 --- a/packages/compiler/src/frontend/lowering/builtins/compression.ts +++ b/packages/compiler/src/frontend/lowering/builtins/compression.ts @@ -136,7 +136,7 @@ export function zlibStreamModeOf( if (alias !== null) return alias.kind === "zlib" ? alias.mode : null; const target = symbol.flags & ts.SymbolFlags.Alias ? lowerer.checker.getAliasedSymbol(symbol) : symbol; - const mode = ZLIB_STREAM_MODES.get(target.name); + const mode = ZLIB_STREAM_MODES.get(ts.symbolName(target)); if (mode === undefined) return null; return target.declarations.some((d) => ts.isClassDeclaration(d) && isZlibFallbackDecl(lowerer, d)) ? mode @@ -144,7 +144,7 @@ export function zlibStreamModeOf( } function isZlibFallbackDecl(lowerer: Lowerer, d: ts.Node): boolean { - const file = d.getSourceFile(); + const file = ts.getSourceFileOfNode(d); if (!lowerer.isStdlibFile(file) || isNodeTypesPath(file.fileName)) return false; for (let node: ts.Node | undefined = d.parent; node; node = node.parent) { if (ts.isModuleDeclaration(node) && ts.isStringLiteral(node.name)) { diff --git a/packages/compiler/src/frontend/lowering/builtins/constants.ts b/packages/compiler/src/frontend/lowering/builtins/constants.ts index 611805d94..35f0fc095 100644 --- a/packages/compiler/src/frontend/lowering/builtins/constants.ts +++ b/packages/compiler/src/frontend/lowering/builtins/constants.ts @@ -170,7 +170,7 @@ export function fsConstantMemberRead(expr: ts.PropertyAccessExpression): IrExpr type: DYN, loc, }; - return isJsSourceFile(expr.getSourceFile()) + return isJsSourceFile(ts.getSourceFileOfNode(expr)) ? read : { kind: "dynCheck", value: read, type: F64, loc }; } diff --git a/packages/compiler/src/frontend/lowering/builtins/crypto.ts b/packages/compiler/src/frontend/lowering/builtins/crypto.ts index cd490bc87..e6834abd4 100644 --- a/packages/compiler/src/frontend/lowering/builtins/crypto.ts +++ b/packages/compiler/src/frontend/lowering/builtins/crypto.ts @@ -240,7 +240,9 @@ function cryptoKdfInput(lowerer: Lowerer, node: ts.Expression): IrExpr { const symbol = lowerer.typeOf(node).getSymbol(); if ( type?.kind === "bytes" || - (symbol?.name === "ArrayBuffer" && lowerer.isStdlibSymbol(symbol)) + (symbol !== undefined && + ts.symbolName(symbol) === "ArrayBuffer" && + lowerer.isStdlibSymbol(symbol)) ) { const value = lowerer.coerceInto(node, lowerer.lowerExpr(node), DYN); return { kind: "libCall", fn: "bytes.bufferSource", args: [value], type: BYTES_U8, loc }; @@ -878,8 +880,8 @@ export function isCipherType(lowerer: Lowerer, type: ts.Type): boolean { const symbol = type.getAliasSymbol() ?? type.getSymbol(); return ( !!symbol && - CIPHER_TYPE_NAMES.has(symbol.name) && - symbol.declarations.some((d) => lowerer.isStdlibFile(d.getSourceFile())) + CIPHER_TYPE_NAMES.has(ts.symbolName(symbol)) && + symbol.declarations.some((d) => lowerer.isStdlibFile(ts.getSourceFileOfNode(d))) ); } @@ -898,7 +900,7 @@ function v8CalleeText(node: ts.Expression): string { if (ts.isPropertyAccessExpression(node)) return `${v8CalleeText(node.expression)}.${node.name.text}`; if (ts.isCallExpression(node)) return `${v8CalleeText(node.expression)}(...)`; - return node.getText(); + return ts.getTextOfNode(node); } /** Calls on native Cipheriv/Decipheriv handles dispatch through the @@ -959,7 +961,7 @@ export function lowerCryptoHashMethodCall( return { kind: "seqExpr", stmts: evaluated, - result: nodeThrowExpr(1, "", `${access.getText()} is not a function`, DYN, loc), + result: nodeThrowExpr(1, "", `${ts.getTextOfNode(access)} is not a function`, DYN, loc), type: DYN, loc, }; diff --git a/packages/compiler/src/frontend/lowering/builtins/disposable-stack.ts b/packages/compiler/src/frontend/lowering/builtins/disposable-stack.ts index fb1c92257..1b9a9c03a 100644 --- a/packages/compiler/src/frontend/lowering/builtins/disposable-stack.ts +++ b/packages/compiler/src/frontend/lowering/builtins/disposable-stack.ts @@ -9,7 +9,12 @@ import { varRef } from "../../../ir/build.js"; function isStdlibDisposableStack(lowerer: Lowerer, node: ts.Expression): boolean { const type = lowerer.checker.getBaseTypeOfLiteralType(lowerer.typeOf(node)); for (const symbol of [type.getSymbol(), type.getAliasSymbol()]) { - if (symbol?.name === "DisposableStack" && lowerer.isStdlibSymbol(symbol)) return true; + if ( + symbol !== undefined && + ts.symbolName(symbol) === "DisposableStack" && + lowerer.isStdlibSymbol(symbol) + ) + return true; } return false; } diff --git a/packages/compiler/src/frontend/lowering/builtins/errors.ts b/packages/compiler/src/frontend/lowering/builtins/errors.ts index 439c9fca3..d95442829 100644 --- a/packages/compiler/src/frontend/lowering/builtins/errors.ts +++ b/packages/compiler/src/frontend/lowering/builtins/errors.ts @@ -27,9 +27,10 @@ const ASSERTION_ERROR_PROPERTIES: ReadonlySet = new Set([ export function isExecExceptionType(lowerer: Lowerer, t: ts.Type): boolean { const sym = t.getSymbol(); return ( - sym?.name === "ExecException" && + sym !== undefined && + ts.symbolName(sym) === "ExecException" && sym.declarations.some( - (d) => ts.isInterfaceDeclaration(d) && lowerer.isStdlibFile(d.getSourceFile()), + (d) => ts.isInterfaceDeclaration(d) && lowerer.isStdlibFile(ts.getSourceFileOfNode(d)), ) ); } diff --git a/packages/compiler/src/frontend/lowering/builtins/filesystem.ts b/packages/compiler/src/frontend/lowering/builtins/filesystem.ts index 3fbea04aa..49875e004 100644 --- a/packages/compiler/src/frontend/lowering/builtins/filesystem.ts +++ b/packages/compiler/src/frontend/lowering/builtins/filesystem.ts @@ -250,7 +250,7 @@ export function lowerFsLadderCall( ); } if (bi.module !== "fs" && bi.module !== "fs/promises") return null; - if (!isJsSourceFile(expr.getSourceFile())) return null; + if (!isJsSourceFile(ts.getSourceFileOfNode(expr))) return null; const args = expr.arguments.nodes; if (args.some(ts.isSpreadElement)) return null; // Every ladder argument crosses as a dyn value; an argument that @@ -1564,7 +1564,7 @@ export function lowerDirentMethodCall( if (call.questionDotToken || access.questionDotToken) return null; if (!lowerer.isStdlibMember(access)) return null; const recvSym = lowerer.typeOf(access.expression).getSymbol(); - if (recvSym?.name !== "Dirent") return null; + if (recvSym === undefined || ts.symbolName(recvSym) !== "Dirent") return null; const receiver = lowerer.lowerExpr(access.expression); if (receiver.type.kind !== "record") return null; const shape = lowerer.shapes.get(receiver.type.shapeId); diff --git a/packages/compiler/src/frontend/lowering/builtins/fs-streams.ts b/packages/compiler/src/frontend/lowering/builtins/fs-streams.ts index 22e524690..498f9b6d4 100644 --- a/packages/compiler/src/frontend/lowering/builtins/fs-streams.ts +++ b/packages/compiler/src/frontend/lowering/builtins/fs-streams.ts @@ -19,7 +19,7 @@ import { } from "../../../ir/ir.js"; function isFsFallbackDecl(lowerer: Lowerer, d: ts.Node): boolean { - const file = d.getSourceFile(); + const file = ts.getSourceFileOfNode(d); if (!lowerer.isStdlibFile(file) || isNodeTypesPath(file.fileName)) return false; for (let node: ts.Node | undefined = d.parent; node; node = node.parent) { if (ts.isModuleDeclaration(node) && ts.isStringLiteral(node.name)) { @@ -153,11 +153,13 @@ export function fsStreamClassOf( lowerer: Lowerer, symbol: ts.Symbol | null | undefined, ): "ReadStream" | "WriteStream" | null { - if (!symbol || (symbol.name !== "ReadStream" && symbol.name !== "WriteStream")) return null; + if (!symbol) return null; + const name = ts.symbolName(symbol); + if (name !== "ReadStream" && name !== "WriteStream") return null; const declared = symbol.declarations.some( (d) => ts.isClassDeclaration(d) && isFsFallbackDecl(lowerer, d), ); - return declared ? symbol.name : null; + return declared ? name : null; } /** `new fs.ReadStream(path, options)` / `new fs.WriteStream(...)`: the diff --git a/packages/compiler/src/frontend/lowering/builtins/json.ts b/packages/compiler/src/frontend/lowering/builtins/json.ts index b48030ba5..54bcae4b5 100644 --- a/packages/compiler/src/frontend/lowering/builtins/json.ts +++ b/packages/compiler/src/frontend/lowering/builtins/json.ts @@ -504,7 +504,7 @@ function jsonNullishArgument(lowerer: Lowerer, node: ts.Expression): boolean { const symbol = lowerer.checker.getSymbolAtLocation(node); return ( symbol !== undefined && - symbol.declarations.every((decl) => lowerer.isStdlibFile(decl.getSourceFile())) + symbol.declarations.every((decl) => lowerer.isStdlibFile(ts.getSourceFileOfNode(decl))) ); } diff --git a/packages/compiler/src/frontend/lowering/builtins/key-object.ts b/packages/compiler/src/frontend/lowering/builtins/key-object.ts index ff6c0b38b..7e680fa20 100644 --- a/packages/compiler/src/frontend/lowering/builtins/key-object.ts +++ b/packages/compiler/src/frontend/lowering/builtins/key-object.ts @@ -23,7 +23,7 @@ export function isKeyObjectClass(lowerer: Lowerer, node: ts.Expression): boolean export function isKeyObjectValue(lowerer: Lowerer, node: ts.Expression): boolean { const type = lowerer.typeOf(node); const sym = type.getAliasSymbol() ?? type.getSymbol(); - return !!sym && sym.name === "KeyObject" && lowerer.isStdlibSymbol(sym); + return !!sym && ts.symbolName(sym) === "KeyObject" && lowerer.isStdlibSymbol(sym); } /** `v instanceof KeyObject`. */ @@ -51,7 +51,7 @@ export function lowerKeyObjectCall(lowerer: Lowerer, call: ts.CallExpression): I kind: "dynInvoke", recv: lowerer.lowerExprExpecting(access.expression, DYN), method: access.name.text, - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args: call.arguments.nodes.map((arg) => lowerer.lowerExprExpecting(arg, DYN)), type: DYN, loc: locOf(call), diff --git a/packages/compiler/src/frontend/lowering/builtins/module-bindings.ts b/packages/compiler/src/frontend/lowering/builtins/module-bindings.ts index 8a5365d18..5465aeafa 100644 --- a/packages/compiler/src/frontend/lowering/builtins/module-bindings.ts +++ b/packages/compiler/src/frontend/lowering/builtins/module-bindings.ts @@ -135,7 +135,7 @@ function builtinModuleBindingOf( if (symbol !== undefined && (symbol.flags & ts.SymbolFlags.Alias) !== 0) { const target = lowerer.checker.getAliasedSymbol(symbol); const tdecl = target.declarations[0]; - if (tdecl !== undefined && tdecl.getSourceFile().isDeclarationFile) { + if (tdecl !== undefined && ts.getSourceFileOfNode(tdecl).isDeclarationFile) { for ( let p: ts.Node | undefined = tdecl.parent; p !== undefined && !ts.isSourceFile(p); @@ -143,7 +143,7 @@ function builtinModuleBindingOf( ) { if (ts.isModuleDeclaration(p) && ts.isStringLiteral(p.name)) { const module = canonicalBuiltinModule(p.name.text); - if (module !== null) return { module, member: target.name }; + if (module !== null) return { module, member: ts.symbolName(target) }; break; } } @@ -297,14 +297,14 @@ export function createRequireCalleeFileOf( ): ts.SourceFile | null { const e = stripTypeCasts(callee); if (ts.isCallExpression(e)) { - return createRequireBaseCallOf(lowerer, e) !== null ? e.getSourceFile() : null; + return createRequireBaseCallOf(lowerer, e) !== null ? ts.getSourceFileOfNode(e) : null; } if (!ts.isIdentifier(e)) return null; const symbol = lowerer.checker.getSymbolAtLocation(e); const decl = symbol ? symbol.declarations[0] : undefined; if (!decl || !ts.isVariableDeclaration(decl) || decl.initializer === undefined) return null; return createRequireBindingDecl(lowerer, decl.name, decl.initializer) - ? decl.getSourceFile() + ? ts.getSourceFileOfNode(decl) : null; } diff --git a/packages/compiler/src/frontend/lowering/builtins/module-calls.ts b/packages/compiler/src/frontend/lowering/builtins/module-calls.ts index 895c1a9d6..c85f2c777 100644 --- a/packages/compiler/src/frontend/lowering/builtins/module-calls.ts +++ b/packages/compiler/src/frontend/lowering/builtins/module-calls.ts @@ -57,7 +57,7 @@ export function lowerBuiltinModuleCall( fn: BuiltinModuleFn, loc: SrcLoc, ): IrExpr { - const name = expr.expression.getText(); + const name = ts.getTextOfNode(expr.expression); if (bi.module === "util/types") { return lowerUtilTypeCall(lowerer, expr, bi, loc, name); } diff --git a/packages/compiler/src/frontend/lowering/builtins/module-resolution.ts b/packages/compiler/src/frontend/lowering/builtins/module-resolution.ts index afa9dc1ea..6b2e9e589 100644 --- a/packages/compiler/src/frontend/lowering/builtins/module-resolution.ts +++ b/packages/compiler/src/frontend/lowering/builtins/module-resolution.ts @@ -292,7 +292,7 @@ export function lowerImportMetaResolveCall( ); } const result = resolveImportMetaRuntime( - call.getSourceFile().fileName, + ts.getSourceFileOfNode(call).fileName, specifier, lowerer.targetPlatform, lowerer.frontendServices, @@ -314,8 +314,8 @@ function requireResolverBaseFile(lowerer: Lowerer, receiver: ts.Expression): ts. const created = createRequireCalleeFileOf(lowerer, receiver); if (created !== null) return created; return lowerer.isStdlibGlobal(receiver, "require") && - !isNodeEsmFile(receiver.getSourceFile(), lowerer.program) - ? receiver.getSourceFile() + !isNodeEsmFile(ts.getSourceFileOfNode(receiver), lowerer.program) + ? ts.getSourceFileOfNode(receiver) : null; } diff --git a/packages/compiler/src/frontend/lowering/builtins/navigator.ts b/packages/compiler/src/frontend/lowering/builtins/navigator.ts index 637edaf3c..f90448c1e 100644 --- a/packages/compiler/src/frontend/lowering/builtins/navigator.ts +++ b/packages/compiler/src/frontend/lowering/builtins/navigator.ts @@ -20,7 +20,8 @@ export function isNavigatorGlobal(lowerer: Lowerer, node: ts.Expression): boolea if (!ts.isIdentifier(expr) || expr.text !== "navigator") return false; if (lowerer.isStdlibGlobal(expr, "navigator")) return true; return ( - isJsSourceFile(expr.getSourceFile()) && lowerer.checker.getSymbolAtLocation(expr) === undefined + isJsSourceFile(ts.getSourceFileOfNode(expr)) && + lowerer.checker.getSymbolAtLocation(expr) === undefined ); } diff --git a/packages/compiler/src/frontend/lowering/builtins/net-address.ts b/packages/compiler/src/frontend/lowering/builtins/net-address.ts index d99d63fd3..9e795bca6 100644 --- a/packages/compiler/src/frontend/lowering/builtins/net-address.ts +++ b/packages/compiler/src/frontend/lowering/builtins/net-address.ts @@ -31,8 +31,10 @@ export function netAddressClassOf(lowerer: Lowerer, node: ts.Expression): NetAdd function netAddressValueClass(lowerer: Lowerer, node: ts.Expression): NetAddressClass | null { const type = lowerer.typeOf(node); const sym = type.getAliasSymbol() ?? type.getSymbol(); - if (!sym || (sym.name !== "SocketAddress" && sym.name !== "BlockList")) return null; - return lowerer.isStdlibSymbol(sym) ? sym.name : null; + if (!sym) return null; + const name = ts.symbolName(sym); + if (name !== "SocketAddress" && name !== "BlockList") return null; + return lowerer.isStdlibSymbol(sym) ? name : null; } /** `new SocketAddress(options?)` and `new BlockList()`: the argument @@ -127,7 +129,7 @@ export function lowerNetAddressCall(lowerer: Lowerer, call: ts.CallExpression): kind: "dynInvoke", recv: lowerer.lowerExprExpecting(access.expression, DYN), method: name, - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args: call.arguments.nodes.map((arg) => lowerer.lowerExprExpecting(arg, DYN)), type: DYN, loc, diff --git a/packages/compiler/src/frontend/lowering/builtins/performance.ts b/packages/compiler/src/frontend/lowering/builtins/performance.ts index 7aac3b80c..72304e6a9 100644 --- a/packages/compiler/src/frontend/lowering/builtins/performance.ts +++ b/packages/compiler/src/frontend/lowering/builtins/performance.ts @@ -285,7 +285,7 @@ function lowerPerfArguments(lowerer: Lowerer, call: ts.CallExpression, loc: SrcL }); continue; } - const spelling = arg.expression.getText(); + const spelling = ts.getTextOfNode(arg.expression); const items = checkedIterableSpread(lowerer, lowerer.lowerExpr(arg.expression), spelling, loc); stmts.push({ kind: "exprStmt", @@ -527,7 +527,11 @@ export function fencePerfEntryKeyWalk( * receiver's checker type. Its value is the runtime histogram handle. */ function isIntervalHistogram(lowerer: Lowerer, node: ts.Expression): boolean { const symbol = lowerer.typeOf(node).getSymbol(); - return symbol?.name === "IntervalHistogram" && lowerer.isStdlibSymbol(symbol); + return ( + symbol !== undefined && + ts.symbolName(symbol) === "IntervalHistogram" && + lowerer.isStdlibSymbol(symbol) + ); } /** IntervalHistogram's numeric members, as perf.eldStat selectors. */ diff --git a/packages/compiler/src/frontend/lowering/builtins/process.ts b/packages/compiler/src/frontend/lowering/builtins/process.ts index 9ac1fc059..78dc6ef14 100644 --- a/packages/compiler/src/frontend/lowering/builtins/process.ts +++ b/packages/compiler/src/frontend/lowering/builtins/process.ts @@ -192,14 +192,14 @@ export function lowerProcStreamMethodCall( if (!lowerer.isStdlibMember(access)) return null; const name = access.name.text; const loc = locOf(call); - if (isJsSourceFile(call.getSourceFile())) { + if (isJsSourceFile(ts.getSourceFileOfNode(call))) { const receiver = lowerer.lowerExpr(access.expression); if (receiver.type.kind === "dyn") { return { kind: "dynInvoke", recv: receiver, method: name, - calleeName: call.expression.getText(), + calleeName: ts.getTextOfNode(call.expression), args: call.arguments.nodes.map((arg) => lowerer.lowerExprExpecting(arg, DYN)), type: DYN, loc, @@ -214,7 +214,7 @@ export function lowerProcStreamMethodCall( kind: "dynInvoke", recv: receiver, method: name, - calleeName: call.expression.getText(), + calleeName: ts.getTextOfNode(call.expression), args: [], type: DYN, loc, @@ -276,7 +276,7 @@ export function lowerProcessStreamProperty( ? lowerer.stdlibGlobalMember(expr.expression, "process") : null; if ( - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && (direct === "stdin" || direct === "stdout" || direct === "stderr" || @@ -584,7 +584,7 @@ export function lowerProcessProperty( } // A stored process.nextTick in JavaScript (`cond ? setImmediate : // process.nextTick`): the interned builtin function value. - if (member === "nextTick" && isJsSourceFile(expr.getSourceFile())) { + if (member === "nextTick" && isJsSourceFile(ts.getSourceFileOfNode(expr))) { return builtinFunctionValue("process.nextTick", loc); } if (member === "getBuiltinModule") { @@ -667,7 +667,7 @@ export function lowerProcessProperty( // OUTER expressions dispatch first. if ( member === "stdin" || - ((member === "stdout" || member === "stderr") && isJsSourceFile(expr.getSourceFile())) + ((member === "stdout" || member === "stderr") && isJsSourceFile(ts.getSourceFileOfNode(expr))) ) { return { kind: "libCall", @@ -697,7 +697,10 @@ export function lowerProcessMethodCall( access: ts.PropertyAccessExpression, ): IrExpr | null { if (call.questionDotToken) return null; - if (isJsSourceFile(call.getSourceFile()) && ts.isPropertyAccessExpression(access.expression)) { + if ( + isJsSourceFile(ts.getSourceFileOfNode(call)) && + ts.isPropertyAccessExpression(access.expression) + ) { const stream = lowerer.stdlibGlobalMember(access.expression, "process"); if (stream === "stdin" || stream === "stdout" || stream === "stderr") { if (call.arguments.nodes.some(ts.isSpreadElement)) @@ -706,7 +709,7 @@ export function lowerProcessMethodCall( kind: "dynInvoke", recv: lowerer.lowerExpr(access.expression), method: access.name.text, - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args: call.arguments.nodes.map((arg) => lowerer.lowerExprExpecting(arg, DYN)), type: DYN, loc: locOf(call), @@ -883,7 +886,7 @@ export function lowerProcessMethodCall( loc, }, receiver, - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args: [ lowerer.lowerExprExpecting(call.arguments.nodes[0]!, DYN), lowerer.lowerExprExpecting(call.arguments.nodes[1]!, DYN), @@ -1080,7 +1083,7 @@ export function lowerProcessMethodCall( // `writeOut: (str) => process.stdout.write(str)` — str untyped): // a runtime string writes; anything else throws the dynCheck's // honest TypeError at the call. - if (data.type.kind === "dyn" && isJsSourceFile(call.getSourceFile())) { + if (data.type.kind === "dyn" && isJsSourceFile(ts.getSourceFileOfNode(call))) { data = { kind: "dynCheck", value: data, type: STRING, loc }; } const isBytes = data.type.kind === "bytes" && data.type.elem === "u8"; @@ -1095,7 +1098,7 @@ export function lowerProcessMethodCall( let callback: IrExpr | undefined; if (callbackNode) { callback = lowerer.lowerExpr(callbackNode); - if (callback.type.kind === "dyn" && isJsSourceFile(call.getSourceFile())) { + if (callback.type.kind === "dyn" && isJsSourceFile(ts.getSourceFileOfNode(call))) { callback = { kind: "dynCheck", value: callback, diff --git a/packages/compiler/src/frontend/lowering/builtins/promises.ts b/packages/compiler/src/frontend/lowering/builtins/promises.ts index 968d3529e..668ef01f8 100644 --- a/packages/compiler/src/frontend/lowering/builtins/promises.ts +++ b/packages/compiler/src/frontend/lowering/builtins/promises.ts @@ -225,7 +225,7 @@ export function lowerPromiseStaticCall( const neverEntry = (el: ts.Expression): boolean => { const t = lowerer.typeOf(el); const arg = - t.getSymbol()?.name === "Promise" + ts.symbolNameOf(t.getSymbol()) === "Promise" ? lowerer.checker.getTypeArguments(t as ts.TypeReference)[0] : undefined; return arg !== undefined && (arg.flags & ts.TypeFlags.Never) !== 0; @@ -349,12 +349,12 @@ export function lowerPromiseStaticCall( return { kind: "intrinsic", name: "promise.all", args: [entriesArr], type: resultT, loc }; } - if (!isJsSourceFile(call.getSourceFile())) { + if (!isJsSourceFile(ts.getSourceFileOfNode(call))) { const tuple = lowerTuplePromiseAll(lowerer, call, elems); if (tuple) return tuple; } if ( - isJsSourceFile(call.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(call)) && elems.every((entry) => lowerer.dynConvertible(entry.type)) ) { return checkedPromiseAll( @@ -375,7 +375,7 @@ export function lowerPromiseStaticCall( ); } const entries = lowerer.lowerExpr(argNode); - if (isJsSourceFile(call.getSourceFile()) && entries.type.kind === "dyn") + if (isJsSourceFile(ts.getSourceFileOfNode(call)) && entries.type.kind === "dyn") return checkedPromiseAll(lowerer, entries, loc); // loadPlugin(p)))` — the loadPlugins shape, where the checker @@ -434,7 +434,7 @@ export function lowerPromiseStaticCall( `one iterable of promises is the supported form: Promise.${member}(ps)`, ); } - if (isJsSourceFile(call.getSourceFile())) { + if (isJsSourceFile(ts.getSourceFileOfNode(call))) { // JavaScript entries are checked-dynamic: any iterable, thenables // and plain values included, exactly like Promise.all. if ( @@ -706,7 +706,7 @@ export function lowerPromiseStaticCall( // Promise.resolve answers THAT promise — the same object, adopted — // and wraps every other value. if ( - isJsSourceFile(call.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(call)) && value.type.kind === "dyn" && resultT.inner.kind === "dyn" ) { diff --git a/packages/compiler/src/frontend/lowering/builtins/readline.ts b/packages/compiler/src/frontend/lowering/builtins/readline.ts index bcf77a39b..ede65d788 100644 --- a/packages/compiler/src/frontend/lowering/builtins/readline.ts +++ b/packages/compiler/src/frontend/lowering/builtins/readline.ts @@ -46,10 +46,10 @@ function isReadlineModule(spec: string | null): boolean { export function isReadlineTyped(lowerer: Lowerer, node: ts.Expression): boolean { const t = lowerer.checker.getTypeAtLocation(node); const sym = t.getAliasSymbol() ?? t.getSymbol(); - if (sym?.name !== "Interface") return false; + if (sym === undefined || ts.symbolName(sym) !== "Interface") return false; return sym.declarations.some((d) => { if (!ts.isClassDeclaration(d) && !ts.isInterfaceDeclaration(d)) return false; - if (!lowerer.isStdlibFile(d.getSourceFile())) return false; + if (!lowerer.isStdlibFile(ts.getSourceFileOfNode(d))) return false; return isReadlineModule(ambientModuleOf(d)); }); } @@ -74,7 +74,7 @@ const RL_EVENT_LIST = * readline/promises question. */ function returnsPromise(lowerer: Lowerer, call: ts.CallExpression): boolean { const t = lowerer.checker.getTypeAtLocation(call); - return (t.getSymbol()?.name ?? "") === "Promise"; + return (ts.symbolNameOf(t.getSymbol()) ?? "") === "Promise"; } /** The `{ signal }` options argument of the promise question: the signal @@ -107,10 +107,10 @@ function questionSignal(lowerer: Lowerer, node: ts.Expression | undefined): ts.E function isReadlineUiTyped(lowerer: Lowerer, node: ts.Expression): boolean { const t = lowerer.checker.getTypeAtLocation(node); const sym = t.getAliasSymbol() ?? t.getSymbol(); - if (sym?.name !== "Readline") return false; + if (sym === undefined || ts.symbolName(sym) !== "Readline") return false; return sym.declarations.some((d) => { if (!ts.isClassDeclaration(d) && !ts.isInterfaceDeclaration(d)) return false; - if (!lowerer.isStdlibFile(d.getSourceFile())) return false; + if (!lowerer.isStdlibFile(ts.getSourceFileOfNode(d))) return false; const spec = ambientModuleOf(d); return spec === "readline/promises" || spec === "node:readline/promises"; }); diff --git a/packages/compiler/src/frontend/lowering/builtins/receiver-types.ts b/packages/compiler/src/frontend/lowering/builtins/receiver-types.ts index fc507e2bd..230441d31 100644 --- a/packages/compiler/src/frontend/lowering/builtins/receiver-types.ts +++ b/packages/compiler/src/frontend/lowering/builtins/receiver-types.ts @@ -10,10 +10,10 @@ export function isBuiltinClassInstance( ): boolean { const type = lowerer.checker.getTypeAtLocation(node); const symbol = type.getAliasSymbol() ?? type.getSymbol(); - if (symbol?.name !== name) return false; + if (symbol === undefined || ts.symbolName(symbol) !== name) return false; return symbol.declarations.some( (declaration) => (ts.isClassDeclaration(declaration) || ts.isInterfaceDeclaration(declaration)) && - lowerer.isStdlibFile(declaration.getSourceFile()), + lowerer.isStdlibFile(ts.getSourceFileOfNode(declaration)), ); } diff --git a/packages/compiler/src/frontend/lowering/builtins/runtime-constructor.ts b/packages/compiler/src/frontend/lowering/builtins/runtime-constructor.ts index 4b61f98c1..7db3f8dcc 100644 --- a/packages/compiler/src/frontend/lowering/builtins/runtime-constructor.ts +++ b/packages/compiler/src/frontend/lowering/builtins/runtime-constructor.ts @@ -17,7 +17,7 @@ export function runtimeConstructorReceiver( let e = expr; while (ts.isParenthesizedExpression(e)) e = e.expression; if (!ts.isPropertyAccessExpression(e) || e.questionDotToken) return null; - if (e.name.text !== "constructor" || isJsSourceFile(e.getSourceFile())) return null; + if (e.name.text !== "constructor" || isJsSourceFile(ts.getSourceFileOfNode(e))) return null; const mapped = lowerer.mapTypeOf(lowerer.typeOf(e.expression)); if (mapped?.kind !== "object") return null; const info = lowerer.classes.get(mapped.className); diff --git a/packages/compiler/src/frontend/lowering/builtins/text-codecs.ts b/packages/compiler/src/frontend/lowering/builtins/text-codecs.ts index e89b1f07e..7229d5b84 100644 --- a/packages/compiler/src/frontend/lowering/builtins/text-codecs.ts +++ b/packages/compiler/src/frontend/lowering/builtins/text-codecs.ts @@ -360,13 +360,15 @@ export function storedTextCodecClassOf( expr: ts.Expression, ): TextCodecCtor["cls"] | null { const sym = lowerer.typeOf(expr).getSymbol(); - if (!sym || (sym.name !== "TextEncoder" && sym.name !== "TextDecoder")) return null; + if (!sym) return null; + const name = ts.symbolName(sym); + if (name !== "TextEncoder" && name !== "TextDecoder") return null; return sym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - lowerer.isStdlibFile(d.getSourceFile()), + lowerer.isStdlibFile(ts.getSourceFileOfNode(d)), ) - ? sym.name + ? name : null; } @@ -517,8 +519,9 @@ function directTextCodecCtorOf(lowerer: Lowerer, expr: ts.Expression): TextCodec if (callee.text !== "TextDecoder" && callee.text !== "TextEncoder") return null; const sym = lowerer.resolveValueSymbol(callee); if (!sym || !lowerer.isStdlibSymbol(sym)) return null; - if (sym.name !== "TextDecoder" && sym.name !== "TextEncoder") return null; - return { cls: sym.name, ctor }; + const name = ts.symbolName(sym); + if (name !== "TextDecoder" && name !== "TextEncoder") return null; + return { cls: name, ctor }; } /** Inline codec calls avoid allocating a receiver; stored receivers use diff --git a/packages/compiler/src/frontend/lowering/builtins/timers.ts b/packages/compiler/src/frontend/lowering/builtins/timers.ts index 9f950cddc..7230d18cd 100644 --- a/packages/compiler/src/frontend/lowering/builtins/timers.ts +++ b/packages/compiler/src/frontend/lowering/builtins/timers.ts @@ -154,9 +154,9 @@ function isTimerHandleTyped( ): boolean { const t = lowerer.checker.getTypeAtLocation(node); const sym = t.getAliasSymbol() ?? t.getSymbol(); - if (sym?.name !== name) return false; + if (sym === undefined || ts.symbolName(sym) !== name) return false; return sym.declarations.some( - (d) => ts.isInterfaceDeclaration(d) && lowerer.isStdlibFile(d.getSourceFile()), + (d) => ts.isInterfaceDeclaration(d) && lowerer.isStdlibFile(ts.getSourceFileOfNode(d)), ); } function isTimeoutTyped(lowerer: Lowerer, node: ts.Expression): boolean { diff --git a/packages/compiler/src/frontend/lowering/builtins/url.ts b/packages/compiler/src/frontend/lowering/builtins/url.ts index 3de973335..dca8a7cc0 100644 --- a/packages/compiler/src/frontend/lowering/builtins/url.ts +++ b/packages/compiler/src/frontend/lowering/builtins/url.ts @@ -842,7 +842,7 @@ export function lowerLegacyUrlCall( if (bi.member === "domainToASCII" || bi.member === "domainToUnicode") { args.push({ kind: "boolLit", value: expr.arguments.nodes.length === 0, type: BOOL, loc }); } else { - const warn = !insideNodeModules(expr.getSourceFile().fileName); + const warn = !insideNodeModules(ts.getSourceFileOfNode(expr).fileName); args.push({ kind: "boolLit", value: warn, type: BOOL, loc }); } return { kind: "libCall", fn: fn.fn, args, type: fn.result, loc }; diff --git a/packages/compiler/src/frontend/lowering/builtins/webcrypto.ts b/packages/compiler/src/frontend/lowering/builtins/webcrypto.ts index 2a9ff9eef..93cba84c5 100644 --- a/packages/compiler/src/frontend/lowering/builtins/webcrypto.ts +++ b/packages/compiler/src/frontend/lowering/builtins/webcrypto.ts @@ -103,7 +103,7 @@ export function fenceWebCryptoMember( use: "read" | "call", ): IrExpr | null { if (use !== "read" || !ts.isPropertyAccessExpression(access)) return null; - if (isJsSourceFile(access.getSourceFile())) return null; + if (isJsSourceFile(ts.getSourceFileOfNode(access))) return null; if (isSubtleReceiver(lowerer, access)) { lowerer.noLowering("crypto.subtle as a value", access, SUBTLE_HINT); } @@ -123,8 +123,8 @@ export function isWebCryptoMemberType(lowerer: Lowerer, type: ts.Type): boolean const symbol = type.getAliasSymbol() ?? type.getSymbol(); return ( symbol !== undefined && - symbol.name !== "Crypto" && - WEBCRYPTO_DYN_TYPES.has(symbol.name) && + ts.symbolName(symbol) !== "Crypto" && + WEBCRYPTO_DYN_TYPES.has(ts.symbolName(symbol)) && lowerer.isStdlibSymbol(symbol) ); } diff --git a/packages/compiler/src/frontend/lowering/builtins/workers.ts b/packages/compiler/src/frontend/lowering/builtins/workers.ts index 335fdb969..1426041a2 100644 --- a/packages/compiler/src/frontend/lowering/builtins/workers.ts +++ b/packages/compiler/src/frontend/lowering/builtins/workers.ts @@ -174,10 +174,10 @@ function propertyNameText(name: ts.PropertyName): string | undefined { function isWorkerThreadsClass(lowerer: Lowerer, node: ts.Expression, name: string): boolean { const t = lowerer.checker.getTypeAtLocation(node); const sym = t.getSymbol(); - if (sym?.name !== name) return false; + if (sym === undefined || ts.symbolName(sym) !== name) return false; return sym.declarations.some((d) => { if (!ts.isClassDeclaration(d) && !ts.isInterfaceDeclaration(d)) return false; - if (!lowerer.isStdlibFile(d.getSourceFile())) return false; + if (!lowerer.isStdlibFile(ts.getSourceFileOfNode(d))) return false; let up: ts.Node | undefined = d.parent; while (up) { if (ts.isModuleDeclaration(up) && ts.isStringLiteral(up.name)) diff --git a/packages/compiler/src/frontend/lowering/call-arguments.ts b/packages/compiler/src/frontend/lowering/call-arguments.ts index db7691a1c..49f7c8ec0 100644 --- a/packages/compiler/src/frontend/lowering/call-arguments.ts +++ b/packages/compiler/src/frontend/lowering/call-arguments.ts @@ -143,7 +143,7 @@ export function completeArgs( loc: value.loc, }; } - value = checkedIterableSpread(lowerer, value, arg.expression.getText(), loc); + value = checkedIterableSpread(lowerer, value, ts.getTextOfNode(arg.expression), loc); if (spreads === 1 && index === argNodes.length - 1) { stmts.push({ kind: "exprStmt", @@ -153,7 +153,7 @@ export function completeArgs( args: [ ref(), value, - { kind: "strLit", value: arg.expression.getText(), type: STRING, loc }, + { kind: "strLit", value: ts.getTextOfNode(arg.expression), type: STRING, loc }, ], type: VOID, loc, @@ -605,7 +605,7 @@ export function lowerSpreadArgsCall( // TypeScript reaches this lane only for checked-dynamic variadic callees // (`(...args: unknown[]) => R`), whose ABI is the JS one. if ( - !isJsSourceFile(expr.getSourceFile()) && + !isJsSourceFile(ts.getSourceFileOfNode(expr)) && !(callee.type.kind === "func" && callee.type.rest === true && callee.type.restAbi === undefined) ) return null; @@ -613,7 +613,7 @@ export function lowerSpreadArgsCall( if (callee.type.kind !== "dyn" && callee.type.kind !== "func") return null; const calleeName = ts.isPropertyAccessExpression(expr.expression) || ts.isElementAccessExpression(expr.expression) - ? expr.expression.getText() + ? ts.getTextOfNode(expr.expression) : ts.isIdentifier(expr.expression) ? expr.expression.text : "value"; @@ -640,8 +640,8 @@ export function lowerSpreadArgsCall( if (p.spreadOf !== null) { // The spelling rides along for V8's nullish spread-call // TypeError ("v is not iterable (cannot read property ...)"). - spreads.push({ arg: args.length, what: p.spreadOf.getText() }); - args.push(checkedIterableSpread(lowerer, p.v, p.spreadOf.getText(), loc)); + spreads.push({ arg: args.length, what: ts.getTextOfNode(p.spreadOf) }); + args.push(checkedIterableSpread(lowerer, p.v, ts.getTextOfNode(p.spreadOf), loc)); } else { args.push(lowerer.coerceInto(p.node!, p.v, DYN)); } diff --git a/packages/compiler/src/frontend/lowering/call-signatures.ts b/packages/compiler/src/frontend/lowering/call-signatures.ts index 954122a16..47cdd834f 100644 --- a/packages/compiler/src/frontend/lowering/call-signatures.ts +++ b/packages/compiler/src/frontend/lowering/call-signatures.ts @@ -148,7 +148,9 @@ export function documentedParameter(param: ts.ParameterDeclaration): boolean { return ( param.type !== undefined || /@(?:param|type)\b/.test( - param.getSourceFile().text.slice(param.parent?.pos ?? param.pos, param.getStart()), + ts + .getSourceFileOfNode(param) + .text.slice(param.parent?.pos ?? param.pos, ts.tokenStartOfNode(param)), ) ); } @@ -158,7 +160,7 @@ export function checkedJsDocumentedParameter( lowerer: Lowerer, param: ts.ParameterDeclaration, ): boolean { - return lowerer.checkedJsFile(param.getSourceFile()) && documentedParameter(param); + return lowerer.checkedJsFile(ts.getSourceFileOfNode(param)) && documentedParameter(param); } /** One parameter's ParamShape — the shared signature-shaped collection @@ -180,10 +182,10 @@ export function checkedJsDocumentedParameter( export function paramShape(lowerer: Lowerer, param: ts.ParameterDeclaration): ParamShape { const stage = lowerer.stageSourceType(param.name); if (stage) return { type: stage, mode: "required" }; - if (isJsSourceFile(param.getSourceFile()) && ts.isSetAccessorDeclaration(param.parent)) + if (isJsSourceFile(ts.getSourceFileOfNode(param)) && ts.isSetAccessorDeclaration(param.parent)) return { type: DYN, mode: "required" }; if ( - isJsSourceFile(param.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(param)) && ts.isConstructorDeclaration(param.parent) && !param.dotDotDotToken && !checkedJsDocumentedParameter(lowerer, param) @@ -207,12 +209,14 @@ export function paramShape(lowerer: Lowerer, param: ts.ParameterDeclaration): Pa lowerer.isStdlibGlobal(construction.expression, "Promise"); if ( !nativeExecutor && - isJsSourceFile(param.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(param)) && !param.type && !param.dotDotDotToken && !ts.isSetAccessorDeclaration(param.parent) && !/@(?:param|type)\b/.test( - param.getSourceFile().text.slice(param.parent?.pos ?? param.pos, param.getStart()), + ts + .getSourceFileOfNode(param) + .text.slice(param.parent?.pos ?? param.pos, ts.tokenStartOfNode(param)), ) ) { return param.initializer @@ -221,10 +225,12 @@ export function paramShape(lowerer: Lowerer, param: ts.ParameterDeclaration): Pa } if ( param.initializer && - isJsSourceFile(param.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(param)) && !param.type && !/@(?:param|type)\b/.test( - param.getSourceFile().text.slice(param.parent?.pos ?? param.pos, param.getStart()), + ts + .getSourceFileOfNode(param) + .text.slice(param.parent?.pos ?? param.pos, ts.tokenStartOfNode(param)), ) ) { return { type: DYN, mode: "omittable", bodyType: DYN }; @@ -233,10 +239,12 @@ export function paramShape(lowerer: Lowerer, param: ts.ParameterDeclaration): Pa // contract. Consume the actual iterable, including surplus elements. if ( ts.isArrayBindingPattern(param.name) && - isJsSourceFile(param.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(param)) && !param.type && !/@(?:param|type)\b/.test( - param.getSourceFile().text.slice(param.parent?.pos ?? param.pos, param.getStart()), + ts + .getSourceFileOfNode(param) + .text.slice(param.parent?.pos ?? param.pos, ts.tokenStartOfNode(param)), ) ) { if (param.dotDotDotToken) return { type: DYN, mode: "dynRest" }; @@ -298,7 +306,7 @@ export function paramShape(lowerer: Lowerer, param: ts.ParameterDeclaration): Pa // ARRAY param the dyn call thunk fills with the call's surplus // arguments; the binding is that array (dynRest — funcType marks // `rest`, and the value only ever calls through the boxed thunk). - if (isJsSourceFile(param.getSourceFile())) { + if (isJsSourceFile(ts.getSourceFileOfNode(param))) { const restMapped = lowerer.mapTypeOf(lowerer.typeOf(param.name)); // Unmapped JS rest parameters use the native variadic checked-value ABI. @@ -325,7 +333,7 @@ export function paramShape(lowerer: Lowerer, param: ts.ParameterDeclaration): Pa // including typed arrays passed through checked callbacks. Extracting // a native array here would copy it and discard element writes. if ( - isJsSourceFile(param.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(param)) && !param.type && (raw.kind === "array" || (raw.kind === "union" && @@ -337,7 +345,7 @@ export function paramShape(lowerer: Lowerer, param: ts.ParameterDeclaration): Pa } let type = lowerer.runtimeOptionalBindingType(param.name, lowerer.irTypeOf(param.name)); const body = param.parent?.body; - if (isJsSourceFile(param.getSourceFile()) && body && type.kind !== "dyn") { + if (isJsSourceFile(ts.getSourceFileOfNode(param)) && body && type.kind !== "dyn") { const symbol = lowerer.checker.getSymbolAtLocation(param.name); let acceptsUndefined = false; ts.walkPreorder(body, (node) => { @@ -397,7 +405,7 @@ export function restTupleShapes( if ( param.dotDotDotToken === undefined || !ts.isIdentifier(param.name) || - isJsSourceFile(param.getSourceFile()) + isJsSourceFile(ts.getSourceFileOfNode(param)) ) { return null; } @@ -435,7 +443,7 @@ export function paramShapes( // rather than as the declaration's unbound type parameter. if ( blameOf !== undefined && - !isJsSourceFile(declParam.getSourceFile()) && + !isJsSourceFile(ts.getSourceFileOfNode(declParam)) && (tsType.flags & ts.TypeFlags.Any) === 0 && declParam.type !== undefined && mentionsTypeParameter(lowerer, lowerer.checker.getTypeFromTypeNode(declParam.type)) && @@ -450,7 +458,8 @@ export function paramShapes( lowerer.mapTypeOf(tsType) ?? // A TypeScript instantiation at `any` (a type argument inferred // from an untyped value) takes the checked-dynamic slot. - ((tsType.flags & ts.TypeFlags.Any) !== 0 && !isJsSourceFile(declParam.getSourceFile()) + ((tsType.flags & ts.TypeFlags.Any) !== 0 && + !isJsSourceFile(ts.getSourceFileOfNode(declParam)) ? DYN : lowerer.irTypeOf(declParam.name)), ), @@ -668,7 +677,7 @@ export function requireExactArityValue( const ownType = lowerer.typeOf(contextual); mappedFn = lowerer.mapTypeOf(ownType) ?? - (isJsSourceFile(contextual.getSourceFile()) + (isJsSourceFile(ts.getSourceFileOfNode(contextual)) ? dynFallbackType(lowerer, contextual, ownType) : null); } @@ -706,7 +715,7 @@ export function requireExactArityValue( target === undefined && contextual !== null && (ts.isArrowFunction(contextual) || ts.isFunctionExpression(contextual)) && - npmStaticPackageOfPath(contextual.getSourceFile().fileName) !== null + npmStaticPackageOfPath(ts.getSourceFileOfNode(contextual).fileName) !== null ) { return; } @@ -771,7 +780,7 @@ export function collectSignatureInner(lowerer: Lowerer, decl: ts.FunctionDeclara // routes generic declarations (direct calls, namespace/CJS member // calls, value references) resolves it through genericFnsBySymbol // unchanged. - if (implicitMonoFile(decl.getSourceFile()) && !isAsync && !isGenerator) { + if (implicitMonoFile(ts.getSourceFileOfNode(decl)) && !isAsync && !isGenerator) { const implicit = implicitAnyParamSymbolsOf(lowerer, decl); if (implicit) { const nameText = decl.name?.text ?? "%default"; @@ -780,7 +789,7 @@ export function collectSignatureInner(lowerer: Lowerer, decl: ts.FunctionDeclara lowerer.genericFnsBySymbol.set(symbol, { decl, baseName: nameText, - qualifiedName: lowerer.qualify(decl.getSourceFile(), nsPathPrefix(decl) + nameText), + qualifiedName: lowerer.qualify(ts.getSourceFileOfNode(decl), nsPathPrefix(decl) + nameText), typeParams: [], instances: new Map(), implicitParams: implicit, @@ -815,7 +824,7 @@ export function collectSignatureInner(lowerer: Lowerer, decl: ts.FunctionDeclara // collide. The anonymous default export takes the synthetic // "%default" spelling ('%' cannot appear in a user identifier). name: lowerer.qualify( - decl.getSourceFile(), + ts.getSourceFileOfNode(decl), nsPathPrefix(decl) + (decl.name?.text ?? "%default"), ), params, @@ -844,7 +853,7 @@ function appendArgumentsParameter( ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node) ) || - !isJsSourceFile(node.getSourceFile()) || + !isJsSourceFile(ts.getSourceFileOfNode(node)) || !bodyReadsArguments(node) ) return; @@ -854,7 +863,7 @@ function appendArgumentsParameter( (ts.isClassDeclaration(node.parent) || ts.isClassExpression(node.parent)); if ( !classMethod && - !isNodeEsmFile(node.getSourceFile(), lowerer.program) && + !isNodeEsmFile(ts.getSourceFileOfNode(node), lowerer.program) && !unmappedArguments(node) ) { lowerer.unsupported( @@ -925,7 +934,7 @@ export function lambdaSignature( // actual call tuple, including overloads and omitted arguments. Node's // contextual overload signature cannot define this closure's ABI. if ( - isJsSourceFile(node.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(node)) && !node.type && (ts.isFunctionExpression(node) || ts.isArrowFunction(node)) && node.parameters.nodes.every( @@ -1043,7 +1052,7 @@ export function lambdaSignature( : []; const ctxDecl = ctxSigs.length === 1 ? ctxSigs[0]!.declaration : undefined; const ctxRetRaw = - ctxDecl && !ctxDecl.getSourceFile().isDeclarationFile + ctxDecl && !ts.getSourceFileOfNode(ctxDecl).isDeclarationFile ? lowerer.mapTypeOf(lowerer.checker.getReturnTypeOfSignature(ctxSigs[0]!)) : null; // An async lambda always produces a promise, so a slot returning a @@ -1065,7 +1074,7 @@ export function lambdaSignature( // must match the slot's ABI exactly. const dynVariadicSlot = ctxRet?.kind === "dyn" && - !isJsSourceFile(node.getSourceFile()) && + !isJsSourceFile(ts.getSourceFileOfNode(node)) && shapes.some((shape) => shape.mode === "dynRest"); if ( dynVariadicSlot || diff --git a/packages/compiler/src/frontend/lowering/cjs-exports-object.ts b/packages/compiler/src/frontend/lowering/cjs-exports-object.ts index f06dde5a2..30030b18f 100644 --- a/packages/compiler/src/frontend/lowering/cjs-exports-object.ts +++ b/packages/compiler/src/frontend/lowering/cjs-exports-object.ts @@ -40,12 +40,12 @@ import { DYN, type IrExpr, type IrGlobal } from "../../ir/ir.js"; * source declaration) in a CommonJS JS file. */ export function isCjsExportsIdentifier(lowerer: Lowerer, node: ts.Expression): boolean { if (!ts.isIdentifier(node) || node.text !== "exports") return false; - if (!isCjsJsFile(node.getSourceFile(), lowerer.program)) return false; + if (!isCjsJsFile(ts.getSourceFileOfNode(node), lowerer.program)) return false; const symbol = lowerer.checker.getSymbolAtLocation(node); const decls = symbol ? symbol.declarations : []; return !decls.some( (d) => - !d.getSourceFile().isDeclarationFile && + !ts.getSourceFileOfNode(d).isDeclarationFile && (ts.isVariableDeclaration(d) || ts.isParameter(d) || ts.isBindingElement(d) || @@ -62,7 +62,7 @@ export function isCjsModuleExports(lowerer: Lowerer, node: ts.Expression): boole return ( isModuleExportsAccess(node) && isCjsModuleGlobal(lowerer, node.expression) && - isCjsJsFile(node.getSourceFile(), lowerer.program) + isCjsJsFile(ts.getSourceFileOfNode(node), lowerer.program) ); } @@ -75,7 +75,7 @@ export function cjsInFileExportGlobalOf( access: ts.PropertyAccessExpression, ): IrGlobal | null { if (access.questionDotToken || !ts.isIdentifier(access.name)) return null; - const sf = access.getSourceFile(); + const sf = ts.getSourceFileOfNode(access); const recv = isModuleExportsAccess(access.expression) || (ts.isIdentifier(access.expression) && @@ -186,7 +186,7 @@ function exportsAssignments( * it runs: `exports` unless the file rebinds it; `module.exports` unless the * file rebinds `exports` or replaces `module.exports`. */ function namesOriginalExports(lowerer: Lowerer, node: ts.Expression): boolean { - const assigned = exportsAssignments(lowerer, node.getSourceFile()); + const assigned = exportsAssignments(lowerer, ts.getSourceFileOfNode(node)); if (assigned.exports) return false; return ts.isIdentifier(node) || !assigned.moduleExports; } @@ -252,7 +252,7 @@ export function usesCjsExportsIdentity(lowerer: Lowerer, sf: ts.SourceFile): boo * refusal. Null when `node` is not such a use. */ export function lowerCjsExportsObjectValue(lowerer: Lowerer, node: ts.Expression): IrExpr | null { if (!isCjsExportsObjectValue(lowerer, node)) return null; - const sf = node.getSourceFile(); + const sf = ts.getSourceFileOfNode(node); // A module that replaces module.exports answers `module.exports` reads // through the replacement's own machinery (the class-expression whole // export, the replaced-exports reads). @@ -328,7 +328,7 @@ export function cjsExportMemberWriteGlobalOf( lowerer: Lowerer, target: ts.PropertyAccessExpression, ): IrGlobal | null { - const sf = target.getSourceFile(); + const sf = ts.getSourceFileOfNode(target); if (!isCjsJsFile(sf, lowerer.program)) return null; const assigned = exportsAssignments(lowerer, sf); if (assigned.exports || assigned.moduleExports) return null; @@ -458,7 +458,7 @@ export function cjsMemberAliasValueOf(lowerer: Lowerer, ref: ts.Identifier): ts. ) return null; const spec = importDecl.moduleSpecifier.text; - const from = importDecl.getSourceFile(); + const from = ts.getSourceFileOfNode(importDecl); const dep = resolveImport(lowerer.program, from, spec, "import") ?? npmStaticDepSf7(lowerer.program, from, spec, "import"); @@ -552,7 +552,7 @@ function rereadableMemberValue( const sym = lowerer.resolveValueSymbol(value); if (sym === null || sym.declarations.length !== 1) return false; const decl = sym.declarations[0]!; - if (decl.getSourceFile() !== stmt.getSourceFile()) return false; + if (ts.getSourceFileOfNode(decl) !== ts.getSourceFileOfNode(stmt)) return false; // A function declaration is initialized when the module starts; any // other binding must be initialized before the write stores it. if (!ts.isFunctionDeclaration(decl) && decl.end > stmt.pos) return false; diff --git a/packages/compiler/src/frontend/lowering/cjs-inline-require.ts b/packages/compiler/src/frontend/lowering/cjs-inline-require.ts index 174f9dc7c..2b28c6533 100644 --- a/packages/compiler/src/frontend/lowering/cjs-inline-require.ts +++ b/packages/compiler/src/frontend/lowering/cjs-inline-require.ts @@ -56,7 +56,7 @@ export function inlineProgramRequireOf( if (!ts.isCallExpression(e) || !ts.isIdentifier(e.expression)) return null; const spec = requireSpecOf(e); if (spec === null || canonicalBuiltinModule(spec) !== null) return null; - const sf = e.getSourceFile(); + const sf = ts.getSourceFileOfNode(e); if (!isCjsJsFile(sf, lowerer.program)) return null; if (!isCjsRequireGlobal(lowerer, e.expression)) return null; if (createRequireCalleeFileOf(lowerer, e.expression) !== null) return null; @@ -201,7 +201,7 @@ export function lowerUncompiledRequireCall( call: ts.CallExpression, ): IrExpr | null { if (!ts.isIdentifier(call.expression) || call.expression.text !== "require") return null; - const sf = call.getSourceFile(); + const sf = ts.getSourceFileOfNode(call); if (!isCjsJsFile(sf, lowerer.program) || !isCjsRequireGlobal(lowerer, call.expression)) return null; if (createRequireCalleeFileOf(lowerer, call.expression) !== null) return null; @@ -240,7 +240,7 @@ export function requireNestedPatternsOf(decl: ts.VariableDeclaration): ts.Bindin if (!ts.isObjectBindingPattern(decl.name) || decl.initializer === undefined) return []; const spec = requireSpecOf(decl.initializer); if (spec === null || canonicalBuiltinModule(spec) !== null) return []; - if (!isJsSourceFile(decl.getSourceFile())) return []; + if (!isJsSourceFile(ts.getSourceFileOfNode(decl))) return []; return decl.name.elements.nodes.filter( (e) => e.name !== undefined && diff --git a/packages/compiler/src/frontend/lowering/cjs-tsc-interop.ts b/packages/compiler/src/frontend/lowering/cjs-tsc-interop.ts index cd83c8709..aae221b30 100644 --- a/packages/compiler/src/frontend/lowering/cjs-tsc-interop.ts +++ b/packages/compiler/src/frontend/lowering/cjs-tsc-interop.ts @@ -175,7 +175,7 @@ export function tscInteropDefaultValueOf( export function isDeadTscHelperDecl(lowerer: Lowerer, stmt: ts.Statement): boolean { const name = tscHelperDeclName(stmt); if (name === null) return false; - const sf = stmt.getSourceFile(); + const sf = ts.getSourceFileOfNode(stmt); const helperSpans = new Map(); for (const s of sf.statements.nodes) { const n = tscHelperDeclName(s); @@ -192,11 +192,11 @@ export function isDeadTscHelperDecl(lowerer: Lowerer, stmt: ts.Statement): boole const parent = node.parent; if (parent !== undefined && ts.isVariableDeclaration(parent) && parent.name === node) return undefined; - const at = node.getStart(sf); - if (at >= decl.getStart(sf) && at < decl.getEnd()) return undefined; + const at = ts.tokenStartOfNode(node); + if (at >= ts.tokenStartOfNode(decl) && at < decl.end) return undefined; for (const [other, otherDecl] of helperSpans) { if (other !== helper && dead.has(other)) { - if (at >= otherDecl.getStart(sf) && at < otherDecl.getEnd()) return undefined; + if (at >= ts.tokenStartOfNode(otherDecl) && at < otherDecl.end) return undefined; } } const call = parent; diff --git a/packages/compiler/src/frontend/lowering/class-callbacks.ts b/packages/compiler/src/frontend/lowering/class-callbacks.ts index 859938845..6ca685684 100644 --- a/packages/compiler/src/frontend/lowering/class-callbacks.ts +++ b/packages/compiler/src/frontend/lowering/class-callbacks.ts @@ -39,8 +39,8 @@ export function isClassCallback(lowerer: Lowerer, info: ClassInfo, name: string) !found.sig.abstract && (lowerer.prototypeMethodAccesses.has(name) || (info.decl !== null && - isJsSourceFile(info.decl.getSourceFile()) && - !lowerer.checkedJsFile(info.decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(info.decl)) && + !lowerer.checkedJsFile(ts.getSourceFileOfNode(info.decl)) && found.sig.params.length === 0 && found.sig.ret.kind === "void")) ); @@ -171,7 +171,7 @@ export function classCallbackCall( const spreads: { arg: number; what: string }[] = []; const args = argumentNodes.map((argument, index) => { if (!ts.isSpreadElement(argument)) return lowerer.lowerExprExpecting(argument, DYN); - const spelling = argument.expression.getText(); + const spelling = ts.getTextOfNode(argument.expression); spreads.push({ arg: index, what: spelling }); return checkedIterableSpread(lowerer, lowerer.lowerExpr(argument.expression), spelling, loc); }); diff --git a/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts b/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts index 5fc2a3cec..de84e3fed 100644 --- a/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts +++ b/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts @@ -1158,7 +1158,7 @@ export class ClassDynamicDispatch { if (this.bagClasses.has(plan.key)) continue; this.bagClasses.add(plan.key); this.ensurePropertyBag(info); - if (info.decl && isJsSourceFile(info.decl.getSourceFile())) { + if (info.decl && isJsSourceFile(ts.getSourceFileOfNode(info.decl))) { let root = info; while (root.base && !root.base.def.runtime) root = root.base; const track = (current: ClassInfo): void => { @@ -3246,7 +3246,7 @@ export class ClassDynamicDispatch { for (const name of this.boxedConstructors) { if (this.staticBagClasses.has(name)) continue; const info = lowerer.classes.get(name); - if (!info?.decl || !isJsSourceFile(info.decl.getSourceFile())) continue; + if (!info?.decl || !isJsSourceFile(ts.getSourceFileOfNode(info.decl))) continue; const loc = this.staticPropertyBag.loc; const bag = classStaticData(lowerer, info, loc); if (!bag) continue; diff --git a/packages/compiler/src/frontend/lowering/class-method-mutations.ts b/packages/compiler/src/frontend/lowering/class-method-mutations.ts index 981cd0c1a..cc8700469 100644 --- a/packages/compiler/src/frontend/lowering/class-method-mutations.ts +++ b/packages/compiler/src/frontend/lowering/class-method-mutations.ts @@ -152,10 +152,11 @@ export function collectClassMethodMutations( const required = properties.filter( (property) => (property.flags & ts.SymbolFlags.Optional) === 0, ); - if (required.some((property) => !FUNCTION_MEMBER_NAMES.has(property.name))) return true; + if (required.some((property) => !FUNCTION_MEMBER_NAMES.has(ts.symbolName(property)))) + return true; return ( required.length === 0 && - properties.every((property) => !FUNCTION_MEMBER_NAMES.has(property.name)) + properties.every((property) => !FUNCTION_MEMBER_NAMES.has(ts.symbolName(property))) ); }; // A string index whose values cannot be functions excludes callable @@ -167,7 +168,7 @@ export function collectClassMethodMutations( .getIndexInfosOfType(lowerer.typeOf(value)) .some( (index) => - (index.keyType.flags & ts.TypeFlags.StringLike) !== 0 && functionFree(index.valueType), + (index.keyType.flags & ts.TypeFlags.StringLike) !== 0 && functionFree(index.type), ); const literalLoopKeys = (key: ts.Expression): (string | number)[] | null => { if (!ts.isIdentifier(key)) return null; diff --git a/packages/compiler/src/frontend/lowering/class-method-values.ts b/packages/compiler/src/frontend/lowering/class-method-values.ts index 795307c93..1197d9bdd 100644 --- a/packages/compiler/src/frontend/lowering/class-method-values.ts +++ b/packages/compiler/src/frontend/lowering/class-method-values.ts @@ -92,7 +92,8 @@ export function lowerClassMethodValue( loc, }; }; - const checked = isJsSourceFile(expr.getSourceFile()) && lowerer.dynConvertible(value.type); + const checked = + isJsSourceFile(ts.getSourceFileOfNode(expr)) && lowerer.dynConvertible(value.type); if (checked) value = lowerer.coerceToExpected(value, DYN); // Select the declaration when extracting the value. Calling the value // later must not redispatch the method name on a different receiver. @@ -374,7 +375,7 @@ function reflectedMethodValue( if (!member || (!ts.isMethodDeclaration(member) && !ts.isAccessor(member)) || !member.body) return null; let needsNativeReceiver = - !isJsSourceFile(declaration.getSourceFile()) || + !isJsSourceFile(ts.getSourceFileOfNode(declaration)) || info.localClass !== undefined || lowerer.errorHierarchyClassOf(info.def.name); ts.walkPreorder(member, (node) => { diff --git a/packages/compiler/src/frontend/lowering/class-protocol-dispatch.ts b/packages/compiler/src/frontend/lowering/class-protocol-dispatch.ts index 298ff45dc..33515bf2c 100644 --- a/packages/compiler/src/frontend/lowering/class-protocol-dispatch.ts +++ b/packages/compiler/src/frontend/lowering/class-protocol-dispatch.ts @@ -87,7 +87,7 @@ function codeOwner(node: ts.Node): ts.Node { !ts.isPropertyDeclaration(current) ) current = current.parent; - return current ?? node.getSourceFile(); + return current ?? ts.getSourceFileOfNode(node); } /** A local variable or parameter that only its own function's code @@ -102,21 +102,21 @@ function ownedBinding(lowerer: Lowerer, id: ts.Identifier): boolean { const decl = symbol.valueDeclaration; let owned = decl !== undefined && - decl.getSourceFile() === id.getSourceFile() && + ts.getSourceFileOfNode(decl) === ts.getSourceFileOfNode(id) && ((ts.isVariableDeclaration(decl) && ts.isIdentifier(decl.name)) || (ts.isParameter(decl) && ts.isIdentifier(decl.name))); if (owned && decl) { const owner = codeOwner(decl); const names = (node: ts.Node): boolean => ts.isIdentifier(node) && - node.text === symbol.name && + node.text === ts.symbolName(symbol) && lowerer.resolveValueSymbol(node) === symbol; const written = (target: ts.Node): boolean => { let found = false; const walk = (node: ts.Node): void => { if (found) return; if (names(node)) found = true; - else node.forEachChild(walk); + else ts.forEachChild(node, walk); }; walk(target); return found; @@ -144,9 +144,9 @@ function ownedBinding(lowerer: Lowerer, id: ts.Identifier): boolean { ) target = node.initializer; if (target && codeOwner(node) !== owner && written(target)) owned = false; - node.forEachChild(visit); + ts.forEachChild(node, visit); }; - owner.forEachChild(visit); + ts.forEachChild(owner, visit); } lowerer.protocolOwnedBindings.set(symbol, owned); return owned; @@ -302,7 +302,7 @@ export function lowerProtocolMethodCall( kind: "dynCall", callee: probed ? varRef("p.callee", DYN, loc) : read(), receiver: varRef("p.0", DYN, loc), - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), calleeNameValue: varRef("p.calleeName", STRING, loc), args: params.map((type, i) => lowerer.coerceToExpected(varRef(`p.${i + 1}`, type, loc), DYN)), type: DYN, @@ -378,7 +378,7 @@ export function lowerProtocolMethodCall( receiver, ...(callee ? [varRef(callee.id, DYN, loc)] : []), ...args, - { kind: "strLit", value: access.getText(), type: STRING, loc }, + { kind: "strLit", value: ts.getTextOfNode(access), type: STRING, loc }, ], type: result, loc, @@ -487,7 +487,7 @@ export function lowerProtocolPropertyRead( lowerer: Lowerer, access: ts.PropertyAccessExpression, ): IrExpr | null { - if (isJsSourceFile(access.getSourceFile())) return null; + if (isJsSourceFile(ts.getSourceFileOfNode(access))) return null; const receiverType = lowerer.typeOf(access.expression); if (!lowerer.classImplementedProtocol(receiverType)) return null; const name = access.name.text; diff --git a/packages/compiler/src/frontend/lowering/class-protocols.ts b/packages/compiler/src/frontend/lowering/class-protocols.ts index 5a27f0815..4de7f03ad 100644 --- a/packages/compiler/src/frontend/lowering/class-protocols.ts +++ b/packages/compiler/src/frontend/lowering/class-protocols.ts @@ -46,9 +46,9 @@ export class ClassProtocols { ) ) continue; - const entries = this.methods.get(property.name) ?? []; + const entries = this.methods.get(ts.symbolName(property)) ?? []; entries.push(node); - this.methods.set(property.name, entries); + this.methods.set(ts.symbolName(property), entries); } } if ( @@ -472,7 +472,7 @@ export class ClassProtocols { if (facts.observed.has(symbol) || facts.written.has(symbol)) return true; const target = facts.targets.get(symbol); if (target === undefined) return true; - const names = new Set(this.checker.getPropertiesOfType(target).map((p) => p.name)); + const names = new Set(this.checker.getPropertiesOfType(target).map((p) => ts.symbolName(p))); const origins = facts.origins.get(symbol) ?? new Set(); const writesNames = (written: Set | "all"): boolean => written === "all" || [...written].some((name) => names.has(name)); @@ -517,7 +517,7 @@ export class ClassProtocols { .getPropertiesOfType(declared) .some( (property) => - names.has(property.name) && + names.has(ts.symbolName(property)) && property.declarations.some( (declaration) => ts.isGetAccessorDeclaration(declaration) || ts.isSetAccessorDeclaration(declaration), @@ -575,7 +575,9 @@ export class ClassProtocols { return ( symbol !== undefined && (symbol.flags & ts.SymbolFlags.Class) !== 0 && - symbol.declarations.some((declaration) => !declaration.getSourceFile().isDeclarationFile) && + symbol.declarations.some( + (declaration) => !ts.getSourceFileOfNode(declaration).isDeclarationFile, + ) && // `typeof C` (the class value) shares the class symbol. this.checker.getConstructSignatures(type).length === 0 ); @@ -598,7 +600,8 @@ export class ClassProtocols { this.methods.size > 0 && symbol.declarations.some( (declaration) => - ts.isInterfaceDeclaration(declaration) && !declaration.getSourceFile().isDeclarationFile, + ts.isInterfaceDeclaration(declaration) && + !ts.getSourceFileOfNode(declaration).isDeclarationFile, ) && this.protocol(type) ); @@ -617,7 +620,7 @@ export class ClassProtocols { const declarations = symbol.declarations; return ( declarations.length > 0 && - declarations.every((declaration) => !declaration.getSourceFile().isDeclarationFile) + declarations.every((declaration) => !ts.getSourceFileOfNode(declaration).isDeclarationFile) ); } @@ -704,14 +707,15 @@ export class ClassProtocols { !symbol || !symbol.declarations.some( (declaration) => - ts.isInterfaceDeclaration(declaration) && !declaration.getSourceFile().isDeclarationFile, + ts.isInterfaceDeclaration(declaration) && + !ts.getSourceFileOfNode(declaration).isDeclarationFile, ) ) return false; for (const property of properties) { const memberType = this.checker.getTypeOfSymbol(property); if (this.checker.getCallSignatures(memberType).length === 0) continue; - for (const declaration of this.methods.get(property.name) ?? []) { + for (const declaration of this.methods.get(ts.symbolName(property)) ?? []) { const classSymbol = declaration.name ? this.checker.getSymbolAtLocation(declaration.name) : undefined; diff --git a/packages/compiler/src/frontend/lowering/class-static-data.ts b/packages/compiler/src/frontend/lowering/class-static-data.ts index af5425727..dd6927d18 100644 --- a/packages/compiler/src/frontend/lowering/class-static-data.ts +++ b/packages/compiler/src/frontend/lowering/class-static-data.ts @@ -38,7 +38,7 @@ export function classStaticDataFor( ): IrExpr | null { if ( !info.decl || - !isJsSourceFile(info.decl.getSourceFile()) || + !isJsSourceFile(ts.getSourceFileOfNode(info.decl)) || name.startsWith("#") || constructorMembers.has(name) ) diff --git a/packages/compiler/src/frontend/lowering/code-generation.ts b/packages/compiler/src/frontend/lowering/code-generation.ts index b3bd9243a..c3a5c0ea7 100644 --- a/packages/compiler/src/frontend/lowering/code-generation.ts +++ b/packages/compiler/src/frontend/lowering/code-generation.ts @@ -126,8 +126,12 @@ function useSiteType(lowerer: Lowerer, expr: ts.Expression): IrType { // elsewhere — the throw always fires first. const type = lowerer.typeOf(expr); const symbol = type.getSymbol(); - if (symbol?.name === "Function" && lowerer.isStdlibSymbol(symbol)) { - return isJsSourceFile(expr.getSourceFile()) ? DYN : F64; + if ( + symbol !== undefined && + ts.symbolName(symbol) === "Function" && + lowerer.isStdlibSymbol(symbol) + ) { + return isJsSourceFile(ts.getSourceFileOfNode(expr)) ? DYN : F64; } const mapped = lowerer.mapTypeOf(type); if ( diff --git a/packages/compiler/src/frontend/lowering/collection-ingestion.ts b/packages/compiler/src/frontend/lowering/collection-ingestion.ts index 5449b0943..5d14d5492 100644 --- a/packages/compiler/src/frontend/lowering/collection-ingestion.ts +++ b/packages/compiler/src/frontend/lowering/collection-ingestion.ts @@ -91,7 +91,7 @@ export function collectionInput( const symbol = type.getSymbol(); if ( !symbol || - (symbol.name !== "MapIterator" && symbol.name !== "SetIterator") || + (ts.symbolName(symbol) !== "MapIterator" && ts.symbolName(symbol) !== "SetIterator") || !lowerer.isStdlibSymbol(symbol) ) return null; diff --git a/packages/compiler/src/frontend/lowering/collection-narrowing.ts b/packages/compiler/src/frontend/lowering/collection-narrowing.ts index 29b358e99..6c1ca80f4 100644 --- a/packages/compiler/src/frontend/lowering/collection-narrowing.ts +++ b/packages/compiler/src/frontend/lowering/collection-narrowing.ts @@ -11,9 +11,12 @@ export function concreteCollectionNarrow( ): ts.Type | null { if (!ts.isIdentifier(node) && !ts.isPropertyAccessExpression(node)) return null; const symbol = narrowed.getSymbol(); - const name = symbol?.name; + const name = ts.symbolNameOf(symbol); if (name !== "Map" && name !== "Set") return null; - if (!symbol || !symbol.declarations.some((decl) => lowerer.isStdlibFile(decl.getSourceFile()))) + if ( + !symbol || + !symbol.declarations.some((decl) => lowerer.isStdlibFile(ts.getSourceFileOfNode(decl))) + ) return null; const args = lowerer.checker.getTypeArguments(narrowed as ts.TypeReference); if (args.length === 0 || !args.every((arg) => (arg.flags & ts.TypeFlags.Any) !== 0)) return null; diff --git a/packages/compiler/src/frontend/lowering/construction-escapes.test.ts b/packages/compiler/src/frontend/lowering/construction-escapes.test.ts index 77f72dc15..6807946d0 100644 --- a/packages/compiler/src/frontend/lowering/construction-escapes.test.ts +++ b/packages/compiler/src/frontend/lowering/construction-escapes.test.ts @@ -16,7 +16,7 @@ function analyze(source: string, options: { derived?: boolean; baseEscapes?: boo const ctor = cls.members.nodes.find(ts.isConstructorDeclaration) ?? null; const fields = cls.members.nodes .filter(ts.isPropertyDeclaration) - .map((member) => ({ name: member.name.getText(), initializer: member.initializer })); + .map((member) => ({ name: ts.getTextOfNode(member.name), initializer: member.initializer })); const fieldNames = new Set(fields.map((field) => field.name)); return constructionEscape({ ctor, @@ -30,7 +30,7 @@ function analyze(source: string, options: { derived?: boolean; baseEscapes?: boo const name = call.expression.name.text; const method = cls.members.nodes.find( (member): member is ts.MethodDeclaration => - ts.isMethodDeclaration(member) && member.name.getText() === name, + ts.isMethodDeclaration(member) && ts.getTextOfNode(member.name) === name, ); const isPrivate = method !== undefined && diff --git a/packages/compiler/src/frontend/lowering/construction-escapes.ts b/packages/compiler/src/frontend/lowering/construction-escapes.ts index b9c8d7e07..3fdae4ab4 100644 --- a/packages/compiler/src/frontend/lowering/construction-escapes.ts +++ b/packages/compiler/src/frontend/lowering/construction-escapes.ts @@ -23,7 +23,7 @@ export function mentionsReceiver(node: ts.Node): boolean { found = true; return; } - child.forEachChild(visit); + ts.forEachChild(child, visit); }; visit(node); return found; @@ -68,9 +68,9 @@ function containsReturn(node: ts.Node): boolean { found = true; return; } - child.forEachChild(visit); + ts.forEachChild(child, visit); }; - node.forEachChild(visit); + ts.forEachChild(node, visit); return found; } @@ -100,7 +100,7 @@ export function constructionEscape(inputs: ConstructionInputs): ConstructionEsca return !(ts.isIdentifier(node.name) && assigned.has(name) && inputs.isField(name)); } let found = false; - node.forEachChild((child) => { + ts.forEachChild(node, (child) => { if (!found && mentionsReceiver(child)) found = exposes(child, depth); }); return found; diff --git a/packages/compiler/src/frontend/lowering/constructor-returns.ts b/packages/compiler/src/frontend/lowering/constructor-returns.ts index bee2e7567..0e600e700 100644 --- a/packages/compiler/src/frontend/lowering/constructor-returns.ts +++ b/packages/compiler/src/frontend/lowering/constructor-returns.ts @@ -86,9 +86,9 @@ function ownValueReturns(ctor: ts.ConstructorDeclaration): ts.ReturnStatement[] const visit = (node: ts.Node): void => { if (ts.isFunctionLike(node) || ts.isClassLikeDeclaration(node)) return; if (ts.isReturnStatement(node) && node.expression !== undefined) out.push(node); - node.forEachChild(visit); + ts.forEachChild(node, visit); }; - ctor.body?.forEachChild(visit); + if (ctor.body !== undefined) ts.forEachChild(ctor.body, visit); return out; } @@ -188,9 +188,9 @@ function ownReturns(body: ts.Node): ts.ReturnStatement[] { const visit = (node: ts.Node): void => { if (ts.isFunctionLike(node) || ts.isClassLikeDeclaration(node)) return; if (ts.isReturnStatement(node)) out.push(node); - node.forEachChild(visit); + ts.forEachChild(node, visit); }; - body.forEachChild(visit); + ts.forEachChild(body, visit); return out; } @@ -225,7 +225,7 @@ export function constructorReturnPlan( // the class too (`if (C.instance) return C.instance;`). const runtimeInstance = info.localClass === undefined && - isJsSourceFile(ctor.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(ctor)) && [...returns].some(([stmt, kind]) => kind === "mixed" && !insideFinallyTry(stmt, ctor)); if ([...returns.values()].includes("instance") || runtimeInstance) { slot = { @@ -277,7 +277,7 @@ export function lowerConstructorReturn(lowerer: Lowerer, stmt: ts.ReturnStatemen }; } const feature = foreignObjectFeature(info); - if (kind === "object" || !isJsSourceFile(stmt.getSourceFile())) + if (kind === "object" || !isJsSourceFile(ts.getSourceFileOfNode(stmt))) lowerer.unsupported("SC1090", stmt, feature); // A JavaScript value that is an object only sometimes: a primitive // still answers the constructed instance, an object refuses. @@ -296,7 +296,7 @@ export function lowerConstructorReturn(lowerer: Lowerer, stmt: ts.ReturnStatemen }); const diag = unsupportedDiag("SC1090", loc, feature); lowerer.runtimeFences.push(diag); - const sf = stmt.getSourceFile(); + const sf = ts.getSourceFileOfNode(stmt); const line = ts.getLineAndCharacterOfPosition(sf, loc.start).line + 1; return { kind: "block", @@ -460,7 +460,7 @@ export function superIntoResultSlot( const slot = plan.slot; const display = base.def.name.replace(/^%|^%m\d+\./, ""); const feature = `extending a class whose constructor can return an existing instance ('super()' would rebind 'this' to the instance '${display}' returns)`; - if (!isJsSourceFile(blame.getSourceFile())) { + if (!isJsSourceFile(ts.getSourceFileOfNode(blame))) { lowerer.unsupported("SC1090", blame, feature); return call; } @@ -470,7 +470,8 @@ export function superIntoResultSlot( if (!lowerer.liftedFns.some((fn) => fn.name === name)) { const diag = unsupportedDiag("SC1090", loc, feature); lowerer.runtimeFences.push(diag); - const line = ts.getLineAndCharacterOfPosition(blame.getSourceFile(), loc.start).line + 1; + const line = + ts.getLineAndCharacterOfPosition(ts.getSourceFileOfNode(blame), loc.start).line + 1; const instance = { id: "instance.0", name: "instance", type, mutable: false }; const returned = { id: "returned.0", name: "returned", type: slot.type, mutable: false }; lowerer.liftedFns.push({ diff --git a/packages/compiler/src/frontend/lowering/containers/array-callbacks.ts b/packages/compiler/src/frontend/lowering/containers/array-callbacks.ts index 735361f54..5406c3ec1 100644 --- a/packages/compiler/src/frontend/lowering/containers/array-callbacks.ts +++ b/packages/compiler/src/frontend/lowering/containers/array-callbacks.ts @@ -304,7 +304,7 @@ function lowerFastCallback( fastLoweringDepth > 0 || slow.arity <= elemIndex || !ts.isArrowFunction(argNode) || - isJsSourceFile(argNode.getSourceFile()) || + isJsSourceFile(ts.getSourceFileOfNode(argNode)) || valueT?.kind !== "union" || typeEquals(valueT, elem) || elem.kind === "union" || @@ -1489,7 +1489,7 @@ export function lowerArrayFlatMapCall( kind: "dynInvoke", recv: { kind: "dynFrom", value: receiver, liveRef: true, type: DYN, loc }, method: "flatMap", - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args: [{ kind: "dynFrom", value: fnArg, type: DYN, loc }], type: DYN, loc, @@ -2085,7 +2085,7 @@ export function lowerArraySortCall( // Arithmetic on inferred JS values retains a checked result because // it can produce BigInt. The sort callback still requires a number. const result = - fnArg.type.ret.kind === "dyn" && isJsSourceFile(argNode.getSourceFile()) + fnArg.type.ret.kind === "dyn" && isJsSourceFile(ts.getSourceFileOfNode(argNode)) ? F64 : fnArg.type.ret; const expected = funcOf( @@ -2223,7 +2223,7 @@ export function lowerDynArrayFilterCall( recv, fnArg, { kind: "strLit", value: access.name.text, type: STRING, loc }, - { kind: "strLit", value: access.getText(), type: STRING, loc }, + { kind: "strLit", value: ts.getTextOfNode(access), type: STRING, loc }, ], type: arrayOf(elemT), loc, @@ -2264,7 +2264,7 @@ export function lowerDynArrayFlatMapCall( kind: "dynInvoke", recv, method: "flatMap", - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args: [boxed], type: DYN, loc, @@ -2286,7 +2286,7 @@ export function lowerDynArrayFlatMapCall( recv, fnArg, { kind: "strLit", value: access.name.text, type: STRING, loc }, - { kind: "strLit", value: access.getText(), type: STRING, loc }, + { kind: "strLit", value: ts.getTextOfNode(access), type: STRING, loc }, ], type: arrayOf(ret.elem), loc, diff --git a/packages/compiler/src/frontend/lowering/containers/array-construction.ts b/packages/compiler/src/frontend/lowering/containers/array-construction.ts index 110af2b56..53d46d00d 100644 --- a/packages/compiler/src/frontend/lowering/containers/array-construction.ts +++ b/packages/compiler/src/frontend/lowering/containers/array-construction.ts @@ -296,7 +296,7 @@ export function lowerArrayConstructor( // reference. Known element forms retain native arrays, including their // existing sparse storage and indexed property contracts. if ( - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && (result?.kind !== "array" || result.elem.kind === "dyn") ) { return { @@ -321,7 +321,7 @@ export function lowerArrayConstructor( : null; if (elem !== null && isSupportedArrayElem(elem)) result = arrayOf(elem); } - if (isJsSourceFile(expr.getSourceFile()) && (result === null || result.kind === "dyn")) + if (isJsSourceFile(ts.getSourceFileOfNode(expr)) && (result === null || result.kind === "dyn")) result = arrayOf(DYN); if (result?.kind !== "array" || !isSupportedArrayElem(result.elem)) { lowerer.badType(expr, lowerer.typeOf(expr)); @@ -823,7 +823,7 @@ export function lowerArrayFromCall( const loc = locOf(call); const args = call.arguments.nodes; if ( - isJsSourceFile(call.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(call)) && args.length >= 1 && args.length <= 3 && !args.some(ts.isSpreadElement) diff --git a/packages/compiler/src/frontend/lowering/containers/array-methods.ts b/packages/compiler/src/frontend/lowering/containers/array-methods.ts index 7ef96457c..12078720e 100644 --- a/packages/compiler/src/frontend/lowering/containers/array-methods.ts +++ b/packages/compiler/src/frontend/lowering/containers/array-methods.ts @@ -353,7 +353,7 @@ export function lowerArrayMethodCall( // machinery answered string[]): same rule, any-typed spelling. ((lowerer.typeOf(access.expression).flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) !== 0 && - isJsSourceFile(access.getSourceFile()))) + isJsSourceFile(ts.getSourceFileOfNode(access)))) ) { const probe = lowerer.lowerExpr(access.expression); if (probe.type.kind === "array") { @@ -1039,7 +1039,7 @@ export function lowerArrayMethodCall( kind: "dynInvoke", recv: receiver, method: "join", - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args, type: DYN, loc, @@ -2096,7 +2096,7 @@ export function lowerTupleReadMethodCall( kind: "dynInvoke", recv: { kind: "dynFrom", value: receiver, type: DYN, loc }, method: "flatMap", - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args: [{ kind: "dynFrom", value: fnArg, type: DYN, loc }], type: DYN, loc, diff --git a/packages/compiler/src/frontend/lowering/containers/bytes.ts b/packages/compiler/src/frontend/lowering/containers/bytes.ts index c5a41cf32..f8e49a516 100644 --- a/packages/compiler/src/frontend/lowering/containers/bytes.ts +++ b/packages/compiler/src/frontend/lowering/containers/bytes.ts @@ -120,7 +120,7 @@ function lowerBytesSortCall( fnArg.type.params.every((param) => lowerer.coercibleValue(F64, param)) ) { const result = - fnArg.type.ret.kind === "dyn" && isJsSourceFile(argNode.getSourceFile()) + fnArg.type.ret.kind === "dyn" && isJsSourceFile(ts.getSourceFileOfNode(argNode)) ? F64 : fnArg.type.ret; const expected = funcOf( @@ -187,7 +187,7 @@ export function lowerBytesNew( ): IrExpr | null { if ( symbol != null && - (symbol.name === "ArrayBuffer" || symbol.name === "SharedArrayBuffer") && + (ts.symbolName(symbol) === "ArrayBuffer" || ts.symbolName(symbol) === "SharedArrayBuffer") && lowerer.isStdlibSymbol(symbol) ) { const args = expr.arguments?.nodes ?? []; @@ -203,18 +203,19 @@ export function lowerBytesNew( : dynUndefinedExpr(locOf(expr)); return { kind: "libCall", - fn: symbol.name === "SharedArrayBuffer" ? "sharedArrayBuffer.new" : "arrayBuffer.new", + fn: + ts.symbolName(symbol) === "SharedArrayBuffer" ? "sharedArrayBuffer.new" : "arrayBuffer.new", args: [length], type: DYN, loc: locOf(expr), }; } - if (symbol && symbol.name === "DataView" && lowerer.isStdlibSymbol(symbol)) { + if (symbol && ts.symbolName(symbol) === "DataView" && lowerer.isStdlibSymbol(symbol)) { return lowerDataViewNew(lowerer, expr); } - const elem = symbol ? own(BYTES_CTORS, symbol.name) : undefined; + const elem = symbol ? own(BYTES_CTORS, ts.symbolName(symbol)) : undefined; if (!elem || !symbol || !lowerer.isStdlibSymbol(symbol)) return null; - const name = symbol.name; + const name = ts.symbolName(symbol); const type = bytesOf(elem); const loc = locOf(expr); const args = expr.arguments?.nodes ?? []; @@ -291,7 +292,7 @@ export function lowerBytesStaticCall( } if (access.name.text !== "from" || !ts.isIdentifier(access.expression)) return null; const symbol = lowerer.resolveValueSymbol(access.expression); - const elem = symbol ? own(BYTES_CTORS, symbol.name) : undefined; + const elem = symbol ? own(BYTES_CTORS, ts.symbolName(symbol)) : undefined; if (!elem || !lowerer.isStdlibSymbol(symbol ?? undefined)) return null; if ( call.arguments.nodes.length < 1 || @@ -442,7 +443,7 @@ export function lowerBytesMethodCall( const nArgs = call.arguments.nodes.length; // Encoded stream callbacks can receive strings even when JavaScript's // contextual inference still describes their parameter as a Buffer. - if (name === "toString" && nArgs <= 1 && isJsSourceFile(call.getSourceFile())) { + if (name === "toString" && nArgs <= 1 && isJsSourceFile(ts.getSourceFileOfNode(call))) { const lowered = lowerer.lowerExpr(access.expression); const receiver = lowered.kind === "dynCheck" && lowered.value.type.kind === "dyn" ? lowered.value : lowered; @@ -455,7 +456,7 @@ export function lowerBytesMethodCall( call.arguments.nodes[0] ? lowerer.lowerExprExpecting(call.arguments.nodes[0], DYN) : dynUndefinedExpr(loc), - { kind: "strLit", value: access.getText(), type: STRING, loc }, + { kind: "strLit", value: ts.getTextOfNode(access), type: STRING, loc }, ], type: STRING, loc, @@ -1334,7 +1335,8 @@ function lowerBufferStaticValue( args.length <= 3 && !args.some(ts.isSpreadElement) && ((symbol) => - symbol != null && (symbol.name === "ArrayBuffer" || symbol.name === "SharedArrayBuffer"))( + symbol != null && + (ts.symbolName(symbol) === "ArrayBuffer" || ts.symbolName(symbol) === "SharedArrayBuffer"))( lowerer.typeOf(args[0]!).getSymbol(), ) ) { @@ -1650,7 +1652,9 @@ function lowerBufferStaticValue( // flavor. Plain bytes are Uint8Arrays at this boundary; inspecting // only the bytes tag would incorrectly identify them as Buffers. const checked = - isJsSourceFile(call.getSourceFile()) && v.kind === "dynCheck" && v.value.type.kind === "dyn" + isJsSourceFile(ts.getSourceFileOfNode(call)) && + v.kind === "dynCheck" && + v.value.type.kind === "dyn" ? v.value : v; if (checked.type.kind === "dyn") { diff --git a/packages/compiler/src/frontend/lowering/containers/callback-arguments.ts b/packages/compiler/src/frontend/lowering/containers/callback-arguments.ts index dcd9934bd..18754f088 100644 --- a/packages/compiler/src/frontend/lowering/containers/callback-arguments.ts +++ b/packages/compiler/src/frontend/lowering/containers/callback-arguments.ts @@ -59,7 +59,7 @@ export function lowerArrayCallback( if ( ts.isIdentifier(argNode) && argNode.text === "Boolean" && - !isJsSourceFile(argNode.getSourceFile()) && + !isJsSourceFile(ts.getSourceFileOfNode(argNode)) && lowerer.isStdlibSymbol(lowerer.resolveValueSymbol(argNode) ?? undefined) ) { const valueT = lead[0]; diff --git a/packages/compiler/src/frontend/lowering/containers/collection-grouping.ts b/packages/compiler/src/frontend/lowering/containers/collection-grouping.ts index 7c4ddf220..0bf126830 100644 --- a/packages/compiler/src/frontend/lowering/containers/collection-grouping.ts +++ b/packages/compiler/src/frontend/lowering/containers/collection-grouping.ts @@ -67,7 +67,7 @@ export function lowerGroupByStaticCall( const fNode = call.arguments.nodes[1]!; // An unannotated JavaScript callback parameter takes the element. const f = - isJsSourceFile(fNode.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(fNode)) && (ts.isArrowFunction(fNode) || ts.isFunctionExpression(fNode)) ? lowerArrayCallback(lowerer, fNode, [elem], groupT, true).fnArg : lowerer.lowerExpr(fNode); diff --git a/packages/compiler/src/frontend/lowering/containers/collection-methods.ts b/packages/compiler/src/frontend/lowering/containers/collection-methods.ts index 6a5290c0f..6aa6d6faf 100644 --- a/packages/compiler/src/frontend/lowering/containers/collection-methods.ts +++ b/packages/compiler/src/frontend/lowering/containers/collection-methods.ts @@ -60,7 +60,7 @@ export function lowerMapMethodCall( if (!MAP_METHODS.has(name)) return null; let receiverIr = lowerer.mapTypeOf(lowerer.typeOf(access.expression)); let probedUntyped = false; - if (receiverIr?.kind !== "map" && isJsSourceFile(access.getSourceFile())) { + if (receiverIr?.kind !== "map" && isJsSourceFile(ts.getSourceFileOfNode(access))) { const probed = tryLowerExpression(lowerer, access.expression); if (probed?.type.kind === "map") { receiverIr = probed.type; @@ -427,7 +427,7 @@ export function lowerCollectionSpread( loc, }, then: entries, - else_: nodeThrowExpr(1, "", `${node.getText()} is not iterable`, entries.type, loc), + else_: nodeThrowExpr(1, "", `${ts.getTextOfNode(node)} is not iterable`, entries.type, loc), type: entries.type, loc, }, @@ -544,7 +544,7 @@ export function lowerSetMethodCall( // Set, but the VALUE lowered as a real Set of // identity tokens (the new-Set probe) — the lowered receiver's type // is the honest dispatch key. - if (receiverIr?.kind !== "set" && isJsSourceFile(access.getSourceFile())) { + if (receiverIr?.kind !== "set" && isJsSourceFile(ts.getSourceFileOfNode(access))) { const probed = tryLowerExpression(lowerer, access.expression); if (probed?.type.kind === "set") { receiverIr = probed.type; @@ -973,7 +973,7 @@ export function lowerSetSeedNew( { kind: "libCall", fn: "dyn.iterPack", - args: [source, strLit(node.getText(), loc)], + args: [source, strLit(ts.getTextOfNode(node), loc)], type: DYN, loc, }, @@ -1165,7 +1165,7 @@ export function lowerMapSeedNew( } if (ts.isArrayLiteralExpression(argNode)) { if ( - isJsSourceFile(argNode.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(argNode)) && argNode.elements.nodes.some((entry) => !ts.isArrayLiteralExpression(entry)) ) { return mapFromSeedValue(lowerer, lowerer.lowerExprExpecting(argNode, DYN), mapT); diff --git a/packages/compiler/src/frontend/lowering/containers/dynamic-receivers.ts b/packages/compiler/src/frontend/lowering/containers/dynamic-receivers.ts index 523fda46e..32f90bfe7 100644 --- a/packages/compiler/src/frontend/lowering/containers/dynamic-receivers.ts +++ b/packages/compiler/src/frontend/lowering/containers/dynamic-receivers.ts @@ -142,7 +142,7 @@ export function dynStringReceiver( args: [ recv, { kind: "strLit", value: access.name.text, type: STRING, loc }, - { kind: "strLit", value: access.getText(), type: STRING, loc }, + { kind: "strLit", value: ts.getTextOfNode(access), type: STRING, loc }, ], type: STRING, loc, diff --git a/packages/compiler/src/frontend/lowering/containers/exec-result-uses.ts b/packages/compiler/src/frontend/lowering/containers/exec-result-uses.ts index f2967c298..397bf6ec8 100644 --- a/packages/compiler/src/frontend/lowering/containers/exec-result-uses.ts +++ b/packages/compiler/src/frontend/lowering/containers/exec-result-uses.ts @@ -29,7 +29,7 @@ export function execResultCaptureMask(lowerer: Lowerer, call: ts.CallExpression) const symbol = lowerer.resolveValueSymbol(decl.name); if (!symbol) return null; const read = new Set(); - for (const use of identifiersNamed(lowerer, decl.getSourceFile(), decl.name.text)) { + for (const use of identifiersNamed(lowerer, ts.getSourceFileOfNode(decl), decl.name.text)) { if (use === decl.name || lowerer.resolveValueSymbol(use) !== symbol) continue; const ref = outerValue(use); const parent = ref.parent; diff --git a/packages/compiler/src/frontend/lowering/containers/iterator-helpers.ts b/packages/compiler/src/frontend/lowering/containers/iterator-helpers.ts index 4017335ae..c23e6931c 100644 --- a/packages/compiler/src/frontend/lowering/containers/iterator-helpers.ts +++ b/packages/compiler/src/frontend/lowering/containers/iterator-helpers.ts @@ -684,7 +684,7 @@ function isHelperReceiver(lowerer: Lowerer, expr: ts.Expression): boolean { return ( mapped?.kind === "dyn" && symbol !== undefined && - ADAPTED_ITERATOR_TYPES.has(symbol.name) && + ADAPTED_ITERATOR_TYPES.has(ts.symbolName(symbol)) && lowerer.isStdlibSymbol(symbol) ); } diff --git a/packages/compiler/src/frontend/lowering/containers/match-row-uses.ts b/packages/compiler/src/frontend/lowering/containers/match-row-uses.ts index f936e27cb..794bd27c2 100644 --- a/packages/compiler/src/frontend/lowering/containers/match-row-uses.ts +++ b/packages/compiler/src/frontend/lowering/containers/match-row-uses.ts @@ -242,7 +242,7 @@ function constBindingReadsByElements( } function usesOf(lowerer: Lowerer, name: ts.Identifier, symbol: ts.Symbol): ts.Identifier[] { - return identifiersNamed(lowerer, name.getSourceFile(), name.text).filter( + return identifiersNamed(lowerer, ts.getSourceFileOfNode(name), name.text).filter( (use) => use !== name && lowerer.resolveValueSymbol(use) === symbol, ); } diff --git a/packages/compiler/src/frontend/lowering/deferred-module-initializers.ts b/packages/compiler/src/frontend/lowering/deferred-module-initializers.ts index e3ca2270d..b80eb7ac0 100644 --- a/packages/compiler/src/frontend/lowering/deferred-module-initializers.ts +++ b/packages/compiler/src/frontend/lowering/deferred-module-initializers.ts @@ -62,8 +62,9 @@ export class DeferredModuleInitializers { const info = exactClassOfReceiver(lowerer, owner); if ( info && - info.decl?.getSourceFile() === statement.getSourceFile() && - info.decl.getStart() < statement.getStart() && + info.decl !== null && + ts.getSourceFileOfNode(info.decl) === ts.getSourceFileOfNode(statement) && + ts.tokenStartOfNode(info.decl) < ts.tokenStartOfNode(statement) && !info.localClass && !info.classDecorators && !info.generic && @@ -101,7 +102,7 @@ export class DeferredModuleInitializers { loc: locOf(statement), }; const context = lowerer.ctx; - const moduleIndex = [...lowerer.initNameOf.keys()].indexOf(statement.getSourceFile()); + const moduleIndex = [...lowerer.initNameOf.keys()].indexOf(ts.getSourceFileOfNode(statement)); const initializers = this.pending.get(key) ?? []; initializers.push({ loc: block.loc, @@ -269,14 +270,14 @@ function registryInitializer( return false; expression = expression.arguments.nodes[0]!; } - const packageSource = npmStaticPackageOfPath(statement.getSourceFile().fileName) !== null; + const packageSource = npmStaticPackageOfPath(ts.getSourceFileOfNode(statement).fileName) !== null; const initialized = (declaration: ts.Node): boolean => { - const source = declaration.getSourceFile(); - return source === statement.getSourceFile() - ? declaration.getStart() < statement.getStart() + const source = ts.getSourceFileOfNode(declaration); + return source === ts.getSourceFileOfNode(statement) + ? ts.tokenStartOfNode(declaration) < ts.tokenStartOfNode(statement) : [...lowerer.initNameOf.keys()].indexOf(source) >= 0 && [...lowerer.initNameOf.keys()].indexOf(source) < - [...lowerer.initNameOf.keys()].indexOf(statement.getSourceFile()); + [...lowerer.initNameOf.keys()].indexOf(ts.getSourceFileOfNode(statement)); }; const callable = (callee: ts.Expression): boolean => { if (!ts.isIdentifier(callee)) return false; @@ -324,7 +325,7 @@ function registryInitializer( // bundlers. Arguments still need to be safe to omit: an annotation // never licenses dropping argument effects or a throwing property read. if (packageSource && (ts.isCallExpression(node) || ts.isNewExpression(node))) { - const trivia = node.getSourceFile().text.slice(node.pos, node.getStart()); + const trivia = ts.getSourceFileOfNode(node).text.slice(node.pos, ts.tokenStartOfNode(node)); if (!/[@#]__PURE__/.test(trivia)) return false; if (!callable(node.expression)) return false; return (node.arguments?.nodes ?? []).every((argument) => value(argument, depth + 1)); @@ -337,8 +338,8 @@ function registryInitializer( const info = exactClassOfReceiver(lowerer, node.expression.expression); return ( !!info?.decl && - info.decl.getSourceFile() === statement.getSourceFile() && - info.decl.getStart() < statement.getStart() && + ts.getSourceFileOfNode(info.decl) === ts.getSourceFileOfNode(statement) && + ts.tokenStartOfNode(info.decl) < ts.tokenStartOfNode(statement) && classWriteIsUnobserved(lowerer, info, statement) && (findMethodOn(lowerer, info, node.name.text) !== null || findGenericMethodOn(lowerer, info, node.name.text) !== null) @@ -398,7 +399,7 @@ function registryInitializer( if ( functionDeclaration && ts.isFunctionDeclaration(functionDeclaration) && - functionDeclaration.getSourceFile() === statement.getSourceFile() + ts.getSourceFileOfNode(functionDeclaration) === ts.getSourceFileOfNode(statement) ) return true; // Reading an earlier initialized binding executes none of its @@ -410,8 +411,8 @@ function registryInitializer( functionDeclaration && ts.isVariableDeclaration(functionDeclaration) && functionDeclaration.initializer && - functionDeclaration.getSourceFile() === statement.getSourceFile() && - functionDeclaration.getStart() < statement.getStart() + ts.getSourceFileOfNode(functionDeclaration) === ts.getSourceFileOfNode(statement) && + ts.tokenStartOfNode(functionDeclaration) < ts.tokenStartOfNode(statement) ) return true; const info = symbol && lowerer.classBySymbol.get(symbol); @@ -421,8 +422,8 @@ function registryInitializer( // decorated classes and local factories keep ordinary initialization. return ( !!declaration && - declaration.getSourceFile() === statement.getSourceFile() && - declaration.getStart() < statement.getStart() && + ts.getSourceFileOfNode(declaration) === ts.getSourceFileOfNode(statement) && + ts.tokenStartOfNode(declaration) < ts.tokenStartOfNode(statement) && ts.isClassDeclaration(declaration) && !info!.classDecorators && !info!.generic && @@ -476,19 +477,20 @@ function registryOwnerIsData( ) ) return false; - if (declaration.getSourceFile() !== statement.getSourceFile()) { + if (ts.getSourceFileOfNode(declaration) !== ts.getSourceFileOfNode(statement)) { const modules = [...lowerer.initNameOf.keys()]; return ( !!frozen && - modules.indexOf(declaration.getSourceFile()) >= 0 && - modules.indexOf(declaration.getSourceFile()) < modules.indexOf(statement.getSourceFile()) + modules.indexOf(ts.getSourceFileOfNode(declaration)) >= 0 && + modules.indexOf(ts.getSourceFileOfNode(declaration)) < + modules.indexOf(ts.getSourceFileOfNode(statement)) ); } - if (declaration.getStart() >= statement.getStart()) return false; + if (ts.tokenStartOfNode(declaration) >= ts.tokenStartOfNode(statement)) return false; if (frozen) return true; let safe = true; const visit = (node: ts.Node): void => { - if (!safe || node.getStart() >= statement.getStart()) return; + if (!safe || ts.tokenStartOfNode(node) >= ts.tokenStartOfNode(statement)) return; if ( ts.isIdentifier(node) && node !== declaration.name && @@ -515,7 +517,7 @@ function registryOwnerIsData( } ts.forEachChild(node, visit); }; - visit(statement.getSourceFile()); + visit(ts.getSourceFileOfNode(statement)); return safe; } @@ -527,7 +529,7 @@ function classWriteIsUnobserved( info: ClassInfo, statement: ts.Statement, ): boolean { - const source = statement.getSourceFile(); + const source = ts.getSourceFileOfNode(statement); const declaration = info.decl!; if ( declaration.members.nodes.some( @@ -543,7 +545,10 @@ function classWriteIsUnobserved( ) return false; for (const earlier of source.statements.nodes) { - if (earlier.getStart() <= declaration.getStart() || earlier.getStart() >= statement.getStart()) + if ( + ts.tokenStartOfNode(earlier) <= ts.tokenStartOfNode(declaration) || + ts.tokenStartOfNode(earlier) >= ts.tokenStartOfNode(statement) + ) continue; if ( ts.isFunctionDeclaration(earlier) || diff --git a/packages/compiler/src/frontend/lowering/divergences.ts b/packages/compiler/src/frontend/lowering/divergences.ts index a77f33c6b..1c362cb09 100644 --- a/packages/compiler/src/frontend/lowering/divergences.ts +++ b/packages/compiler/src/frontend/lowering/divergences.ts @@ -281,7 +281,7 @@ export function scanDivergences( ts.isVariableDeclaration(decl) && decl.initializer !== undefined && ts.isObjectLiteralExpression(decl.initializer) && - decl.getSourceFile() === inner.getSourceFile() + ts.getSourceFileOfNode(decl) === ts.getSourceFileOfNode(inner) ) { lit = decl.initializer; binding = inner.text; @@ -291,7 +291,9 @@ export function scanDivergences( const contextual = safely(() => checker.getContextualType(lit!)); if (contextual === undefined) continue; if (namedObjectDeclaration(checker, contextual) === null) continue; - const declared = safely(() => checker.getPropertiesOfType(contextual).map((p) => p.name)); + const declared = safely(() => + checker.getPropertiesOfType(contextual).map((p) => ts.symbolName(p)), + ); if (declared === undefined) continue; const written = lit.properties.nodes.map((p) => propertyNameText(p)!); const assigned = @@ -463,7 +465,7 @@ function identityUse(root: ts.Node, name: string, from = -1): IdentityUse | null return cur; }; const textOf = (node: ts.Node): string => { - const text = node.getText(); + const text = ts.getTextOfNode(node); return text.length > 60 ? `${text.slice(0, 57)}...` : text; }; const visit = (node: ts.Node): "skip" | "stop" | undefined => { @@ -591,7 +593,7 @@ function elementWrite(root: ts.Node, name: string, from: number): string | null ) target = node.operand; if (target !== undefined && throughElement(target)) { - found = target.getText(); + found = ts.getTextOfNode(target); return "stop"; } if ( @@ -670,8 +672,8 @@ function widthCopyBetween( // still observes the same writes: no divergence to report. const accessor = ts.SymbolFlags.GetAccessor | ts.SymbolFlags.SetAccessor; if (sourceProps.some((p) => (p.flags & accessor) !== 0)) return null; - const sourceNames = new Set(sourceProps.map((p) => p.name)); - const targetNames = checker.getPropertiesOfType(t).map((p) => p.name); + const sourceNames = new Set(sourceProps.map((p) => ts.symbolName(p))); + const targetNames = checker.getPropertiesOfType(t).map((p) => ts.symbolName(p)); if (targetNames.length === sourceNames.size && targetNames.every((n) => sourceNames.has(n))) return null; } @@ -684,7 +686,7 @@ function isProgramClassType(checker: ts.TypeChecker, type: ts.Type): boolean { return ( symbol !== undefined && (symbol.flags & ts.SymbolFlags.Class) !== 0 && - symbol.declarations.some((d) => !d.getSourceFile().isDeclarationFile) + symbol.declarations.some((d) => !ts.getSourceFileOfNode(d).isDeclarationFile) ); } @@ -698,7 +700,7 @@ function isPlainRecordType(checker: ts.TypeChecker, type: ts.Type): boolean { if (symbol !== undefined && (symbol.flags & ts.SymbolFlags.Class) !== 0) return false; // Library-declared object types (Map, Date, Promise, ...) are not records. const decls = symbol === undefined ? [] : symbol.declarations; - if (decls.some((d) => d.getSourceFile().isDeclarationFile)) return false; + if (decls.some((d) => ts.getSourceFileOfNode(d).isDeclarationFile)) return false; return true; } @@ -711,7 +713,7 @@ function namedObjectDeclaration(checker: ts.TypeChecker, type: ts.Type): ts.Node if (symbol === undefined) return null; if ((symbol.flags & ts.SymbolFlags.Class) !== 0) return null; const decl = symbol.declarations[0]; - if (decl === undefined || decl.getSourceFile().isDeclarationFile) return null; + if (decl === undefined || ts.getSourceFileOfNode(decl).isDeclarationFile) return null; return decl; } @@ -765,7 +767,7 @@ function classDeclarationOfTypeNode( const symbol = lowerer.checker.getSymbolAtLocation(node.typeName); if (symbol === undefined || (symbol.flags & ts.SymbolFlags.Class) === 0) return null; const decl = symbol.declarations.find((d) => ts.isClassDeclaration(d) || ts.isClassExpression(d)); - if (decl === undefined || decl.getSourceFile().isDeclarationFile) return null; + if (decl === undefined || ts.getSourceFileOfNode(decl).isDeclarationFile) return null; return decl as ts.ClassLikeDeclaration; } @@ -776,7 +778,7 @@ function classDeclarationOfType( const symbol = type.getSymbol(); if (symbol === undefined || (symbol.flags & ts.SymbolFlags.Class) === 0) return null; const decl = symbol.declarations.find((d) => ts.isClassDeclaration(d) || ts.isClassExpression(d)); - if (decl === undefined || decl.getSourceFile().isDeclarationFile) return null; + if (decl === undefined || ts.getSourceFileOfNode(decl).isDeclarationFile) return null; return decl as ts.ClassLikeDeclaration; } diff --git a/packages/compiler/src/frontend/lowering/dyn-streams.ts b/packages/compiler/src/frontend/lowering/dyn-streams.ts index 9694e3820..51bcf0ea8 100644 --- a/packages/compiler/src/frontend/lowering/dyn-streams.ts +++ b/packages/compiler/src/frontend/lowering/dyn-streams.ts @@ -34,14 +34,18 @@ export function nodeStreamInterfaceSide(lowerer: Lowerer, type: ts.Type): "r" | (t.flags & ts.TypeFlags.Intersection) !== 0 ? ts.constituentTypes(t).flatMap(parts) : [t]; for (const part of parts(lowerer.checker.getNonNullableType(type))) { const psym = part.getSymbol() ?? part.getAliasSymbol(); - if (psym?.name !== "ReadableStream" && psym?.name !== "WritableStream") continue; + if ( + (psym === undefined || ts.symbolName(psym) !== "ReadableStream") && + (psym === undefined || ts.symbolName(psym) !== "WritableStream") + ) + continue; const declared = psym.declarations.some( (d) => ts.isInterfaceDeclaration(d) && - lowerer.isStdlibFile(d.getSourceFile()) && + lowerer.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientNamespace(d, "NodeJS"), ); - if (declared) return psym.name === "ReadableStream" ? "r" : "w"; + if (declared) return ts.symbolName(psym) === "ReadableStream" ? "r" : "w"; } return null; } @@ -161,7 +165,7 @@ export function lowerDynStreamMemberCall( kind: "dynInvoke", recv: receiverRef, method: access.name.text, - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args: call.arguments.nodes.map((arg) => lowerer.lowerExprExpecting(arg, DYN)), type: DYN, loc, diff --git a/packages/compiler/src/frontend/lowering/expressions/array-literals.ts b/packages/compiler/src/frontend/lowering/expressions/array-literals.ts index d311d6d79..a80367dd0 100644 --- a/packages/compiler/src/frontend/lowering/expressions/array-literals.ts +++ b/packages/compiler/src/frontend/lowering/expressions/array-literals.ts @@ -83,7 +83,7 @@ export function lowerArrayLiteral( // structurally equal checker types. Their native class ABIs still differ. if ( !expected && - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && mapped?.kind === "array" && mapped.elem.kind === "classval" ) @@ -202,7 +202,7 @@ export function lowerArrayLiteral( // keep the checked-dynamic fallback below. if ( lowerer.implicitParamTypes !== null && - npmStaticPackageOfPath(expr.getSourceFile().fileName) !== null && + npmStaticPackageOfPath(ts.getSourceFileOfNode(expr).fileName) !== null && (mapped === null || mapped.kind === "dyn" || (mapped.kind === "array" && mapped.elem.kind === "dyn")) && @@ -222,7 +222,7 @@ export function lowerArrayLiteral( mapped = arrayOf(first.type); } } - if (!expected && isJsSourceFile(expr.getSourceFile()) && mapped?.kind === "array") { + if (!expected && isJsSourceFile(ts.getSourceFileOfNode(expr)) && mapped?.kind === "array") { const elem = mapped.elem; const arms = elem.kind === "union" ? lowerer.unions.get(elem.unionId)?.arms : [elem]; if (arms?.length && arms.every((arm) => arm.kind === "classval")) mapped = DYN; @@ -234,7 +234,7 @@ export function lowerArrayLiteral( } if ( !expected && - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && expr.elements.nodes.some( (element) => ts.isSpreadElement(element) && @@ -247,7 +247,7 @@ export function lowerArrayLiteral( // only in a checked-dynamic array: a static element type would copy it. if ( !expected && - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && mapped?.kind !== "dyn" && expr.elements.nodes.some( (element) => !ts.isSpreadElement(element) && jsHoldsDynObject(lowerer, element), @@ -269,7 +269,7 @@ export function lowerArrayLiteral( // real keys. A structural union can otherwise drop descriptor fields. if ( !expected && - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && mapped?.kind === "array" && mapped.elem.kind === "union" && expr.elements.nodes.some(ts.isObjectLiteralExpression) @@ -316,7 +316,7 @@ export function lowerArrayLiteral( // to DYN wholesale now (mapType's dyn-element array rule), so the // literal IS the dyn array. if ( - isJsSourceFile(expr.getSourceFile()) || + isJsSourceFile(ts.getSourceFileOfNode(expr)) || mapped?.kind === "dyn" || lowerer.checkerAnyArray(expr) ) { diff --git a/packages/compiler/src/frontend/lowering/expressions/binary.ts b/packages/compiler/src/frontend/lowering/expressions/binary.ts index 8ef631f60..07d983cc0 100644 --- a/packages/compiler/src/frontend/lowering/expressions/binary.ts +++ b/packages/compiler/src/frontend/lowering/expressions/binary.ts @@ -531,7 +531,7 @@ export function lowerNativeFunctionAssignment( ): IrExpr | null { const target = expr.left; if ( - !isJsSourceFile(expr.getSourceFile()) || + !isJsSourceFile(ts.getSourceFileOfNode(expr)) || (!ts.isPropertyAccessExpression(target) && !ts.isElementAccessExpression(target)) || target.questionDotToken ) @@ -903,7 +903,7 @@ export function lowerCompoundValueToTarget( // catchable TypeError, as the expanded assignment does). if ( (target.type.kind === "dyn" || rhs.type.kind === "dyn") && - (isJsSourceFile(expr.getSourceFile()) || + (isJsSourceFile(ts.getSourceFileOfNode(expr)) || (lowerer.dynConvertible(read.type) && lowerer.dynConvertible(rhs.type))) ) { const left = lowerer.coerceInto(expr.left, read, DYN); @@ -1142,7 +1142,7 @@ export function lowerBinary(lowerer: Lowerer, expr: ts.BinaryExpression): IrExpr recv && (recv.type.kind === "dyn" || (recv.type.kind === "array" && - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && lowerer.dynConvertible(recv.type))) ) { return lowerDynMemberAssignment( @@ -1534,7 +1534,7 @@ export function lowerBinary(lowerer: Lowerer, expr: ts.BinaryExpression): IrExpr // Numeric operators dispatch on ToNumeric's actual Number/BigInt // result, including values flowing through native callable properties. if ( - isJsSourceFile(expr.getSourceFile()) || + isJsSourceFile(ts.getSourceFileOfNode(expr)) || op === ts.SyntaxKind.PlusToken || [ ts.SyntaxKind.LessThanToken, @@ -2409,7 +2409,7 @@ function lowerLogicalPair(lowerer: Lowerer, expr: ts.BinaryExpression, left: IrE } if ( - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && op === ts.SyntaxKind.AmpersandAmpersandToken && left.type.kind === "union" && lowerer.unions.get(left.type.unionId)?.arms.some((arm) => arm.kind === "bool") && @@ -2457,7 +2457,7 @@ function lowerLogicalPair(lowerer: Lowerer, expr: ts.BinaryExpression, left: IrE op === ts.SyntaxKind.AmpersandAmpersandToken && target === null && lowerer.implicitParamTypes !== null && - npmStaticPackageOfPath(expr.getSourceFile().fileName) !== null && + npmStaticPackageOfPath(ts.getSourceFileOfNode(expr).fileName) !== null && (lowerer.typeOf(expr).flags & ts.TypeFlags.Any) !== 0 && left.type.kind === "union" ) { @@ -2618,7 +2618,7 @@ function lowerLogicalPair(lowerer: Lowerer, expr: ts.BinaryExpression, left: IrE // When both operands have native checked representations, preserve // the deciding value and short-circuit evaluation in that domain. if ( - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && lowerer.dynConvertible(left.type) && lowerer.dynConvertible(right.type) ) { diff --git a/packages/compiler/src/frontend/lowering/expressions/element-access.ts b/packages/compiler/src/frontend/lowering/expressions/element-access.ts index f39b7c428..7ef4b9f25 100644 --- a/packages/compiler/src/frontend/lowering/expressions/element-access.ts +++ b/packages/compiler/src/frontend/lowering/expressions/element-access.ts @@ -231,7 +231,7 @@ export function lowerElementAccess(lowerer: Lowerer, expr: ts.ElementAccessExpre let receiverIr = neverTaintedJsType(lowerer, expr.expression, lowerer.typeOf(expr.expression)) ? null : lowerer.mapTypeOf(lowerer.typeOf(expr.expression)); - if (isJsSourceFile(expr.getSourceFile())) { + if (isJsSourceFile(ts.getSourceFileOfNode(expr))) { const receiver = tryLowerExpression(lowerer, expr.expression); if (receiver?.type.kind === "dyn") { const key = lowerer.lowerExprExpecting(expr.argumentExpression, DYN); @@ -316,7 +316,7 @@ export function lowerElementAccess(lowerer: Lowerer, expr: ts.ElementAccessExpre locOf(expr.argumentExpression), expr.argumentExpression, ); - if (isJsSourceFile(expr.getSourceFile()) && recv.type.kind === "bytes") { + if (isJsSourceFile(ts.getSourceFileOfNode(expr)) && recv.type.kind === "bytes") { return { kind: "dynKeyGet", value: lowerer.coerceInto(expr.expression, recv, DYN), @@ -565,7 +565,7 @@ export function lowerElementAccess(lowerer: Lowerer, expr: ts.ElementAccessExpre if ( receiver?.type.kind === "dyn" || (receiver && - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && (receiver.type.kind === "union" || receiver.type.kind === "bytes") && lowerer.dynConvertible(receiver.type)) ) { @@ -769,7 +769,7 @@ export function lowerElementAccess(lowerer: Lowerer, expr: ts.ElementAccessExpre if ( arr.type.kind === "array" && (index.type.kind === "dyn" || - (isJsSourceFile(expr.getSourceFile()) && + (isJsSourceFile(ts.getSourceFileOfNode(expr)) && index.type.kind !== "f64" && lowerer.dynConvertible(index.type))) ) { @@ -822,7 +822,7 @@ export function lowerElementAccess(lowerer: Lowerer, expr: ts.ElementAccessExpre arr.type.kind === "array" && arr.kind === "arrIntrinsic" && arr.method === "slice" && - npmStaticPackageOfPath(expr.getSourceFile().fileName) !== null + npmStaticPackageOfPath(ts.getSourceFileOfNode(expr).fileName) !== null ) { const safe = lowerSafeIndexRead(lowerer, arr, index, locOf(expr)); if (safe) return safe; @@ -1306,7 +1306,11 @@ export function lowerRecordKeyRead( ) { declared = shape.fields[0]!.type; } - if (!declared && isJsSourceFile(expr.getSourceFile()) && recordKeyResultOk(lowerer, shape, DYN)) + if ( + !declared && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && + recordKeyResultOk(lowerer, shape, DYN) + ) declared = DYN; if (!declared) { lowerer.unsupported( @@ -1425,7 +1429,7 @@ export function lowerElementCompoundParts( if (isKeyedFieldAccess(lowerer, target)) return { expr: lowerFieldCompoundValue(lowerer, target, op, expr.right, loc), stmts: null }; const checked = lowerer.lowerExpr(target.expression); - if (checked?.type.kind === "dyn" && isJsSourceFile(expr.getSourceFile())) { + if (checked?.type.kind === "dyn" && isJsSourceFile(ts.getSourceFileOfNode(expr))) { const stmts: IrStmt[] = []; const save = (value: IrExpr, name: string): IrExpr => { const local = lowerer.declareHiddenLocal(name, value.type); @@ -1516,7 +1520,7 @@ export function lowerElementCompoundParts( const rhs = lowerer.lowerExpr(expr.right); const numericRhs = rhs.type.kind === "dyn" && - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && receiver.type.kind === "bytes" ? { kind: "libCall" as const, fn: "dyn.toNumberCoerce" as const, args: [rhs], type: F64, loc } : lowerOptionalNumber(lowerer, rhs, loc); @@ -1566,7 +1570,7 @@ export function lowerElementCompoundParts( type: F64, loc, }; - } else if (valueType.kind === "dyn" && isJsSourceFile(expr.getSourceFile())) { + } else if (valueType.kind === "dyn" && isJsSourceFile(ts.getSourceFileOfNode(expr))) { const left = lowerer.coerceToExpected(oldRef(), DYN), right = lowerer.coerceToExpected(rhs, DYN); computed = dynCompoundValue(op, left, right, loc); @@ -1700,7 +1704,7 @@ export function lowerElementWrite(lowerer: Lowerer, expr: ts.BinaryExpression): loc: locOf(expr), }; if ( - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && receiver.type.kind === "object" && lowerer.dynConvertible(receiver.type) ) { @@ -1724,7 +1728,7 @@ export function lowerElementWrite(lowerer: Lowerer, expr: ts.BinaryExpression): loc: locOf(expr), }; } - if (checkedReceiver?.type.kind === "object" && isJsSourceFile(expr.getSourceFile())) { + if (checkedReceiver?.type.kind === "object" && isJsSourceFile(ts.getSourceFileOfNode(expr))) { const info = lowerer.classes.get(checkedReceiver.type.className); const key = recordKeyLiteralText(target.argumentExpression) ?? @@ -1739,7 +1743,7 @@ export function lowerElementWrite(lowerer: Lowerer, expr: ts.BinaryExpression): } } let receiverIr = lowerer.mapTypeOf(lowerer.typeOf(target.expression)); - if (isJsSourceFile(expr.getSourceFile()) && checkedReceiver) { + if (isJsSourceFile(ts.getSourceFileOfNode(expr)) && checkedReceiver) { const represented = checkedReceiver.type.kind === "union" ? lowerer.stripUndefinedArm(checkedReceiver.type) diff --git a/packages/compiler/src/frontend/lowering/expressions/identifiers.ts b/packages/compiler/src/frontend/lowering/expressions/identifiers.ts index c2e5ddcbb..b5f99d682 100644 --- a/packages/compiler/src/frontend/lowering/expressions/identifiers.ts +++ b/packages/compiler/src/frontend/lowering/expressions/identifiers.ts @@ -133,7 +133,7 @@ export function lowerIdentifierExpr(lowerer: Lowerer, expr: ts.Identifier, loc: ); } if (projection?.kind === "builtin-function") { - if (!isJsSourceFile(expr.getSourceFile())) { + if (!isJsSourceFile(ts.getSourceFileOfNode(expr))) { const callable = lowerer.lowerBuiltinCallableValue( { module: projection.module, member: projection.member }, loc, @@ -149,7 +149,7 @@ export function lowerIdentifierExpr(lowerer: Lowerer, expr: ts.Identifier, loc: // `EventEmitter`, `Readable` or `TypeError` (or a const alias of // one) as a value in a TypeScript file: the runtime class's class // object. JavaScript files keep the identity token. - if (!isJsSourceFile(expr.getSourceFile())) { + if (!isJsSourceFile(ts.getSourceFileOfNode(expr))) { const runtimeClass = runtimeClassValueOf(lowerer, expr); if (runtimeClass) return lowerer.classValueRef(runtimeClass, expr); } @@ -187,7 +187,7 @@ export function lowerIdentifierExpr(lowerer: Lowerer, expr: ts.Identifier, loc: const clause = decl.parent.parent.parent; if ( (ts.isCaseClause(clause) || ts.isDefaultClause(clause)) && - !(expr.getStart() >= clause.getStart() && expr.end <= clause.end) + !(ts.tokenStartOfNode(expr) >= ts.tokenStartOfNode(clause) && expr.end <= clause.end) ) { lowerer.unsupported( "SC1090", @@ -385,7 +385,7 @@ export function lowerIdentifierExpr(lowerer: Lowerer, expr: ts.Identifier, loc: (expr.text === "__dirname" || expr.text === "__filename") && lowerer.isStdlibGlobal(expr, expr.text) ) { - const sf = expr.getSourceFile(); + const sf = ts.getSourceFileOfNode(expr); if (isNodeEsmFile(sf, lowerer.program) && !/\.tsx?$/.test(sf.fileName)) { return { kind: "libCall", @@ -552,7 +552,7 @@ export function lowerIdentifierExpr(lowerer: Lowerer, expr: ts.Identifier, loc: // JavaScript sources: an otherwise unsupported builtin VALUE is // the same identity-token story as stdlib globals above (the // harness adds worker_threads.Worker to its identity Set). - if (isJsSourceFile(expr.getSourceFile())) { + if (isJsSourceFile(ts.getSourceFileOfNode(expr))) { // EventEmitter and the stream classes are the runtime's untyped // constructors, so constructor functions can inherit from them. const classKind = builtinMemberClassKind(bi.module, bi.member); @@ -652,7 +652,7 @@ export function lowerIdentifierExpr(lowerer: Lowerer, expr: ts.Identifier, loc: // type-mapper.ts pins the one concrete signature `(value: string) => // primitive`; direct calls `String(x)` never reach here — the call // lowering intercepts them with the wider static coercions). - const jsPrimitiveCtorSelector = isJsSourceFile(expr.getSourceFile()); + const jsPrimitiveCtorSelector = isJsSourceFile(ts.getSourceFileOfNode(expr)); const primitiveName = stdlibGlobalNameOf(lowerer, expr); if ( primitiveName === "String" || @@ -664,7 +664,7 @@ export function lowerIdentifierExpr(lowerer: Lowerer, expr: ts.Identifier, loc: } if (lowerer.isStdlibGlobal(expr, "ArrayBuffer")) return { kind: "libCall", fn: "arrayBuffer.constructor", args: [], type: DYN, loc }; - if (isJsSourceFile(expr.getSourceFile()) && lowerer.isStdlibGlobal(expr, "Array")) + if (isJsSourceFile(ts.getSourceFileOfNode(expr)) && lowerer.isStdlibGlobal(expr, "Array")) return { kind: "libCall", fn: "dyn.arrayConstructor", args: [], type: DYN, loc }; if ( Object.values(BYTES_ELEMENT_NAME).includes(expr.text) && @@ -763,10 +763,10 @@ export function lowerIdentifierExpr(lowerer: Lowerer, expr: ts.Identifier, loc: expr, canonical, loc, - isJsSourceFile(expr.getSourceFile()), + isJsSourceFile(ts.getSourceFileOfNode(expr)), ); if (builtinValue !== null) return builtinValue; - if (isJsSourceFile(expr.getSourceFile())) { + if (isJsSourceFile(ts.getSourceFileOfNode(expr))) { return { kind: "strLit", value: `[builtin ${canonical}]`, type: STRING, loc }; } // The families with a WHY: each hint states what makes the @@ -986,7 +986,7 @@ function cjsExportAccessorRead(lowerer: Lowerer, ident: ts.Identifier): IrExpr | if ( binding !== undefined && (ts.isImportSpecifier(binding) || ts.isExportSpecifier(binding)) && - !isCjsJsFile(binding.getSourceFile(), lowerer.program) + !isCjsJsFile(ts.getSourceFileOfNode(binding), lowerer.program) ) { const snapshot = cjsAccessorSnapshotOf(lowerer, getter); if (snapshot !== null) diff --git a/packages/compiler/src/frontend/lowering/expressions/keyed-field-targets.ts b/packages/compiler/src/frontend/lowering/expressions/keyed-field-targets.ts index 7a4f9c557..e53d27aa9 100644 --- a/packages/compiler/src/frontend/lowering/expressions/keyed-field-targets.ts +++ b/packages/compiler/src/frontend/lowering/expressions/keyed-field-targets.ts @@ -44,7 +44,7 @@ export function literalFieldKeys(lowerer: Lowerer, keyNode: ts.Expression): stri export function isKeyedFieldAccess(lowerer: Lowerer, access: ts.ElementAccessExpression): boolean { if ( access.questionDotToken || - isJsSourceFile(access.getSourceFile()) || + isJsSourceFile(ts.getSourceFileOfNode(access)) || lowerer.isProcessEnv(access.expression) ) return false; diff --git a/packages/compiler/src/frontend/lowering/expressions/narrowing.ts b/packages/compiler/src/frontend/lowering/expressions/narrowing.ts index 9f98ac2db..dec06fdd9 100644 --- a/packages/compiler/src/frontend/lowering/expressions/narrowing.ts +++ b/packages/compiler/src/frontend/lowering/expressions/narrowing.ts @@ -92,7 +92,7 @@ export function lowerTernary( ) { falseArmNarrows.push(n); } - n.forEachChild(collect); + ts.forEachChild(n, collect); }; collect(expr.whenFalse); } @@ -333,7 +333,7 @@ export function lowerTernary( // call ABI. A checker join can erase a rest/arguments pack even though // a later call supplies additional observable arguments. const callableJoin = - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && thenRaw.type.kind === "func" && elseRaw.type.kind === "func" && !typeEquals(thenRaw.type, elseRaw.type) && @@ -343,7 +343,7 @@ export function lowerTernary( // a conditional's type even after that field acquired other values. // A checked arm must retain its actual representation at the join. const checkedJsJoin = - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && (thenRaw.type.kind === "dyn" || elseRaw.type.kind === "dyn") && lowerer.dynConvertible(thenRaw.type) && lowerer.dynConvertible(elseRaw.type); @@ -385,7 +385,7 @@ export function lowerTernary( export function maybeNarrow(lowerer: Lowerer, expr: IrExpr, node: ts.Node): IrExpr { if ( (expr.type.kind === "union" || expr.type.kind === "dyn") && - isJsSourceFile(node.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(node)) && ts.isPropertyAccessExpression(node) ) { const symbol = lowerer.checker.getSymbolAtLocation(node.name); @@ -396,7 +396,7 @@ export function maybeNarrow(lowerer: Lowerer, expr: IrExpr, node: ts.Node): IrEx ts.isPropertyDeclaration(decl) && !decl.initializer && !decl.type && - isJsSourceFile(decl.getSourceFile()), + isJsSourceFile(ts.getSourceFileOfNode(decl)), ) ) { // Inference describes later writes, not the initial undefined. @@ -416,7 +416,7 @@ export function maybeNarrow(lowerer: Lowerer, expr: IrExpr, node: ts.Node): IrEx // An inferred JS member's storage may be deliberately wider than its // declaration (prototype-selected buffers, null-initialized scratch // objects). Extract at the consuming operation, not at the read. - if (isJsSourceFile(node.getSourceFile()) && ts.isPropertyAccessExpression(node)) { + if (isJsSourceFile(ts.getSourceFileOfNode(node)) && ts.isPropertyAccessExpression(node)) { const receiver = lowerer.mapTypeOf(lowerer.typeOf(node.expression)); if (receiver?.kind === "object") { const info = lowerer.classes.get(receiver.className); @@ -432,7 +432,7 @@ export function maybeNarrow(lowerer: Lowerer, expr: IrExpr, node: ts.Node): IrEx } } if ( - isJsSourceFile(node.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(node)) && (ts.isPropertyAccessExpression(node) || ts.isElementAccessExpression(node)) ) { let receiver = node.expression; @@ -461,7 +461,7 @@ export function maybeNarrow(lowerer: Lowerer, expr: IrExpr, node: ts.Node): IrEx if (bound && lowerer.mapTypeOf(bound)?.kind === "dyn") return expr; } } - if (isJsSourceFile(node.getSourceFile()) && ts.isIdentifier(node)) { + if (isJsSourceFile(ts.getSourceFileOfNode(node)) && ts.isIdentifier(node)) { const symbol = lowerer.resolveValueSymbol(node); if ( symbol && @@ -495,7 +495,9 @@ export function maybeNarrow(lowerer: Lowerer, expr: IrExpr, node: ts.Node): IrEx decl.initializer && !decl.type && !/@(?:param|type)\b/.test( - decl.getSourceFile().text.slice(decl.parent?.pos ?? decl.pos, decl.getStart()), + ts + .getSourceFileOfNode(decl) + .text.slice(decl.parent?.pos ?? decl.pos, ts.tokenStartOfNode(decl)), ), ) ) @@ -522,7 +524,7 @@ export function maybeNarrow(lowerer: Lowerer, expr: IrExpr, node: ts.Node): IrEx // has no such property. Preserve the checked read for JS capability // probes instead of treating a declaration as a runtime type guard. if ( - isJsSourceFile(node.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(node)) && ts.isPropertyAccessExpression(node) && ["isTTY", "columns", "rows"].includes(node.name.text) && lowerer.isStdlibMember(node) @@ -532,7 +534,7 @@ export function maybeNarrow(lowerer: Lowerer, expr: IrExpr, node: ts.Node): IrEx // checker flow calls its non-null branch never. The live checked // value remains authoritative; never is not a numeric guard. if ( - isJsSourceFile(node.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(node)) && (lowerer.typeOf(node).flags & ts.TypeFlags.Never) !== 0 ) return expr; @@ -839,11 +841,11 @@ export function v8CalleeText(node: ts.Node): string { const object = v8CalleeText(node.expression); return ts.isStringLiteral(key) || ts.isNoSubstitutionTemplateLiteral(key) ? `${object}.${key.text}` - : `${object}[${key.getText()}]`; + : `${object}[${ts.getTextOfNode(key)}]`; } if (ts.isPropertyAccessExpression(node) && !node.questionDotToken) return `${v8CalleeText(node.expression)}.${node.name.text}`; - return node.getText(); + return ts.getTextOfNode(node); } /** Node's TypeError for the operation consuming `node` when it holds a diff --git a/packages/compiler/src/frontend/lowering/expressions/native-proxy.ts b/packages/compiler/src/frontend/lowering/expressions/native-proxy.ts index e3b9bf025..51b7f4028 100644 --- a/packages/compiler/src/frontend/lowering/expressions/native-proxy.ts +++ b/packages/compiler/src/frontend/lowering/expressions/native-proxy.ts @@ -25,7 +25,7 @@ export function lowerNativeProxy(lowerer: Lowerer, expr: ts.NewExpression): IrEx if (args.length !== 2 || args.some(ts.isSpreadElement)) lowerer.noLowering("new Proxy with this argument list", expr); const handler = args[1]!; - if (isJsSourceFile(expr.getSourceFile()) && ts.isObjectLiteralExpression(handler)) { + if (isJsSourceFile(ts.getSourceFileOfNode(expr)) && ts.isObjectLiteralExpression(handler)) { for (const prop of handler.properties.nodes) { const callback = ts.isMethodDeclaration(prop) ? prop diff --git a/packages/compiler/src/frontend/lowering/expressions/object-literals.ts b/packages/compiler/src/frontend/lowering/expressions/object-literals.ts index 77c4c8fc5..303b6ed55 100644 --- a/packages/compiler/src/frontend/lowering/expressions/object-literals.ts +++ b/packages/compiler/src/frontend/lowering/expressions/object-literals.ts @@ -276,7 +276,7 @@ export function lowerDynObjectLiteral( const raw = builtin === "path" || builtin === "path/posix" || builtin === "path/win32" ? pathModuleValue(lowerer, builtin === "path" ? "path/posix" : builtin, locOf(prop)) - : isJsSourceFile(expr.getSourceFile()) + : isJsSourceFile(ts.getSourceFileOfNode(expr)) ? lowerer.lowerExprExpecting(prop.expression, DYN) : lowerer.lowerExpr(prop.expression); fenceSymbolFieldCopy(lowerer, prop.expression, raw.type); @@ -410,7 +410,7 @@ export function lowerDynObjectLiteral( // Repeated references must compare equal and mutations remain shared. if ( !boxValue && - isJsSourceFile(prop.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(prop)) && v.kind === "dynFrom" && v.value.type.kind === "array" ) { @@ -495,7 +495,7 @@ function fenceClosureProbe( slotType: IrType | undefined, attempt: () => IrExpr, ): IrExpr | null { - if (!isJsSourceFile(node.getSourceFile())) return null; + if (!isJsSourceFile(ts.getSourceFileOfNode(node))) return null; let inner: ts.Node = node; while (ts.isParenthesizedExpression(inner)) inner = inner.expression; if ( @@ -580,7 +580,10 @@ function literalUnionArmOf( if (own.isUnionType()) return null; props.length = 0; for (const property of lowerer.checker.getPropertiesOfType(own)) { - props.push({ name: property.name, type: lowerer.checker.getTypeOfSymbol(property) }); + props.push({ + name: ts.symbolName(property), + type: lowerer.checker.getTypeOfSymbol(property), + }); } } /** Cheap literal discriminants avoid checker traffic for unrelated arms. @@ -766,7 +769,7 @@ export function lowerObjectLiteral( // Preserve their checked arrays instead of assigning never[] a numeric ABI. if ( !expected && - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && expr.properties.nodes.some( (prop) => ts.isPropertyAssignment(prop) && @@ -779,7 +782,7 @@ export function lowerObjectLiteral( } if ( !expected && - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && expr.properties.nodes.some( (prop) => ts.isPropertyAssignment(prop) && @@ -794,7 +797,7 @@ export function lowerObjectLiteral( // the represented binding rather than checking it back into that type. if ( !expected && - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && expr.properties.nodes.some((prop) => { let value = ts.isPropertyAssignment(prop) ? prop.initializer @@ -822,7 +825,7 @@ export function lowerObjectLiteral( // JavaScript object methods/accessors carry a live receiver. Keep the // literal in checked native storage instead of erasing its prototype ABI. if ( - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && (expr.properties.nodes.length === 0 || expr.properties.nodes.some( (prop) => @@ -838,7 +841,7 @@ export function lowerObjectLiteral( // binding. Construct in that representation: an intermediate record would // check nested aliases back into inferred shapes and lose their identity. if ( - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && ts.isVariableDeclaration(expr.parent) && expr.parent.initializer === expr && ts.isIdentifier(expr.parent.name) && @@ -850,7 +853,7 @@ export function lowerObjectLiteral( // methods absent from its inferred record shape. Copy the real properties. if ( !expected && - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && expr.properties.nodes.some( (property) => ts.isSpreadAssignment(property) && @@ -870,7 +873,7 @@ export function lowerObjectLiteral( // values. TypeScript keeps the record world and its fence: this shape // only arises in checked-dynamic JS. if ( - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && expr.properties.nodes.some((p) => { const n = ts.isSpreadAssignment(p) ? undefined : p.name; return n !== undefined && ts.isComputedPropertyName(n); @@ -917,7 +920,7 @@ export function lowerObjectLiteral( // receiver (capturing the record under construction would be an // RC cycle), and the generic lexical-this walk would incorrectly // capture an ENCLOSING method's `this` — the object-method rule. - if (!isJsSourceFile(expr.getSourceFile())) { + if (!isJsSourceFile(ts.getSourceFileOfNode(expr))) { rejectThisInObjectAccessor(lowerer, prop.body ?? prop); } } @@ -993,7 +996,7 @@ export function lowerObjectLiteral( // `null`, which maps to nothing on its own). if ( tsType.isUnionType() && - ts.constituentTypes(tsType).some((t) => t.getSymbol()?.name === "PromiseLike") + ts.constituentTypes(tsType).some((t) => ts.symbolNameOf(t.getSymbol()) === "PromiseLike") ) { tsType = lowerer.checker.getAwaitedType(tsType) ?? tsType; } @@ -1014,7 +1017,7 @@ export function lowerObjectLiteral( // A JavaScript call can contextually type an object literal as string even // though the literal itself is a record. Keep its own shape so a caller // performing ToString can convert it after the value is built. - if (mapped?.kind === "string" && isJsSourceFile(expr.getSourceFile())) { + if (mapped?.kind === "string" && isJsSourceFile(ts.getSourceFileOfNode(expr))) { const own = lowerer.mapTypeOf(lowerer.typeOf(expr)); if (own?.kind === "record") mapped = own; } @@ -1147,7 +1150,7 @@ export function lowerObjectLiteral( // (rest-param function types) narrow away. SEMANTICS.md documents the // enumeration divergence. if ( - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && (!mapped || expr.properties.nodes.some( (p) => ts.isGetAccessorDeclaration(p) || ts.isSetAccessorDeclaration(p), @@ -1187,7 +1190,7 @@ export function lowerObjectLiteral( // consumers ride the keyed-dyn paths. TypeScript keeps the fence. if ( mapped?.kind === "dyn" || - (!mapped && isJsSourceFile(expr.getSourceFile())) || + (!mapped && isJsSourceFile(ts.getSourceFileOfNode(expr))) || (!expected && mapped && jsOpenObjectType(expr, mapped, lowerer.shapes, lowerer.unions).kind === "dyn") @@ -1203,7 +1206,7 @@ export function lowerObjectLiteral( // extra fields, spreads, accessors, and keyed shapes retain their paths. if ( expected && - !isJsSourceFile(expr.getSourceFile()) && + !isJsSourceFile(ts.getSourceFileOfNode(expr)) && expr.properties.nodes.every( (p) => ts.isPropertyAssignment(p) || ts.isShorthandPropertyAssignment(p), ) @@ -1255,7 +1258,7 @@ export function lowerObjectLiteral( // JavaScript Dates carry native object identity in checked storage. An // inferred scalar Date field would erase that identity before boxing. if ( - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && shape.fields.some((field) => field.type.kind === "date") ) { return lowerDynObjectLiteral(lowerer, expr); @@ -1265,7 +1268,7 @@ export function lowerObjectLiteral( // so the literal's shape NARROWS to its plain fields (reads resolve // through the lifted accessors; Object.keys over the value omits // accessor names — the documented divergence). - if (isJsSourceFile(expr.getSourceFile())) { + if (isJsSourceFile(ts.getSourceFileOfNode(expr))) { const accessorNames = new Set( expr.properties.nodes .filter((p) => ts.isGetAccessorDeclaration(p) || ts.isSetAccessorDeclaration(p)) @@ -1301,7 +1304,7 @@ export function lowerObjectLiteral( ); if ( topLevelJsDecl && - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && assignedProperties.length === expr.properties.nodes.length ) { const provided = new Set(assignedProperties.map((prop) => propNameText(lowerer, prop.name))); @@ -1328,7 +1331,7 @@ export function lowerObjectLiteral( // multiple spreads, spread-after-explicit order, and shape-changing width // copies retain their existing lowering and diagnostics. if ( - !isJsSourceFile(expr.getSourceFile()) && + !isJsSourceFile(ts.getSourceFileOfNode(expr)) && !shape.indexValue && !shape.tuple && !shapeHasAccessorSlots(shape) && @@ -2219,7 +2222,7 @@ export function lowerObjectLiteral( // closures is side-effect-free, so their position among the data // fields' source-order evaluation is unobservable — exactly JS, // where accessor definitions evaluate nothing. - if (isJsSourceFile(expr.getSourceFile())) continue; + if (isJsSourceFile(ts.getSourceFileOfNode(expr))) continue; const name = propNameText(lowerer, prop.name); // key-form-checked above const slotName = `${ts.isGetAccessorDeclaration(prop) ? "%get" : "%set"}:${name}`; const slotT = fieldTypes.get(slotName); @@ -2341,7 +2344,7 @@ export function lowerObjectLiteral( if ( !(err instanceof PoisonError) || !pureMember || - !isJsSourceFile(expr.getSourceFile()) || + !isJsSourceFile(ts.getSourceFileOfNode(expr)) || lowerer.diagSink !== null || lowerer.diags.length <= propDiagsBefore || lowerer.diags.slice(propDiagsBefore).some((d) => d.code === "SC9001") @@ -2436,7 +2439,7 @@ export function lowerObjectLiteral( * their identity. Changing a discriminator or adding variant-specific * fields needs a different result layout and stays on the ordinary path. */ function lowerUnionRecordClone(lowerer: Lowerer, expr: ts.ObjectLiteralExpression): IrExpr | null { - if (isJsSourceFile(expr.getSourceFile())) return null; + if (isJsSourceFile(ts.getSourceFileOfNode(expr))) return null; const spread = expr.properties.nodes[0]; if (!spread || !ts.isSpreadAssignment(spread)) return null; let sourceNode = spread.expression; diff --git a/packages/compiler/src/frontend/lowering/expressions/optional-chains.ts b/packages/compiler/src/frontend/lowering/expressions/optional-chains.ts index 995514a4d..5dc512a6c 100644 --- a/packages/compiler/src/frontend/lowering/expressions/optional-chains.ts +++ b/packages/compiler/src/frontend/lowering/expressions/optional-chains.ts @@ -92,7 +92,7 @@ export function isRequireMainFilename(lowerer: Lowerer, expr: ts.Expression): bo const main = expr.expression; if (!ts.isPropertyAccessExpression(main) || main.name.text !== "main") return false; if (!lowerer.isStdlibGlobal(main.expression, "require")) return false; - return !isNodeEsmFile(expr.getSourceFile(), lowerer.program); + return !isNodeEsmFile(ts.getSourceFileOfNode(expr), lowerer.program); } export function isOptionalChainTail(lowerer: Lowerer, expr: ts.Expression): boolean { @@ -257,14 +257,14 @@ export function lowerOptionalChain( const spreads: { arg: number; what: string }[] = []; const args = expr.arguments.nodes.map((arg, index) => { if (!ts.isSpreadElement(arg)) return lowerer.lowerExprExpecting(arg, DYN); - spreads.push({ arg: index, what: arg.expression.getText() }); + spreads.push({ arg: index, what: ts.getTextOfNode(arg.expression) }); return lowerer.lowerExprExpecting(arg.expression, DYN); }); const body: IrExpr = { kind: "dynCall", callee, ...(thisValue ? { receiver: thisValue } : {}), - calleeName: recvNode.getText(), + calleeName: ts.getTextOfNode(recvNode), args, ...(spreads.length ? { spreads } : {}), type: DYN, diff --git a/packages/compiler/src/frontend/lowering/expressions/property-access.ts b/packages/compiler/src/frontend/lowering/expressions/property-access.ts index 941f5c3f6..36ea0e8c2 100644 --- a/packages/compiler/src/frontend/lowering/expressions/property-access.ts +++ b/packages/compiler/src/frontend/lowering/expressions/property-access.ts @@ -205,7 +205,7 @@ function isParseArgsDynProperty(lowerer: Lowerer, expr: ts.PropertyAccessExpress const sym = lowerer.checker.getSymbolAtLocation(expr.name); if (!sym) return false; return sym.declarations.some((d) => { - if (!lowerer.isStdlibFile(d.getSourceFile())) return false; + if (!lowerer.isStdlibFile(ts.getSourceFileOfNode(d))) return false; let inParseArgsType = false; for (let node: ts.Node | undefined = d.parent; node; node = node.parent) { if ( @@ -233,7 +233,7 @@ function isStatFsDynProperty(lowerer: Lowerer, expr: ts.PropertyAccessExpression return ( !!symbol && symbol.declarations.some((declaration) => { - if (!lowerer.isStdlibFile(declaration.getSourceFile())) return false; + if (!lowerer.isStdlibFile(ts.getSourceFileOfNode(declaration))) return false; const parent = declaration.parent; if (!ts.isInterfaceDeclaration(parent)) return false; if ( @@ -536,7 +536,7 @@ export function lowerPropertyAccessExpr( if ( represented && isMatchSliceType(lowerer, represented.type) && - (lowerer.isStdlibMember(expr) || isJsSourceFile(expr.getSourceFile())) + (lowerer.isStdlibMember(expr) || isJsSourceFile(ts.getSourceFileOfNode(expr))) ) { const recv = represented; const key: IrExpr = { kind: "strLit", value: expr.name.text, type: STRING, loc }; @@ -569,7 +569,7 @@ export function lowerPropertyAccessExpr( ts.isIdentifier(expr.expression) && expr.expression.text === "arguments" && !lowerer.ctx.argumentsLocal && - lowerer.typeOf(expr.expression).getSymbol()?.name === "IArguments" && + ts.symbolNameOf(lowerer.typeOf(expr.expression).getSymbol()) === "IArguments" && lowerer.isStdlibSymbol(lowerer.typeOf(expr.expression).getSymbol()) ) { let fn: ts.Node | undefined = expr.parent; @@ -640,9 +640,9 @@ export function lowerPropertyAccessExpr( if ( !expr.questionDotToken && isModuleExportsAccess(expr) && - isCjsJsFile(expr.getSourceFile(), lowerer.program) + isCjsJsFile(ts.getSourceFileOfNode(expr), lowerer.program) ) { - const whole = cjsClassExprWholeExportOf(expr.getSourceFile()); + const whole = cjsClassExprWholeExportOf(ts.getSourceFileOfNode(expr)); if (whole) { let inBody = false; for ( @@ -655,7 +655,7 @@ export function lowerPropertyAccessExpr( break; } } - if (inBody || expr.getStart() > whole.stmt.getEnd()) { + if (inBody || ts.tokenStartOfNode(expr) > whole.stmt.end) { return lowerer.classValueRef(lowerer.lowerClassExpressionInfo(whole.classExpr), expr); } } @@ -802,7 +802,7 @@ export function lowerPropertyAccessExpr( if ( !expr.questionDotToken && stdlibGlobalNameOf(lowerer, expr.expression) === "globalThis" && - isJsSourceFile(expr.getSourceFile()) + isJsSourceFile(ts.getSourceFileOfNode(expr)) ) { const canonical = stdlibGlobalNameOf(lowerer, expr); if (canonical !== null) { @@ -850,7 +850,7 @@ export function lowerPropertyAccessExpr( // them, and calls dispatch through the dyn method machinery // instead. if ( - (isJsSourceFile(expr.getSourceFile()) && + (isJsSourceFile(ts.getSourceFileOfNode(expr)) && (lowerer.mapTypeOf(lowerer.typeOf(expr.expression)) === null || (lowerer.typeOf(expr.expression).flags & ts.TypeFlags.Never) !== 0 || // A never-tainted receiver type maps (never rides as f64) but @@ -891,7 +891,7 @@ export function lowerPropertyAccessExpr( : { kind: "strIntrinsic", method: "length", receiver: recv, args: [], type: F64, loc }; } } - if (isJsSourceFile(expr.getSourceFile()) && expr.name.text === "constructor") { + if (isJsSourceFile(ts.getSourceFileOfNode(expr)) && expr.name.text === "constructor") { const receiver = tryLowerExpression(lowerer, expr.expression); if ( receiver?.type.kind === "dyn" || @@ -955,7 +955,7 @@ export function lowerPropertyAccessExpr( // storage can expose its own name and length in both source languages. if ( !["apply", "bind", "call", "toString", "caller", "arguments"].includes(expr.name.text) && - (isJsSourceFile(expr.getSourceFile()) || + (isJsSourceFile(ts.getSourceFileOfNode(expr)) || expr.name.text === "name" || expr.name.text === "length" || expr.name.text === "constructor") @@ -968,7 +968,9 @@ export function lowerPropertyAccessExpr( const receiverSymbol = receiverType.getSymbol(); const checkedFunction = lowerer.checker.getCallSignatures(receiverType).length > 0 || - (receiverSymbol?.name === "Function" && lowerer.isStdlibSymbol(receiverSymbol)); + (receiverSymbol !== undefined && + ts.symbolName(receiverSymbol) === "Function" && + lowerer.isStdlibSymbol(receiverSymbol)); if (probed?.type.kind === "dyn" && checkedFunction) { const key: IrExpr = { kind: "strLit", @@ -990,7 +992,7 @@ export function lowerPropertyAccessExpr( ); } if ( - (isJsSourceFile(expr.getSourceFile()) || expr.name.text === "constructor") && + (isJsSourceFile(ts.getSourceFileOfNode(expr)) || expr.name.text === "constructor") && probed?.type.kind === "func" && canBoxFuncIntoDyn( probed.type, @@ -1024,17 +1026,17 @@ export function lowerPropertyAccessExpr( if (expr.name.text === "done" || expr.name.text === "value") { const receiverType = lowerer.typeOf(expr.expression); const recvSym = - receiverType.getAliasSymbol()?.name === "IteratorResult" + ts.symbolNameOf(receiverType.getAliasSymbol()) === "IteratorResult" ? receiverType.getAliasSymbol() : receiverType.getSymbol(); if ( - (recvSym?.name === "IteratorResult" || - recvSym?.name === "IteratorYieldResult" || - recvSym?.name === "IteratorReturnResult") && + ((recvSym !== undefined && ts.symbolName(recvSym) === "IteratorResult") || + (recvSym !== undefined && ts.symbolName(recvSym) === "IteratorYieldResult") || + (recvSym !== undefined && ts.symbolName(recvSym) === "IteratorReturnResult")) && recvSym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isTypeAliasDeclaration(d)) && - lowerer.isStdlibFile(d.getSourceFile()), + lowerer.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { const recv = lowerer.lowerExpr(expr.expression); @@ -1085,7 +1087,8 @@ export function lowerPropertyAccessExpr( // members owned by these implemented families bypass the stdlib fence. const segmentDataSymbol = lowerer.typeOf(expr.expression).getSymbol(); const segmentDataProperty = - segmentDataSymbol?.name === "SegmentData" && + segmentDataSymbol !== undefined && + ts.symbolName(segmentDataSymbol) === "SegmentData" && lowerer.isStdlibSymbol(segmentDataSymbol) && ["segment", "index", "input", "isWordLike"].includes(expr.name.text); // os.constants members vary by platform (the declared groups list @@ -1192,7 +1195,7 @@ export function lowerPropertyAccessExpr( // validated dynCheck as usual. if ( recvLowered.type.kind === "dyn" || - (isJsSourceFile(expr.getSourceFile()) && + (isJsSourceFile(ts.getSourceFileOfNode(expr)) && (["f64", "bool", "bigint", "string", "symbol"].includes(recvLowered.type.kind) || (recvLowered.type.kind === "union" && lowerer.dynConvertible(recvLowered.type)))) ) { @@ -1260,8 +1263,8 @@ export function lowerPropertyAccessExpr( recvShape && !recvShape.tuple && lowerer.implicitParamTypes !== null && - isJsSourceFile(expr.getSourceFile()) && - npmStaticPackageOfPath(expr.getSourceFile().fileName) !== null + isJsSourceFile(ts.getSourceFileOfNode(expr)) && + npmStaticPackageOfPath(ts.getSourceFileOfNode(expr).fileName) !== null ) { return { kind: "seqExpr", @@ -1508,7 +1511,12 @@ export function lowerIntrinsicProperty( | "BigInt" )[]) { if (!lowerer.chainBlocked(expr) && lowerer.isStdlibGlobal(expr, name)) { - return primitiveCtorClosure(lowerer, name, locOf(expr), isJsSourceFile(expr.getSourceFile())); + return primitiveCtorClosure( + lowerer, + name, + locOf(expr), + isJsSourceFile(ts.getSourceFileOfNode(expr)), + ); } } if (lowerer.stdlibGlobalMember(expr, "Reflect") === "ownKeys") { @@ -1686,7 +1694,7 @@ export function lowerIntrinsicProperty( ) return null; const name = expr.name.text; - if (kind === "array" && name === "constructor" && isJsSourceFile(expr.getSourceFile())) { + if (kind === "array" && name === "constructor" && isJsSourceFile(ts.getSourceFileOfNode(expr))) { const receiver = lowerer.lowerExpr(expr.expression); if (receiver.type.kind === "dyn") return { @@ -1714,7 +1722,7 @@ export function lowerIntrinsicProperty( const strictReceiver = (expected: IrType): IrExpr => { const value = lowerer.lowerExpr(expr.expression); if (value.type.kind === "dyn") { - if (isJsSourceFile(expr.getSourceFile()) && name === "length") return value; + if (isJsSourceFile(ts.getSourceFileOfNode(expr)) && name === "length") return value; return lowerer.coerceInto(expr.expression, value, expected); } return lowerer.runtimeOptionalPropertyReceiver(expr.expression, value, expected, name) ?? value; @@ -2234,7 +2242,7 @@ function lowerReplacedExportsRead( if (expr.questionDotToken) return null; const recv = expr.expression; if (!ts.isIdentifier(recv) || recv.text !== "exports") return null; - const sf = expr.getSourceFile(); + const sf = ts.getSourceFileOfNode(expr); if (!isJsSourceFile(sf) || isNodeEsmFile(sf, lowerer.program)) return null; if (lowerer.peekLocal(recv) || lowerer.globalOf(recv)) return null; // a user binding shadows // Write position is the export-assignment machinery's territory. @@ -2271,7 +2279,7 @@ function lowerCjsExportTableThisMember( expr: ts.PropertyAccessExpression, ): IrExpr | null { if (expr.expression.kind !== ts.SyntaxKind.ThisKeyword || expr.questionDotToken) return null; - if (!isJsSourceFile(expr.getSourceFile())) return null; + if (!isJsSourceFile(ts.getSourceFileOfNode(expr))) return null; // The enclosing accessor, crossing only arrow boundaries (arrows // inherit `this`; any other function form rebinds it). let n: ts.Node | undefined = expr.parent; @@ -2471,7 +2479,8 @@ function lowerMatchGroupsRead(lowerer: Lowerer, expr: ts.PropertyAccessExpressio const groups = matchResultNamedGroupsOf(lowerer, expr.expression); // An untraced regex: TypeScript reaches the member fence; JavaScript // would otherwise read the slice's absent expando as undefined. - if (groups === null && isJsSourceFile(expr.getSourceFile())) lowerer.stdlibMemberFence(expr); + if (groups === null && isJsSourceFile(ts.getSourceFileOfNode(expr))) + lowerer.stdlibMemberFence(expr); if (groups === null) return null; if (groups.length === 0) { // Node: a match result of a group-less regex answers undefined — @@ -2488,7 +2497,7 @@ function lowerMatchGroupsRead(lowerer: Lowerer, expr: ts.PropertyAccessExpressio } const recv = lowerer.lowerExpr(expr.expression); // A JavaScript callback parameter holds the row as a dynamic array. - const dynRow = recv.type.kind === "dyn" && isJsSourceFile(expr.getSourceFile()); + const dynRow = recv.type.kind === "dyn" && isJsSourceFile(ts.getSourceFileOfNode(expr)); if (!dynRow && !isMatchSliceType(lowerer, recv.type)) return null; return lowerGroupsProjection(lowerer, recv, groups, loc); } @@ -3065,7 +3074,7 @@ export function lowerUnionProperty( // instance argument (e.g. Matrix4.makeTranslation). Preserve primitive // missing-property semantics and dispatch class data through its native // capsule instead of demanding a field shared by every union arm. - if (isJsSourceFile(expr.getSourceFile()) && lowerer.dynConvertible(value.type)) { + if (isJsSourceFile(ts.getSourceFileOfNode(expr)) && lowerer.dynConvertible(value.type)) { return { kind: "dynKeyGet", value: lowerer.coerceToExpected(value, DYN), @@ -3217,7 +3226,7 @@ export function classFieldTarget( const represented = saved ?? lowerer.lowerExpr(receiverNode); if ( represented.type.kind === "dyn" && - isJsSourceFile(receiverNode.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(receiverNode)) && !fieldName.startsWith("#") ) { return { container: "dynamic", obj: represented, field: fieldName, fieldType: DYN }; @@ -3264,7 +3273,7 @@ export function classFieldTarget( if (fieldType) { const obj = lowerObjectReceiver(); if ( - isJsSourceFile(receiverNode.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(receiverNode)) && !fieldName.startsWith("#") && [...lowerer.classes.values()].some( (candidate) => @@ -3302,7 +3311,7 @@ export function classFieldTarget( fieldType: getF ? getF.sig.ret : setF!.sig.params[0]!.type, }; } - if (info.decl && isJsSourceFile(info.decl.getSourceFile())) { + if (info.decl && isJsSourceFile(ts.getSourceFileOfNode(info.decl))) { const property = lowerer.checker.getPropertyOfType( lowerer.checker.getTypeAtLocation(receiverNode), fieldName, @@ -3521,7 +3530,7 @@ export function fieldTarget( : [declared]; return declaredArms.every((a) => ivArms.some((b) => typeEquals(a, b))); }; - if (!nameSym || nameSym.name === ts.InternalSymbolName.Index || canonicalized()) { + if (!nameSym || ts.symbolName(nameSym) === ts.InternalSymbolName.Index || canonicalized()) { let obj = lowerer.lowerExpr(access.expression); if (obj.type.kind === "union" && lowerer.armTag(obj.type.unionId, UNDEFINED_T) >= 0) { const present = lowerer.stripUndefinedArm(obj.type); @@ -3633,7 +3642,7 @@ export function fieldGetExpr( } if ( !target.field.startsWith("#") && - isJsSourceFile(blame.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(blame)) && lowerer.dynConvertible(getF.sig.ret) ) { const value: IrExpr = { @@ -3915,7 +3924,9 @@ export function builtinGlobalValueAt( )); if (reflects) { const name = - owner !== null ? `${owner}.${callee.name.text}` : callee.getText().replace(/\s+/g, ""); + owner !== null + ? `${owner}.${callee.name.text}` + : ts.getTextOfNode(callee).replace(/\s+/g, ""); lowerer.noLowering( `${name}(${canonical}, ...)`, expr, @@ -3958,8 +3969,8 @@ function fenceBuiltinConstructorStaticRead( const symbol = lowerer.typeOf(access.expression).getSymbol(); if ( symbol !== undefined && - symbol.name.endsWith("Constructor") && - GLOBAL_CONSTRUCTOR_VALUES.has(symbol.name.slice(0, -"Constructor".length)) && + ts.symbolName(symbol).endsWith("Constructor") && + GLOBAL_CONSTRUCTOR_VALUES.has(ts.symbolName(symbol).slice(0, -"Constructor".length)) && lowerer.isStdlibSymbol(symbol) ) lowerer.stdlibMemberFence(access); @@ -4036,7 +4047,7 @@ function jsFunctionExpandoMember(lowerer: Lowerer, access: ts.PropertyAccessExpr return ( decls.length > 0 && decls.every((d) => { - if (!isJsSourceFile(d.getSourceFile())) return false; + if (!isJsSourceFile(ts.getSourceFileOfNode(d))) return false; const assign = ts.isBinaryExpression(d) ? d : d.parent; return ( assign !== undefined && diff --git a/packages/compiler/src/frontend/lowering/expressions/relational.ts b/packages/compiler/src/frontend/lowering/expressions/relational.ts index 91d1468c2..f8b8c7604 100644 --- a/packages/compiler/src/frontend/lowering/expressions/relational.ts +++ b/packages/compiler/src/frontend/lowering/expressions/relational.ts @@ -470,7 +470,7 @@ export function lowerInstanceOf(lowerer: Lowerer, expr: ts.BinaryExpression, loc ); } const constructor = tryLowerExpression(lowerer, expr.right); - if (constructor?.type.kind === "dyn" && isJsSourceFile(expr.getSourceFile())) { + if (constructor?.type.kind === "dyn" && isJsSourceFile(ts.getSourceFileOfNode(expr))) { const value = lowerer.coerceInto(expr.left, lowerer.lowerExpr(expr.left), DYN); return checkedClassInstanceOf(lowerer, value, constructor, loc); } @@ -478,7 +478,7 @@ export function lowerInstanceOf(lowerer: Lowerer, expr: ts.BinaryExpression, loc // function's prototype against the value's prototype chain. if ( constructor?.type.kind === "func" && - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && lowerer.dynConvertible(constructor.type) ) { const value = lowerer.coerceInto(expr.left, lowerer.lowerExpr(expr.left), DYN); @@ -1080,7 +1080,11 @@ function lowerPrivateIn( // when the value is a class instance at or below the declarer. A typed // reference carries the class it was boxed under: the declarer or a // descendant answers true; an ancestor answers per instance. - if (recv.type.kind === "dyn" && recv.kind === "varRef" && isJsSourceFile(expr.getSourceFile())) { + if ( + recv.type.kind === "dyn" && + recv.kind === "varRef" && + isJsSourceFile(ts.getSourceFileOfNode(expr)) + ) { // A function-local class has one brand per evaluation. if (info.localClass) { const owner = localPrivateBrandClass(lowerer, priv); @@ -1381,7 +1385,7 @@ export function lowerInExpression( recv.type.kind === "classval" ? lowerer.classes.get(recv.type.className) : undefined; if ( (classInfo && (classInfo.callableBase || hasRuntimeStatics(classInfo))) || - (isJsSourceFile(expr.getSourceFile()) && + (isJsSourceFile(ts.getSourceFileOfNode(expr)) && recv.type.kind === "func" && lowerer.dynConvertible(recv.type)) ) { diff --git a/packages/compiler/src/frontend/lowering/expressions/typeof.ts b/packages/compiler/src/frontend/lowering/expressions/typeof.ts index 41a05ac2b..46005e228 100644 --- a/packages/compiler/src/frontend/lowering/expressions/typeof.ts +++ b/packages/compiler/src/frontend/lowering/expressions/typeof.ts @@ -71,7 +71,7 @@ export function lowerTypeofExpr(lowerer: Lowerer, expr: ts.TypeOfExpression, loc (operand.text === "__dirname" || operand.text === "__filename") && lowerer.isStdlibGlobal(operand, operand.text) ) { - const sf = operand.getSourceFile(); + const sf = ts.getSourceFileOfNode(operand); if (isNodeEsmFile(sf, lowerer.program) && !/\.tsx?$/.test(sf.fileName)) return { kind: "strLit", value: "undefined", type: STRING, loc }; } @@ -204,7 +204,7 @@ export function lowerTypeofExpr(lowerer: Lowerer, expr: ts.TypeOfExpression, loc // its own lowering as an identity token (a string), so the operand's // representation cannot answer. Node's answer follows the export's // kind, and the binding read has no effects. - const imported = isJsSourceFile(tested.getSourceFile()) + const imported = isJsSourceFile(ts.getSourceFileOfNode(tested)) ? lowerer.builtinImportOf(tested) : null; if (imported && builtinModuleConstOf(lowerer, imported.module, imported.member) === undefined) { diff --git a/packages/compiler/src/frontend/lowering/expressions/union-fields.ts b/packages/compiler/src/frontend/lowering/expressions/union-fields.ts index f2dd0d0f0..6454bc1fd 100644 --- a/packages/compiler/src/frontend/lowering/expressions/union-fields.ts +++ b/packages/compiler/src/frontend/lowering/expressions/union-fields.ts @@ -117,7 +117,7 @@ export function lowerUnionFieldRead( access: ts.PropertyAccessExpression, receiver: IrExpr, ): IrExpr | null { - if (receiver.type.kind !== "union" || isJsSourceFile(access.getSourceFile())) return null; + if (receiver.type.kind !== "union" || isJsSourceFile(ts.getSourceFileOfNode(access))) return null; const unionId = receiver.type.unionId; const arms = lowerer.unions.get(unionId)?.arms; if (!arms || arms.length < 2) return null; diff --git a/packages/compiler/src/frontend/lowering/expressions/updates.ts b/packages/compiler/src/frontend/lowering/expressions/updates.ts index 41d8f6332..104057906 100644 --- a/packages/compiler/src/frontend/lowering/expressions/updates.ts +++ b/packages/compiler/src/frontend/lowering/expressions/updates.ts @@ -110,7 +110,7 @@ export function lowerPrefixUnary(lowerer: Lowerer, expr: ts.PrefixUnaryExpressio } // Match the checked numeric boundary used by JS binary arithmetic. const operand = - raw.type.kind === "dyn" && isJsSourceFile(expr.getSourceFile()) + raw.type.kind === "dyn" && isJsSourceFile(ts.getSourceFileOfNode(expr)) ? { kind: "dynCheck" as const, value: raw, type: F64, loc } : lowerOptionalNumber(lowerer, raw, loc, expr.operand); if (operand.type.kind !== "f64") operatorUnsupported(lowerer, expr, "-", [raw.type]); @@ -263,7 +263,7 @@ export function lowerIncDec( const receiver = save(raw, "%incrementArray"); const rawIndex = lowerer.lowerExpr(access.argumentExpression); const index = save( - rawIndex.type.kind === "dyn" && isJsSourceFile(expr.getSourceFile()) + rawIndex.type.kind === "dyn" && isJsSourceFile(ts.getSourceFileOfNode(expr)) ? { kind: "libCall", fn: "dyn.toNumberCoerce", args: [rawIndex], type: F64, loc } : rawIndex, "%incrementIndex", @@ -308,10 +308,13 @@ export function lowerIncDec( // class-backed protocol view): the expanded form `o.v = o.v + 1`, its // read checked into number like any other read of the member. const checkedNumber = - !isJsSourceFile(expr.getSourceFile()) && + !isJsSourceFile(ts.getSourceFileOfNode(expr)) && raw.type.kind === "dyn" && lowerer.mapTypeOf(lowerer.typeOf(access))?.kind === "f64"; - if ((isJsSourceFile(expr.getSourceFile()) || checkedNumber) && raw.type.kind === "dyn") { + if ( + (isJsSourceFile(ts.getSourceFileOfNode(expr)) || checkedNumber) && + raw.type.kind === "dyn" + ) { const receiver = save(raw, "%incrementReceiver"); const key = save( ts.isElementAccessExpression(access) @@ -352,7 +355,7 @@ export function lowerIncDec( ), ); } - if (isJsSourceFile(expr.getSourceFile()) && lowerer.dynConvertible(raw.type)) { + if (isJsSourceFile(ts.getSourceFileOfNode(expr)) && lowerer.dynConvertible(raw.type)) { const receiver = save(lowerer.coerceInto(access.expression, raw, DYN), "%incrementReceiver"); const key = save( ts.isElementAccessExpression(access) @@ -818,7 +821,7 @@ export function lowerFieldCompoundValue( rhsNode ? lowerer.lowerExpr(rhsNode) : { kind: "numLit", value: 1, type: F64, loc }, "%compoundRhs", ); - if (isJsSourceFile(access.getSourceFile()) && lowerer.dynConvertible(rhs.type)) { + if (isJsSourceFile(ts.getSourceFileOfNode(access)) && lowerer.dynConvertible(rhs.type)) { const numeric = ["-", "*", "/", "%", "**"].includes(op); const right = lowerer.coerceToExpected(rhs, DYN); const value = save( @@ -912,7 +915,7 @@ export function lowerFieldCompoundValue( "%compoundRhs", ); const numericRhs: IrExpr = - rhs.type.kind === "dyn" && isJsSourceFile(access.getSourceFile()) + rhs.type.kind === "dyn" && isJsSourceFile(ts.getSourceFileOfNode(access)) ? { kind: "dynCheck", value: rhs, type: F64, loc: rhs.loc } : lowerOptionalNumber(lowerer, rhs, loc); let value: IrExpr; @@ -927,7 +930,7 @@ export function lowerFieldCompoundValue( } else if (bigintValue) { value = bigintValue; } else if ( - (target.fieldType.kind === "dyn" && isJsSourceFile(access.getSourceFile())) || + (target.fieldType.kind === "dyn" && isJsSourceFile(ts.getSourceFileOfNode(access))) || // An untyped right operand: the expanded `o.f = o.f op rhs`; the field // write below checks the result into the declared field type. (rhs.type.kind === "dyn" && diff --git a/packages/compiler/src/frontend/lowering/function-returns.ts b/packages/compiler/src/frontend/lowering/function-returns.ts index c0e70013c..9500d6d2d 100644 --- a/packages/compiler/src/frontend/lowering/function-returns.ts +++ b/packages/compiler/src/frontend/lowering/function-returns.ts @@ -98,8 +98,8 @@ export function appendImplicitUndefinedReturn( /** Whether the source explicitly documents a return contract. */ export function hasExplicitJsDocReturn(decl: ts.SignatureDeclaration): boolean { - const sourceFile = decl.getSourceFile(); - return /@returns?\b/.test(sourceFile.text.slice(decl.pos, decl.getStart(sourceFile))); + const sourceFile = ts.getSourceFileOfNode(decl); + return /@returns?\b/.test(sourceFile.text.slice(decl.pos, ts.tokenStartOfNode(decl))); } export function promiseCarriesDyn(lowerer: Lowerer, type: IrType): boolean { @@ -130,14 +130,14 @@ export function declaredReturnType( decl: ts.SignatureDeclaration, blame: ts.Node, ): IrType { - if (isJsSourceFile(decl.getSourceFile()) && ts.isGetAccessorDeclaration(decl)) return DYN; + if (isJsSourceFile(ts.getSourceFileOfNode(decl)) && ts.isGetAccessorDeclaration(decl)) return DYN; const sig = lowerer.checker.getSignatureFromDeclaration(decl); if (!sig) lowerer.unsupported("SC1090", decl, "this function form"); const retTsType = lowerer.checker.getReturnTypeOfSignature(sig); // An inferred null/undefined return in JS can read a mutable checked-value // field. Keep the actual value instead of restricting it to the initializer. if ( - isJsSourceFile(decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(decl)) && (retTsType.flags & (ts.TypeFlags.Null | ts.TypeFlags.Undefined)) !== 0 ) return DYN; @@ -154,7 +154,7 @@ export function declaredReturnType( ) mappedReturn = mapAsyncGeneratorAnnotation(retTsType, lowerer.typeCtx); if ( - isJsSourceFile(decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(decl)) && decl.modifiers?.nodes.some((modifier) => modifier.kind === ts.SyntaxKind.AsyncKeyword) && !decl.asteriskToken && mappedReturn?.kind !== "promise" @@ -162,7 +162,11 @@ export function declaredReturnType( const fallback = dynFallbackType(lowerer, decl, retTsType); return fallback?.kind === "promise" ? fallback : { kind: "promise", inner: DYN }; } - if (mappedReturn && isJsSourceFile(decl.getSourceFile()) && functionCanReturnUndefined(decl)) + if ( + mappedReturn && + isJsSourceFile(ts.getSourceFileOfNode(decl)) && + functionCanReturnUndefined(decl) + ) mappedReturn = lowerer.withUndefinedArmOf(mappedReturn) ?? mappedReturn; // A generic-class method body that can return an indexed read that // misses returns undefined there, whatever the declared result says. @@ -171,33 +175,33 @@ export function declaredReturnType( mappedReturn.kind === "promise" ? { kind: "promise", inner: lowerer.runtimeOptionalType(mappedReturn.inner) } : lowerer.runtimeOptionalType(mappedReturn); - if (isJsSourceFile(decl.getSourceFile()) && mappedReturn?.kind === "void") { + if (isJsSourceFile(ts.getSourceFileOfNode(decl)) && mappedReturn?.kind === "void") { let returnsValue = false; const scan = (node: ts.Node): void => { if (node !== decl && ts.isFunctionLike(node)) return; if (ts.isReturnStatement(node) && node.expression) returnsValue = true; - node.forEachChild(scan); + ts.forEachChild(node, scan); }; scan(decl); if (returnsValue) return DYN; } - if (isJsSourceFile(decl.getSourceFile()) && mappedReturn?.kind === "array") return DYN; + if (isJsSourceFile(ts.getSourceFileOfNode(decl)) && mappedReturn?.kind === "array") return DYN; if ( - isJsSourceFile(decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(decl)) && !decl.type && !hasExplicitJsDocReturn(decl) && mappedReturn?.kind === "record" ) return DYN; if ( - isJsSourceFile(decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(decl)) && !decl.type && !hasExplicitJsDocReturn(decl) && mappedReturn?.kind === "date" ) return DYN; if ( - isJsSourceFile(decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(decl)) && !decl.type && !hasExplicitJsDocReturn(decl) && mappedReturn?.kind === "union" && @@ -207,7 +211,7 @@ export function declaredReturnType( // JS inference assigns Number to arithmetic over any, although the // runtime operands can both be BigInts. Preserve that native result. if ( - isJsSourceFile(decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(decl)) && !decl.type && !hasExplicitJsDocReturn(decl) && mappedReturn?.kind === "f64" @@ -237,13 +241,13 @@ export function declaredReturnType( ) ) numericAnyReturn = true; - node.forEachChild(check); + ts.forEachChild(node, check); }; check(decl); if (numericAnyReturn) return DYN; } if ( - isJsSourceFile(decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(decl)) && !hasExplicitJsDocReturn(decl) && mappedReturn !== null && promiseCarriesDyn(lowerer, mappedReturn) && @@ -264,7 +268,7 @@ export function declaredReturnType( // value rides its own box, calls go through the boxed thunk (JS // arity), and typed slots re-check with dynCheck as usual. if ( - isJsSourceFile(decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(decl)) && decl.type === undefined && lowerer.mapTypeOf(retTsType)?.kind === "func" ) { @@ -304,7 +308,7 @@ export function declaredReturnType( (arm.kind === "object" && !hasExplicitJsDocReturn(decl)), ); if ( - isJsSourceFile(decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(decl)) && decl.type === undefined && recordReturn !== null && recordReturn !== undefined && @@ -468,5 +472,5 @@ export function blockBodyOf(decl: ts.FunctionLikeDeclaration): ts.Block | null { const body = decl.body; if (body === undefined) return null; if (ts.isBlock(body)) return body; - return decl.forEachChild((c) => (ts.isBlock(c) ? c : undefined)) ?? null; + return ts.forEachChild(decl, (c) => (ts.isBlock(c) ? c : undefined)) ?? null; } diff --git a/packages/compiler/src/frontend/lowering/generic-functions.ts b/packages/compiler/src/frontend/lowering/generic-functions.ts index b586884c5..880cccc8b 100644 --- a/packages/compiler/src/frontend/lowering/generic-functions.ts +++ b/packages/compiler/src/frontend/lowering/generic-functions.ts @@ -225,7 +225,7 @@ export function collectGenericSignature(lowerer: Lowerer, decl: ts.FunctionDecla lowerer.genericFnsBySymbol.set(symbol, { decl, baseName: nameText, - qualifiedName: lowerer.qualify(decl.getSourceFile(), nsPathPrefix(decl) + nameText), + qualifiedName: lowerer.qualify(ts.getSourceFileOfNode(decl), nsPathPrefix(decl) + nameText), typeParams, instances: new Map(), }); @@ -306,7 +306,7 @@ export function genericCallInstance( // untyped value, such as an untyped forwarding call) is checked-dynamic. const mappedReturn = lowerer.mapTypeOf(retTs) ?? - ((retTs.flags & ts.TypeFlags.Any) !== 0 && !isJsSourceFile(info.decl.getSourceFile()) + ((retTs.flags & ts.TypeFlags.Any) !== 0 && !isJsSourceFile(ts.getSourceFileOfNode(info.decl)) ? DYN : null); if (!mappedReturn) { @@ -552,7 +552,7 @@ function internGenericInstance( return JSON.stringify(tsBound.value); } const bound = bindings.get(tp); - return bound ? lowerer.fmt(bound) : tp.name; + return bound ? lowerer.fmt(bound) : ts.symbolName(tp); }) .join(", "); // Deep polymorphic recursion renders unbounded types — keep messages sane. @@ -699,7 +699,8 @@ function unifySignatureBindings( if (!bindings.has(sym)) { const mapped = lowerer.mapTypeOf(inst) ?? - ((inst.flags & ts.TypeFlags.Any) !== 0 && !isJsSourceFile(info.decl.getSourceFile()) + ((inst.flags & ts.TypeFlags.Any) !== 0 && + !isJsSourceFile(ts.getSourceFileOfNode(info.decl)) ? DYN : null); // A visitor's inferred T can be void. It still binds the @@ -811,7 +812,7 @@ function unifySignatureBindings( } if (declared.flags & ts.TypeFlags.Object) { for (const dp of lowerer.checker.getPropertiesOfType(declared)) { - const ip = lowerer.checker.getPropertyOfType(inst, dp.name); + const ip = lowerer.checker.getPropertyOfType(inst, ts.symbolName(dp)); if (ip) unify( lowerer.checker.getTypeOfSymbol(dp), @@ -1171,7 +1172,7 @@ function genericValueInstance( const rendered = info.typeParams .map((tp) => { const bound = bindings.get(tp); - return bound ? lowerer.fmt(bound) : tp.name; + return bound ? lowerer.fmt(bound) : ts.symbolName(tp); }) .join(", "); lowerer.typeParamBindings = bindings; @@ -1270,7 +1271,7 @@ function paramWrittenInBody( hit = true; return; } - m.forEachChild(scan); + ts.forEachChild(m, scan); }; scan(n); return hit; @@ -1305,7 +1306,7 @@ function paramWrittenInBody( written = true; return; } - n.forEachChild(walk); + ts.forEachChild(n, walk); }; walk(body); return written; @@ -1480,7 +1481,7 @@ export function implicitAnyParamSymbolsOf( }); // Destructured object parameters monomorphize too (implicitPatternsOf); // their slots stay null here. - if (!hasImplicitParams && isJsSourceFile(decl.getSourceFile())) + if (!hasImplicitParams && isJsSourceFile(ts.getSourceFileOfNode(decl))) hasImplicitParams = implicitPatternsOf(lowerer, decl).some((pattern) => pattern !== null); return hasImplicitParams ? out : null; } @@ -1630,7 +1631,7 @@ function implicitDeclaredReturn(lowerer: Lowerer, info: GenericFnInfo): IrType | // declaredReturnType widens non-generic JavaScript functions. if ( mapped !== null && - isJsSourceFile(info.decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(info.decl)) && functionCanReturnUndefined(info.decl) ) return lowerer.withUndefinedArmOf(mapped) ?? mapped; @@ -1678,7 +1679,7 @@ function bindableImplicitIr(t: IrType | null): t is IrType { function storedImplicitArgumentType(lowerer: Lowerer, arg: ts.Expression): IrType | null { while (ts.isParenthesizedExpression(arg)) arg = arg.expression; if ( - isJsSourceFile(arg.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(arg)) && (ts.isArrowFunction(arg) || ts.isFunctionExpression(arg)) ) { const saved = lowerer.diagSink; @@ -1701,7 +1702,7 @@ function storedImplicitArgumentType(lowerer: Lowerer, arg: ts.Expression): IrTyp } if ( ts.isObjectLiteralExpression(arg) && - isJsSourceFile(arg.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(arg)) && (arg.properties.nodes.length === 0 || arg.properties.nodes.some( (property) => @@ -1716,7 +1717,7 @@ function storedImplicitArgumentType(lowerer: Lowerer, arg: ts.Expression): IrTyp if (ts.isIdentifier(arg)) return lowerer.peekLocal(arg)?.type ?? lowerer.globalOf(arg)?.type ?? null; if ( - isJsSourceFile(arg.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(arg)) && (ts.isPropertyAccessExpression(arg) || ts.isElementAccessExpression(arg)) ) { // Mutable JS members can retain an initializer-only checker type (for @@ -1726,7 +1727,7 @@ function storedImplicitArgumentType(lowerer: Lowerer, arg: ts.Expression): IrTyp if (represented) return represented.type; } if ( - isJsSourceFile(arg.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(arg)) && (ts.isCallExpression(arg) || ts.isConditionalExpression(arg) || (ts.isBinaryExpression(arg) && @@ -1843,7 +1844,7 @@ export function implicitCallInstance( (neverTaintedJsType(lowerer, arg, t) ? null : lowerer.mapTypeOf(t)) ?? dynFallbackType(lowerer, arg, t); if ( - isJsSourceFile(arg.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(arg)) && ts.isArrayLiteralExpression(arg) && mapped?.kind === "array" && (mapped.elem.kind === "classval" || @@ -1868,7 +1869,7 @@ export function implicitCallInstance( // preserve represented arms through the checked boundary instead // of specializing a call to a union that would discard them. if ( - isJsSourceFile(arg.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(arg)) && mapped?.kind === "func" && !lowerer.dynConvertible(mapped) ) { @@ -2066,7 +2067,7 @@ export function implicitLocalFnNodeOf( if (cached !== undefined) return cached ? (cached.decl as ts.FunctionExpression | ts.ArrowFunction) : null; const probe = (): ts.FunctionExpression | ts.ArrowFunction | null => { - if (!implicitMonoFile(decl.getSourceFile())) return null; + if (!implicitMonoFile(ts.getSourceFileOfNode(decl))) return null; if (!ts.isIdentifier(decl.name) || decl.initializer === undefined) return null; let init: ts.Expression = decl.initializer; while (ts.isParenthesizedExpression(init)) init = init.expression; @@ -2097,7 +2098,7 @@ export function implicitLocalFnNodeOf( const d = s ? s.valueDeclaration : undefined; if ( d && - d.getSourceFile() === decl.getSourceFile() && + ts.getSourceFileOfNode(d) === ts.getSourceFileOfNode(decl) && !(d.pos >= init.pos && d.end <= init.end) ) { // Declared outside the initializer, in this file: a capture @@ -2116,7 +2117,7 @@ export function implicitLocalFnNodeOf( } } } - n.forEachChild(scan); + ts.forEachChild(n, scan); }; scan(init.body); return captures ? null : init; @@ -2154,8 +2155,8 @@ export function implicitLocalFnInfoOf( decl: fnNode, baseName: name, qualifiedName: lowerer.qualify( - decl.getSourceFile(), - nsPathPrefix(stmt ?? decl, decl) + `${name}%l${decl.getStart()}`, + ts.getSourceFileOfNode(decl), + nsPathPrefix(stmt ?? decl, decl) + `${name}%l${ts.tokenStartOfNode(decl)}`, ), typeParams: [], instances: new Map(), @@ -2249,7 +2250,10 @@ export function objLitGenericFnInfoOf( const info: GenericFnInfo = { decl: fnNode, baseName: name, - qualifiedName: lowerer.qualify(fnNode.getSourceFile(), `%ol${fnNode.getStart()}.${name}`), + qualifiedName: lowerer.qualify( + ts.getSourceFileOfNode(fnNode), + `%ol${ts.tokenStartOfNode(fnNode)}.${name}`, + ), typeParams, instances: new Map(), objectLiteral: true, @@ -2381,7 +2385,10 @@ export function bindingGenericFnInfoOf( const info: GenericFnInfo = { decl: fnNode, baseName: name, - qualifiedName: lowerer.qualify(decl.getSourceFile(), nsPathPrefix(stmt ?? decl, decl) + name), + qualifiedName: lowerer.qualify( + ts.getSourceFileOfNode(decl), + nsPathPrefix(stmt ?? decl, decl) + name, + ), typeParams, instances: new Map(), }; diff --git a/packages/compiler/src/frontend/lowering/import-meta.ts b/packages/compiler/src/frontend/lowering/import-meta.ts index baf550e6d..796e64b69 100644 --- a/packages/compiler/src/frontend/lowering/import-meta.ts +++ b/packages/compiler/src/frontend/lowering/import-meta.ts @@ -20,7 +20,7 @@ export function isImportMeta(expr: ts.Expression): boolean { /** File-backed metadata is fixed by the compiled module graph. Reads share * one implementation across dot access, folded keys and selected bindings. */ export function importMetaField(lowerer: Lowerer, node: ts.Node, name: string): IrExpr { - const sf = node.getSourceFile(); + const sf = ts.getSourceFileOfNode(node); const fileName = lowerer.targetPlatform === "wasi" ? (wasiGuestPath(sf.fileName) ?? sf.fileName.replace(/\\/g, "/")) diff --git a/packages/compiler/src/frontend/lowering/intern-tables.ts b/packages/compiler/src/frontend/lowering/intern-tables.ts index 32bb71858..fc69237db 100644 --- a/packages/compiler/src/frontend/lowering/intern-tables.ts +++ b/packages/compiler/src/frontend/lowering/intern-tables.ts @@ -142,7 +142,7 @@ interface TableTypes { /** The key and value types of an InternTable checker type. */ function tableTypes(lowerer: Lowerer, type: ts.Type, blame: ts.Node): TableTypes { const parts = type.isUnionType() ? ts.constituentTypes(type) : [type]; - const table = parts.find((p) => p.getSymbol()?.name === "InternTable"); + const table = parts.find((p) => ts.symbolNameOf(p.getSymbol()) === "InternTable"); const args = table ? lowerer.checker.getTypeArguments(table as ts.TypeReference) : []; if (args.length !== 2) lowerer.noLowering("InternTable without type arguments", blame); const key = lowerer.mapTypeOf(args[0]!); diff --git a/packages/compiler/src/frontend/lowering/js-dynamic-objects.ts b/packages/compiler/src/frontend/lowering/js-dynamic-objects.ts index d39e5cceb..b9bbf6216 100644 --- a/packages/compiler/src/frontend/lowering/js-dynamic-objects.ts +++ b/packages/compiler/src/frontend/lowering/js-dynamic-objects.ts @@ -81,10 +81,10 @@ export function isValueReturningConstructorClass(decl: ts.ClassDeclaration): boo if (ts.isClassDeclaration(node) || ts.isClassExpression(node)) return; if (ts.isReturnStatement(node) && node.expression !== undefined && !insideArrow(node, ctor!)) returnsValue = true; - node.forEachChild(visit); + ts.forEachChild(node, visit); }; - ctor.body.forEachChild(visit); - for (const parameter of ctor.parameters.nodes) parameter.forEachChild(visit); + ts.forEachChild(ctor.body, visit); + for (const parameter of ctor.parameters.nodes) ts.forEachChild(parameter, visit); return returnsValue && !observesThis; } @@ -215,7 +215,7 @@ export function lowerValueReturningConstructorNew( "spread arguments when constructing a class whose constructor returns a value", ); } - const key = `ctorValue:${decl.getSourceFile().fileName}:${decl.getStart()}`; + const key = `ctorValue:${ts.getSourceFileOfNode(decl).fileName}:${ts.tokenStartOfNode(decl)}`; let name = lowerer.valueHelpers.get(key); if (name === undefined) { name = `%ctorValue.${lowerer.valueHelpers.size}`; @@ -235,7 +235,7 @@ export function lowerValueReturningConstructorNew( { kind: "runtimeFence", code: "SC1090", - message: `constructors that return no object (the bare instance of '${decl.name?.text ?? "class"}' has no representation here) are not supported yet [SC1090 at ${decl.getSourceFile().fileName}:${ts.getLineAndCharacterOfPosition(decl.getSourceFile(), ctor.getStart()).line + 1}]`, + message: `constructors that return no object (the bare instance of '${decl.name?.text ?? "class"}' has no representation here) are not supported yet [SC1090 at ${ts.getSourceFileOfNode(decl).fileName}:${ts.getLineAndCharacterOfPosition(ts.getSourceFileOfNode(decl), ts.tokenStartOfNode(ctor)).line + 1}]`, loc: locOf(ctor), }, ]; @@ -261,8 +261,8 @@ export function lowerValueReturningConstructorNew( .map((arg) => ({ kind: "exprStmt", expr: lowerer.lowerExpr(arg), loc: locOf(arg) })); const result = lowerer.declareHiddenLocal("%constructed", DYN); const value: IrExpr = { kind: "varRef", localId: result.id, type: DYN, loc }; - const sf = expr.getSourceFile(); - const line = ts.getLineAndCharacterOfPosition(sf, expr.getStart()).line + 1; + const sf = ts.getSourceFileOfNode(expr); + const line = ts.getLineAndCharacterOfPosition(sf, ts.tokenStartOfNode(expr)).line + 1; const objectLike: IrExpr = { kind: "logical", op: "||", diff --git a/packages/compiler/src/frontend/lowering/json-require.ts b/packages/compiler/src/frontend/lowering/json-require.ts index 7c688dcda..927745253 100644 --- a/packages/compiler/src/frontend/lowering/json-require.ts +++ b/packages/compiler/src/frontend/lowering/json-require.ts @@ -36,7 +36,7 @@ export function jsonRequireTarget( if (call === undefined || !ts.isCallExpression(call)) return null; const spec = requireSpecOf(call); if (spec === null || !ts.isIdentifier(call.expression)) return null; - const sf = call.getSourceFile(); + const sf = ts.getSourceFileOfNode(call); if (!isCjsJsFile(sf, lowerer.program)) return null; if (createRequireCalleeFileOf(lowerer, call.expression) !== null) return null; const target = resolveImport(lowerer.program, sf, spec, "require"); diff --git a/packages/compiler/src/frontend/lowering/lower-assert.ts b/packages/compiler/src/frontend/lowering/lower-assert.ts index cb31ecc79..460f4d53a 100644 --- a/packages/compiler/src/frontend/lowering/lower-assert.ts +++ b/packages/compiler/src/frontend/lowering/lower-assert.ts @@ -887,10 +887,10 @@ export function lowerAssertDirectCall( * call's SOURCE TEXT (Node re-reads the file at runtime — the frontend has * the same bytes at compile time). */ function lowerAssertOk(lowerer: Lowerer, expr: ts.CallExpression, loc: SrcLoc): IrExpr { - requireStatementPosition(lowerer, expr, expr.expression.getText()); + requireStatementPosition(lowerer, expr, ts.getTextOfNode(expr.expression)); if (expr.arguments.nodes.length < 1 || expr.arguments.nodes.length > 2) { lowerer.noLowering( - `${expr.expression.getText()} with ${expr.arguments.nodes.length} arguments`, + `${ts.getTextOfNode(expr.expression)} with ${expr.arguments.nodes.length} arguments`, expr, "the supported forms are assert(value) and assert(value, message)", ); @@ -912,7 +912,7 @@ function lowerAssertOk(lowerer: Lowerer, expr: ts.CallExpression, loc: SrcLoc): // Node reads the CALLSITE LINE and slices the expression out of it // (internal/errors/error_source.js) — a call spanning lines truncates // at the first newline, so the compile-time text does too. - const source = expr.getText().split("\n", 1)[0]!; + const source = ts.getTextOfNode(expr).split("\n", 1)[0]!; const text = `The expression evaluated to a falsy value:\n\n ${source}\n`; msg = { kind: "strLit", value: text, type: STRING, loc }; generated = true; diff --git a/packages/compiler/src/frontend/lowering/lower-calls.ts b/packages/compiler/src/frontend/lowering/lower-calls.ts index db4c03402..b1b4086cd 100644 --- a/packages/compiler/src/frontend/lowering/lower-calls.ts +++ b/packages/compiler/src/frontend/lowering/lower-calls.ts @@ -678,7 +678,7 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { const isStorage = parts.length > 0 && parts.every((part) => { - const name = part.getSymbol()?.name; + const name = ts.symbolNameOf(part.getSymbol()); return name === "ArrayBuffer" || name === "SharedArrayBuffer"; }); if (isStorage) { @@ -852,7 +852,7 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { (source.type.kind === "dyn" || source.type.kind === "func" || (source.type.kind === "union" && - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && lowerer.dynConvertible(source.type)) || ((source.type.kind === "map" || source.type.kind === "set") && lowerer.dynConvertible(source.type)) || @@ -868,7 +868,7 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { lowerer.lowerExprExpecting(ts.isSpreadElement(arg) ? arg.expression : arg, DYN), ); const spreads = expr.arguments.nodes.flatMap((arg, index) => - ts.isSpreadElement(arg) ? [{ arg: index, what: arg.expression.getText() }] : [], + ts.isSpreadElement(arg) ? [{ arg: index, what: ts.getTextOfNode(arg.expression) }] : [], ); return { kind: "seqExpr", @@ -879,7 +879,7 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { kind: "dynCall", callee, receiver, - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args, ...(spreads.length > 0 ? { spreads } : {}), type: DYN, @@ -1184,7 +1184,7 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { // argument list. Typed .ts spreads keep completeArgs' rest packing. const jsSpreadArgs = expr.arguments.nodes.some((a) => ts.isSpreadElement(a)) && - isJsSourceFile(expr.getSourceFile()); + isJsSourceFile(ts.getSourceFileOfNode(expr)); if (lowerer.isTopLevelFnSymbol(expr.expression) && !lowerer.peekLocal(expr.expression)) { // `import g = N.f; g()` — the alias's own source-order guards // (a no-op for every non-import= binding). @@ -1394,12 +1394,12 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { if ( g && (g.type.kind === "func" || g.type.kind === "dyn") && - !cjsExportCallIgnoresThis(lowerer, expr.getSourceFile(), expr.expression.name.text) + !cjsExportCallIgnoresThis(lowerer, ts.getSourceFileOfNode(expr), expr.expression.name.text) ) { lowerer.unsupported( "SC1090", expr, - `calling '${expr.expression.getText()}' through the module's exports object when the function may read 'this' (Node binds it to the exports object, which compiles to a static table — call a local function instead)`, + `calling '${ts.getTextOfNode(expr.expression)}' through the module's exports object when the function may read 'this' (Node binds it to the exports object, which compiles to a static table — call a local function instead)`, ); } if (g && (g.type.kind === "func" || g.type.kind === "dyn")) { @@ -1598,7 +1598,7 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { const boxed = lowerDynReceiverMethodCall(lowerer, expr, expr.expression, true); if (boxed) return boxed; } - if (isJsSourceFile(expr.getSourceFile())) { + if (isJsSourceFile(ts.getSourceFileOfNode(expr))) { const staticField = lowerStaticFieldRead(lowerer, expr.expression); if (staticField?.type.kind === "dyn" && !expr.arguments.nodes.some(ts.isSpreadElement)) { return { @@ -1666,7 +1666,7 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { lowerer.unsupported( "SC1090", expr, - `calls through function-with-properties values ('${expr.expression.expression.getText()}' is callable AND carries members — the chalk shape; no hybrid representation exists yet: export the base and the property as separate functions)`, + `calls through function-with-properties values ('${ts.getTextOfNode(expr.expression.expression)}' is callable AND carries members — the chalk shape; no hybrid representation exists yet: export the base and the property as separate functions)`, ); } } @@ -1691,7 +1691,7 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { ); } } - lowerer.unsupported("SC1090", expr, `method calls like '${expr.expression.getText()}'`); + lowerer.unsupported("SC1090", expr, `method calls like '${ts.getTextOfNode(expr.expression)}'`); } // `s[Symbol.iterator]()` on a string: the string iterator as a value. @@ -1730,7 +1730,7 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { ) { const access = expr.expression; const value = tryLowerExpression(lowerer, access.expression); - if (value?.type.kind === "array" && !isJsSourceFile(expr.getSourceFile())) { + if (value?.type.kind === "array" && !isJsSourceFile(ts.getSourceFileOfNode(expr))) { const callback = lowerArrayCallbackCall(lowerer, expr, access, value); if (callback) return callback; } @@ -1765,7 +1765,7 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { kind: "dynCall", callee, receiver, - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args: expr.arguments.nodes.map((arg) => lowerer.lowerExprExpecting(arg, DYN)), type: DYN, loc, @@ -1884,7 +1884,7 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { const calleeName = ts.isPropertyAccessExpression(expr.expression) || ts.isElementAccessExpression(expr.expression) - ? expr.expression.getText() + ? ts.getTextOfNode(expr.expression) : ts.isIdentifier(expr.expression) ? expr.expression.text : "value"; @@ -1946,7 +1946,7 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { // per use like every any-origin value. if ( (expr.arguments.nodes.length > callee.type.params.length || callee.type.rest === true) && - (isJsSourceFile(expr.getSourceFile()) || + (isJsSourceFile(ts.getSourceFileOfNode(expr)) || (callee.type.rest === true && callee.type.restAbi === undefined)) && !expr.arguments.nodes.some((a) => ts.isSpreadElement(a)) && canBoxFuncIntoDyn( @@ -1978,7 +1978,7 @@ function lowerCheckedCallableValue( callee.type.kind !== "func" || !( (callee.type.rest === true && callee.type.restAbi === undefined) || - (isJsSourceFile(call.getSourceFile()) && + (isJsSourceFile(ts.getSourceFileOfNode(call)) && call.arguments.nodes.length > callee.type.params.length) ) || !lowerer.dynConvertible(callee.type) @@ -2543,7 +2543,7 @@ function lowerArrayPrototypeBorrowCall( access: ts.PropertyAccessExpression, ): IrExpr | null { if ( - !isJsSourceFile(call.getSourceFile()) || + !isJsSourceFile(ts.getSourceFileOfNode(call)) || call.questionDotToken || access.questionDotToken || access.name.text !== "call" || @@ -2755,7 +2755,7 @@ function lowerObjectOwnCall( result = guardNullProto( receiverRef, type.shapeId, - `${methodAccess.getText()} is not a function`, + `${ts.getTextOfNode(methodAccess)} is not a function`, result, loc, ); @@ -3377,7 +3377,7 @@ function lowerDynReceiverMethodCall( const recvSymbol = recvTs.getSymbol(); if ( recvSymbol && - CHECKED_ITERATOR_TYPES.has(recvSymbol.name) && + CHECKED_ITERATOR_TYPES.has(ts.symbolName(recvSymbol)) && lowerer.isStdlibSymbol(recvSymbol) && ITERATOR_HELPER_METHODS.has(access.name.text) && lowerer.isStdlibMember(access) @@ -3445,7 +3445,7 @@ function lowerDynReceiverMethodCall( kind: "dynCall", callee: member, receiver, - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args: call.arguments.nodes.map((arg) => lowerer.lowerExprExpecting(arg, DYN)), type: DYN, loc, @@ -3467,7 +3467,7 @@ function lowerDynReceiverMethodCall( return null; if ( probed?.type.kind === "union" && - isJsSourceFile(call.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(call)) && lowerer.unions .get(probed.type.unionId) ?.arms.every( @@ -3517,7 +3517,7 @@ function lowerDynReceiverMethodCall( if ( checkerUntyped && recv.type.kind === "union" && - isJsSourceFile(call.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(call)) && isStringMethodName(access.name.text) && lowerer.unions .get(recv.type.unionId) @@ -3603,7 +3603,7 @@ function lowerDynReceiverMethodCall( kind: "dynInvoke", recv: value, method: "normalize", - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args: hasArgument ? [argument] : [], type: DYN, loc, @@ -3639,7 +3639,7 @@ function lowerDynReceiverMethodCall( // dynKeyGet cannot stand in for JavaScript's prototype lookup. Only // an authored declaration can justify the own-member path. const authored = prop.declarations.some( - (decl) => !lowerer.isStdlibFile(decl.getSourceFile()), + (decl) => !lowerer.isStdlibFile(ts.getSourceFileOfNode(decl)), ); return ( authored && @@ -3681,7 +3681,7 @@ function lowerDynReceiverMethodCall( lowerer, call, access.name.text, - access.getText(), + ts.getTextOfNode(access), recv, ); if (primitive) return primitive; @@ -3696,7 +3696,7 @@ function lowerDynReceiverMethodCall( args: [ recv, argument ? lowerer.lowerExprExpecting(argument, DYN) : dynUndefinedExpr(locOf(call)), - { kind: "strLit", value: access.getText(), type: STRING, loc: locOf(call) }, + { kind: "strLit", value: ts.getTextOfNode(access), type: STRING, loc: locOf(call) }, ], type: STRING, loc: locOf(call), @@ -3711,7 +3711,10 @@ function lowerDynReceiverMethodCall( return { kind: "libCall", fn: "dyn.valueOf", - args: [recv, { kind: "strLit", value: access.getText(), type: STRING, loc: locOf(call) }], + args: [ + recv, + { kind: "strLit", value: ts.getTextOfNode(access), type: STRING, loc: locOf(call) }, + ], type: DYN, loc: locOf(call), }; @@ -3799,15 +3802,15 @@ function lowerDynReceiverMethodCall( // statement cannot leave the expression). const stringRefusal = (read: IrExpr): IrExpr => { if (!stringOnlyName) return read; - const sf = call.getSourceFile(); - const line = ts.getLineAndCharacterOfPosition(sf, call.getStart(sf)).line + 1; + const sf = ts.getSourceFileOfNode(call); + const line = ts.getLineAndCharacterOfPosition(sf, ts.tokenStartOfNode(call)).line + 1; return { kind: "ternary", cond: { kind: "dynTest", test: "string", value: receiver, type: BOOL, loc }, then: nodeThrowExpr( 0, "SC1090", - `method calls like '${access.getText()}' on a string are not supported yet [SC1090 at ${sf.fileName}:${line}]`, + `method calls like '${ts.getTextOfNode(access)}' on a string are not supported yet [SC1090 at ${sf.fileName}:${line}]`, DYN, loc, ), @@ -3843,7 +3846,7 @@ function lowerDynReceiverMethodCall( kind: "dynCall", callee, receiver, - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args, type: DYN, loc, @@ -4133,7 +4136,7 @@ export function lowerDynDispatchMethodCall( ): IrExpr | null { const method = access.name.text; if ( - (!DYN_DISPATCH_METHODS.has(method) && !isJsSourceFile(call.getSourceFile())) || + (!DYN_DISPATCH_METHODS.has(method) && !isJsSourceFile(ts.getSourceFileOfNode(call))) || lowerer.chainBlocked(call, access) ) return null; @@ -4242,7 +4245,7 @@ export function lowerDynDispatchMethodCall( kind: "dynInvoke", recv: { kind: "varRef", localId: saved.id, type: DYN, loc: locOf(access.expression) }, method, - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args: [dynUndefinedExpr(locOf(call.arguments.nodes[0]!))], type: DYN, loc: locOf(call), @@ -4264,7 +4267,7 @@ export function lowerDynDispatchMethodCall( kind: "dynInvoke", recv, method, - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args, type: DYN, loc: locOf(call), @@ -4484,7 +4487,7 @@ function lowerNumberToStringCall( call.arguments.nodes[0] ? lowerer.lowerExprExpecting(call.arguments.nodes[0], DYN) : dynUndefinedExpr(locOf(call)), - { kind: "strLit", value: access.getText(), type: STRING, loc: locOf(call) }, + { kind: "strLit", value: ts.getTextOfNode(access), type: STRING, loc: locOf(call) }, ], type: STRING, loc: locOf(call), @@ -4607,7 +4610,7 @@ function lowerDefaultToStringCall( // A null-prototype record (Object.groupBy) has no toString: Node // throws " is not a function". const nullProtoT = nullProtoRecordShape(lowerer, recvT); - const missing = nullProtoT ? `${access.getText()} is not a function` : null; + const missing = nullProtoT ? `${ts.getTextOfNode(access)} is not a function` : null; if (!missing && pureObjectToStringReceiver(access.expression)) return constant; const recv = lowerer.lowerExpr(access.expression); const key = `objToStr:${typeKey(recv.type)}${missing ? `:${missing}` : ""}`; @@ -4758,7 +4761,7 @@ function lowerPrimitiveProtoCall( (name === "toFixed" || name === "toExponential") && call.arguments.nodes.length <= 1 && !call.arguments.nodes.some(ts.isSpreadElement) && - isJsSourceFile(call.getSourceFile()) + isJsSourceFile(ts.getSourceFileOfNode(call)) ) { const operand = lowerer.lowerExpr(recv); if ( @@ -4771,7 +4774,7 @@ function lowerPrimitiveProtoCall( lowerer, call, name, - `${recv.getText()}.${name}`, + `${ts.getTextOfNode(recv)}.${name}`, lowerer.coerceToExpected(operand, DYN), ); if (dispatched) return dispatched; @@ -4784,7 +4787,7 @@ function lowerPrimitiveProtoCall( kind: "dynInvoke", recv: lowerer.coerceToExpected(operand, DYN), method: name, - calleeName: `${recv.getText()}.${name}`, + calleeName: `${ts.getTextOfNode(recv)}.${name}`, args: call.arguments.nodes.map((argument) => lowerer.lowerExprExpecting(argument, DYN)), type: DYN, loc: locOf(call), @@ -4794,7 +4797,7 @@ function lowerPrimitiveProtoCall( name === "toPrecision" && call.arguments.nodes.length <= 1 && !call.arguments.nodes.some(ts.isSpreadElement) && - isJsSourceFile(call.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(call)) && recvKind === "union" ) { const operand = lowerer.lowerExpr(recv); @@ -4803,7 +4806,7 @@ function lowerPrimitiveProtoCall( lowerer, call, name, - `${recv.getText()}.${name}`, + `${ts.getTextOfNode(recv)}.${name}`, lowerer.coerceToExpected(operand, DYN), ); if (dispatched) return dispatched; @@ -5023,9 +5026,10 @@ function isSoleAbstractMethod(decl: ts.FunctionDeclaration | ts.MethodDeclaratio if (!ts.getModifiers(decl)?.some((m) => m.kind === ts.SyntaxKind.AbstractKeyword)) return false; const owner = decl.parent; if (!ts.isClassDeclaration(owner) && !ts.isClassExpression(owner)) return false; - const name = decl.name.getText(); + const name = ts.getTextOfNode(decl.name); return !owner.members.nodes.some( - (member) => member !== decl && ts.isMethodDeclaration(member) && member.name.getText() === name, + (member) => + member !== decl && ts.isMethodDeclaration(member) && ts.getTextOfNode(member.name) === name, ); } @@ -5043,7 +5047,7 @@ function reconcileOverloadReturn( call.type.kind === "object" && rdecl && ts.isMethodDeclaration(rdecl) && - (!isJsSourceFile(rdecl.getSourceFile()) || returnsOnlyThis(rdecl)) + (!isJsSourceFile(ts.getSourceFileOfNode(rdecl)) || returnsOnlyThis(rdecl)) ) { call = lowerer.maybeNarrow(call, expr); } @@ -5205,7 +5209,7 @@ export function lowerTaggedTemplate(lowerer: Lowerer, expr: ts.TaggedTemplateExp let callee = lowerer.lowerExpr(expr.tag); if ( callee.type.kind === "func" && - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && lowerer.dynConvertible(callee.type) ) { callee = { kind: "dynFrom", value: callee, type: DYN, loc }; @@ -5223,7 +5227,7 @@ export function lowerTaggedTemplate(lowerer: Lowerer, expr: ts.TaggedTemplateExp ]; const calleeName = ts.isPropertyAccessExpression(expr.tag) || ts.isElementAccessExpression(expr.tag) - ? expr.tag.getText() + ? ts.getTextOfNode(expr.tag) : ts.isIdentifier(expr.tag) ? expr.tag.text : "value"; @@ -5293,7 +5297,7 @@ function loweredTemplateStrings( loc, }) : null; - const key = `templateStringsGlobal:${loc.file}:${expr.template.getStart()}:${raw ? "checked" : "typed"}`; + const key = `templateStringsGlobal:${loc.file}:${ts.tokenStartOfNode(expr.template)}:${raw ? "checked" : "typed"}`; let getter = lowerer.valueHelpers.get(key); if (!getter) { const site = lowerer.globalsList.length; @@ -6008,7 +6012,7 @@ export function lowerPromiseMethodCall( kind: "dynInvoke", recv: lowerer.coerceToExpected(receiver, DYN), method: "finally", - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args: [lowerer.coerceToExpected(cb, DYN)], type: DYN, loc, @@ -6109,7 +6113,7 @@ export function lowerPromiseMethodCall( if ( member === "catch" && (inner.kind === "dyn" || - (isJsSourceFile(call.getSourceFile()) && + (isJsSourceFile(ts.getSourceFileOfNode(call)) && (inner.kind === "void" || lowerer.dynConvertible(inner)))) ) { let cb = lowerer.lowerExpr(call.arguments.nodes[0]!); @@ -7503,7 +7507,7 @@ function lowerObjectStaticCall( const prototype = lowerClassPrototypeAssign(lowerer, call); if (prototype) return prototype; if ( - isJsSourceFile(call.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(call)) && call.arguments.nodes.length > 0 && !call.arguments.nodes.some(ts.isSpreadElement) ) { @@ -7775,7 +7779,12 @@ function lowerObjectStaticCall( args: [ pRef(), src, - { kind: "strLit", value: srcNode.getText(), type: STRING, loc: argLoc }, + { + kind: "strLit", + value: ts.getTextOfNode(srcNode), + type: STRING, + loc: argLoc, + }, ], type: VOID, loc: argLoc, @@ -7832,7 +7841,9 @@ function lowerObjectStaticCall( (exactClassOfReceiver(lowerer, targetNode.expression) !== null || storedClassValueType(lowerer, targetNode.expression)?.kind === "classval"); const declaredTarget = - target?.type.kind === "dyn" && !prototypeTarget && !isJsSourceFile(targetNode.getSourceFile()) + target?.type.kind === "dyn" && + !prototypeTarget && + !isJsSourceFile(ts.getSourceFileOfNode(targetNode)) ? lowerer.mapTypeOf(lowerer.typeOf(targetNode)) : null; if (target && declaredTarget && isDynTypedRefType(declaredTarget)) @@ -8031,7 +8042,7 @@ function lowerObjectStaticCall( while (ts.isParenthesizedExpression(inner) || ts.isAsExpression(inner)) inner = inner.expression; const value = - isJsSourceFile(call.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(call)) && (ts.isObjectLiteralExpression(inner) || ts.isArrayLiteralExpression(inner)) ? lowerer.lowerExprExpecting(argNode, DYN) : lowerer.lowerExpr(argNode); @@ -8252,7 +8263,7 @@ function lowerObjectStaticCall( probed.type.kind === "bool"); const isJsUnion = probed?.type.kind === "union" && - isJsSourceFile(call.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(call)) && lowerer.dynConvertible(probed.type); if ( isDyn || @@ -8274,7 +8285,7 @@ function lowerObjectStaticCall( // write to the arrays well typed. JavaScript may store anything into // them, so it keeps the checked-dynamic arrays. const resultT = - member !== "values" && !isJsSourceFile(call.getSourceFile()) + member !== "values" && !isJsSourceFile(ts.getSourceFileOfNode(call)) ? lowerer.mapTypeOf(lowerer.typeOf(call)) : null; if (member === "keys" && resultT?.kind === "array" && resultT.elem.kind === "string") @@ -8297,7 +8308,11 @@ function lowerObjectStaticCall( // program only iterates or indexes the result, or passes it to a // read-only array method, a native vector of the checked values skips // building that array and, for for-of, the iterator protocol. - if (member === "values" && !isJsSourceFile(call.getSourceFile()) && consumesValueList(call)) + if ( + member === "values" && + !isJsSourceFile(ts.getSourceFileOfNode(call)) && + consumesValueList(call) + ) return { kind: "libCall", fn: "dyn.objValueList", @@ -8335,7 +8350,7 @@ function lowerObjectStaticCall( if ( (member === "values" || member === "entries") && (enumerationType?.kind === "dyn" || - (enumerationType === null && isJsSourceFile(call.getSourceFile()))) + (enumerationType === null && isJsSourceFile(ts.getSourceFileOfNode(call)))) ) { let receiver = lowerer.coerceToExpected(lowerer.lowerExpr(argNode), DYN); if (receiver.type.kind !== "dyn") lowerer.badType(argNode, lowerer.typeOf(argNode)); @@ -8866,7 +8881,7 @@ export function lowerRecordFieldCall( const target = lowerer.fieldTarget(access); const loc = locOf(call); const receiverLocal = - target && isJsSourceFile(call.getSourceFile()) + target && isJsSourceFile(ts.getSourceFileOfNode(call)) ? lowerer.declareHiddenLocal("%callReceiver", target.obj.type) : null; const init: IrStmt | null = @@ -8886,7 +8901,7 @@ export function lowerRecordFieldCall( // values (util.DebugLogger). if ( callee.type.kind === "dyn" && - (isJsSourceFile(call.getSourceFile()) || + (isJsSourceFile(ts.getSourceFileOfNode(call)) || lowerer.checker.getCallSignatures(lowerer.typeOf(access)).length > 0) ) { const receiver = @@ -8904,7 +8919,7 @@ export function lowerRecordFieldCall( kind: "dynCall", callee, ...(receiver ? { receiver } : {}), - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args: call.arguments.nodes.map((arg) => lowerer.lowerExprExpecting(arg, DYN)), type: DYN, loc, @@ -8975,7 +8990,7 @@ function lowerUrlStaticCall( if (!ts.isPropertyAccessExpression(callee) || callee.questionDotToken !== undefined) return null; if (!ts.isIdentifier(callee.expression)) return null; const sym = lowerer.resolveValueSymbol(callee.expression); - if (!sym || !lowerer.isStdlibSymbol(sym) || sym.name !== "URL") return null; + if (!sym || !lowerer.isStdlibSymbol(sym) || ts.symbolName(sym) !== "URL") return null; if (callee.name.text === "canParse" || callee.name.text === "parse") { return lowerUrlFactory(lowerer, call, callee.name.text); } @@ -9333,7 +9348,7 @@ function lowerObjLitGenericMethodCall( kind: "dynInvoke", recv: { kind: "dynFrom", value, type: DYN, loc }, method: name, - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args: call.arguments.nodes.map((arg) => lowerer.lowerExprExpecting(arg, DYN)), type: DYN, loc, @@ -9342,7 +9357,7 @@ function lowerObjLitGenericMethodCall( lowerer.unsupported( "SC1090", call, - `Function.prototype.${name} on a function whose signature cannot cross the checked-value boundary (call '${access.expression.getText()}(...)' directly)`, + `Function.prototype.${name} on a function whose signature cannot cross the checked-value boundary (call '${ts.getTextOfNode(access.expression)}(...)' directly)`, ); } // STANDARD-LIBRARY generic members (Promise.then, Object. @@ -9370,7 +9385,7 @@ function lowerObjLitGenericMethodCall( kind: "dynInvoke", recv: receiver, method: name, - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args: call.arguments.nodes.map((arg) => lowerer.lowerExprExpecting(arg, DYN)), type: DYN, loc, @@ -9431,7 +9446,7 @@ export function lowerObjectMethodCall( (lowerer.implicitParamTypes !== null || lowerer.classImplementedProtocol(lowerer.typeOf(access.expression)) || ts.isNewExpression(access.expression) || - isJsSourceFile(call.getSourceFile())) + isJsSourceFile(ts.getSourceFileOfNode(call))) ? ts.isNewExpression(access.expression) ? lowerer.lowerExpr(access.expression) : tryLowerExpression(lowerer, access.expression) @@ -9450,7 +9465,7 @@ export function lowerObjectMethodCall( if (dispatched) return dispatched; const arms = lowerer.unions.get(mappedReceiver.unionId)?.arms; if ( - isJsSourceFile(call.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(call)) && arms?.every( (arm) => arm.kind === "object" && !lowerer.classes.get(arm.className)?.def.runtime, ) && @@ -9604,7 +9619,7 @@ export function lowerObjectMethodCall( callee, lowerer.armTag(callee.type.unionId, callableType), callableType, - () => `${access.getText()} is not a function`, + () => `${ts.getTextOfNode(access)} is not a function`, ); const helper = inPlace ? null @@ -9618,7 +9633,7 @@ export function lowerObjectMethodCall( if ( ((callee.type.rest === true && callee.type.restAbi === undefined) || call.arguments.nodes.length > callee.type.params.length) && - isJsSourceFile(call.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(call)) && lowerer.dynConvertible(callee.type) ) { const boxed: IrExpr = { kind: "dynFrom", value: callee, type: DYN, loc }; @@ -9629,7 +9644,7 @@ export function lowerObjectMethodCall( kind: "dynCall", callee: boxed, ...receiver, - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args, type: DYN, loc, @@ -9647,7 +9662,7 @@ export function lowerObjectMethodCall( kind: "dynCall", callee, ...receiver, - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args, type: DYN, loc, @@ -9656,7 +9671,7 @@ export function lowerObjectMethodCall( } if ( !fieldType && - isJsSourceFile(call.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(call)) && !info.def.runtime && !info.builtinError && !info.builtinEmitter && @@ -9686,7 +9701,7 @@ export function lowerObjectMethodCall( kind: "dynCall", callee, receiver, - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args: call.arguments.nodes.map((arg) => lowerer.lowerExprExpecting(arg, DYN)), type: DYN, loc, @@ -9735,7 +9750,7 @@ export function lowerObjectMethodCall( call.arguments.nodes, fallback, loc, - access.getText(), + ts.getTextOfNode(access), ); return { kind: "seqExpr", @@ -9771,7 +9786,7 @@ export function lowerObjectMethodCall( const declarerDecl = found.declarer.decl; if ( adapted && - !(declarerDecl && isJsSourceFile(declarerDecl.getSourceFile())) && + !(declarerDecl && isJsSourceFile(ts.getSourceFileOfNode(declarerDecl))) && !lowerer.overrideBelow(info, method) ) { lowerer.noteEdge(`%${found.declarer.def.name}.${method}`); @@ -9861,7 +9876,7 @@ function lowerUnionObjectMethodCall( kind: "dynCall", callee, receiver, - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args: call.arguments.nodes.map((arg) => lowerer.lowerExprExpecting(arg, DYN)), type: DYN, loc, @@ -10023,7 +10038,7 @@ function lowerUnionObjectDynFieldCall( kind: "dynCall", callee, receiver: lowerer.coerceToExpected(receiverRef, DYN), - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args, type: DYN, loc, @@ -10405,7 +10420,7 @@ function maybePromiseHandlerResult( function lowerObjectAssignOntoError(lowerer: Lowerer, call: ts.CallExpression): IrExpr | null { const argNodes = call.arguments.nodes; if ( - isJsSourceFile(call.getSourceFile()) || + isJsSourceFile(ts.getSourceFileOfNode(call)) || argNodes.length < 2 || argNodes.some((a) => ts.isSpreadElement(a)) ) diff --git a/packages/compiler/src/frontend/lowering/lower-classes.ts b/packages/compiler/src/frontend/lowering/lower-classes.ts index 99d854a6a..49e7ad9fc 100644 --- a/packages/compiler/src/frontend/lowering/lower-classes.ts +++ b/packages/compiler/src/frontend/lowering/lower-classes.ts @@ -183,9 +183,9 @@ export function returnsOnlyThis(member: ts.MethodDeclaration): boolean { while (value && ts.isParenthesizedExpression(value)) value = value.expression; if (!value || value.kind !== ts.SyntaxKind.ThisKeyword) valid = false; } - node.forEachChild(visit); + ts.forEachChild(node, visit); }; - body.forEachChild(visit); + ts.forEachChild(body, visit); return valid; } @@ -216,9 +216,9 @@ export function returnsOnlyThisValue(member: ts.MethodDeclaration): boolean { } if (!value || value.kind !== ts.SyntaxKind.ThisKeyword) valid = false; } - node.forEachChild(visit); + ts.forEachChild(node, visit); }; - body.forEachChild(visit); + ts.forEachChild(body, visit); return valid; } @@ -341,7 +341,7 @@ export function storedClassValueType( } if ( (ts.isArrowFunction(callee) || ts.isFunctionExpression(callee)) && - isJsSourceFile(callee.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(callee)) && !callee.typeParameters?.nodes.length && !lowerer.preparingClassFactories.has(callee) ) { @@ -410,7 +410,7 @@ export function storedClassValueType( } if ( !factory || - !isJsSourceFile(factory.getSourceFile()) || + !isJsSourceFile(ts.getSourceFileOfNode(factory)) || factory.typeParameters?.nodes.length || !ts.isVariableDeclaration(factory.parent) || factory.parent.initializer !== factory || @@ -808,7 +808,7 @@ function symbolSlotReturnType( ): IrType | null { if (symbolFields.size === 0) return null; if (fnLike.type !== undefined || !fnLike.body) return null; - if (!isJsSourceFile(fnLike.getSourceFile())) return null; + if (!isJsSourceFile(ts.getSourceFileOfNode(fnLike))) return null; if (fnLike.asteriskToken !== undefined) return null; if (fnLike.modifiers?.nodes.some((m) => m.kind === ts.SyntaxKind.AsyncKeyword)) return null; const stmts = fnLike.body.statements.nodes; @@ -818,9 +818,9 @@ function symbolSlotReturnType( const visit = (n: ts.Node): void => { if (ts.isFunctionLike(n)) return; if (ts.isReturnStatement(n)) returns.push(n); - n.forEachChild(visit); + ts.forEachChild(n, visit); }; - fnLike.body.forEachChild(visit); + ts.forEachChild(fnLike.body, visit); let out: IrType | null = null; for (const r of returns) { let e = r.expression; @@ -1055,7 +1055,7 @@ export function builtinStreamInfoOf( } let irName: string | null = null; for (const [name, rec] of RUNTIME_STREAM_CLASSES) { - if (rec.lib === symbol.name) irName = name; + if (rec.lib === ts.symbolName(symbol)) irName = name; } if (!irName) return null; // Both the bundled declarations and @types/node declare the classes @@ -1096,7 +1096,7 @@ function jsFieldInitializerType(lowerer: Lowerer, type: IrType, expression: ts.E // JavaScript documentation cannot guarantee that a call or an indexed // read returns a present number. Numeric fields receiving those values // must retain undefined, so later arithmetic performs JS coercion (NaN). - if (type.kind === "f64" && isJsSourceFile(expression.getSourceFile())) { + if (type.kind === "f64" && isJsSourceFile(ts.getSourceFileOfNode(expression))) { const assignment = expression.parent; const left = ts.isBinaryExpression(assignment) ? assignment.left : null; const field = @@ -1128,7 +1128,7 @@ function jsFieldInitializerType(lowerer: Lowerer, type: IrType, expression: ts.E if (ts.isCallExpression(rhs) || ts.isElementAccessExpression(rhs)) acceptsUnchecked = true; } - node.forEachChild(scan); + ts.forEachChild(node, scan); }; scan(owner); if (acceptsUnchecked) return DYN; @@ -1165,7 +1165,7 @@ function jsFieldInitializerType(lowerer: Lowerer, type: IrType, expression: ts.E owner = owner.parent; if (owner && (ts.isClassDeclaration(owner) || ts.isClassExpression(owner)) && owner.name) { const className = owner.name.text; - for (const statement of owner.getSourceFile().statements.nodes) { + for (const statement of ts.getSourceFileOfNode(owner).statements.nodes) { if ( !ts.isExpressionStatement(statement) || !ts.isBinaryExpression(statement.expression) @@ -1268,7 +1268,7 @@ function returnsWidenedJsField( ret: IrType, ): boolean { if ( - !isJsSourceFile(member.getSourceFile()) || + !isJsSourceFile(ts.getSourceFileOfNode(member)) || member.type || member.asteriskToken || hasExplicitJsDocReturn(member) @@ -1331,7 +1331,7 @@ function returnsWidenedJsField( const scan = (node: ts.Node): void => { if (result || ts.isFunctionLike(node)) return; if (ts.isReturnStatement(node) && node.expression && widened(node.expression)) result = true; - node.forEachChild(scan); + ts.forEachChild(node, scan); }; if (member.body) scan(member.body); return result; @@ -1410,7 +1410,7 @@ export function builtinEmitterInfoOf( } return null; } - if (symbol.name !== "EventEmitter") return null; + if (ts.symbolName(symbol) !== "EventEmitter") return null; const declared = symbol.declarations.some((d) => { if (!ts.isClassDeclaration(d) && !ts.isInterfaceDeclaration(d)) return false; let node: ts.Node | undefined = d.parent; @@ -1440,7 +1440,7 @@ export function builtinErrorInfoOf( if (alias !== null) return alias.kind === "error" ? alias.info : null; if (!lowerer.isStdlibSymbol(symbol)) return null; for (const [irName, rec] of RUNTIME_ERROR_CLASSES) { - if (rec.lib === symbol.name) return lowerer.classes.get(irName) ?? null; + if (rec.lib === ts.symbolName(symbol)) return lowerer.classes.get(irName) ?? null; } return null; } @@ -1585,9 +1585,9 @@ function collectClassShapeDeferring(lowerer: Lowerer, decl: ts.ClassDeclaration) if ( baseDeclaration && !lowerer.isStdlibSymbol(baseSymbol ?? undefined) && - !baseDeclaration.getSourceFile().isDeclarationFile && - (baseDeclaration.getSourceFile() !== decl.getSourceFile() || - baseDeclaration.getStart() < decl.getStart()) + !ts.getSourceFileOfNode(baseDeclaration).isDeclarationFile && + (ts.getSourceFileOfNode(baseDeclaration) !== ts.getSourceFileOfNode(decl) || + ts.tokenStartOfNode(baseDeclaration) < ts.tokenStartOfNode(decl)) ) lowerer.collectClassShape(baseDeclaration); lowerer.collectClassShapeInner(decl); @@ -1792,7 +1792,7 @@ export function collectClassShapeInner( // source (V8 has not shipped the proposal; the type-stripping // loaders leave `@dec` in place) — there is no runtime behavior // to be exact against. - if (isJsSourceFile(decl.getSourceFile())) { + if (isJsSourceFile(ts.getSourceFileOfNode(decl))) { lowerer.unsupported( "SC1090", (classDecoratorNodes[0] ?? decoratorNodesOf(decoratedMembers[0]!)[0])!, @@ -1930,7 +1930,7 @@ export function collectClassShapeInner( const source = signatures.length === 1 ? signatures[0]!.declaration : undefined; if ( !source || - !isJsSourceFile(source.getSourceFile()) || + !isJsSourceFile(ts.getSourceFileOfNode(source)) || !(ts.isFunctionDeclaration(source) || ts.isFunctionExpression(source)) ) return false; @@ -2046,13 +2046,13 @@ export function collectClassShapeInner( const baseDecl = propBase.decl; if ( baseDecl != null && - baseDecl.getSourceFile() === decl.getSourceFile() && - baseDecl.getStart() > decl.getStart() + ts.getSourceFileOfNode(baseDecl) === ts.getSourceFileOfNode(decl) && + ts.tokenStartOfNode(baseDecl) > ts.tokenStartOfNode(decl) ) { lowerer.unsupported( "SC1090", t, - `extending '${t.expression.getText()}' above the statement that assigns it (the property is still undefined when this class evaluates — Node throws here; assign the base first)`, + `extending '${ts.getTextOfNode(t.expression)}' above the statement that assigns it (the property is still undefined when this class evaluates — Node throws here; assign the base first)`, ); } base = propBase; @@ -2070,7 +2070,7 @@ export function collectClassShapeInner( lowerer.unsupported( "SC1090", t, - `extending the reassigned property '${t.expression.getText()}' (the runtime base is whichever assignment ran last — bind the class exactly once)`, + `extending the reassigned property '${ts.getTextOfNode(t.expression)}' (the runtime base is whichever assignment ran last — bind the class exactly once)`, ); } } @@ -2289,7 +2289,7 @@ export function collectClassShapeInner( // Public JS accessors need live constructor descriptors, including // receiver-sensitive getters and setters inherited by subclasses. const checkedStatics = - isJsSourceFile(decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(decl)) && ((base && hasRuntimeStatics(base)) || decl.members.nodes.some( (member) => @@ -2363,7 +2363,7 @@ export function collectClassShapeInner( for (const member of decl.members.nodes) { if ( (functionLocalClass(decl) || checkedStatics) && - isJsSourceFile(decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(decl)) && (ts.isClassStaticBlockDeclaration(member) || ts .getModifiers(member) @@ -2374,7 +2374,7 @@ export function collectClassShapeInner( } if ( functionLocalClass(decl) && - isJsSourceFile(decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(decl)) && (ts.isMethodDeclaration(member) || ts.isAccessor(member)) && member.body && !ts.isPrivateIdentifier(member.name) @@ -2446,7 +2446,7 @@ export function collectClassShapeInner( if (type.kind === "void") lowerer.badType(member.name, lowerer.typeOf(member.name)); if ( type.kind === "dyn" && - !isJsSourceFile(member.getSourceFile()) && + !isJsSourceFile(ts.getSourceFileOfNode(member)) && !lowerer.classFieldStoresDyn(lowerer.typeOf(member.name)) && (member.type !== undefined || (lowerer.typeOf(member.name).flags & ts.TypeFlags.Any) === 0) @@ -2480,7 +2480,7 @@ export function collectClassShapeInner( } staticMethods.set(member.name.text, { params: shapes, ret: ft.ret, member }); if ( - implicitMonoFile(decl.getSourceFile()) && + implicitMonoFile(ts.getSourceFileOfNode(decl)) && !functionLocalClass(decl) && inst === undefined && decl.typeParameters === undefined @@ -2537,7 +2537,7 @@ export function collectClassShapeInner( // read of an Error-rooted class is the checked-dynamic property // (fieldTarget's errorCause container), so only types that read // back unchanged from it are accepted. - if (!isJsSourceFile(member.getSourceFile())) { + if (!isJsSourceFile(ts.getSourceFileOfNode(member))) { if ( modifiers?.some( (m) => @@ -2756,10 +2756,12 @@ export function collectClassShapeInner( // unconditional `this[key] = ...` at the constructor's top // level), checked with the unguarded fields below. const declaredOnly = - !member.initializer && !isJsSourceFile(member.getSourceFile()) && !!member.type; + !member.initializer && !isJsSourceFile(ts.getSourceFileOfNode(member)) && !!member.type; if ( fieldName === null || - (!member.initializer && !isJsSourceFile(member.getSourceFile()) && !declaredOnly) + (!member.initializer && + !isJsSourceFile(ts.getSourceFileOfNode(member)) && + !declaredOnly) ) { lowerer.unsupported( "SC1090", @@ -2777,7 +2779,9 @@ export function collectClassShapeInner( ? (lowerer.mapTypeOf(inferred) ?? dynFallbackType(lowerer, member, inferred)) : DYN; let type = - mapped?.kind === "record" && isJsSourceFile(member.getSourceFile()) && !member.type + mapped?.kind === "record" && + isJsSourceFile(ts.getSourceFileOfNode(member)) && + !member.type ? DYN : mapped ? jsOpenObjectType(member, mapped, lowerer.shapes, lowerer.unions) @@ -2786,11 +2790,11 @@ export function collectClassShapeInner( !type || type.kind === "void" || (type.kind === "dyn" && - !isJsSourceFile(member.getSourceFile()) && + !isJsSourceFile(ts.getSourceFileOfNode(member)) && !lowerer.classFieldStoresDyn(inferred)) ) lowerer.badType(member, inferred); - if (isJsSourceFile(member.getSourceFile())) { + if (isJsSourceFile(ts.getSourceFileOfNode(member))) { type = inheritedJsFieldType(lowerer, decl, fieldName, type); if (base?.fields.get(fieldName)?.kind === "dyn") type = DYN; if (base && !fields.has(fieldName) && lowerer.dynConvertible(type)) type = DYN; @@ -2904,7 +2908,7 @@ export function collectClassShapeInner( // as undefined on construction, verified), and reads/writes ride // the ordinary undefined-armed union machinery. let type = lowerer.irTypeOf(member.name); - if (isJsSourceFile(member.getSourceFile()) && !member.initializer) { + if (isJsSourceFile(ts.getSourceFileOfNode(member)) && !member.initializer) { const constructor = decl.members.nodes.find( (part): part is ts.ConstructorDeclaration => ts.isConstructorDeclaration(part) && !!part.body, @@ -2932,19 +2936,25 @@ export function collectClassShapeInner( // Unannotated JavaScript callback slots retain the original // callable and its receiver instead of adapting its signature. if ( - isJsSourceFile(member.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(member)) && !member.type && - !/@type\b/.test(member.getSourceFile().text.slice(member.pos, member.getStart())) && + !/@type\b/.test( + ts.getSourceFileOfNode(member).text.slice(member.pos, ts.tokenStartOfNode(member)), + ) && (type.kind === "func" || type.kind === "record") && lowerer.dynConvertible(type) ) type = DYN; // JS permits reads before a constructor's first assignment even // when its inferred field type omits undefined. - if (isJsSourceFile(member.getSourceFile()) && !member.initializer && type.kind !== "dyn") { + if ( + isJsSourceFile(ts.getSourceFileOfNode(member)) && + !member.initializer && + type.kind !== "dyn" + ) { type = lowerer.withUndefinedArmOf(type) ?? DYN; } - if (isJsSourceFile(member.getSourceFile())) { + if (isJsSourceFile(ts.getSourceFileOfNode(member))) { type = inheritedJsFieldType(lowerer, decl, member.name.text, type); if (base?.fields.get(member.name.text)?.kind === "dyn") type = DYN; if (base && !fields.has(member.name.text) && lowerer.dynConvertible(type)) type = DYN; @@ -2955,7 +2965,7 @@ export function collectClassShapeInner( // storage as implicit constructor-assigned JS fields. if ( type.kind === "dyn" && - !isJsSourceFile(member.getSourceFile()) && + !isJsSourceFile(ts.getSourceFileOfNode(member)) && !lowerer.classFieldStoresDyn(lowerer.typeOf(member.name)) ) { lowerer.unsupported( @@ -3227,7 +3237,7 @@ export function collectClassShapeInner( // name (the override stays on the vtable), a shadowed field, or // a generic-class instantiation's member. if ( - implicitMonoFile(decl.getSourceFile()) && + implicitMonoFile(ts.getSourceFileOfNode(decl)) && !functionLocalClass(decl) && ts.isIdentifier(member.name) && !lowerer.virtualJsMethods.has(member) && @@ -3268,14 +3278,14 @@ export function collectClassShapeInner( // JS inference may retain only a symbol sentinel while a method // also returns values from an untyped mutable field or local. if ( - isJsSourceFile(member.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(member)) && !member.type && !hasExplicitJsDocReturn(member) && ft.ret.kind === "symbol" ) ft.ret = DYN; if ( - isJsSourceFile(member.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(member)) && lowerer.prototypeMethodAccesses.has(mName) && !lowerer.findMethodOn(base, mName)?.declarer.def.runtime && shapes.every((p) => p.mode === "required" || p.mode === "omittable") @@ -3329,7 +3339,7 @@ export function collectClassShapeInner( // against the STATIC receiver's shape, so `m(x?: number)` and // `m(x: number | undefined)` interchange soundly in overrides. const overridden = lowerer.findMethodOn(base, mName); - if (overridden && isJsSourceFile(member.getSourceFile())) { + if (overridden && isJsSourceFile(ts.getSourceFileOfNode(member))) { // An unannotated JS override can return a checked value into // the inherited native ABI, just as its untyped parameters do. // Body lowering inserts the conversion at each return. @@ -3377,7 +3387,7 @@ export function collectClassShapeInner( // readers keep the exact-signature fence. if ( overridden && - isJsSourceFile(member.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(member)) && shapes.length < overridden.sig.params.length && !bodyReadsArguments(member) && shapes.every( @@ -3405,9 +3415,9 @@ export function collectClassShapeInner( // a statically typed receiver see the same ABI. if ( overridden && - !isJsSourceFile(member.getSourceFile()) && + !isJsSourceFile(ts.getSourceFileOfNode(member)) && overridden.declarer.decl !== null && - isJsSourceFile(overridden.declarer.decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(overridden.declarer.decl)) && member.asteriskToken === undefined && !overridden.sig.async && !overridden.sig.gen && @@ -3458,9 +3468,9 @@ export function collectClassShapeInner( )) ) { const fencedJsOverride = - isJsSourceFile(member.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(member)) && overridden.declarer.decl !== null && - isJsSourceFile(overridden.declarer.decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(overridden.declarer.decl)) && overridden.sig.gen === undefined; // A TypeScript override that only changes the result, into a // type that converts into the inherited one (`Promise` over @@ -3510,7 +3520,7 @@ export function collectClassShapeInner( // into the inherited type (`toString() { return // this.message }` over Error's). (ft.ret.kind === "dyn" && - isJsSourceFile(member.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(member)) && overridden.sig.ret.kind !== "void" && canDynCheckTo( overridden.sig.ret, @@ -3584,7 +3594,7 @@ export function collectClassShapeInner( overridden && !returnAdapters.has(mName) && (asyncMember || overridden.sig.async === true) && - !isJsSourceFile(member.getSourceFile()) && + !isJsSourceFile(ts.getSourceFileOfNode(member)) && !functionLocalClass(decl) && member.asteriskToken === undefined && overridden.sig.gen === undefined && @@ -3606,7 +3616,7 @@ export function collectClassShapeInner( (asyncMember || overridden.sig.async === true) ) { const message = `overriding ${overridden.sig.async === true ? "the async method" : "a method with an async method"} '${mName}' (async methods dispatch statically — the vtable slot machinery has no fiber-spawn story)`; - if (isJsSourceFile(member.getSourceFile())) + if (isJsSourceFile(ts.getSourceFileOfNode(member))) methodEntryFences.set(mName, unsupportedDiag("SC1090", locOf(member.name), message)); else lowerer.unsupported("SC1090", member.name, message); } @@ -3615,7 +3625,7 @@ export function collectClassShapeInner( (member.asteriskToken !== undefined || overridden.sig.gen !== undefined) ) { const message = `overriding the generator method '${mName}' (generator methods dispatch statically — a virtual slot cannot enter their spawn wrapper)`; - if (isJsSourceFile(member.getSourceFile())) + if (isJsSourceFile(ts.getSourceFileOfNode(member))) methodEntryFences.set(mName, unsupportedDiag("SC1090", locOf(member.name), message)); else lowerer.unsupported("SC1090", member.name, message); } @@ -3687,7 +3697,7 @@ export function collectClassShapeInner( const mName = `${isGet ? "get" : "set"}:${prop}`; if ( fields.has(prop) && - isJsSourceFile(decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(decl)) && base?.fields.has(prop) && !fieldOrder.some((field) => field.name === prop) ) { @@ -4002,7 +4012,7 @@ export function collectClassShapeInner( // assignment ran — the zeroed-memory trap a TS declaration order // forbids via strictPropertyInitialization — so they keep a named // fence instead of reading undefined without a diagnostic. - if (isJsSourceFile(decl.getSourceFile())) { + if (isJsSourceFile(ts.getSourceFileOfNode(decl))) { const hasAccessor = (name: string): boolean => methods.has(`get:${name}`) || methods.has(`set:${name}`) || @@ -4026,7 +4036,7 @@ export function collectClassShapeInner( // goes through the key. const lateBoundByKey = new Map(); for (const p of instType ? lowerer.checker.getPropertiesOfType(instType) : []) { - if (!p.name.startsWith("__@")) continue; + if (!ts.symbolName(p).startsWith("__@")) continue; const keySym = lateBoundKeySymOf(lowerer, p); if (keySym) lateBoundByKey.set(keySym, p); } @@ -4082,7 +4092,7 @@ export function collectClassShapeInner( if ( t && (t.flags & (ts.TypeFlags.Null | ts.TypeFlags.Undefined)) !== 0 && - isJsSourceFile(decl.getSourceFile()) + isJsSourceFile(ts.getSourceFileOfNode(decl)) ) type = DYN; if (type) type = jsOpenObjectType(assign, type, lowerer.shapes, lowerer.unions); @@ -4200,10 +4210,14 @@ export function collectClassShapeInner( // static layouts cannot represent. Named fence, at the first // assignment site. for (const p of instType ? lowerer.checker.getPropertiesOfType(instType) : []) { - if (errorRooted && p.name === "cause") continue; - if (fields.has(p.name) || methods.has(p.name)) continue; - if (hasAccessor(p.name)) continue; - if (base && (base.fields.has(p.name) || lowerer.findMethodOn(base, p.name))) continue; + if (errorRooted && ts.symbolName(p) === "cause") continue; + if (fields.has(ts.symbolName(p)) || methods.has(ts.symbolName(p))) continue; + if (hasAccessor(ts.symbolName(p))) continue; + if ( + base && + (base.fields.has(ts.symbolName(p)) || lowerer.findMethodOn(base, ts.symbolName(p))) + ) + continue; const site = p.declarations.find( (d) => ts.isPropertyAccessExpression(d) || @@ -4217,7 +4231,7 @@ export function collectClassShapeInner( // identities (symbol parameters, mutable bindings, computed // descriptions) or assignments outside the constructor's top // level. - if (p.name.startsWith("__@")) { + if (ts.symbolName(p).startsWith("__@")) { const keySym = lateBoundKeySymOf(lowerer, p); const key = keySym ? classSymbolKeyOfSymbol(lowerer, keySym) : null; if (key && symbolFields.has(key.identity)) continue; @@ -4237,24 +4251,26 @@ export function collectClassShapeInner( // Unmappable inferences and arm-less kinds keep the fence. // TypeScript classes keep the compile-time fence too: an annotated // program can spell `T | undefined` itself. - if (isJsSourceFile(decl.getSourceFile())) { + if (isJsSourceFile(ts.getSourceFileOfNode(decl))) { const original = undefArmedFieldType(lowerer, p); const armed = - original === null ? null : inheritedJsFieldType(lowerer, decl, p.name, original); + original === null + ? null + : inheritedJsFieldType(lowerer, decl, ts.symbolName(p), original); if (armed !== null) { // Unknown JS properties use the instance bag: they do not // exist until assigned, and a subclass may declare the same // name as an ordinary layout field. if (armed.kind === "dyn") continue; - fields.set(p.name, armed); - fieldOrder.push({ name: p.name, type: armed, initializer: undefined }); + fields.set(ts.symbolName(p), armed); + fieldOrder.push({ name: ts.symbolName(p), type: armed, initializer: undefined }); continue; } } lowerer.unsupported( "SC1090", site, - `fields assigned outside the constructor's top level ('this.${p.name}' would be undefined until the first assignment runs — assign it unconditionally at the top of the constructor)`, + `fields assigned outside the constructor's top level ('this.${ts.symbolName(p)}' would be undefined until the first assignment runs — assign it unconditionally at the top of the constructor)`, ); } } @@ -4297,7 +4313,7 @@ export function collectClassShapeInner( if ( ctor && !mixinForwarding && - isJsSourceFile(ctor.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(ctor)) && !ctorParams.some((shape) => shape.mode === "rest" || shape.mode === "dynRest") && bodyReadsArguments(ctor) ) { @@ -4692,7 +4708,7 @@ export function lowerStaticFieldInits(lowerer: Lowerer, info: ClassInfo): IrStmt // Keep their refusal attached to the class declaration instead of // letting a poisoned initializer escape the module lowering pass. if (!(error instanceof PoisonError)) throw error; - if (info.decl && isJsSourceFile(info.decl.getSourceFile())) { + if (info.decl && isJsSourceFile(ts.getSourceFileOfNode(info.decl))) { const fence = lowerer.diagSink === null ? lowerer.deferToRuntimeFence(diagsBefore, info.decl, { kind: "statement" }) @@ -4819,8 +4835,16 @@ function lowerStaticFieldInitsInner(lowerer: Lowerer, info: ClassInfo): IrStmt[] | { pos: number; kind: "field"; f: ClassInfo["staticFields"][number] } | { pos: number; kind: "block"; b: ts.ClassStaticBlockDeclaration }; const items: Item[] = [ - ...info.staticFields.map((f): Item => ({ pos: f.initializer.getStart(), kind: "field", f })), - ...(info.staticBlocks ?? []).map((b): Item => ({ pos: b.getStart(), kind: "block", b })), + ...info.staticFields.map((f): Item => ({ + pos: ts.tokenStartOfNode(f.initializer), + kind: "field", + f, + })), + ...(info.staticBlocks ?? []).map((b): Item => ({ + pos: ts.tokenStartOfNode(b), + kind: "block", + b, + })), ].sort((a, b) => a.pos - b.pos); for (const item of items) { if (item.kind === "block") { @@ -4867,7 +4891,7 @@ function lowerStaticFieldInitsInner(lowerer: Lowerer, info: ClassInfo): IrStmt[] } catch (e) { if (!(e instanceof PoisonError)) throw e; lowerer.countStatementFailed(f.initializer, locOf(f.initializer).file); - if (isJsSourceFile(f.initializer.getSourceFile())) { + if (isJsSourceFile(ts.getSourceFileOfNode(f.initializer))) { const fence = lowerer.diagSink === null ? lowerer.deferToRuntimeFence(diagsBefore, f.initializer, { kind: "statement" }) @@ -4926,7 +4950,7 @@ export function analyzeClassDecoration(lowerer: Lowerer, info: ClassInfo): void ts.isVariableDeclaration(vdecl) && vdecl.initializer === undefined && (ts.getCombinedModifierFlags(vdecl) & ts.ModifierFlags.Ambient) !== 0 && - !vdecl.getSourceFile().isDeclarationFile; + !ts.getSourceFileOfNode(vdecl).isDeclarationFile; if (ambientVar || ambientUndefinedFnSymbolOf(lowerer, ambientCallee) !== null) { shapes.push({ kind: "ambientThrow", name: ambientCallee.text }); continue; @@ -5549,7 +5573,7 @@ export function lowerStaticFieldRead( // JavaScript often assigns public statics after the class declaration. // Their storage stays mutable and checked, while reads retain the // checker's concrete type instead of losing the method receiver. - const type = isJsSourceFile(expr.getSourceFile()) + const type = isJsSourceFile(ts.getSourceFileOfNode(expr)) ? null : lowerer.mapTypeOf(lowerer.typeOf(expr)); return type ? lowerer.coerceToExpected(value, type) : value; @@ -5590,7 +5614,7 @@ function namedEvaluationName(expr: ts.ClassLikeDeclaration): string { * (private-brands.ts), so the class must be a named declaration (undici's * webidl FastIterableIterator). The checker rejects writes to that binding. */ function localPrivateFieldsAgree(expr: ts.ClassLikeDeclaration): boolean { - if (!isJsSourceFile(expr.getSourceFile()) || !ts.isClassDeclaration(expr) || !expr.name) + if (!isJsSourceFile(ts.getSourceFileOfNode(expr)) || !ts.isClassDeclaration(expr) || !expr.name) return false; if ( expr.members.nodes.some( @@ -5698,7 +5722,7 @@ export function lowerClassExpressionInfo( expr.members.nodes.some( (member) => decoratorNodesOf(member).length || - (!isJsSourceFile(expr.getSourceFile()) && + (!isJsSourceFile(ts.getSourceFileOfNode(expr)) && (ts.isClassStaticBlockDeclaration(member) || ts .getModifiers(member) @@ -5822,7 +5846,7 @@ export function lowerClassExpression(lowerer: Lowerer, expr: ts.ClassLikeDeclara decoratorNodesOf(expr).length > 0 || expr.members.nodes.some((m) => decoratorNodesOf(m).length > 0) ) { - if (!isJsSourceFile(expr.getSourceFile()) && expr.typeParameters === undefined) { + if (!isJsSourceFile(ts.getSourceFileOfNode(expr)) && expr.typeParameters === undefined) { const thrown = guaranteedDecorationThrow(lowerer, expr); if (thrown) { // The expression's static type never materializes — the read @@ -6108,12 +6132,12 @@ function enclosingTopLevelClassAssignment(expr: ts.ClassExpression): ts.BinaryEx * `Common.I`): a second assignment makes the binding dynamic, so * callers refuse to pin the first one's class. */ function countAssignmentsTo(assign: ts.BinaryExpression): number { - const sf = assign.getSourceFile(); + const sf = ts.getSourceFileOfNode(assign); // `exports.I` and `module.exports.I` are the SAME binding in Node // (exports aliases module.exports until a table replaces it) — fold // the member spellings together before comparing. const canon = (lhs: ts.Expression): string => { - const text = lhs.getText().replace(/\s+/g, ""); + const text = ts.getTextOfNode(lhs).replace(/\s+/g, ""); return text.startsWith("module.exports.") ? text.slice("module.".length) : text; }; const target = canon(assign.left); @@ -6171,10 +6195,10 @@ export function lowerStaticMethodCall( if (!ts.isIdentifier(access.expression)) { if ( !isModuleExportsAccess(access.expression) || - !isCjsJsFile(access.getSourceFile(), lowerer.program) + !isCjsJsFile(ts.getSourceFileOfNode(access), lowerer.program) ) { let receiverType = lowerer.mapTypeOf(lowerer.typeOf(access.expression)); - if (receiverType?.kind !== "classval" && isJsSourceFile(call.getSourceFile())) { + if (receiverType?.kind !== "classval" && isJsSourceFile(ts.getSourceFileOfNode(call))) { const receiver = tryLowerExpression(lowerer, access.expression); if (receiver?.type.kind === "classval") receiverType = receiver.type; } @@ -6195,7 +6219,7 @@ export function lowerStaticMethodCall( loc: locOf(call), }; } - const whole = cjsClassExprWholeExportOf(access.getSourceFile()); + const whole = cjsClassExprWholeExportOf(ts.getSourceFileOfNode(access)); if (!whole) return null; return staticCallOn( lowerer, @@ -6492,9 +6516,9 @@ function hasLexicalSuper(root: ts.Node): boolean { ts.isClassExpression(node) ) return; - node.forEachChild(visit); + ts.forEachChild(node, visit); }; - root.forEachChild(visit); + ts.forEachChild(root, visit); return found; } @@ -6523,9 +6547,9 @@ function capturesEnclosingScope( if (decl && decl !== scope && inside(decl, scope) && !inside(decl, member)) captured = true; return; } - node.forEachChild(visit); + ts.forEachChild(node, visit); }; - member.forEachChild(visit); + ts.forEachChild(member, visit); return captured; } @@ -6648,7 +6672,10 @@ export function lowerClassValueProperty( // `module.exports.label` in a class-replaced CJS module: the // receiver is the exact exported class, and the read is // side-effect-free — as bindable as an identifier. - !(isModuleExportsAccess(expr.expression) && isCjsJsFile(expr.getSourceFile(), lowerer.program)) + !( + isModuleExportsAccess(expr.expression) && + isCjsJsFile(ts.getSourceFileOfNode(expr), lowerer.program) + ) ) { // Devirtualized reads DISCARD the receiver value, so only // side-effect-free receivers are claimed (the instanceOf fold @@ -6912,7 +6939,7 @@ export function collectInheritedJsFieldWrites( if (name !== null) addWrite(name, part.right); } } - part.forEachChild(scan); + ts.forEachChild(part, scan); }; for (const member of node.members.nodes) { if ( @@ -6953,7 +6980,7 @@ export function collectInheritedJsFieldWrites( if ( !owner || (!ts.isClassDeclaration(owner) && !ts.isClassExpression(owner)) || - !isJsSourceFile(owner.getSourceFile()) + !isJsSourceFile(ts.getSourceFileOfNode(owner)) ) continue; let fields = lowerer.inheritedJsFieldWrites.get(owner); @@ -6991,7 +7018,7 @@ export function collectVirtualJsMethods(lowerer: Lowerer, files: readonly ts.Sou if ( node.heritageClauses?.nodes.length && accessors.length && - isJsSourceFile(node.getSourceFile()) + isJsSourceFile(ts.getSourceFileOfNode(node)) ) { const symbol = lowerer.typeOf(node).getSymbol(); const instance = symbol ? lowerer.checker.getDeclaredTypeOfSymbol(symbol) : null; @@ -7008,7 +7035,7 @@ export function collectVirtualJsMethods(lowerer: Lowerer, files: readonly ts.Sou if ( (ts.isGetAccessorDeclaration(declaration) || ts.isSetAccessorDeclaration(declaration)) && - isJsSourceFile(declaration.getSourceFile()) + isJsSourceFile(ts.getSourceFileOfNode(declaration)) ) { lowerer.checkedVirtualJsAccessors.add(declaration); lowerer.checkedVirtualJsAccessors.add(member); @@ -7050,12 +7077,12 @@ export function collectVirtualJsMethods(lowerer: Lowerer, files: readonly ts.Sou for (const declaration of property ? property.declarations : []) { if ( ts.isMethodDeclaration(declaration) && - isJsSourceFile(declaration.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(declaration)) && !checkedJsOverride(lowerer, declaration, member) ) { lowerer.virtualJsMethods.add(declaration); if ( - isJsSourceFile(member.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(member)) && !member.typeParameters?.nodes && !member.asteriskToken && !member.modifiers?.nodes.some((m) => m.kind === ts.SyntaxKind.AsyncKeyword) && @@ -7101,7 +7128,7 @@ export function collectVirtualJsMethods(lowerer: Lowerer, files: readonly ts.Sou if (node !== member && ts.isFunctionLike(node)) return; if (ts.isReturnStatement(node) && node.expression) lowerer.checkedVirtualJsValueReturns.add(member); - node.forEachChild(scan); + ts.forEachChild(node, scan); }; scan(member); } @@ -7134,7 +7161,7 @@ function checkedJsOverride( ): boolean { return [base, member].every( (method) => - lowerer.checkedJsFile(method.getSourceFile()) && + lowerer.checkedJsFile(ts.getSourceFileOfNode(method)) && method.parameters.nodes.every(documentedParameter), ); } @@ -7282,7 +7309,7 @@ export function lowerClassGenericMethodCall( result.type.kind === "object" && !ts.isExpressionStatement(call.parent) && ts.isMethodDeclaration(info.decl) && - isJsSourceFile(info.decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(info.decl)) && returnsOnlyThisValue(info.decl) ) { return lowerer.maybeNarrow(result, call); @@ -7387,7 +7414,7 @@ export function lowerClassMembers(lowerer: Lowerer, info: ClassInfo): IrFunction out.push(lowerer.lowerClassCtor(info)); } catch (e) { if (!(e instanceof PoisonError)) throw e; - if (info.decl && isJsSourceFile(info.decl.getSourceFile())) { + if (info.decl && isJsSourceFile(ts.getSourceFileOfNode(info.decl))) { const types: IrType[] = [ { kind: "object", className }, ...info.ctorParams.map((param) => param.type), @@ -8300,7 +8327,7 @@ export function fieldInitStmts(lowerer: Lowerer, info: ClassInfo, thisLocal: IrL } catch (e) { if (!(e instanceof PoisonError)) throw e; lowerer.countStatementFailed(f.initializer, locOf(f.initializer).file); - if (isJsSourceFile(f.initializer.getSourceFile())) { + if (isJsSourceFile(ts.getSourceFileOfNode(f.initializer))) { const fence = lowerer.diagSink === null ? lowerer.deferToRuntimeFence(diagsBefore, f.initializer, { kind: "statement" }) @@ -8799,7 +8826,7 @@ export function lowerSuperMethodCall( kind: "dynCall", callee, receiver: lowerer.coerceToExpected(thisRef, DYN), - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args: call.arguments.nodes.map((arg) => lowerer.lowerExprExpecting(arg, DYN)), type: DYN, loc, @@ -8974,7 +9001,7 @@ export function inheritedBuiltinCtorFence( lowerer.runtimeFences.push(diag); const line = info.decl !== null - ? ts.getLineAndCharacterOfPosition(info.decl.getSourceFile(), at.start).line + 1 + ? ts.getLineAndCharacterOfPosition(ts.getSourceFileOfNode(info.decl), at.start).line + 1 : null; const where = line !== null ? ` [${diag.code} at ${at.file}:${line}]` : ""; return { kind: "runtimeFence", code: diag.code, message: `${diag.message}${where}`, loc }; @@ -9422,7 +9449,7 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { // lowering ahead of the assignment statement). if ( !expr.expression.questionDotToken && - ((isCjsJsFile(expr.getSourceFile(), lowerer.program) && + ((isCjsJsFile(ts.getSourceFileOfNode(expr), lowerer.program) && (isModuleExportsAccess(expr.expression.expression) || (ts.isIdentifier(expr.expression.expression) && expr.expression.expression.text === "exports" && @@ -9438,7 +9465,7 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { memberSym, resolved, lowerer.resolveValueSymbol(expr.expression.name) ?? undefined, - lowerer.cjsModuleExportSymbol(expr.getSourceFile(), expr.expression.name.text), + lowerer.cjsModuleExportSymbol(ts.getSourceFileOfNode(expr), expr.expression.name.text), ]; const exportSym = candidates.find((s) => s !== undefined); const g = candidates @@ -9653,7 +9680,7 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { // libregexp engine the literals ride. The pattern compiles EAGERLY, // so bad input throws Node's catchable SyntaxError at construction. // Checked arguments retain native regexes and perform runtime coercion. - if (symbol && symbol.name === "RegExp" && lowerer.isStdlibSymbol(symbol)) { + if (symbol && ts.symbolName(symbol) === "RegExp" && lowerer.isStdlibSymbol(symbol)) { const args = expr.arguments?.nodes ?? []; if (args.length > 2) { lowerer.noLowering(`new RegExp with ${args.length} arguments`, expr); @@ -9721,19 +9748,19 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { loc, }; } - if (symbol && symbol.name === "URL" && lowerer.isStdlibSymbol(symbol)) { + if (symbol && ts.symbolName(symbol) === "URL" && lowerer.isStdlibSymbol(symbol)) { return lowerUrlNew(lowerer, expr); } // `new URLSearchParams(init?)`: the WHATWG list (stdlib provenance — // see lowerSearchParamsNew for the lowered init shapes). - if (symbol && symbol.name === "URLSearchParams" && lowerer.isStdlibSymbol(symbol)) { + if (symbol && ts.symbolName(symbol) === "URLSearchParams" && lowerer.isStdlibSymbol(symbol)) { return lowerSearchParamsNew(lowerer, expr, loc); } // Date's read-only value slice: store the constructor's TimeClip'd // epoch milliseconds as the scalar date kind. That is sufficient // for every getter and toISOString; identity and setters stay // fenced, so copying the scalar cannot create an observable lie. - if (symbol && symbol.name === "Date" && lowerer.isStdlibSymbol(symbol)) { + if (symbol && ts.symbolName(symbol) === "Date" && lowerer.isStdlibSymbol(symbol)) { const args = expr.arguments?.nodes ?? []; if (args.some(ts.isSpreadElement)) { lowerer.noLowering( @@ -9750,7 +9777,7 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { // pass numbers, completed with the spec's defaults (date 1, time // parts 0) for the seven-f64 ABI. if (args.length > 1) { - if (isJsSourceFile(expr.getSourceFile())) { + if (isJsSourceFile(ts.getSourceFileOfNode(expr))) { return { kind: "libCall", fn: "date.nativeNewFields", @@ -9781,7 +9808,7 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { } return { kind: "libCall", fn: "date.newLocal", args: fields, type: DATE_T, loc }; } - if (isJsSourceFile(expr.getSourceFile())) { + if (isJsSourceFile(ts.getSourceFileOfNode(expr))) { return { kind: "libCall", // One argument: a string parses with the full date parser. @@ -9826,7 +9853,7 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { // normalized at run time (Node's normalizeEncoding, which throws // ERR_UNKNOWN_ENCODING for an unknown name). Omitted means utf8, // Node's default. - if (symbol && symbol.name === "StringDecoder" && lowerer.isStdlibSymbol(symbol)) { + if (symbol && ts.symbolName(symbol) === "StringDecoder" && lowerer.isStdlibSymbol(symbol)) { const args = expr.arguments?.nodes ?? []; if (args.length > 1) { lowerer.noLowering("new StringDecoder with 2 arguments", expr, undefined, symbol); @@ -9860,12 +9887,13 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { loc, }; } + const codec = symbol ? ts.symbolName(symbol) : undefined; if ( symbol && - (symbol.name === "TextDecoder" || symbol.name === "TextEncoder") && + (codec === "TextDecoder" || codec === "TextEncoder") && lowerer.isStdlibSymbol(symbol) ) { - return lowerTextCodecNew(lowerer, expr, symbol.name); + return lowerTextCodecNew(lowerer, expr, codec); } // `new Uint8Array(...)` / `new Uint32Array(...)` / `new // Float32Array(...)` / `new Float64Array(...)`: the typed-array @@ -9899,11 +9927,11 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { // types get their half named specifically instead of the component // fence (SC2009, which names Map slots at value positions elsewhere). // Both Array() and new Array() share the elements/count lowering. - if (symbol?.name === "Object" && lowerer.isStdlibSymbol(symbol)) { + if (symbol !== null && ts.symbolName(symbol) === "Object" && lowerer.isStdlibSymbol(symbol)) { const object = lowerObjectConstructor(lowerer, expr.arguments?.nodes ?? [], loc); if (object) return object; } - if (symbol?.name === "Array" && lowerer.isStdlibSymbol(symbol)) { + if (symbol !== null && ts.symbolName(symbol) === "Array" && lowerer.isStdlibSymbol(symbol)) { return lowerArrayConstructor(lowerer, expr, expr.arguments?.nodes ?? []); } // A const alias (`const M = Map`) constructs the same Map. @@ -9919,7 +9947,8 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { : null; let tsType = lowerer.typeOf(expr); let mapped = lowerer.mapTypeOf(tsType); - if (mapped === null && isJsSourceFile(expr.getSourceFile())) mapped = lowerer.irTypeOf(expr); + if (mapped === null && isJsSourceFile(ts.getSourceFileOfNode(expr))) + mapped = lowerer.irTypeOf(expr); const fieldType = assignedThisFieldType(lowerer, expr); if (mapped?.kind !== "map" && fieldType?.kind === "map") mapped = fieldType; // A fresh map built from pair literals can use its destination's @@ -9981,7 +10010,7 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { // unconditionally; the format path never touches them. TypeScript // keeps the compile fence. if ( - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && (expr.arguments?.nodes.length ?? 0) === 0 && targs.length > 0 && targs.every((t) => (t.flags & ts.TypeFlags.Any) !== 0) @@ -10017,7 +10046,8 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { } // Weak containers share checked native key identities and lifetime hooks. if ( - (symbol?.name === "WeakMap" || symbol?.name === "WeakSet") && + ((symbol !== null && ts.symbolName(symbol) === "WeakMap") || + (symbol !== null && ts.symbolName(symbol) === "WeakSet")) && lowerer.isStdlibSymbol(symbol) ) { const args = expr.arguments?.nodes ?? []; @@ -10025,13 +10055,13 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { lowerer.unsupported( "SC1090", expr, - `${symbol.name} construction with surplus or spread arguments`, + `${ts.symbolName(symbol)} construction with surplus or spread arguments`, ); } return { kind: "libCall", - fn: symbol.name === "WeakMap" ? "weakMap.new" : "weakSet.new", + fn: ts.symbolName(symbol) === "WeakMap" ? "weakMap.new" : "weakSet.new", args: [args[0] ? lowerer.lowerExprExpecting(args[0], DYN) : dynUndefinedExpr(loc)], type: DYN, loc, @@ -10050,7 +10080,7 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { } if ( !mapped && - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && (expr.arguments?.nodes.length ?? 0) <= 1 && !expr.arguments?.nodes.some(ts.isSpreadElement) ) { @@ -10073,7 +10103,7 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { // the honest construction is a Set of those scalars. if ( !mapped && - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && (expr.arguments?.nodes.length ?? 0) === 1 && ts.isArrayLiteralExpression(expr.arguments!.nodes[0]!) && !(expr.arguments!.nodes[0] as ts.ArrayLiteralExpression).elements.nodes.some( @@ -10116,7 +10146,11 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { // `new AsyncLocalStorage()` (node:async_hooks): a fresh store id — // an f64 handle (type-mapper.ts), the Channel story. Construction options // ({ defaultValue, name }) have no lowering yet. - if (symbol?.name === "AsyncLocalStorage" && lowerer.isStdlibSymbol(symbol)) { + if ( + symbol !== null && + ts.symbolName(symbol) === "AsyncLocalStorage" && + lowerer.isStdlibSymbol(symbol) + ) { if ((expr.arguments?.nodes.length ?? 0) > 0) { lowerer.noLowering( "new AsyncLocalStorage(options)", @@ -10127,7 +10161,7 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { return { kind: "libCall", fn: "als.new", args: [], type: F64, loc }; } // `new Promise((resolve) => ...)`: the ambient Promise constructor. - if (symbol?.name === "Promise" && lowerer.isStdlibSymbol(symbol)) { + if (symbol !== null && ts.symbolName(symbol) === "Promise" && lowerer.isStdlibSymbol(symbol)) { const type = lowerer.irTypeOf(expr); if (type.kind !== "promise") lowerer.badType(expr, lowerer.typeOf(expr)); const args = expr.arguments?.nodes ?? []; @@ -10143,7 +10177,10 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { if (ts.isIdentifier(a0) && a0.text === "setImmediate") { const sym = lowerer.checker.getSymbolAtLocation(a0); const decls = sym ? sym.declarations : []; - if (decls.length > 0 && decls.every((d) => lowerer.isStdlibFile(d.getSourceFile()))) { + if ( + decls.length > 0 && + decls.every((d) => lowerer.isStdlibFile(ts.getSourceFileOfNode(d))) + ) { // The settled value is the undefined dyn value — the result // is promise whatever T the checker inferred for the // unusual executor (Promise in the suite's shape). @@ -10474,7 +10511,8 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { if ( def.arms.every( (arm) => - arm.kind === "classval" || (isJsSourceFile(expr.getSourceFile()) && isUnitType(arm)), + arm.kind === "classval" || + (isJsSourceFile(ts.getSourceFileOfNode(expr)) && isUnitType(arm)), ) && lowerer.dynConvertible(calleeT) ) { @@ -10507,7 +10545,7 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { const sym = raw && raw.flags & ts.SymbolFlags.Alias ? lowerer.checker.getAliasedSymbol(raw) : raw; const hint = - sym && lowerer.isStdlibSymbol(sym) ? own(STDLIB_CTOR_HINTS, sym.name) : undefined; + sym && lowerer.isStdlibSymbol(sym) ? own(STDLIB_CTOR_HINTS, ts.symbolName(sym)) : undefined; if (hint !== undefined) { lowerer.unsupported("SC1090", expr, hint); } diff --git a/packages/compiler/src/frontend/lowering/lower-comptime.ts b/packages/compiler/src/frontend/lowering/lower-comptime.ts index f239eea82..e442b34a5 100644 --- a/packages/compiler/src/frontend/lowering/lower-comptime.ts +++ b/packages/compiler/src/frontend/lowering/lower-comptime.ts @@ -86,7 +86,7 @@ export function lowerComptime(lowerer: Lowerer, expr: ts.CallExpression): IrExpr try { if (lowerer.frontendServices === undefined) throw new Error("compile-time evaluation requires frontend services"); - result = lowerer.frontendServices.evaluateComptime(cb.getText(), COMPTIME_TIMEOUT_MS); + result = lowerer.frontendServices.evaluateComptime(ts.getTextOfNode(cb), COMPTIME_TIMEOUT_MS); } catch (e) { const detail = comptimeFailureDetail(e); lowerer.pushDiag(comptimeFailedDiag(detail, locOf(cb))); @@ -226,7 +226,7 @@ export function rejectComptimeCaptures( lowerer: Lowerer, cb: ts.ArrowFunction | ts.FunctionExpression, ): void { - const sf = cb.getSourceFile(); + const sf = ts.getSourceFileOfNode(cb); const inTypePosition = (node: ts.Node): boolean => { for (let p: ts.Node | undefined = node.parent; p && p !== cb; p = p.parent) { if (ts.isTypeNode(p)) return true; @@ -249,7 +249,7 @@ export function rejectComptimeCaptures( const decls = symbol ? symbol.declarations : undefined; if (symbol && decls && decls.length > 0) { const ownLocal = decls.every( - (d) => d.getSourceFile() === sf && d.pos >= cb.pos && d.end <= cb.end, + (d) => ts.getSourceFileOfNode(d) === sf && d.pos >= cb.pos && d.end <= cb.end, ); if (!ownLocal) { if (node.text === "comptime" && lowerer.isStdlibSymbol(symbol)) { diff --git a/packages/compiler/src/frontend/lowering/lower-console.ts b/packages/compiler/src/frontend/lowering/lower-console.ts index a7fac4f51..eec308342 100644 --- a/packages/compiler/src/frontend/lowering/lower-console.ts +++ b/packages/compiler/src/frontend/lowering/lower-console.ts @@ -209,7 +209,7 @@ export function lowerConsoleInstanceCall( if (access.questionDotToken) return null; const recvType = lowerer.typeOf(access.expression); const sym = recvType.getAliasSymbol() ?? recvType.getSymbol(); - if (!sym || sym.name !== "Console" || !lowerer.isStdlibSymbol(sym)) return null; + if (!sym || ts.symbolName(sym) !== "Console" || !lowerer.isStdlibSymbol(sym)) return null; const toStderr = OUTPUT_MEMBERS.get(access.name.text); if (toStderr === undefined) { lowerer.noLowering( diff --git a/packages/compiler/src/frontend/lowering/lower-dgram.ts b/packages/compiler/src/frontend/lowering/lower-dgram.ts index 17522971d..4cb238352 100644 --- a/packages/compiler/src/frontend/lowering/lower-dgram.ts +++ b/packages/compiler/src/frontend/lowering/lower-dgram.ts @@ -195,7 +195,7 @@ export function lowerDgramDnsModuleCall( sawType = true; } else if (name === "reuseAddr") { reuseAddr = lowerer.lowerExprExpecting(prop.initializer, BOOL); - } else if (name === "signal" && isJsSourceFile(expr.getSourceFile())) { + } else if (name === "signal" && isJsSourceFile(ts.getSourceFileOfNode(expr))) { // A provably-non-AbortSignal signal (the invalid-input probes: // strings, numbers, plain records) throws Node's // validateAbortSignal ladder; plausible signal values keep the @@ -578,7 +578,7 @@ export function lowerDgramMethodCall( return { kind: "libCall", fn, args: [receiver, data, port, host], type: VOID, loc }; } if ( - isJsSourceFile(call.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(call)) && args.length <= 5 && !args.some(ts.isSpreadElement) ) { diff --git a/packages/compiler/src/frontend/lowering/lower-enums.ts b/packages/compiler/src/frontend/lowering/lower-enums.ts index cc2797fb9..e72dd2780 100644 --- a/packages/compiler/src/frontend/lowering/lower-enums.ts +++ b/packages/compiler/src/frontend/lowering/lower-enums.ts @@ -36,14 +36,14 @@ import { nodeThrowExpr } from "../../ir/build.js"; * the only name forms tsc allows on enum members). */ function memberNameText(member: ts.EnumMember): string { const n = member.name; - return ts.isIdentifier(n) || ts.isStringLiteralLike(n) ? n.text : n.getText(); + return ts.isIdentifier(n) || ts.isStringLiteralLike(n) ? n.text : ts.getTextOfNode(n); } /** Ambient: a `declare`d declaration, or one living in a .d.ts. */ function isAmbientEnumDecl(decl: ts.EnumDeclaration): boolean { return ( (ts.getCombinedModifierFlags(decl) & ts.ModifierFlags.Ambient) !== 0 || - decl.getSourceFile().isDeclarationFile + ts.getSourceFileOfNode(decl).isDeclarationFile ); } @@ -126,7 +126,7 @@ function lowerEnumMemberRead( // runtime values, so SC2020-family member fences own those. A user's // `declare enum` follows the // `declare const __VERSION__` stance: ReferenceError at the access. - const sf = enumDecl.getSourceFile(); + const sf = ts.getSourceFileOfNode(enumDecl); if (lowerer.isStdlibFile(sf)) return null; const declared = lowerer.mapTypeOf(lowerer.typeOf(expr)); if (!declared || declared.kind === "void") return null; @@ -165,10 +165,10 @@ function lowerEnumReverseRead( key: ts.Expression, ): IrExpr | null { const loc = locOf(expr); - const enumName = recvSym.name; + const enumName = ts.symbolName(recvSym); // Ambient: no object exists — the member-read stance. if (decls.every((d) => isAmbientEnumDecl(d))) { - if (decls.some((d) => lowerer.isStdlibFile(d.getSourceFile()))) { + if (decls.some((d) => lowerer.isStdlibFile(ts.getSourceFileOfNode(d)))) { return null; } return { diff --git a/packages/compiler/src/frontend/lowering/lower-event-emitter.ts b/packages/compiler/src/frontend/lowering/lower-event-emitter.ts index f2968cebd..f8588fe48 100644 --- a/packages/compiler/src/frontend/lowering/lower-event-emitter.ts +++ b/packages/compiler/src/frontend/lowering/lower-event-emitter.ts @@ -414,7 +414,11 @@ function isErrorMonitorRef(lowerer: Lowerer, arg: ts.Expression): boolean { const isMonitorType = (t: ts.Type): boolean => { if (!(t.flags & ts.TypeFlags.UniqueESSymbol)) return false; const symbol = t.getSymbol(); - return symbol?.name === "errorMonitor" && lowerer.isStdlibSymbol(symbol); + return ( + symbol !== undefined && + ts.symbolName(symbol) === "errorMonitor" && + lowerer.isStdlibSymbol(symbol) + ); }; // The unique symbol TYPE names its declaration through any alias. if (isMonitorType(lowerer.typeOf(arg))) return true; @@ -1697,7 +1701,7 @@ export function emitOverrideShapeReason( const p0T = lowerer.mapTypeOf(lowerer.typeOf(p0.name)); const jsUnannotated = p0.type === undefined && - isJsSourceFile(member.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(member)) && (p0T === null || p0T.kind === "dyn"); if (p0T?.kind !== "string" && !jsUnannotated) { return "the event parameter must be typed 'string' (symbol event names have no lowering)"; @@ -1731,9 +1735,9 @@ export function emitOverrideShapeReason( "the event parameter cannot be reassigned (the forward's event name must stay pinned)"; } } - n.forEachChild(visit); + ts.forEachChild(n, visit); }; - member.body.forEachChild(visit); + ts.forEachChild(member.body, visit); return reason; } diff --git a/packages/compiler/src/frontend/lowering/lower-expando.ts b/packages/compiler/src/frontend/lowering/lower-expando.ts index 5ed97d4d7..6adce8f67 100644 --- a/packages/compiler/src/frontend/lowering/lower-expando.ts +++ b/packages/compiler/src/frontend/lowering/lower-expando.ts @@ -98,8 +98,8 @@ function expandoFnSymbolOf(lowerer: Lowerer, recv: ts.Expression): ts.Symbol | n const sym = lowerer.resolveValueSymbol(recv); if (!sym) return null; const decl = sym.valueDeclaration; - if (!decl || decl.getSourceFile().isDeclarationFile) return null; - if (isJsSourceFile(decl.getSourceFile())) return null; + if (!decl || ts.getSourceFileOfNode(decl).isDeclarationFile) return null; + if (isJsSourceFile(ts.getSourceFileOfNode(decl))) return null; if (ts.isFunctionDeclaration(decl)) { return ts.isSourceFile(decl.parent) ? sym : null; } @@ -177,7 +177,7 @@ export function collectExpandoMembers(lowerer: Lowerer, sf: ts.SourceFile): void } const existing = memberOf(members, w.key); if (existing) { - existing.firstWriteStart = Math.min(existing.firstWriteStart, node.getStart()); + existing.firstWriteStart = Math.min(existing.firstWriteStart, ts.tokenStartOfNode(node)); } else if ( !(typeof w.key === "string" && READONLY_FN_MEMBERS.has(w.key)) && !nsOwnedMember(lowerer, w.access) @@ -198,18 +198,22 @@ export function collectExpandoMembers(lowerer: Lowerer, sf: ts.SourceFile): void } if (type?.kind === "void" && isUnitOnlyTsType(tsType)) type = unitOnlyUnion(lowerer.unions); if (type && type.kind !== "void") { - const fnName = w.fnSym.name; + const fnName = ts.symbolName(w.fnSym); const memberName = typeof w.key === "string" ? w.key - : `sym%${w.key.name}%${w.key.declarations[0]?.getStart() ?? 0}`; + : `sym%${ts.symbolName(w.key)}%${(w.key.declarations.length === 0 ? undefined : ts.tokenStartOfNode(w.key.declarations[0]!)) ?? 0}`; const g: IrGlobal = { id: `%g.${tag}${fnName}%.${memberName}`, name: `${fnName}.${memberName}`, type, mutable: true, }; - const entry: ExpandoMember = { global: g, firstWriteStart: node.getStart(), file: sf }; + const entry: ExpandoMember = { + global: g, + firstWriteStart: ts.tokenStartOfNode(node), + file: sf, + }; if (typeof w.key === "string") members.names.set(w.key, entry); else members.symbols.set(w.key, entry); lowerer.globalsList.push(g); @@ -248,7 +252,7 @@ function nsOwnedMember( if (ts.isModuleDeclaration(p)) { return !( p.modifiers?.nodes.some((m) => m.kind === ts.SyntaxKind.DeclareKeyword) || - p.getSourceFile().isDeclarationFile + ts.getSourceFileOfNode(p).isDeclarationFile ); } } @@ -308,11 +312,11 @@ export function expandoMemberRead( const member = memberOf(lowerer.expandoMembers.get(fnSym), key); if (!member) return null; if ( - access.getSourceFile() === member.file && + ts.getSourceFileOfNode(access) === member.file && initExecutingPosition(access) && - access.getStart() < member.firstWriteStart + ts.tokenStartOfNode(access) < member.firstWriteStart ) { - const name = typeof key === "string" ? key : key.name; + const name = typeof key === "string" ? key : ts.symbolName(key); lowerer.unsupported( "SC1090", access, diff --git a/packages/compiler/src/frontend/lowering/lower-exprs.ts b/packages/compiler/src/frontend/lowering/lower-exprs.ts index e770b8d58..7789f8a86 100644 --- a/packages/compiler/src/frontend/lowering/lower-exprs.ts +++ b/packages/compiler/src/frontend/lowering/lower-exprs.ts @@ -193,9 +193,9 @@ export function templateRawTextOf( ): string { const own = (node as { rawText?: string }).rawText; if (own !== undefined) return own; - const sf = node.getSourceFile(); - const start = node.getStart(sf); - const end = node.getEnd(); + const sf = ts.getSourceFileOfNode(node); + const start = ts.tokenStartOfNode(node); + const end = node.end; const tailTrim = node.kind === ts.SyntaxKind.TemplateHead || node.kind === ts.SyntaxKind.TemplateMiddle ? 2 : 1; return sf.text.slice(start + 1, end - tailTrim); @@ -296,14 +296,14 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr { // integer within ±(2^53−1)) and non-integer spellings carry // nothing, so the IR is unchanged for programs that held their // numbers. - const spelled = expr.getText().replace(/_/g, ""); + const spelled = ts.getTextOfNode(expr).replace(/_/g, ""); if (/^\d+$/.test(spelled) && String(Number(spelled)) !== spelled) { return { kind: "numLit", value: Number(spelled), spelling: spelled, type: F64, loc }; } return { kind: "numLit", value, type: F64, loc }; } if (ts.isBigIntLiteral(expr)) { - const spelling = expr.getText().replace(/_/g, "").replace(/n$/i, ""); + const spelling = ts.getTextOfNode(expr).replace(/_/g, "").replace(/n$/i, ""); return { kind: "libCall", fn: "bigint.parse", @@ -549,7 +549,7 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr { // the read answers the strict-mode plain-call undefined, the old // constant. TypeScript keeps the fence (noImplicitThis makes it a // compile-time story there). - if (isJsSourceFile(expr.getSourceFile())) { + if (isJsSourceFile(ts.getSourceFileOfNode(expr))) { // Module scope of a CommonJS file: `this` is module.exports. const exportsThis = lowerCjsTopLevelThis(lowerer, expr); if (exportsThis) return exportsThis; @@ -1548,7 +1548,7 @@ export function lowerAbsenceProbe(lowerer: Lowerer, node: ts.Expression): IrExpr // statement ruled non-null without narrowing it (jose's secs()). if ( receiverT?.kind === "union" && - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && !expr.questionDotToken && !hasOptionalChainGuard(expr.expression) ) { @@ -1738,7 +1738,8 @@ export function stringWrapperToString(lowerer: Lowerer, node: ts.Expression): Ir while (ts.isParenthesizedExpression(e)) e = e.expression; if (!ts.isNewExpression(e) || !ts.isIdentifier(e.expression)) return null; const symbol = lowerer.resolveValueSymbol(e.expression); - if (symbol?.name !== "String" || !lowerer.isStdlibSymbol(symbol)) return null; + if (symbol === null || ts.symbolName(symbol) !== "String" || !lowerer.isStdlibSymbol(symbol)) + return null; const args = e.arguments?.nodes ?? []; if (args.length > 1 || args.some(ts.isSpreadElement)) return null; if (args.length === 0) return { kind: "strLit", value: "", type: STRING, loc: locOf(e) }; @@ -2027,7 +2028,7 @@ export function aliasTypeofNarrows( const leaf = aliasTypeofArmOf(lowerer, e, strip); if (leaf) out.push(leaf); }; - if (implicitMonoFile(cond.getSourceFile())) walk(cond, polarity); + if (implicitMonoFile(ts.getSourceFileOfNode(cond))) walk(cond, polarity); return out; } @@ -2127,7 +2128,7 @@ export function cjsAccessorSnapshotOf( ): { id: string; type: IrType } | null { const hit = lowerer.cjsAccessorSnapshots.get(getter); if (hit !== undefined) return hit; - if (!lexerVisibleAccessors(getter.getSourceFile()).includes(getter)) return null; + if (!lexerVisibleAccessors(ts.getSourceFileOfNode(getter)).includes(getter)) return null; const call = cjsAccessorCall(lowerer, getter, locOf(getter)); if (call === null) return null; const snapshot = { id: `%g.cjsSnapshot.${lowerer.cjsAccessorSnapshots.size}`, type: call.type }; @@ -2576,7 +2577,7 @@ export function lowerOptionalArrayIndexRead( key: IrExpr, ): IrExpr | null { if ( - !isJsSourceFile(expr.getSourceFile()) || + !isJsSourceFile(ts.getSourceFileOfNode(expr)) || expr.questionDotToken || hasOptionalChainGuard(expr.expression) ) diff --git a/packages/compiler/src/frontend/lowering/lower-functions.ts b/packages/compiler/src/frontend/lowering/lower-functions.ts index 58778fc14..bec8528c4 100644 --- a/packages/compiler/src/frontend/lowering/lower-functions.ts +++ b/packages/compiler/src/frontend/lowering/lower-functions.ts @@ -130,7 +130,7 @@ function declareFunctionArguments( lowerer.ctx.argumentsLocal = local; } if ( - isJsSourceFile(node.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(node)) && !ts.isArrowFunction(node) && shapes.length === 1 && shapes[0]?.mode === "dynRest" && @@ -204,7 +204,7 @@ export function lowerLambda( // Fluent methods must return that receiver without a class cast. if ( ts.isMethodDeclaration(node) && - isJsSourceFile(node.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(node)) && !node.type && !node.asteriskToken && !hasExplicitJsDocReturn(node) && @@ -236,7 +236,7 @@ export function lowerLambda( if ( !( ts.isMethodDeclaration(node) && - isJsSourceFile(node.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(node)) && canBoxFuncIntoDyn( funcType, (id) => lowerer.shapes.get(id), @@ -287,14 +287,14 @@ export function lowerLambda( if (lowerer.methodLambdasInProgress.has(node)) lowerer.methodLambdasInProgress.set(node, { fnName, type: funcType, selfReads: 0 }); const inferredSignature = - isJsSourceFile(node.getSourceFile()) && !node.type + isJsSourceFile(ts.getSourceFileOfNode(node)) && !node.type ? lowerer.checker.getSignatureFromDeclaration(node) : undefined; const returnsConstructor = inferredSignature && producesConstructor(lowerer, lowerer.checker.getReturnTypeOfSignature(inferredSignature)); if ( - isJsSourceFile(node.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(node)) && !node.type && !isAsync && !isGenerator && @@ -316,7 +316,7 @@ export function lowerLambda( try { const receiver = (checkedReceiver || - isJsSourceFile(node.getSourceFile()) || + isJsSourceFile(ts.getSourceFileOfNode(node)) || node.parameters.nodes.some(isThisParameter)) && !ts.isArrowFunction(node) ? declareCallReceiver(lowerer, node, isGenerator) @@ -461,7 +461,7 @@ export function lowerLambda( // position. ICEs (SC9001) stay compile errors, exactly like // lowerStmts; probe mode (diagSink) keeps the poison. if (!(e instanceof PoisonError)) throw e; - if (!isJsSourceFile(node.getSourceFile())) throw e; + if (!isJsSourceFile(ts.getSourceFileOfNode(node))) throw e; const params: IrParam[] = funcType.params.map((t, i) => ({ localId: `%pf${i}`, name: `%pf${i}`, @@ -510,7 +510,7 @@ export function lowerFunction(lowerer: Lowerer, decl: ts.FunctionDeclaration): I lowerer.fnStack.push(ctx); try { const receiver = - decl.parameters.nodes.some(isThisParameter) || isJsSourceFile(decl.getSourceFile()) + decl.parameters.nodes.some(isThisParameter) || isJsSourceFile(ts.getSourceFileOfNode(decl)) ? declareCallReceiver(lowerer, decl, sig.generator !== undefined) : null; const { params, prologue } = lowerer.declareParams(decl.parameters.nodes, sig.params); @@ -563,7 +563,7 @@ export function lowerFunction(lowerer: Lowerer, decl: ts.FunctionDeclaration): I // at the declaration's position — so a reachable-but-broken // signature stops the RUN at its own site instead of the build. // ICEs (SC9001) stay compile errors, exactly like lowerStmts. - if (isJsSourceFile(decl.getSourceFile())) { + if (isJsSourceFile(ts.getSourceFileOfNode(decl))) { // An ABI type naming a class that never REGISTERED (the sentence- // walker idiom's path type — the #private fence) is fine to emit: // callers CAN lower calls to this symbol (a same-typed param diff --git a/packages/compiler/src/frontend/lowering/lower-generators.ts b/packages/compiler/src/frontend/lowering/lower-generators.ts index b1d0a1aa5..19a75a0b4 100644 --- a/packages/compiler/src/frontend/lowering/lower-generators.ts +++ b/packages/compiler/src/frontend/lowering/lower-generators.ts @@ -207,7 +207,7 @@ function isGeneratorMember(lowerer: Lowerer, access: ts.PropertyAccessExpression (d) => ts.isInterfaceDeclaration(d.parent) && (d.parent.name.text === "Generator" || d.parent.name.text === "AsyncGenerator") && - lowerer.isStdlibFile(d.getSourceFile()), + lowerer.isStdlibFile(ts.getSourceFileOfNode(d)), ) ); } @@ -272,7 +272,7 @@ export function isSettledIteratorValueRead( ts.isInterfaceDeclaration(d.parent) && (d.parent.name.text === "IteratorYieldResult" || d.parent.name.text === "IteratorReturnResult") && - lowerer.isStdlibFile(d.getSourceFile()), + lowerer.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) return false; @@ -282,13 +282,13 @@ export function isSettledIteratorValueRead( sym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isTypeAliasDeclaration(d)) && - lowerer.isStdlibFile(d.getSourceFile()), + lowerer.isStdlibFile(ts.getSourceFileOfNode(d)), ); const alias = receiver.getAliasSymbol(); const symbol = receiver.getSymbol(); if ( - !(alias?.name === "IteratorResult" && stdlib(alias)) && - !(symbol?.name === "IteratorReturnResult" && stdlib(symbol)) + !(alias !== undefined && ts.symbolName(alias) === "IteratorResult" && stdlib(alias)) && + !(symbol !== undefined && ts.symbolName(symbol) === "IteratorReturnResult" && stdlib(symbol)) ) return false; const checked = lowerer.mapTypeOf(lowerer.typeOf(node)); diff --git a/packages/compiler/src/frontend/lowering/lower-global-value.ts b/packages/compiler/src/frontend/lowering/lower-global-value.ts index d0b251e84..4b8e7b10a 100644 --- a/packages/compiler/src/frontend/lowering/lower-global-value.ts +++ b/packages/compiler/src/frontend/lowering/lower-global-value.ts @@ -14,7 +14,7 @@ export function lowerGlobalValue(lowerer: Lowerer, expr: ts.Expression): IrExpr // lib slice) but absent in Node 24: reads answer undefined. const names = lowerer.checker .getPropertiesOfType(lowerer.typeOf(expr)) - .map((property) => property.name) + .map((property) => ts.symbolName(property)) .filter((name) => name !== TEMPORAL_GLOBAL); return { kind: "libCall", diff --git a/packages/compiler/src/frontend/lowering/lower-inspect.ts b/packages/compiler/src/frontend/lowering/lower-inspect.ts index 1310469fb..a8657f978 100644 --- a/packages/compiler/src/frontend/lowering/lower-inspect.ts +++ b/packages/compiler/src/frontend/lowering/lower-inspect.ts @@ -493,7 +493,7 @@ function classInspectsThroughView(lowerer: Lowerer, info: ClassInfo | undefined) if (info.subclasses.length > 0) return true; return ( info.decl != null && - isJsSourceFile(info.decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(info.decl)) && classDynViewSupported( info.def.fields, (id) => lowerer.shapes.get(id), @@ -2660,7 +2660,7 @@ function lowerUtilCallableCall( * a statically typed callback parameter cannot receive a value its type * excludes. Untyped (JavaScript or unknown) slots keep the wrapper dyn. */ function callbackifyErrorExcludesNull(lowerer: Lowerer, expr: ts.CallExpression): boolean { - if (isJsSourceFile(expr.getSourceFile())) return false; + if (isJsSourceFile(ts.getSourceFileOfNode(expr))) return false; const checker = lowerer.checker; const slot = checker.getContextualType(expr) ?? lowerer.typeOf(expr); const wrapper = checker.getCallSignatures(slot)[0]; @@ -2766,7 +2766,7 @@ function lowerGetCallSitesCall( loc: SrcLoc, ): IrExpr | null { if (expr.arguments.nodes.length !== 0) return null; - if (!isJsSourceFile(expr.getSourceFile())) return null; + if (!isJsSourceFile(ts.getSourceFileOfNode(expr))) return null; const frame = (): IrExpr => { const field = (name: string, value: IrExpr): { key: IrExpr; value: IrExpr } => ({ key: { kind: "strLit", value: name, type: STRING, loc }, @@ -2778,7 +2778,7 @@ function lowerGetCallSitesCall( field("functionName", { kind: "strLit", value: "", type: STRING, loc }), field("scriptName", { kind: "strLit", - value: expr.getSourceFile().fileName, + value: ts.getSourceFileOfNode(expr).fileName, type: STRING, loc, }), diff --git a/packages/compiler/src/frontend/lowering/lower-mixins.ts b/packages/compiler/src/frontend/lowering/lower-mixins.ts index 90789b4a0..766059dfb 100644 --- a/packages/compiler/src/frontend/lowering/lower-mixins.ts +++ b/packages/compiler/src/frontend/lowering/lower-mixins.ts @@ -174,7 +174,7 @@ function mixinFnShapeInner( if (n === t.expression) return undefined; if ( ts.isIdentifier(n) && - n.text === p.name.getText() && + n.text === ts.getTextOfNode(p.name) && lowerer.checker.getSymbolAtLocation(n) === paramSym ) { extraRef = true; @@ -451,8 +451,8 @@ function instantiateMixinCall( const base = mixinBaseClassOf(lowerer, call.arguments.nodes[0]!); const instName = lowerer.qualify( - call.getSourceFile(), - `%mx${call.getStart()}.${shape.classNode.name?.text ?? ""}`, + ts.getSourceFileOfNode(call), + `%mx${ts.tokenStartOfNode(call)}.${shape.classNode.name?.text ?? ""}`, ); const ordinal = lowerer.mixinOrdinals.get(shape.classNode) ?? 0; lowerer.mixinOrdinals.set(shape.classNode, ordinal + 1); @@ -513,7 +513,10 @@ function instantiateMixinCall( ); } if (info.mixinInstance) { - info.mixinInstance.statics = { sf: call.getSourceFile(), pos: holder.getStart() }; + info.mixinInstance.statics = { + sf: ts.getSourceFileOfNode(call), + pos: ts.tokenStartOfNode(holder), + }; } } return info; diff --git a/packages/compiler/src/frontend/lowering/lower-mock.ts b/packages/compiler/src/frontend/lowering/lower-mock.ts index be68abaca..fcc68c787 100644 --- a/packages/compiler/src/frontend/lowering/lower-mock.ts +++ b/packages/compiler/src/frontend/lowering/lower-mock.ts @@ -797,7 +797,7 @@ export function lowerMockMethod( const objShape = objType?.kind === "record" ? lowerer.shapes.get(objType.shapeId) : undefined; const fn = objShape?.fields.find((f) => f.name === field)?.type; if (!objType || !objShape || fn?.kind !== "func") { - const typeName = lowerer.typeOf(args[0]!).getSymbol()?.name; + const typeName = ts.symbolNameOf(lowerer.typeOf(args[0]!).getSymbol()); lowerer.noLowering( typeName && !typeName.startsWith("__") ? `${what} on '${typeName}'` diff --git a/packages/compiler/src/frontend/lowering/lower-modules.ts b/packages/compiler/src/frontend/lowering/lower-modules.ts index 11db0ec8a..f0199d6a4 100644 --- a/packages/compiler/src/frontend/lowering/lower-modules.ts +++ b/packages/compiler/src/frontend/lowering/lower-modules.ts @@ -692,7 +692,10 @@ export function collectJsonImports(lowerer: Lowerer, parts: FileParts[]): void { const nameSym = lowerer.checker.getSymbolAtLocation(clause.name); if (!nameSym || !(nameSym.flags & ts.SymbolFlags.Alias)) continue; const target = lowerer.checker.getAliasedSymbol(nameSym); - const jsonSf = target.declarations[0]?.getSourceFile(); + const jsonSf = + target.declarations.length === 0 + ? undefined + : ts.getSourceFileOfNode(target.declarations[0]!); if (!jsonSf || !jsonSf.fileName.endsWith(".json")) continue; try { const tsType = lowerer.typeOf(clause.name); @@ -1175,7 +1178,7 @@ function jsDynHoldableInitializer(lowerer: Lowerer, init: ts.Expression | undefi if ( (ts.isFunctionExpression(e) || ts.isArrowFunction(e)) && !e.typeParameters?.nodes.length && - isJsSourceFile(e.getSourceFile()) + isJsSourceFile(ts.getSourceFileOfNode(e)) ) return true; if (ts.isIdentifier(e) || ts.isArrowFunction(e) || ts.isFunctionExpression(e)) { @@ -1274,7 +1277,7 @@ export function collectGlobals( const mutableVar = vd !== undefined && ts.isVariableDeclaration(vd) && - !vd.getSourceFile().isDeclarationFile && + !ts.getSourceFileOfNode(vd).isDeclarationFile && (ts.getCombinedNodeFlags(vd) & ts.NodeFlags.Const) === 0; // Immutable targets: alias plumbing — importers resolve to the // target's storage (the alias-resolved read IS the snapshot when @@ -1546,7 +1549,7 @@ export function collectGlobals( const mutableVar = vd !== undefined && ts.isVariableDeclaration(vd) && - !vd.getSourceFile().isDeclarationFile && + !ts.getSourceFileOfNode(vd).isDeclarationFile && (ts.getCombinedNodeFlags(vd) & ts.NodeFlags.Const) === 0; if (!mutableVar) continue; const diagsBefore = lowerer.diags.length; @@ -1557,7 +1560,7 @@ export function collectGlobals( } if (type.kind === "void") lowerer.badType(stmt.name, lowerer.typeOf(stmt.name)); const g: IrGlobal = { - id: `%g.${tag}${nsPathPrefix(stmt)}%alias.${stmt.name.text}%${stmt.getStart()}`, + id: `%g.${tag}${nsPathPrefix(stmt)}%alias.${stmt.name.text}%${ts.tokenStartOfNode(stmt)}`, name: stmt.name.text, type, mutable: false, @@ -2282,12 +2285,12 @@ export function collectGlobals( : undefined; const callDeclaration = callSignature && callSignature.declaration; const callType = - callDeclaration && isJsSourceFile(callDeclaration.getSourceFile()) + callDeclaration && isJsSourceFile(ts.getSourceFileOfNode(callDeclaration)) ? lowerer.mapTypeOf(lowerer.typeOf(nameNode)) : null; const inferredJsCall = callDeclaration && - isJsSourceFile(callDeclaration.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(callDeclaration)) && (!callType || (unannotatedCall && callType.kind === "union" && @@ -2710,7 +2713,7 @@ export function declSymbolOf( ts.canHaveModifiers(decl) && ts.getModifiers(decl)?.some((m) => m.kind === ts.SyntaxKind.DefaultKeyword) === true; if (!isDefault) return undefined; - const sym = defaultExportSymbolOf(lowerer, decl.getSourceFile()); + const sym = defaultExportSymbolOf(lowerer, ts.getSourceFileOfNode(decl)); return sym ?? undefined; } @@ -2728,7 +2731,7 @@ export function declSymbolOf( * expression so the expression's OWN diagnostic wins when it has one, * then poisons. */ export function lowerDefaultExport(lowerer: Lowerer, stmt: ts.ExportAssignment): IrStmt | null { - const symbol = defaultExportSymbolOf(lowerer, stmt.getSourceFile()); + const symbol = defaultExportSymbolOf(lowerer, ts.getSourceFileOfNode(stmt)); if (symbol && symbol.flags & ts.SymbolFlags.Alias && !lowerer.globalsBySymbol.has(symbol)) return null; const g = symbol ? lowerer.globalsBySymbol.get(symbol) : undefined; @@ -2741,7 +2744,7 @@ export function lowerDefaultExport(lowerer: Lowerer, stmt: ts.ExportAssignment): while (ts.isParenthesizedExpression(inner)) inner = inner.expression; if ( ts.isClassExpression(inner) && - !isJsSourceFile(stmt.getSourceFile()) && + !isJsSourceFile(ts.getSourceFileOfNode(stmt)) && inner.typeParameters === undefined && (decoratorNodesOf(inner).length > 0 || inner.members.nodes.some((m) => decoratorNodesOf(m).length > 0)) && @@ -2961,7 +2964,7 @@ export function lowerFileInit( c.decl && ts.isClassDeclaration(c.decl) && !c.mixinInstance && - c.decl.getSourceFile() === sf && + ts.getSourceFileOfNode(c.decl) === sf && (lowerer.classInitFlags.has(c.def.name) || c.callableBase !== undefined || c.def.baseValueGlobal !== undefined || @@ -2970,7 +2973,7 @@ export function lowerFileInit( (c.staticBlocks?.length ?? 0) > 0 || c.classDecorators !== undefined), ) - .map((c) => ({ pos: c.decl!.getStart(), info: c })); + .map((c) => ({ pos: ts.tokenStartOfNode(c.decl!), info: c })); // Statics-bearing MIXIN instantiations whose call evaluates in THIS // file: their declaration-time code runs when the call does — the // top-level statement lower-mixins recorded. Insertion order is @@ -2990,7 +2993,7 @@ export function lowerFileInit( // anything the statement's lowering emits. Hoisted class // declarations never share a kept statement's start, so `<=` // changes nothing for them. - while (at < statics.length && statics[at]!.pos <= stmt.getStart()) { + while (at < statics.length && statics[at]!.pos <= ts.tokenStartOfNode(stmt)) { body.push( ...withClassInitFlag( lowerer, diff --git a/packages/compiler/src/frontend/lowering/lower-namespaces.ts b/packages/compiler/src/frontend/lowering/lower-namespaces.ts index 79ac7c278..9997eb7d9 100644 --- a/packages/compiler/src/frontend/lowering/lower-namespaces.ts +++ b/packages/compiler/src/frontend/lowering/lower-namespaces.ts @@ -178,10 +178,10 @@ export function nsPathPrefix(node: ts.Node, modifierDecl?: ts.Node): string { const nested = p.parent !== undefined && ts.isModuleBlock(p.parent); const nonExported = nested && (ts.getCombinedModifierFlags(p) & ts.ModifierFlags.Export) === 0; - segs.unshift(p.name.text + (nonExported ? `%${p.getStart()}` : "")); + segs.unshift(p.name.text + (nonExported ? `%${ts.tokenStartOfNode(p)}` : "")); } } - return `${segs.join("%")}${exported ? "" : `%${parent.getStart()}`}.`; + return `${segs.join("%")}${exported ? "" : `%${ts.tokenStartOfNode(parent)}`}.`; } /** The registered namespace-block kind a symbol's declaration sits in @@ -275,7 +275,11 @@ function moduleNsSourceFileOf(lowerer: Lowerer, e: ts.Expression): ts.SourceFile stmt.moduleSpecifier !== undefined && ts.isStringLiteral(stmt.moduleSpecifier) ) { - const dep = resolveImport(lowerer.program, d.getSourceFile(), stmt.moduleSpecifier.text); + const dep = resolveImport( + lowerer.program, + ts.getSourceFileOfNode(d), + stmt.moduleSpecifier.text, + ); if (dep && !dep.isDeclarationFile && lowerer.fileTag.has(dep)) return dep; } } @@ -334,7 +338,7 @@ export function ambientNsRootOf(lowerer: Lowerer, e: ts.Expression): ts.Identifi return null; } if (ts.isModuleDeclaration(d) && !ts.isIdentifier(d.name)) return null; - if (d.getSourceFile().isDeclarationFile) return null; // lib/@types keep their own fences + if (ts.getSourceFileOfNode(d).isDeclarationFile) return null; // lib/@types keep their own fences if ( ts.isModuleDeclaration(d) ? !isAmbientModuleDecl(d) @@ -432,7 +436,7 @@ export function ambientUndefVarRootOf(lowerer: Lowerer, e: ts.Expression): ts.Id if (decls.length === 0) return null; for (const d of decls) { if (!ts.isVariableDeclaration(d) || d.initializer !== undefined) return null; - if (d.getSourceFile().isDeclarationFile) return null; // lib/@types keep their own fences + if (ts.getSourceFileOfNode(d).isDeclarationFile) return null; // lib/@types keep their own fences if (!(ts.getCombinedModifierFlags(d) & ts.ModifierFlags.Ambient)) return null; } return root; @@ -491,7 +495,7 @@ export function trapDeclRootOf( if (root === null) return null; for (const nameNode of boundIdentifiersOf(decl.name)) { const sym = lowerer.checker.getSymbolAtLocation(nameNode); - if (sym && bindingEverWritten(lowerer, sym, decl.getSourceFile())) { + if (sym && bindingEverWritten(lowerer, sym, ts.getSourceFileOfNode(decl))) { const mapped = lowerer.mapTypeOf(lowerer.checker.getTypeOfSymbol(sym)); if (mapped !== null && mapped.kind !== "void") return null; } @@ -516,7 +520,7 @@ export function trapDeclRootOf( * initializer into an ordinary lowering that fences). ambientUndefVarRootOf * carries its own collect-guard, so the extra question stays a probe. */ function bindingEverWritten(lowerer: Lowerer, sym: ts.Symbol, sf: ts.SourceFile): boolean { - const symText = sym.name; + const symText = ts.symbolName(sym); const namesSym = (e: ts.Node): boolean => ts.isIdentifier(e) && e.text === symText && lowerer.checker.getSymbolAtLocation(e) === sym; const patternWrites = (target: ts.Node): boolean => { @@ -525,9 +529,9 @@ function bindingEverWritten(lowerer: Lowerer, sym: ts.Symbol, sf: ts.SourceFile) const walk = (m: ts.Node): void => { if (hit) return; if (namesSym(m)) hit = true; - else m.forEachChild(walk); + else ts.forEachChild(m, walk); }; - target.forEachChild(walk); + ts.forEachChild(target, walk); return hit; }; let written = false; @@ -572,9 +576,9 @@ function bindingEverWritten(lowerer: Lowerer, sym: ts.Symbol, sf: ts.SourceFile) written = true; } } - n.forEachChild(visit); + ts.forEachChild(n, visit); }; - sf.forEachChild(visit); + ts.forEachChild(sf, visit); return written; } @@ -635,7 +639,7 @@ export function ambientUndefinedFnSymbolOf( } else if (!ts.isModuleDeclaration(d) && !ts.isClassDeclaration(d)) { return null; } - if (d.getSourceFile().isDeclarationFile) return null; + if (ts.getSourceFileOfNode(d).isDeclarationFile) return null; if (!(ts.getCombinedModifierFlags(d) & ts.ModifierFlags.Ambient)) return null; } return sawFn ? sym : null; @@ -676,7 +680,7 @@ function initExecutingPosition(node: ts.Node): boolean { function earliestSameFileDeclStart(lowerer: Lowerer, sym: ts.Symbol, sf: ts.SourceFile): number { let earliest = Infinity; for (const d of sym.declarations) { - if (d.getSourceFile() === sf) earliest = Math.min(earliest, d.getStart()); + if (ts.getSourceFileOfNode(d) === sf) earliest = Math.min(earliest, ts.tokenStartOfNode(d)); } return earliest; } @@ -691,10 +695,10 @@ export function fenceEarlyAliasUse(lowerer: Lowerer, ident: ts.Identifier, refNo const aliasDecl = sym.declarations.find(ts.isImportEqualsDeclaration); if (!aliasDecl || ts.isExternalModuleReference(aliasDecl.moduleReference)) return; if (!initExecutingPosition(refNode)) return; - const sf = refNode.getSourceFile(); - if (aliasDecl.getSourceFile() !== sf) return; // cross-file: module init order applies - const aliasStart = aliasDecl.getStart(); - if (refNode.getStart() < aliasStart) { + const sf = ts.getSourceFileOfNode(refNode); + if (ts.getSourceFileOfNode(aliasDecl) !== sf) return; // cross-file: module init order applies + const aliasStart = ts.tokenStartOfNode(aliasDecl); + if (ts.tokenStartOfNode(refNode) < aliasStart) { lowerer.unsupported( "SC1090", refNode, @@ -730,8 +734,8 @@ export function fenceEarlyNsMemberRef( memberSym: ts.Symbol, ): void { if (!initExecutingPosition(access)) return; - const sf = access.getSourceFile(); - const refStart = access.getStart(); + const sf = ts.getSourceFileOfNode(access); + const refStart = ts.tokenStartOfNode(access); const memberStart = earliestSameFileDeclStart(lowerer, memberSym, sf); if (memberStart !== Infinity && refStart < memberStart) { lowerer.unsupported( diff --git a/packages/compiler/src/frontend/lowering/lower-node-module.ts b/packages/compiler/src/frontend/lowering/lower-node-module.ts index fee25b10f..37675777c 100644 --- a/packages/compiler/src/frontend/lowering/lower-node-module.ts +++ b/packages/compiler/src/frontend/lowering/lower-node-module.ts @@ -45,7 +45,7 @@ function cjsGlobal(lowerer: Lowerer, node: ts.Expression, name: "module" | "requ const decls = symbol ? symbol.declarations : []; const sourceShadow = decls.some( (decl) => - !decl.getSourceFile().isDeclarationFile && + !ts.getSourceFileOfNode(decl).isDeclarationFile && (ts.isVariableDeclaration(decl) || ts.isParameter(decl) || ts.isBindingElement(decl) || @@ -55,7 +55,7 @@ function cjsGlobal(lowerer: Lowerer, node: ts.Expression, name: "module" | "requ ts.isNamespaceImport(decl) || ts.isImportEqualsDeclaration(decl)), ); - return !sourceShadow && !isNodeEsmFile(node.getSourceFile(), lowerer.program); + return !sourceShadow && !isNodeEsmFile(ts.getSourceFileOfNode(node), lowerer.program); } function graphSyntax(lowerer: Lowerer, sf: ts.SourceFile): boolean { @@ -174,7 +174,7 @@ export function cjsModuleRegistryPrelude(lowerer: Lowerer, loc: IrExpr["loc"]): } export function cjsModuleRef(lowerer: Lowerer, node: ts.Node): IrExpr | null { - const id = lowerer.cjsModuleIdOf.get(node.getSourceFile()); + const id = lowerer.cjsModuleIdOf.get(ts.getSourceFileOfNode(node)); return id === undefined ? null : numLit(id, locOf(node)); } @@ -203,8 +203,8 @@ function nodeModuleType(lowerer: Lowerer, node: ts.Expression): boolean { const symbol = type.getSymbol(); if ( symbol && - (symbol.name === "Module" || symbol.name === "ScriptcModule") && - symbol.declarations.some((decl) => lowerer.isStdlibFile(decl.getSourceFile())) + (ts.symbolName(symbol) === "Module" || ts.symbolName(symbol) === "ScriptcModule") && + symbol.declarations.some((decl) => lowerer.isStdlibFile(ts.getSourceFileOfNode(decl))) ) { return true; } @@ -217,7 +217,7 @@ function nodeModuleType(lowerer: Lowerer, node: ts.Expression): boolean { (ts.isInterfaceDeclaration(parent) || ts.isClassDeclaration(parent)) && parent.name && (parent.name.text === "ScriptcModule" || parent.name.text === "Module") && - lowerer.isStdlibFile(parent.getSourceFile()) + lowerer.isStdlibFile(ts.getSourceFileOfNode(parent)) ) { return true; } @@ -237,7 +237,7 @@ function nodeModuleType(lowerer: Lowerer, node: ts.Expression): boolean { * file assigns an object or function. */ function moduleExportsAlwaysTruthy(lowerer: Lowerer, expr: ts.PropertyAccessExpression): boolean { if (!truthinessOnly(expr)) return false; - const sf = expr.getSourceFile(); + const sf = ts.getSourceFileOfNode(expr); if (isNodeEsmFile(sf, lowerer.program)) return false; let truthy = true; ts.walkPreorder(sf, (node) => { @@ -307,7 +307,7 @@ export function isCjsTopLevelThis(lowerer: Lowerer, node: ts.Node): boolean { ) return false; } - return isCjsJsFile(node.getSourceFile(), lowerer.program); + return isCjsJsFile(ts.getSourceFileOfNode(node), lowerer.program); } export function usesCjsTopLevelThis(lowerer: Lowerer, sf: ts.SourceFile): boolean { @@ -341,7 +341,7 @@ export function usesCjsTopLevelThis(lowerer: Lowerer, sf: ts.SourceFile): boolea * behave as in Node. */ export function lowerCjsTopLevelThis(lowerer: Lowerer, expr: ts.Expression): IrExpr | null { if (!isCjsTopLevelThis(lowerer, expr)) return null; - const global = lowerer.cjsExportsThis.get(expr.getSourceFile()); + const global = lowerer.cjsExportsThis.get(ts.getSourceFileOfNode(expr)); if (!global) return null; let receiver: ts.Node = expr; while (ts.isParenthesizedExpression(receiver.parent)) receiver = receiver.parent; @@ -433,7 +433,7 @@ export function lowerNodeModuleIdentifier(lowerer: Lowerer, expr: ts.Identifier) if (!isCjsModuleGlobal(lowerer, expr)) return null; const ref = cjsModuleRef(lowerer, expr); if (ref) return ref; - if (isCjsJsFile(expr.getSourceFile(), lowerer.program)) { + if (isCjsJsFile(ts.getSourceFileOfNode(expr), lowerer.program)) { lowerer.unsupported( "SC1090", expr, diff --git a/packages/compiler/src/frontend/lowering/lower-server.ts b/packages/compiler/src/frontend/lowering/lower-server.ts index b4e64c9d0..afae7dd7b 100644 --- a/packages/compiler/src/frontend/lowering/lower-server.ts +++ b/packages/compiler/src/frontend/lowering/lower-server.ts @@ -719,7 +719,7 @@ export function lowerNetModuleCall( // ERR_INVALID_ARG_TYPE on 'options' before any server exists. DYN // result — the checked-dynamic lane's concise arrows box it, and // the throw means it never materializes. - if (isJsSourceFile(expr.getSourceFile())) { + if (isJsSourceFile(ts.getSourceFileOfNode(expr))) { const t = lowerer.mapTypeOf(lowerer.typeOf(args[0]!)); if (t !== null && (t.kind === "string" || t.kind === "f64" || t.kind === "bool")) { const raw = lowerer.lowerExpr(args[0]!); @@ -770,7 +770,7 @@ export function lowerNetModuleCall( // invalid-input probes build theirs with computed keys): the // checked-dynamic walk validates Node-order and the compiler-rendered // fence is the post-validation tail. - if (args.length === 1 && isJsSourceFile(expr.getSourceFile())) { + if (args.length === 1 && isJsSourceFile(ts.getSourceFileOfNode(expr))) { const t = lowerer.mapTypeOf(lowerer.typeOf(args[0]!)); if (t !== null && (t.kind === "dyn" || t.kind === "record")) { const raw = lowerer.lowerExpr(args[0]!); @@ -885,7 +885,7 @@ function lowerNetConnectOptions( ): IrExpr { const args = expr.arguments.nodes; const optsNode = args[0] as ts.ObjectLiteralExpression; - const isJs = isJsSourceFile(expr.getSourceFile()); + const isJs = isJsSourceFile(ts.getSourceFileOfNode(expr)); // The checked-dynamic option-bag route (JS sources): a literal with // computed keys (the invalid-input probes' spelling) or an objectMode- // trio member rides WHOLE to the runtime walk — Node's Socket-ctor @@ -1524,7 +1524,7 @@ function lowerNetServerMethodCall( } else if ( key === "signal" && ts.isPropertyAssignment(prop) && - isJsSourceFile(call.getSourceFile()) + isJsSourceFile(ts.getSourceFileOfNode(call)) ) { // A provably-non-AbortSignal signal (the invalid-input probes: // strings, numbers, plain records) throws Node's @@ -2334,7 +2334,7 @@ function lowerNetSocketMethodCall( if ( encT.value === "buffer" && chunkT.kind === "string" && - isJsSourceFile(call.getSourceFile()) + isJsSourceFile(ts.getSourceFileOfNode(call)) ) { lowerer.lowerExpr(args[0]!); // evaluation order (effect-free in practice) return nodeThrowExpr(1, "ERR_UNKNOWN_ENCODING", "Unknown encoding: buffer", VOID, loc); @@ -4851,7 +4851,7 @@ function tlsLiteralNeedsRuntimeWalk( node: ts.ObjectLiteralExpression, includeHttpTimeoutOptions = false, ): boolean { - const jsSource = isJsSourceFile(node.getSourceFile()); + const jsSource = isJsSourceFile(ts.getSourceFileOfNode(node)); return node.properties.nodes.some( (p) => (ts.isPropertyAssignment(p) || ts.isShorthandPropertyAssignment(p)) && @@ -5071,7 +5071,7 @@ function lowerTlsModuleCall( bi.member === "getCACertificates" && args.length === 1 && !args.some(ts.isSpreadElement) && - isJsSourceFile(expr.getSourceFile()) + isJsSourceFile(ts.getSourceFileOfNode(expr)) ) { // The type-argument ladder (validateString + the documented name // set); the real CA list has no lowering, so a valid name meets the @@ -5737,7 +5737,7 @@ function lowerHttp2ModuleCall( if (ts.isConditionalExpression(inner)) { const whenTrue = stripParensAndCasts(inner.whenTrue); const whenFalse = stripParensAndCasts(inner.whenFalse); - const condText = inner.condition.getText(); + const condText = ts.getTextOfNode(inner.condition); const ok = ts.isObjectLiteralExpression(whenTrue) && ts.isObjectLiteralExpression(whenFalse) && @@ -5748,7 +5748,7 @@ function lowerHttp2ModuleCall( ts.isStringLiteral(whenTrue.properties.nodes[0]!.name)) && (whenTrue.properties.nodes[0]!.name as ts.Identifier | ts.StringLiteral).text === "SNICallback" && - whenTrue.properties.nodes[0]!.initializer.getText() === condText; + ts.getTextOfNode(whenTrue.properties.nodes[0]!.initializer) === condText; if (!ok) { lowerer.noLowering( "createSecureServer options with a conditional spread", @@ -7533,7 +7533,7 @@ function lowerHttpResMethodCall( } if (name === "writeContinue" || name === "writeProcessing" || name === "writeEarlyHints") { requireStatementPosition(lowerer, call, `res.${name}(...)`); - if (lowerer.typeOf(access.expression).getSymbol()?.name === "Http2ServerResponse") { + if (ts.symbolNameOf(lowerer.typeOf(access.expression).getSymbol()) === "Http2ServerResponse") { lowerer.noLowering( `Http2ServerResponse.${name}`, call, @@ -7642,7 +7642,7 @@ function lowerHttpResMethodCall( } if (name === "addTrailers") { requireStatementPosition(lowerer, call, "res.addTrailers(...)"); - if (lowerer.typeOf(access.expression).getSymbol()?.name === "Http2ServerResponse") { + if (ts.symbolNameOf(lowerer.typeOf(access.expression).getSymbol()) === "Http2ServerResponse") { lowerer.noLowering( "Http2ServerResponse.addTrailers", call, diff --git a/packages/compiler/src/frontend/lowering/lower-stdlib.ts b/packages/compiler/src/frontend/lowering/lower-stdlib.ts index 78d690c6b..bc8955a81 100644 --- a/packages/compiler/src/frontend/lowering/lower-stdlib.ts +++ b/packages/compiler/src/frontend/lowering/lower-stdlib.ts @@ -667,7 +667,7 @@ export function lowerStaticResponseCall(lowerer: Lowerer, call: ts.CallExpressio } const recvType = lowerer.checker.getBaseTypeOfLiteralType(lowerer.typeOf(access.expression)); const sym = recvType.getAliasSymbol() ?? recvType.getSymbol(); - if (!sym || sym.name !== "Response" || !lowerer.isStdlibSymbol(sym)) return null; + if (!sym || ts.symbolName(sym) !== "Response" || !lowerer.isStdlibSymbol(sym)) return null; const recv = lowerer.lowerExpr(access.expression); if (recv.type.kind !== "dyn") return null; return lowerStaticFixedFetchMethodCall(lowerer, call, access, member, recv, [], (receiver) => @@ -753,7 +753,7 @@ function lowerStaticFixedFetchMethodCall( loc, }, then: answer, - else_: nodeThrowExpr(1, "", `${access.getText()} is not a function`, answer.type, loc), + else_: nodeThrowExpr(1, "", `${ts.getTextOfNode(access)} is not a function`, answer.type, loc), type: answer.type, loc, }; @@ -927,7 +927,7 @@ export function fenceStaticResponseMember( ): IrExpr | null { const recvType = lowerer.checker.getBaseTypeOfLiteralType(lowerer.typeOf(access.expression)); const sym = recvType.getAliasSymbol() ?? recvType.getSymbol(); - if (!sym || sym.name !== "Response" || !lowerer.isStdlibSymbol(sym)) return null; + if (!sym || ts.symbolName(sym) !== "Response" || !lowerer.isStdlibSymbol(sym)) return null; const members = fetchAccessMemberNames(lowerer, access); if (members === null) return null; const status = (member: string) => @@ -969,7 +969,7 @@ export function fenceStaticHeadersMember( ): IrExpr | null { const recvType = lowerer.checker.getBaseTypeOfLiteralType(lowerer.typeOf(access.expression)); const sym = recvType.getAliasSymbol() ?? recvType.getSymbol(); - if (!sym || sym.name !== "Headers" || !lowerer.isStdlibSymbol(sym)) return null; + if (!sym || ts.symbolName(sym) !== "Headers" || !lowerer.isStdlibSymbol(sym)) return null; const members = fetchAccessMemberNames(lowerer, access); if (members === null) return null; const status = (member: string) => @@ -1026,7 +1026,8 @@ function isStdlibFetchInterface(lowerer: Lowerer, node: ts.Node, owner: string): for (const symbol of [type.getSymbol(), type.getAliasSymbol()]) { // NodeJS.ReadableStream shares the WHATWG interface's name. if ( - symbol?.name === owner && + symbol !== undefined && + ts.symbolName(symbol) === owner && lowerer.isStdlibSymbol(symbol) && !symbol.declarations.some( (d) => ts.isInterfaceDeclaration(d) && isDeclaredInAmbientNamespace(d, "NodeJS"), @@ -1093,10 +1094,10 @@ function fetchInterfaceInventoryOwner( const symbol = [type.getSymbol(), type.getAliasSymbol()].find( (candidate) => candidate !== undefined && - NODE24_FETCH_COMPAT_PROFILE.inventory.interfaces.includes(candidate.name) && + NODE24_FETCH_COMPAT_PROFILE.inventory.interfaces.includes(ts.symbolName(candidate)) && lowerer.isStdlibSymbol(candidate), ); - return symbol ? { owner: symbol.name, symbol } : null; + return symbol ? { owner: ts.symbolName(symbol), symbol } : null; } function fenceFetchObjectMembers( @@ -1308,7 +1309,7 @@ export function lowerFetchElementMethodCall( kind: "dynInvoke", recv: receiverRef, method: member, - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args: argumentRefs, type: DYN, loc, @@ -1321,7 +1322,7 @@ export function lowerFetchElementMethodCall( let result: IrExpr = nodeThrowExpr( 1, "", - `${access.getText()} is not a function`, + `${ts.getTextOfNode(access)} is not a function`, invocationType, loc, ); @@ -1627,9 +1628,10 @@ export function lowerStaticAbortControllerCall( const receiverTs = lowerer.checker.getBaseTypeOfLiteralType(lowerer.typeOf(access.expression)); const symbol = receiverTs.getAliasSymbol() ?? receiverTs.getSymbol(); if ( - symbol?.name !== "AbortController" || + symbol === undefined || + ts.symbolName(symbol) !== "AbortController" || !symbol.declarations.some( - (d) => ts.isInterfaceDeclaration(d) && lowerer.isStdlibFile(d.getSourceFile()), + (d) => ts.isInterfaceDeclaration(d) && lowerer.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { return null; @@ -1652,7 +1654,7 @@ export function lowerStaticAbortControllerCall( kind: "dynInvoke", recv: receiverRef, method: "abort", - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args: argumentRefs, type: DYN, loc, @@ -1681,9 +1683,10 @@ export function lowerStaticReadableStreamControllerCall( const receiverTs = lowerer.checker.getBaseTypeOfLiteralType(lowerer.typeOf(access.expression)); const symbol = receiverTs.getAliasSymbol() ?? receiverTs.getSymbol(); if ( - symbol?.name !== "ReadableStreamDefaultController" || + symbol === undefined || + ts.symbolName(symbol) !== "ReadableStreamDefaultController" || !symbol.declarations.some( - (d) => ts.isInterfaceDeclaration(d) && lowerer.isStdlibFile(d.getSourceFile()), + (d) => ts.isInterfaceDeclaration(d) && lowerer.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { return null; @@ -1707,7 +1710,7 @@ export function lowerStaticReadableStreamControllerCall( kind: "dynInvoke", recv: receiver, method: member, - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args: argumentRefs, type: DYN, loc, @@ -1756,7 +1759,7 @@ export function lowerStaticReadableStreamCancelCall( kind: "dynInvoke", recv: receiverRef, method: "cancel", - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args: argumentRefs, type: DYN, loc, @@ -1789,9 +1792,10 @@ export function lowerStaticAbortSignalListenerCall( const receiverTs = lowerer.checker.getBaseTypeOfLiteralType(lowerer.typeOf(access.expression)); const symbol = receiverTs.getAliasSymbol() ?? receiverTs.getSymbol(); if ( - (symbol?.name !== "AbortSignal" && symbol?.name !== "EventTarget") || + ((symbol === undefined || ts.symbolName(symbol) !== "AbortSignal") && + (symbol === undefined || ts.symbolName(symbol) !== "EventTarget")) || !symbol.declarations.some( - (d) => ts.isInterfaceDeclaration(d) && lowerer.isStdlibFile(d.getSourceFile()), + (d) => ts.isInterfaceDeclaration(d) && lowerer.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { return null; @@ -1814,7 +1818,7 @@ export function lowerStaticAbortSignalListenerCall( kind: "dynInvoke", recv: receiver, method: member, - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args: argumentRefs, type: DYN, loc, @@ -2146,9 +2150,10 @@ export function lowerStaticReadableStreamReaderCall( const receiverTs = lowerer.typeOf(access.expression); const symbol = receiverTs.getSymbol(); if ( - symbol?.name !== "ReadableStreamDefaultReader" || + symbol === undefined || + ts.symbolName(symbol) !== "ReadableStreamDefaultReader" || !symbol.declarations.some( - (d) => ts.isInterfaceDeclaration(d) && lowerer.isStdlibFile(d.getSourceFile()), + (d) => ts.isInterfaceDeclaration(d) && lowerer.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { return null; @@ -2189,7 +2194,7 @@ export function lowerDynamicImportCall(lowerer: Lowerer, call: ts.CallExpression if (call.arguments.nodes.length !== 1) { lowerer.unsupported("SC1090", call, "dynamic import() with import attributes"); } - const res = lowerer.dynImports.get(`${call.getSourceFile().fileName}\u0000${arg.text}`); + const res = lowerer.dynImports.get(`${ts.getSourceFileOfNode(call).fileName}\u0000${arg.text}`); if (res === undefined) lowerer.refuseUnsupportedApi("'import()'", call); if (res.kind === "program-module") { return lowerOwnStaticModuleImport(lowerer, call, arg); @@ -2569,6 +2574,6 @@ export function npmMemberFence(lowerer: Lowerer, access: ts.PropertyAccessExpres export function npmPackageOfSymbol(lowerer: Lowerer, sym: ts.Symbol | undefined): string | null { const decls = sym ? sym.declarations : undefined; if (!decls || decls.length === 0) return null; - if (!decls.every((d) => lowerer.isNpmFile(d.getSourceFile()))) return null; - return npmPackageNameOf(decls[0]!.getSourceFile().fileName); + if (!decls.every((d) => lowerer.isNpmFile(ts.getSourceFileOfNode(d)))) return null; + return npmPackageNameOf(ts.getSourceFileOfNode(decls[0]!).fileName); } diff --git a/packages/compiler/src/frontend/lowering/lower-stmts.ts b/packages/compiler/src/frontend/lowering/lower-stmts.ts index 5efdfae76..69f6cc99b 100644 --- a/packages/compiler/src/frontend/lowering/lower-stmts.ts +++ b/packages/compiler/src/frontend/lowering/lower-stmts.ts @@ -256,8 +256,8 @@ function npmStaticReturnedEmptyArrayType( decl.initializer === undefined || !ts.isArrayLiteralExpression(decl.initializer) || decl.initializer.elements.nodes.length !== 0 || - !isJsSourceFile(decl.getSourceFile()) || - npmStaticPackageOfPath(decl.getSourceFile().fileName) === null || + !isJsSourceFile(ts.getSourceFileOfNode(decl)) || + npmStaticPackageOfPath(ts.getSourceFileOfNode(decl).fileName) === null || lowerer.ctx.returnType.kind !== "array" ) { return null; @@ -296,8 +296,8 @@ function npmStaticOptionalSelfWriteType( ): (IrType & { kind: "union" }) | null { if ( !ts.isIdentifier(decl.name) || - !isJsSourceFile(decl.getSourceFile()) || - npmStaticPackageOfPath(decl.getSourceFile().fileName) === null || + !isJsSourceFile(ts.getSourceFileOfNode(decl)) || + npmStaticPackageOfPath(ts.getSourceFileOfNode(decl).fileName) === null || initial.kind === "union" ) { return null; @@ -359,7 +359,9 @@ function stdlibTypeNamed(lowerer: Lowerer, node: ts.Node, name: string): boolean if (t.flags & ts.TypeFlags.Union) return constituentTypes(t).some(visit); const sym = t.getAliasSymbol() ?? t.getSymbol(); return ( - sym?.name === name && sym.declarations.some((d) => lowerer.isStdlibFile(d.getSourceFile())) + sym !== undefined && + ts.symbolName(sym) === name && + sym.declarations.some((d) => lowerer.isStdlibFile(ts.getSourceFileOfNode(d))) ); }; return visit(lowerer.typeOf(node)); @@ -613,7 +615,7 @@ function pureAnnotatedDeadConst(stmt: ts.Statement, sf: ts.SourceFile): boolean return decls.every((d) => { let init = d.initializer; if (init === undefined) return false; - const trivia = sf.text.slice(init.pos, init.getStart(sf)); + const trivia = sf.text.slice(init.pos, ts.tokenStartOfNode(init)); if (!trivia.includes("@__PURE__") && !trivia.includes("#__PURE__")) return false; // `as` casts are type-world wrappers (`(() => {...})() as unknown as // { new (): any }` — cookie's NullObject shape); the runtime @@ -640,7 +642,7 @@ function pureAnnotatedDeadConst(stmt: ts.Statement, sf: ts.SourceFile): boolean * probes that would otherwise claim (and diagnose) the call-initializer * shape. */ export function provenanceElidedConstDecl(lowerer: Lowerer, decl: ts.VariableDeclaration): boolean { - const sf = decl.getSourceFile(); + const sf = ts.getSourceFileOfNode(decl); if (!isProvenanceSourceFile(sf.fileName)) return false; const stmt = decl.parent?.parent; if (!ts.isVariableStatement(stmt) || !pureAnnotatedDeadConst(stmt, sf)) return false; @@ -683,7 +685,7 @@ export function lowerStmts(lowerer: Lowerer, stmts: readonly ts.Statement[]): Ir // runtimeFence — a catchable, SC-coded throw at the statement's // position — instead of failing the whole build). TypeScript keeps // compile fences. ICEs (SC9001) always stay compile errors. - const sf = stmts[0]?.getSourceFile(); + const sf = stmts.length === 0 ? undefined : ts.getSourceFileOfNode(stmts[0]!); const deferFences = sf !== undefined && isJsSourceFile(sf); // Register this list as open for the forward-capture machinery: a // nested function lowering mid-list may pre-declare a LATER const of @@ -959,7 +961,7 @@ export function predeclareForwardCapture(lowerer: Lowerer, symbol: ts.Symbol): b return false; let type = lowerer.irTypeOf(decl.name); if ( - isJsSourceFile(decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(decl)) && !decl.type && !hasJsTypeAnnotation(decl) && (ts.isObjectLiteralExpression(decl.initializer) || @@ -1107,7 +1109,7 @@ function jsBindingKeepsCheckedObject( nameNode: ts.Identifier, type: IrType, ): boolean { - if (!isJsSourceFile(nameNode.getSourceFile())) return false; + if (!isJsSourceFile(ts.getSourceFileOfNode(nameNode))) return false; const host = hostVariableDeclarationOf(nameNode); if (!host || host.type || hasJsTypeAnnotation(host)) return false; const arms = type.kind === "union" ? (lowerer.unions.get(type.unionId)?.arms ?? []) : [type]; @@ -1122,8 +1124,9 @@ function jsBindingKeepsCheckedObject( function mentionsSymbol(lowerer: Lowerer, node: ts.Node, symbol: ts.Symbol): boolean { if (ts.isIdentifier(node)) return lowerer.checker.getSymbolAtLocation(node) === symbol; return ( - node.forEachChild((child) => (mentionsSymbol(lowerer, child, symbol) ? true : undefined)) === - true + ts.forEachChild(node, (child) => + mentionsSymbol(lowerer, child, symbol) ? true : undefined, + ) === true ); } @@ -1169,7 +1172,8 @@ function lowerJsVarFromExisting( symbol: ts.Symbol, ): IrStmt | null { if (!ts.isIdentifier(decl.name) || !decl.initializer) return null; - if (!isJsSourceFile(decl.getSourceFile()) || lowerer.ctx.hoistedVars.has(symbol)) return null; + if (!isJsSourceFile(ts.getSourceFileOfNode(decl)) || lowerer.ctx.hoistedVars.has(symbol)) + return null; if (lowerer.bindingIn(lowerer.ctx, symbol)) return null; let node: ts.Expression = decl.initializer; while (ts.isParenthesizedExpression(node) || ts.isSatisfiesExpression(node)) @@ -1212,13 +1216,13 @@ function varBindingType(lowerer: Lowerer, nameNode: ts.Identifier): IrType | nul ) return DYN; if ( - isJsSourceFile(nameNode.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(nameNode)) && ts.isVariableDeclaration(nameNode.parent) && isNativeBuiltinValueInitializer(lowerer, nameNode.parent.initializer) ) return DYN; let type = lowerer.mapTypeOf(lowerer.typeOf(nameNode)); - if (type?.kind === "record" && isJsSourceFile(nameNode.getSourceFile())) { + if (type?.kind === "record" && isJsSourceFile(ts.getSourceFileOfNode(nameNode))) { const shape = lowerer.shapes.get(type.shapeId); if (shape && shape.fields.length === 0 && !shape.indexValue && !shape.tuple) type = DYN; } @@ -1821,7 +1825,7 @@ function lowerVarDeclarator( requireSpecOf(decl.initializer) !== null && !isNativeFfiRequire(lowerer, decl.initializer) && !isJsonRequireDecl(lowerer, decl) && - isJsSourceFile(decl.getSourceFile()) + isJsSourceFile(ts.getSourceFileOfNode(decl)) ) { const spec = requireSpecOf(decl.initializer)!; if (lowerer.externalTypes.has(spec)) lowerer.externalHostFence(spec, decl, false); @@ -2070,7 +2074,7 @@ function lowerMatchAllDrainDecl( export function isParseArgsDynCheckerType(lowerer: Lowerer, type: ts.Type): boolean { const widened = lowerer.checker.getBaseTypeOfLiteralType(type); const declarationBelongs = (d: ts.Node): boolean => { - if (!lowerer.isStdlibFile(d.getSourceFile())) return false; + if (!lowerer.isStdlibFile(ts.getSourceFileOfNode(d))) return false; let inParseArgsType = false; for (let node: ts.Node | undefined = d.parent; node; node = node.parent) { if ( @@ -2086,7 +2090,11 @@ export function isParseArgsDynCheckerType(lowerer: Lowerer, type: ts.Type): bool return false; }; const sym = widened.getAliasSymbol() ?? widened.getSymbol(); - if (sym && isParseArgsDynTypeName(sym.name) && sym.declarations.some(declarationBelongs)) { + if ( + sym && + isParseArgsDynTypeName(ts.symbolName(sym)) && + sym.declarations.some(declarationBelongs) + ) { return true; } // A discriminant check erases the token alias and leaves one anonymous @@ -2143,7 +2151,7 @@ export function lowerDestructuringDecl( // sources keep their own fences below; other carriers keep this one. if ( lowerer.anyOrigin(decl.initializer) && - !isJsSourceFile(decl.getSourceFile()) && + !isJsSourceFile(ts.getSourceFileOfNode(decl)) && init.type.kind !== "record" && init.type.kind !== "array" && init.type.kind !== "dyn" @@ -2159,7 +2167,7 @@ export function lowerDestructuringDecl( // the checker's own type instead of the carrier. if ( init.type.kind === "string" && - isJsSourceFile(decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(decl)) && !checkerStringSource(lowerer, decl.initializer) ) { lowerer.unsupported( @@ -2177,7 +2185,7 @@ export function lowerDestructuringDecl( init.type.kind === "bool" || init.type.kind === "string" || isUnitType(init.type)) && - !isJsSourceFile(decl.getSourceFile()) && + !isJsSourceFile(ts.getSourceFileOfNode(decl)) && !( init.type.kind === "string" && staticStringPattern(lowerer, decl.name as ts.ArrayBindingPattern | ts.ObjectBindingPattern) @@ -2201,7 +2209,7 @@ export function lowerDestructuringDecl( // (the message is the fence's — a unit value here is the failure // path either way). TypeScript keeps the fence: narrowing first is // the annotated fix. - if (isJsSourceFile(decl.getSourceFile()) && init.type.kind === "union") { + if (isJsSourceFile(ts.getSourceFileOfNode(decl)) && init.type.kind === "union") { const def = lowerer.unions.get(init.type.unionId); const dataArms = (def?.arms ?? []).filter((a) => !isUnitType(a)); const arm = dataArms[0]; @@ -2260,7 +2268,7 @@ export function lowerDestructuringDecl( if ( init.type.kind === "dyn" && ts.isObjectBindingPattern(decl.name) && - !isJsSourceFile(decl.getSourceFile()) && + !isJsSourceFile(ts.getSourceFileOfNode(decl)) && lowerer.anyOrigin(decl.initializer) ) { const spelling = destrSpellingOf(decl.initializer); @@ -2341,7 +2349,7 @@ export function matchGroupsPatternSource( !ts.isObjectBindingPattern(pattern) || pattern.elements.nodes.length === 0 || !(row.type.kind === "dyn" - ? isJsSourceFile(pattern.getSourceFile()) + ? isJsSourceFile(ts.getSourceFileOfNode(pattern)) : isMatchSliceType(lowerer, row.type)) || !pattern.elements.nodes.every((el) => { if (el.dotDotDotToken !== undefined || el.name === undefined) return false; @@ -2384,7 +2392,9 @@ export function lowerBindingPattern( const patternType = lowerer.typeOf(pattern); const patternSymbol = patternType.getSymbol(); allowDynObject ||= - patternSymbol?.name === "SegmentData" && lowerer.isStdlibSymbol(patternSymbol); + patternSymbol !== undefined && + ts.symbolName(patternSymbol) === "SegmentData" && + lowerer.isStdlibSymbol(patternSymbol); allowDynObject ||= (patternType.flags & ts.TypeFlags.Object) !== 0 && lowerer.mapTypeOf(patternType)?.kind === "dyn"; @@ -2842,7 +2852,7 @@ export function lowerBindingPattern( // object patterns use the same exact keyed-read desugar. if ( srcType.kind === "union" && - isJsSourceFile(pattern.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(pattern)) && lowerer.dynConvertible(srcType) ) { lowerer.lowerBindingPattern( @@ -3250,7 +3260,7 @@ export function lowerBindingPattern( lowerer.bindPatternTarget(el.name, value, isLet, out); continue; } - if (!fieldType && isJsSourceFile(el.getSourceFile())) { + if (!fieldType && isJsSourceFile(ts.getSourceFileOfNode(el))) { // Inferred JavaScript shapes can omit optional own properties and // inherit values from their prototype. Read the live checked view. const loc = locOf(el); @@ -3384,7 +3394,7 @@ function patternBindingType(lowerer: Lowerer, name: ts.BindingName): IrType | nu const type = lowerer.typeOf(name); return ( lowerer.mapTypeOf(type) ?? - (isJsSourceFile(name.getSourceFile()) ? dynFallbackType(lowerer, name, type) : null) + (isJsSourceFile(ts.getSourceFileOfNode(name)) ? dynFallbackType(lowerer, name, type) : null) ); } @@ -3477,7 +3487,7 @@ function isRegexMatchResultPattern(lowerer: Lowerer, pattern: ts.ObjectBindingPa const symbol = type.getAliasSymbol() ?? type.getSymbol(); return ( symbol !== undefined && - (symbol.name === "RegExpExecArray" || symbol.name === "RegExpMatchArray") && + (ts.symbolName(symbol) === "RegExpExecArray" || ts.symbolName(symbol) === "RegExpMatchArray") && lowerer.isStdlibSymbol(symbol) ); } @@ -3788,7 +3798,7 @@ function stdlibGlobalTokenDestructure( decl: ts.VariableDeclaration, isLet: boolean, ): IrStmt[] | null { - if (decl.initializer === undefined || !isJsSourceFile(decl.getSourceFile())) return null; + if (decl.initializer === undefined || !isJsSourceFile(ts.getSourceFileOfNode(decl))) return null; const globalName = stdlibGlobalNameOf(lowerer, decl.initializer); if (globalName === null) return null; if (!ts.isObjectBindingPattern(decl.name)) { @@ -3864,7 +3874,7 @@ function stdlibGlobalTokenDestructure( `destructuring the non-builtin member '${propName}' of 'globalThis'`, ); } - const canonical = member.name === "global" ? "globalThis" : member.name; + const canonical = ts.symbolName(member) === "global" ? "globalThis" : ts.symbolName(member); token = `[builtin ${canonical}]`; alias = canonical; // Builtin constructors read as their interned function values, the @@ -4468,7 +4478,7 @@ function classInstanceRestValue( const deferOrderCheck = lowerer.instantiationContext !== null && lowerer.onEdge !== null && - !isJsSourceFile(blame.getSourceFile()); + !isJsSourceFile(ts.getSourceFileOfNode(blame)); if (!deferOrderCheck) { if (!orderMatches(emitOrder)) { lowerer.unsupported( @@ -4486,7 +4496,7 @@ function classInstanceRestValue( // matters. const context = lowerer.instantiationContext; const countFailure = !lowerer.suppressStats; - const sf = blame.getSourceFile(); + const sf = ts.getSourceFileOfNode(blame); lowerer.shapeOrderMetadataFinalizers.push(() => { const current = lowerer.shapes.get(restT.shapeId) ?? restShape; const currentOrder = current.declaredOrder ?? current.fields.map((f) => f.name); @@ -4747,7 +4757,7 @@ function inferredEvolvingLetType(lowerer: Lowerer, decl: ts.VariableDeclaration) if ( ts.isCallExpression(node.right) && (implicitMethodCallInfersReturn(lowerer, node.right) || - isJsSourceFile(node.right.getSourceFile())) + isJsSourceFile(ts.getSourceFileOfNode(node.right))) ) { invalid = true; return "stop"; @@ -4868,9 +4878,9 @@ export function lowerVarDecl( (decl.name.text === "module" || decl.name.text === "exports") && ts.isVariableStatement(decl.parent?.parent) && ts.isSourceFile(decl.parent?.parent?.parent) && - !isJsSourceFile(decl.getSourceFile()) && - !decl - .getSourceFile() + !isJsSourceFile(ts.getSourceFileOfNode(decl)) && + !ts + .getSourceFileOfNode(decl) .statements.nodes.some( (s) => ts.isImportDeclaration(s) || @@ -5075,7 +5085,7 @@ export function lowerVarDecl( : undefined, ); if ( - isJsSourceFile(decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(decl)) && !decl.type && !hasJsTypeAnnotation(decl) && raw.type.kind === "dyn" && @@ -5147,7 +5157,7 @@ export function lowerVarDecl( // immediately (the type covers the unassigned state), so they // initialize to the interned undefined arm like an omitted optional. let type = lowerer.irTypeOf(decl.name); - if (type.kind === "dyn" && isLet && isJsSourceFile(decl.getSourceFile())) { + if (type.kind === "dyn" && isLet && isJsSourceFile(ts.getSourceFileOfNode(decl))) { type = inferredEvolvingLetType(lowerer, decl) ?? type; } // `var v: void;` / `let x: undefined;` — a unit-only binding rides @@ -5174,7 +5184,7 @@ export function lowerVarDecl( // function (with its hint), not the unmappable declared type. If it // poisons, still declare the local when its type is representable, so // later references to this name don't produce cascading errors. - const inferredObjectType = isJsSourceFile(decl.getSourceFile()) + const inferredObjectType = isJsSourceFile(ts.getSourceFileOfNode(decl)) ? lowerer.mapTypeOf(lowerer.typeOf(decl.name)) : null; const inferredObjectArms = @@ -5191,7 +5201,7 @@ export function lowerVarDecl( inferredObjectArms.some( (arm) => arm.kind === "record" && jsObjectBindingExpands(lowerer, decl, arm), ) || - (isJsSourceFile(decl.getSourceFile()) && + (isJsSourceFile(ts.getSourceFileOfNode(decl)) && !decl.type && !hasJsTypeAnnotation(decl) && ts.isObjectLiteralExpression(decl.initializer) && @@ -5207,7 +5217,7 @@ export function lowerVarDecl( try { init = expandsObject || - (isJsSourceFile(decl.getSourceFile()) && + (isJsSourceFile(ts.getSourceFileOfNode(decl)) && !decl.type && isNativeBuiltinValueInitializer(lowerer, decl.initializer)) ? lowerer.lowerExprExpecting(decl.initializer, DYN) @@ -5244,7 +5254,7 @@ export function lowerVarDecl( (init.type.kind === "moduleNs" ? init.type : null) ?? (init.type.kind === "dyn" ? DYN : null); if ( - isJsSourceFile(decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(decl)) && !decl.type && !hasJsTypeAnnotation(decl) && init.type.kind === "func" @@ -5275,8 +5285,8 @@ export function lowerVarDecl( if ( lowerer.implicitParamTypes !== null && init.type.kind === "dyn" && - isJsSourceFile(decl.getSourceFile()) && - npmStaticPackageOfPath(decl.getSourceFile().fileName) !== null + isJsSourceFile(ts.getSourceFileOfNode(decl)) && + npmStaticPackageOfPath(ts.getSourceFileOfNode(decl).fileName) !== null ) { type = DYN; } @@ -5284,17 +5294,17 @@ export function lowerVarDecl( init.type.kind === "dyn" && (inferredObjectType?.kind === "record" || inferredObjectType?.kind === "array" || - (isJsSourceFile(decl.getSourceFile()) && + (isJsSourceFile(ts.getSourceFileOfNode(decl)) && inferredObjectArms.some((arm) => arm.kind === "record") && inferredObjectArms.every((arm) => isUnitType(arm) || arm.kind === "record")) || - (isJsSourceFile(decl.getSourceFile()) && + (isJsSourceFile(ts.getSourceFileOfNode(decl)) && inferredObjectType !== null && (inferredObjectType.kind === "date" || inferredObjectType.kind === "func" || inferredObjectType.kind === "object" || jsOpenObjectType(decl, inferredObjectType, lowerer.shapes, lowerer.unions).kind === "dyn")) || - (isJsSourceFile(decl.getSourceFile()) && + (isJsSourceFile(ts.getSourceFileOfNode(decl)) && inferredObjectType !== null && (inferredObjectType.kind === "void" || isUnitType(inferredObjectType) || @@ -5312,14 +5322,14 @@ export function lowerVarDecl( expandsObject || preservesObjectIdentity || keepsAssertedObject || - (isJsSourceFile(decl.getSourceFile()) && + (isJsSourceFile(ts.getSourceFileOfNode(decl)) && !decl.type && isNativeBuiltinValueInitializer(lowerer, decl.initializer)) ) { type = DYN; } if ( - isJsSourceFile(decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(decl)) && !decl.type && init.type.kind === "array" && init.type.elem.kind === "dyn" && @@ -5335,14 +5345,14 @@ export function lowerVarDecl( // cannot hold the binding's future. The binding is checked-dynamic // instead (irTypeOf's JS story): writes dynFrom, typed exits dynCheck. // Const keeps the static empty record — no reassignment exists. - if (isLet && type?.kind === "record" && isJsSourceFile(decl.getSourceFile())) { + if (isLet && type?.kind === "record" && isJsSourceFile(ts.getSourceFileOfNode(decl))) { const shape = lowerer.shapes.get(type.shapeId); if (shape && shape.fields.length === 0 && !shape.indexValue && !shape.tuple) type = DYN; } if ( isLet && type !== null && - isJsSourceFile(decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(decl)) && !decl.type && !hasJsTypeAnnotation(decl) && (type.kind === "object" || type.kind === "symbol" || jsBindingHasOpenWrites(lowerer, decl)) @@ -5366,7 +5376,7 @@ export function lowerVarDecl( if ( returnedEmptyArrayType === null && (type === null || type.kind === "array") && - isJsSourceFile(decl.getSourceFile()) + isJsSourceFile(ts.getSourceFileOfNode(decl)) ) { let initExpr: ts.Expression = decl.initializer; while (ts.isParenthesizedExpression(initExpr)) initExpr = initExpr.expression; @@ -5429,7 +5439,7 @@ export function lowerVarDecl( if ( type?.kind === "func" && init.type.kind === "dyn" && - (isJsSourceFile(decl.getSourceFile()) || + (isJsSourceFile(ts.getSourceFileOfNode(decl)) || (!decl.type && !hasJsTypeAnnotation(decl) && type.rest === true && @@ -5438,7 +5448,7 @@ export function lowerVarDecl( type = DYN; } if ( - isJsSourceFile(decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(decl)) && !decl.type && !hasJsTypeAnnotation(decl) && init.type.kind === "dyn" && @@ -5465,7 +5475,7 @@ export function lowerVarDecl( // type (a union with a fence-folded arm — the typeof-'bigint' // dual-mode ternary) adopts the initializer's static type, the // same rule genuine `any` consts take. - (type === null && isJsSourceFile(decl.getSourceFile()))) && + (type === null && isJsSourceFile(ts.getSourceFileOfNode(decl)))) && init.type.kind !== "void" && init.type.kind !== "caught" && !isUnitType(init.type) @@ -5486,7 +5496,7 @@ export function lowerVarDecl( let settledType: IrType = type; if ( !isLet && - isJsSourceFile(decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(decl)) && !hasJsTypeAnnotation(decl) && decl.initializer && ts.isNewExpression(decl.initializer) @@ -5758,7 +5768,7 @@ function jsObjectBindingExpands( decl.type || !decl.initializer || !ts.isIdentifier(decl.name) || - !isJsSourceFile(decl.getSourceFile()) + !isJsSourceFile(ts.getSourceFileOfNode(decl)) ) return false; let initializer = decl.initializer; @@ -5868,7 +5878,10 @@ function jsObjectBindingExpands( export function hasJsTypeAnnotation(decl: ts.VariableDeclaration): boolean { const statement = decl.parent?.parent; return ( - !!statement && /@type\b/.test(decl.getSourceFile().text.slice(statement.pos, decl.getStart())) + !!statement && + /@type\b/.test( + ts.getSourceFileOfNode(decl).text.slice(statement.pos, ts.tokenStartOfNode(decl)), + ) ); } @@ -5876,7 +5889,7 @@ export function hasJsTypeAnnotation(decl: ts.VariableDeclaration): boolean { * an any-valued callback. Keep those bindings open, including captured writes. */ export function jsBindingHasOpenWrites(lowerer: Lowerer, decl: ts.VariableDeclaration): boolean { if ( - !isJsSourceFile(decl.getSourceFile()) || + !isJsSourceFile(ts.getSourceFileOfNode(decl)) || decl.type || hasJsTypeAnnotation(decl) || !ts.isIdentifier(decl.name) @@ -6378,7 +6391,7 @@ function lowerDeleteStatement(lowerer: Lowerer, expr: ts.DeleteExpression): IrSt } if (accessor && errorBase) return { kind: "exprStmt", expr: obj, loc }; } - if (isJsSourceFile(expr.getSourceFile()) && obj.type.kind === "object") { + if (isJsSourceFile(ts.getSourceFileOfNode(expr)) && obj.type.kind === "object") { const info = lowerer.classes.get(obj.type.className); const keyType = ts.isElementAccessExpression(target) ? lowerer.typeOf(target.argumentExpression) @@ -6399,7 +6412,7 @@ function lowerDeleteStatement(lowerer: Lowerer, expr: ts.DeleteExpression): IrSt } } if ( - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && obj.type.kind === "func" && lowerer.dynConvertible(obj.type) ) { @@ -6510,7 +6523,7 @@ function lowerCjsExportStatement( const symbol = lowerer.checker.getSymbolAtLocation(nameNode) ?? (ts.isIdentifier(nameNode) || ts.isPrivateIdentifier(nameNode) - ? lowerer.cjsModuleExportSymbol(cjs.expr.getSourceFile(), nameNode.text) + ? lowerer.cjsModuleExportSymbol(ts.getSourceFileOfNode(cjs.expr), nameNode.text) : undefined); const g = symbol ? lowerer.globalsBySymbol.get(symbol) : undefined; if (!g) { @@ -6585,7 +6598,7 @@ function lowerCjsExportStatement( // later real assignment keep the ordinary path (the export IS // undefined then). if (cjsVoidInitPreambleReals(cjs) !== null) return { kind: "block", body: [], loc }; - const sf = cjs.expr.getSourceFile(); + const sf = ts.getSourceFileOfNode(cjs.expr); let root: IrGlobal | undefined; for (const stmt of sf.statements.nodes) { const whole = cjsExportAssignmentOf(stmt); @@ -6648,7 +6661,7 @@ function lowerCjsExportStatement( // were already evaluated by the const's own declaration, so this // statement may not RE-evaluate anything: expression-valued entries // (which the direct-table lowering assigns here) keep a fence. - tableObj = cjsExportTargetLiteral(cjs.expr.right, cjs.expr.getSourceFile()); + tableObj = cjsExportTargetLiteral(cjs.expr.right, ts.getSourceFileOfNode(cjs.expr)); resolvedTarget = tableObj !== null; if (!tableObj) { const single = lowerCjsSingleValueExport(lowerer, cjs.expr, loc); @@ -6710,7 +6723,7 @@ function lowerCjsExportStatement( lowerer.rejectThisInObjectMethod(method.body); const symbol = lowerer.checker.getSymbolAtLocation(method.name) ?? - lowerer.cjsModuleExportSymbol(cjs.expr.getSourceFile(), method.name.text); + lowerer.cjsModuleExportSymbol(ts.getSourceFileOfNode(cjs.expr), method.name.text); const g = symbol ? lowerer.globalsBySymbol.get(symbol) : undefined; if (g) { const fn = lowerer.lowerLambda(method); @@ -6985,7 +6998,7 @@ export function lowerExprStatement(lowerer: Lowerer, expr: ts.Expression): IrStm // global — the expression path owns it (lowerCreateRequireCall). createRequireCalleeFileOf(lowerer, expr.expression) === null ) { - const sf = expr.getSourceFile(); + const sf = ts.getSourceFileOfNode(expr); const spec = requireSpecOf(expr)!; if (lowerer.externalTypes.has(spec)) lowerer.externalHostFence(spec, expr, false); // A bare require of a package NOTHING INSTALLED resolves, in ANY @@ -7031,7 +7044,7 @@ export function lowerExprStatement(lowerer: Lowerer, expr: ts.Expression): IrStm // environment-dependent branch); the static export table has one shape. if ( !topLevelJs && - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && ts.isBinaryExpression(expr) && expr.operatorToken.kind === ts.SyntaxKind.EqualsToken && (isCjsModuleExports(lowerer, expr.left) || isCjsExportsIdentifier(lowerer, expr.left)) @@ -7535,7 +7548,7 @@ export function lowerExprStatement(lowerer: Lowerer, expr: ts.Expression): IrStm if ( recv && (recv.type.kind === "dyn" || - (isJsSourceFile(expr.getSourceFile()) && + (isJsSourceFile(ts.getSourceFileOfNode(expr)) && recv.type.kind === "union" && lowerer.dynConvertible(recv.type))) ) { @@ -7565,7 +7578,7 @@ export function lowerExprStatement(lowerer: Lowerer, expr: ts.Expression): IrStm // through the checked-dynamic node and keep the dynamic write, as // do JavaScript files, whose reads use that node too. if ( - !isJsSourceFile(expr.getSourceFile()) && + !isJsSourceFile(ts.getSourceFileOfNode(expr)) && recv?.type.kind === "object" && lowerer.isErrorRootedClass(recv.type.className) && expr.left.name.text === "code" @@ -7838,8 +7851,8 @@ function lowerDiscardedLogical(lowerer: Lowerer, expr: ts.BinaryExpression): IrS // picks another lowering, exactly as before. if (!(e instanceof PoisonError) || lowerer.diagSink !== null) throw e; const added = lowerer.diags.slice(diagsBefore); - const file = expr.getSourceFile().fileName; - const start = expr.getStart(); + const file = ts.getSourceFileOfNode(expr).fileName; + const start = ts.tokenStartOfNode(expr); const local = added.length > 0 && added.every( @@ -8110,7 +8123,7 @@ function destructuringAssignInto( const patternEmpty = ts.isObjectLiteralExpression(target) ? target.properties.nodes.length === 0 : target.elements.nodes.length === 0; - if (!patternEmpty && rhs !== null && isJsSourceFile(rhs.getSourceFile())) { + if (!patternEmpty && rhs !== null && isJsSourceFile(ts.getSourceFileOfNode(rhs))) { const globalName = stdlibGlobalNameOf(lowerer, rhs); if (globalName !== null) { lowerer.unsupported( @@ -10905,7 +10918,7 @@ function lowerForIn(lowerer: Lowerer, stmt: ts.ForInStatement): IrStmt { while (root?.base) root = root.base; if ( info?.decl && - isJsSourceFile(info.decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(info.decl)) && !info.def.runtime && !root?.def.runtime && !info.builtinError && diff --git a/packages/compiler/src/frontend/lowering/lower-stream.ts b/packages/compiler/src/frontend/lowering/lower-stream.ts index 51d1e0377..b9add95a6 100644 --- a/packages/compiler/src/frontend/lowering/lower-stream.ts +++ b/packages/compiler/src/frontend/lowering/lower-stream.ts @@ -1475,7 +1475,7 @@ export function lowerStreamStaticCall( member === "toWeb" && cls === "%Readable" && call.arguments.nodes.length === 2 && - isJsSourceFile(call.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(call)) && ts.isObjectLiteralExpression(call.arguments.nodes[1]!) ) { for (const p of call.arguments.nodes[1]!.properties.nodes) { @@ -1509,7 +1509,7 @@ export function lowerStreamStaticCall( ); } const type: IrType = { kind: "object", className: "%Readable" }; - const js = isJsSourceFile(call.getSourceFile()); + const js = isJsSourceFile(ts.getSourceFileOfNode(call)); const srcNode = args[0]; const optsNode = args[1]; const srcT = srcNode ? lowerer.mapTypeOf(lowerer.typeOf(srcNode)) : null; @@ -2127,7 +2127,7 @@ function lowerStreamArg( loc: locOf(node), }); }; - if (isJsSourceFile(node.getSourceFile()) && lowerer.dynConvertible(v.type)) { + if (isJsSourceFile(ts.getSourceFileOfNode(node)) && lowerer.dynConvertible(v.type)) { if (what === "pipeline") { // Strings and arrays are iterables — VALID pipeline stages in // Node — and a record carrying a readable/writable member may be @@ -3141,7 +3141,7 @@ function lowerOperatorCallback( if (v.type.kind === "dyn") return v; if ( v.type.kind !== "func" && - isJsSourceFile(node.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(node)) && lowerer.dynConvertible(v.type) ) { // JavaScript passes any value; Node's validateFunction throws. diff --git a/packages/compiler/src/frontend/lowering/lower-test.ts b/packages/compiler/src/frontend/lowering/lower-test.ts index 32870c2cf..391a77245 100644 --- a/packages/compiler/src/frontend/lowering/lower-test.ts +++ b/packages/compiler/src/frontend/lowering/lower-test.ts @@ -86,8 +86,8 @@ function requireStatementPosition(lowerer: Lowerer, call: ts.CallExpression, wha * reports the `test` property's column), at the call for plain ones. */ function atStringOf(expr: ts.CallExpression): string { const target = ts.isPropertyAccessExpression(expr.expression) ? expr.expression.name : expr; - const sf = expr.getSourceFile(); - const pos = sf.getLineAndCharacterOfPosition(target.getStart()); + const sf = ts.getSourceFileOfNode(expr); + const pos = sf.getLineAndCharacterOfPosition(ts.tokenStartOfNode(target)); return `${sf.fileName}:${pos.line + 1}:${pos.character + 1}`; } diff --git a/packages/compiler/src/frontend/lowering/lower-timers.ts b/packages/compiler/src/frontend/lowering/lower-timers.ts index 22983fbb4..6b6a1b5d1 100644 --- a/packages/compiler/src/frontend/lowering/lower-timers.ts +++ b/packages/compiler/src/frontend/lowering/lower-timers.ts @@ -420,7 +420,7 @@ function lowerTimerCall( lowerer.noLowering(`clearTimeout with ${expr.arguments.nodes.length} arguments`, expr); } let handle = lowerer.lowerExpr(expr.arguments.nodes[0]!); - if (handle.type.kind === "dyn" && isJsSourceFile(expr.getSourceFile())) { + if (handle.type.kind === "dyn" && isJsSourceFile(ts.getSourceFileOfNode(expr))) { const stored = lowerer.declareHiddenLocal("%timer.clear", DYN); const ref = varRef(stored.id, DYN, loc); handle = { diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index a5f376fc0..e2236f592 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -1349,7 +1349,7 @@ export function uncheckedOverloadHandleCall( * `never` at the ROOT stays out (`for (const v of [])`'s loop var — the * dead read the f64 mapping is FOR). */ export function neverTaintedJsType(lowerer: Lowerer, node: ts.Node, t: ts.Type): boolean { - if (!isJsSourceFile(node.getSourceFile())) return false; + if (!isJsSourceFile(ts.getSourceFileOfNode(node))) return false; const walk = (x: ts.Type, depth: number): boolean => { if (depth === 0) return false; if (x.isUnionType()) return ts.constituentTypes(x).some((a) => walk(a, depth - 1)); @@ -1379,7 +1379,7 @@ export function ladderFenceExpr( const loc = locOf(node); const d = noLoweringDiag(surface, loc, hint); lowerer.runtimeFences.push(d); - const sf = node.getSourceFile(); + const sf = ts.getSourceFileOfNode(node); const pos = ts.getLineAndCharacterOfPosition(sf, loc.start); return { kind: "strLit", @@ -1391,7 +1391,7 @@ export function ladderFenceExpr( export function dynFallbackType(lowerer: Lowerer, node: ts.Node, t: ts.Type): IrType | null { if (t.flags & ts.TypeFlags.Void) return null; - if (!isJsSourceFile(node.getSourceFile())) { + if (!isJsSourceFile(ts.getSourceFileOfNode(node))) { if (t.flags & ts.TypeFlags.Any) return DYN; const symbol = t.getSymbol(); const args = @@ -1406,16 +1406,17 @@ export function dynFallbackType(lowerer: Lowerer, node: ts.Node, t: ts.Type): Ir ts.TypeFlags.Any) !== 0; if ( - symbol?.name === "Promise" && + symbol !== undefined && + ts.symbolName(symbol) === "Promise" && args[0] && (args[0].flags & ts.TypeFlags.Any || anyArray(args[0])) ) - return symbol.name === "Promise" && anyArray(args[0]) + return ts.symbolName(symbol) === "Promise" && anyArray(args[0]) ? { kind: "promise", inner: DYN } : jsFallbackPromiseType(lowerer, t); if ( symbol && - ["Generator", "AsyncGenerator", "IterableIterator"].includes(symbol.name) && + ["Generator", "AsyncGenerator", "IterableIterator"].includes(ts.symbolName(symbol)) && args.some((arg) => arg.flags & ts.TypeFlags.Any) ) return jsFallbackGeneratorType(lowerer, t); @@ -1437,7 +1438,8 @@ export function dynFallbackType(lowerer: Lowerer, node: ts.Node, t: ts.Type): Ir if (generator) return generator; const symbol = t.getSymbol(); if ( - (symbol?.name === "Map" || symbol?.name === "ReadonlyMap") && + ((symbol !== undefined && ts.symbolName(symbol) === "Map") || + (symbol !== undefined && ts.symbolName(symbol) === "ReadonlyMap")) && lowerer.isStdlibSymbol(symbol) ) { const args = lowerer.checker.getTypeArguments(t as ts.TypeReference); @@ -1526,7 +1528,12 @@ export function dynFallbackType(lowerer: Lowerer, node: ts.Node, t: ts.Type): Ir * has no fixed shape. The settled value crosses the checked native boundary. */ function jsFallbackPromiseType(lowerer: Lowerer, t: ts.Type): IrType | null { const symbol = t.getSymbol(); - if (symbol?.name !== "Promise" || !lowerer.isStdlibSymbol(symbol)) return null; + if ( + symbol === undefined || + ts.symbolName(symbol) !== "Promise" || + !lowerer.isStdlibSymbol(symbol) + ) + return null; const inner = lowerer.checker.getTypeArguments(t as ts.TypeReference)[0]; return inner ? { kind: "promise", inner: lowerer.mapTypeOf(inner) ?? DYN } : null; } @@ -1537,7 +1544,7 @@ function jsFallbackGeneratorType(lowerer: Lowerer, t: ts.Type): IrType | null { const symbol = t.getSymbol(); if ( !symbol || - !["Generator", "AsyncGenerator", "IterableIterator"].includes(symbol.name) || + !["Generator", "AsyncGenerator", "IterableIterator"].includes(ts.symbolName(symbol)) || !lowerer.isStdlibSymbol(symbol) ) return null; @@ -1545,7 +1552,7 @@ function jsFallbackGeneratorType(lowerer: Lowerer, t: ts.Type): IrType | null { const unit = ts.TypeFlags.Void | ts.TypeFlags.Undefined | ts.TypeFlags.Never; return { kind: "generator", - ...(symbol.name === "AsyncGenerator" ? { async: true as const } : {}), + ...(ts.symbolName(symbol) === "AsyncGenerator" ? { async: true as const } : {}), yieldT: yielded && yielded.flags & ts.TypeFlags.Never ? VOID : DYN, retT: !returned || returned.flags & unit ? VOID : DYN, nextT: !resumed || resumed.flags & unit ? UNDEFINED_T : DYN, @@ -1579,7 +1586,7 @@ function jsFallbackFunctionType(lowerer: Lowerer, node: ts.Node, t: ts.Type): Ir const sigDecl = sig.declaration; const jsUnitReturn = sigDecl !== undefined && - isJsSourceFile(sigDecl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(sigDecl)) && (retT.flags & (ts.TypeFlags.Null | ts.TypeFlags.Undefined)) !== 0; const ret: IrType = jsUnitReturn ? DYN @@ -1613,7 +1620,7 @@ function jsArgumentsFunctionType(lowerer: Lowerer, t: ts.Type): IrType | null { ts.isFunctionExpression(decl) || ts.isArrowFunction(decl) ) || - !isJsSourceFile(decl.getSourceFile()) + !isJsSourceFile(ts.getSourceFileOfNode(decl)) ) return null; const hasRest = decl.parameters.nodes.some((p) => p.dotDotDotToken !== undefined); @@ -1621,7 +1628,8 @@ function jsArgumentsFunctionType(lowerer: Lowerer, t: ts.Type): IrType | null { !hasRest && (ts.isArrowFunction(decl) || !bodyReadsArguments(decl) || - (decl.parameters.nodes.length > 0 && !isNodeEsmFile(decl.getSourceFile(), lowerer.program))) + (decl.parameters.nodes.length > 0 && + !isNodeEsmFile(ts.getSourceFileOfNode(decl), lowerer.program))) ) return null; const shapes = paramShapes(lowerer, decl.parameters.nodes); @@ -2265,7 +2273,7 @@ export class Lowerer { return ( sym !== undefined && sym.declarations.some((d) => { - if (!isJsSourceFile(d.getSourceFile())) return false; + if (!isJsSourceFile(ts.getSourceFileOfNode(d))) return false; // The pattern itself, or the `= {}` default of a pattern parameter. const pattern = ts.isObjectLiteralExpression(d) && @@ -2381,7 +2389,7 @@ export class Lowerer { // A checked JavaScript indexed read can already be boxed. The checker // calls the element present, but that inference is not a runtime guard. if ( - isJsSourceFile(node.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(node)) && value.kind === "dynCheck" && value.value.type.kind === "dyn" ) @@ -3305,7 +3313,8 @@ export class Lowerer { if (this.localClassInstantiations.some((entry) => entry.owner === parent)) break; if ( parent.typeParameters?.nodes.length || - (implicitMonoFile(parent.getSourceFile()) && monomorphizedImplicit(this, parent)) + (implicitMonoFile(ts.getSourceFileOfNode(parent)) && + monomorphizedImplicit(this, parent)) ) return null; } @@ -3331,15 +3340,15 @@ export class Lowerer { // lowering, long after the constructor completes. isProgramFile: (sf) => this.fileTag.has(sf), lifetimeRegionClass: (decl) => - isExperimentalDeclarationFile(decl.getSourceFile(), this.experimentalFiles) + isExperimentalDeclarationFile(ts.getSourceFileOfNode(decl), this.experimentalFiles) ? lifetimeRegionType(this) : null, internTableClass: (decl) => - isExperimentalDeclarationFile(decl.getSourceFile(), this.experimentalFiles) + isExperimentalDeclarationFile(ts.getSourceFileOfNode(decl), this.experimentalFiles) ? internTableType(this) : null, threadSlotsClass: (decl) => - isExperimentalDeclarationFile(decl.getSourceFile(), this.experimentalFiles) + isExperimentalDeclarationFile(ts.getSourceFileOfNode(decl), this.experimentalFiles) ? threadSlotsType(this) : null, moduleNamespaceId: (type) => this.moduleNamespaceIdOfType(type), @@ -3405,9 +3414,10 @@ export class Lowerer { const weak = (part: ts.Type): boolean => { const symbol = part.getSymbol(); return ( - (symbol?.name === "WeakMap" || symbol?.name === "WeakSet") && + ((symbol !== undefined && ts.symbolName(symbol) === "WeakMap") || + (symbol !== undefined && ts.symbolName(symbol) === "WeakSet")) && symbol.declarations.some( - (d) => ts.isInterfaceDeclaration(d) && this.isStdlibFile(d.getSourceFile()), + (d) => ts.isInterfaceDeclaration(d) && this.isStdlibFile(ts.getSourceFileOfNode(d)), ) ); }; @@ -3516,9 +3526,12 @@ export class Lowerer { } return false; })() - ? this.qualify(decl.getSourceFile(), `%cx${decl.getStart()}.${decl.name?.text ?? ""}`) + ? this.qualify( + ts.getSourceFileOfNode(decl), + `%cx${ts.tokenStartOfNode(decl)}.${decl.name?.text ?? ""}`, + ) : this.qualify( - decl.getSourceFile(), + ts.getSourceFileOfNode(decl), nsPathPrefix(decl) + (decl.name ? decl.name.text : "%anon"), ); for (let index = this.localClassInstantiations.length - 1; index >= 0; index--) { @@ -3762,7 +3775,10 @@ export class Lowerer { return null; if (this.externalTypes.has(importDecl.moduleSpecifier.text)) return importDecl.moduleSpecifier.text; - const dep = this.moduleSourceFileOf(importDecl.getSourceFile(), importDecl.moduleSpecifier); + const dep = this.moduleSourceFileOf( + ts.getSourceFileOfNode(importDecl), + importDecl.moduleSpecifier, + ); return dep !== null && !dep.isDeclarationFile ? this.externalTypeSpecifierOfModuleExport(dep, member, new Set(), new Set()) : null; @@ -3810,7 +3826,7 @@ export class Lowerer { // A namespace OBJECT from a project module is not wholly external; // property accesses resolve their selected member separately above. if (importedName === null) return null; - const dep = this.moduleSourceFileOf(decl.getSourceFile(), specNode); + const dep = this.moduleSourceFileOf(ts.getSourceFileOfNode(decl), specNode); return dep !== null && !dep.isDeclarationFile ? this.externalTypeSpecifierOfModuleExport(dep, importedName, seenSymbols, seenExports) : null; @@ -3818,7 +3834,7 @@ export class Lowerer { for (const decl of declarations) { const owners = this.externalTypeSpecifiersByFile.get( - tsgoPath(resolve(decl.getSourceFile().fileName)), + tsgoPath(resolve(ts.getSourceFileOfNode(decl).fileName)), ); if (owners !== undefined && owners.length > 0) return owners[0]!; } @@ -3863,7 +3879,7 @@ export class Lowerer { } if (d === undefined || spec === undefined || !ts.isStringLiteral(spec) || name !== "default") return null; - const dep = resolveImport(this.program, d.getSourceFile(), spec.text); + const dep = resolveImport(this.program, ts.getSourceFileOfNode(d), spec.text); if (!dep) return null; sym = defaultExportSymbolOf(this, dep) ?? undefined; } @@ -3979,8 +3995,8 @@ export class Lowerer { // Project aliases/self-references resolve through the same entry point as // relative imports; an opted-in --npm-static package is the fallback. const dep = - resolveImport(this.program, importDecl.getSourceFile(), spec) ?? - npmStaticDepSf7(this.program, importDecl.getSourceFile(), spec); + resolveImport(this.program, ts.getSourceFileOfNode(importDecl), spec) ?? + npmStaticDepSf7(this.program, ts.getSourceFileOfNode(importDecl), spec); if (!dep || !isJsSourceFile(dep) || isNodeEsmFile(dep, this.program)) return null; return dep; } @@ -4020,8 +4036,8 @@ export class Lowerer { if (directSpec !== null) { if (isJsonRequireDecl(this, decl)) return null; return ( - resolveImport(this.program, decl.getSourceFile(), directSpec, "require") ?? - npmStaticDepSf7(this.program, decl.getSourceFile(), directSpec, "require") + resolveImport(this.program, ts.getSourceFileOfNode(decl), directSpec, "require") ?? + npmStaticDepSf7(this.program, ts.getSourceFileOfNode(decl), directSpec, "require") ); } return createRequireProgramModuleOf(this, decl.initializer)?.dep ?? null; @@ -4361,7 +4377,7 @@ export class Lowerer { * report on the next tick. Later loads are cache hits either way. */ punycodeRequireWarning(node: ts.Node): IrStmt { const loc = locOf(node); - const fromDependency = /[\\/]node_modules[\\/]/.test(node.getSourceFile().fileName); + const fromDependency = /[\\/]node_modules[\\/]/.test(ts.getSourceFileOfNode(node).fileName); return { kind: "exprStmt", expr: { @@ -4432,8 +4448,8 @@ export class Lowerer { // those globals stay uninitialized: the dep's module body would never // run. const dep = - resolveImport(this.program, node.getSourceFile(), spec, "require") ?? - npmStaticDepSf7(this.program, node.getSourceFile(), spec, "require"); + resolveImport(this.program, ts.getSourceFileOfNode(node), spec, "require") ?? + npmStaticDepSf7(this.program, ts.getSourceFileOfNode(node), spec, "require"); if (!dep || dep.fileName.endsWith(".json")) return null; if (this.asyncInitFiles.has(dep)) { this.unsupported( @@ -4450,7 +4466,7 @@ export class Lowerer { expr: { kind: "call", callee: initName, args: [], type: VOID, loc }, loc, }; - const parentModule = this.cjsModuleIdOf.get(node.getSourceFile()); + const parentModule = this.cjsModuleIdOf.get(ts.getSourceFileOfNode(node)); const childModule = this.cjsModuleIdOf.get(dep); if (parentModule === undefined || childModule === undefined) return init; return { @@ -5211,7 +5227,7 @@ export class Lowerer { const decl = symbol.valueDeclaration; if (!decl || !ts.isPropertyDeclaration(decl) || !ts.isIdentifier(decl.name)) return false; if (ts.getCombinedModifierFlags(decl) & ts.ModifierFlags.Static) return false; - const sf = decl.getSourceFile(); + const sf = ts.getSourceFileOfNode(decl); if (isJsSourceFile(sf) || sf.isDeclarationFile) return false; const declared = typeOrNull(decl.name); const mapped = declared ? this.mapTypeOf(declared) : null; @@ -5301,7 +5317,8 @@ export class Lowerer { const initSymbol = initType.getAliasSymbol() ?? initType.getSymbol(); const matchResult = initSymbol !== undefined && - (initSymbol.name === "RegExpExecArray" || initSymbol.name === "RegExpMatchArray"); + (ts.symbolName(initSymbol) === "RegExpExecArray" || + ts.symbolName(initSymbol) === "RegExpMatchArray"); if (arrayType?.kind !== "array" || matchResult) return scanRecordPattern(name, init); const positions = name.elements.nodes.filter((el) => { const key = el.propertyName; @@ -5477,7 +5494,7 @@ export class Lowerer { }; for (const [symbol, signature] of signatureBySymbol) { const declaration = functionDeclBySymbol.get(symbol); - if (!declaration || !isJsSourceFile(declaration.getSourceFile())) continue; + if (!declaration || !isJsSourceFile(ts.getSourceFileOfNode(declaration))) continue; const checked = this.checker.getSignatureFromDeclaration(declaration); const checkedReturn = checked ? this.checker.getReturnTypeOfSignature(checked) : null; // Checker-known optional results already narrow at each use. Only @@ -6319,7 +6336,7 @@ export class Lowerer { const callee = functionDeclBySymbol.get(symbol); if ( callee && - isJsSourceFile(callee.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(callee)) && !node.arguments.nodes.some(ts.isSpreadElement) ) { for (let i = 0; i < sig.params.length; i++) { @@ -6535,7 +6552,7 @@ export class Lowerer { for (const [symbol, decl] of functionDeclBySymbol) if (scanReturns(symbol, decl)) changed = true; const noteGenericReturn = (symbol: ts.Symbol, decl: ts.FunctionLikeDeclaration): void => { - if (optionalReturns.has(symbol) || isJsSourceFile(decl.getSourceFile())) return; + if (optionalReturns.has(symbol) || isJsSourceFile(ts.getSourceFileOfNode(decl))) return; if (!returnsOf(decl).some(genericMayBeOptional)) return; optionalReturns.add(symbol); this.runtimeOptionalGenericReturns.add(decl); @@ -6812,7 +6829,7 @@ export class Lowerer { if ( global.type.kind !== "func" || !ts.isBinaryExpression(declaration) || - !isJsSourceFile(declaration.getSourceFile()) + !isJsSourceFile(ts.getSourceFileOfNode(declaration)) ) continue; const value = peel(declaration.right); @@ -7288,7 +7305,7 @@ export class Lowerer { if (n === 0) return; this.stats.statementsTotal += n; this.stats.statementsFailed += n; - const file = decl.getSourceFile().fileName; + const file = ts.getSourceFileOfNode(decl).fileName; for (let i = 0; i < n; i++) { this.bumpFileStat(file, "total"); this.bumpFileStat(file, "failed"); @@ -7907,8 +7924,8 @@ export class Lowerer { "this statement uses a construct with no static lowering"; const messageLoc = first?.loc ?? loc; const source = first - ? (this.program.getSourceFile(messageLoc.file) ?? node.getSourceFile()) - : node.getSourceFile(); + ? (this.program.getSourceFile(messageLoc.file) ?? ts.getSourceFileOfNode(node)) + : ts.getSourceFileOfNode(node); const pos = ts.getLineAndCharacterOfPosition(source, messageLoc.start); return { kind: "runtimeFence", @@ -8027,10 +8044,12 @@ export class Lowerer { // here; constructor objects such as Headers still can. if ( typeSym && - (FETCH_AMBIENT_TYPES as readonly string[]).includes(typeSym.name) && + (FETCH_AMBIENT_TYPES as readonly string[]).includes(ts.symbolName(typeSym)) && this.isStdlibSymbol(typeSym) ) { - this.pushDiag(unsupportedNativeApiDiag(`a value of type '${typeSym.name}'`, locOf(node))); + this.pushDiag( + unsupportedNativeApiDiag(`a value of type '${ts.symbolName(typeSym)}'`, locOf(node)), + ); throw new PoisonError(); } // ENUM OBJECTS (`typeof e` — a numeric enum's type carries the @@ -8044,7 +8063,7 @@ export class Lowerer { unsupportedDiag( "SC1090", locOf(node), - `enum objects as values ('${typeSym.name}' — member reads like '${typeSym.name}.X' compile to constants; the object itself has no runtime representation)`, + `enum objects as values ('${ts.symbolName(typeSym)}' — member reads like '${ts.symbolName(typeSym)}.X' compile to constants; the object itself has no runtime representation)`, ), ); throw new PoisonError(); @@ -8074,7 +8093,7 @@ export class Lowerer { ownSym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - this.isStdlibFile(d.getSourceFile()), + this.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { this.pushDiag(noLoweringDiag(this.checker.typeToString(type), locOf(node))); @@ -8120,7 +8139,7 @@ export class Lowerer { stdlibOwnSym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - this.isStdlibFile(d.getSourceFile()), + this.isStdlibFile(ts.getSourceFileOfNode(d)), ); // OVERLOADED call signatures (SC2007) — after the generic branch, so a // generic overload set keeps the monomorphization story: a value of a @@ -8182,7 +8201,7 @@ export class Lowerer { noLoweringDiag( this.checker.typeToString(type), locOf(node), - typeHints[stdlibOwnSym?.name ?? ""], + typeHints[ts.symbolNameOf(stdlibOwnSym) ?? ""], ), ); throw new PoisonError(); @@ -8259,7 +8278,7 @@ export class Lowerer { } typeOf(node: ts.Node): ts.Type { - if (ts.isPropertyAccessExpression(node) && isJsSourceFile(node.getSourceFile())) { + if (ts.isPropertyAccessExpression(node) && isJsSourceFile(ts.getSourceFileOfNode(node))) { const receiver = node.expression.kind === ts.SyntaxKind.ThisKeyword && this.fnStack.length > 0 ? this.resolveThis()?.type @@ -8280,7 +8299,7 @@ export class Lowerer { } // A symbol-keyed field read through a CommonJS key-table key reads as // the field's layout type (tableKeyFieldType). - if (ts.isElementAccessExpression(node) && isJsSourceFile(node.getSourceFile())) { + if (ts.isElementAccessExpression(node) && isJsSourceFile(ts.getSourceFileOfNode(node))) { const field = tableKeyFieldType(this, node); if (field) { if (field.kind === "f64") return this.checker.getNumberType(); @@ -8294,7 +8313,7 @@ export class Lowerer { // into native field loads when the method is borrowed by another object. if ( node.kind === ts.SyntaxKind.ThisKeyword && - isJsSourceFile(node.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(node)) && this.fnStack.length > 0 && this.resolveThis()?.type.kind === "dyn" ) @@ -8568,7 +8587,7 @@ export class Lowerer { // locals at null/undefined despite later writes. Keep mutable native // storage instead of a slot restricted to the initializer's unit value. if ( - isJsSourceFile(node.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(node)) && (t.flags & (ts.TypeFlags.Null | ts.TypeFlags.Undefined)) !== 0 ) return DYN; @@ -9599,7 +9618,9 @@ export class Lowerer { : []; const errorOptions = parts.some((part) => { const sym = part.getAliasSymbol() ?? part.getSymbol(); - return sym?.name === "ErrorOptions" && this.isStdlibSymbol(sym); + return ( + sym !== undefined && ts.symbolName(sym) === "ErrorOptions" && this.isStdlibSymbol(sym) + ); }); if (errorOptions) { const actual = this.typeOf(node); @@ -9635,7 +9656,7 @@ export class Lowerer { let literal = node; while (ts.isParenthesizedExpression(literal)) literal = literal.expression; if ( - isJsSourceFile(node.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(node)) && !ts.isArrayLiteralExpression(literal) && e.kind === "dynFrom" && e.value.type.kind === "array" @@ -9667,7 +9688,7 @@ export class Lowerer { if ( e.type.kind === "union" && !typeEquals(e.type, expected) && - isJsSourceFile(node.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(node)) && this.dynConvertible(e.type) ) { const sourceArms = this.unions.get(e.type.unionId)?.arms ?? []; @@ -9722,7 +9743,7 @@ export class Lowerer { !ts.isIdentifier(d.name) || !ts.isVariableDeclarationList(d.parent) || !ts.isVariableStatement(d.parent.parent) || - d.getSourceFile().isDeclarationFile || + ts.getSourceFileOfNode(d).isDeclarationFile || (ts.getCombinedModifierFlags(d) & ts.ModifierFlags.Ambient) !== 0 ) { return null; @@ -9941,11 +9962,11 @@ export class Lowerer { const builtinOption = (name: string): boolean => parts.some((part) => { const sym = part.getAliasSymbol() ?? part.getSymbol(); - return sym?.name === name && this.isStdlibSymbol(sym); + return ts.symbolNameOf(sym) === name && this.isStdlibSymbol(sym); }); if (ts.isObjectLiteralExpression(x)) { if ( - isJsSourceFile(x.getSourceFile()) || + isJsSourceFile(ts.getSourceFileOfNode(x)) || builtinOption("RequestInit") || builtinOption("ErrorOptions") || builtinOption("SpawnOptions") || @@ -10028,7 +10049,7 @@ export class Lowerer { coerceExistingInto(node: ts.Expression, e: IrExpr, expected: IrType): IrExpr { if ( (expected.kind === "union" && this.armTag(expected.unionId, UNDEFINED_T) >= 0) || - (expected.kind === "dyn" && isJsSourceFile(node.getSourceFile())) + (expected.kind === "dyn" && isJsSourceFile(ts.getSourceFileOfNode(node))) ) { // A destination that accepts undefined must retain an unchecked // read's storage value even when the checker still calls it present. @@ -10051,7 +10072,8 @@ export class Lowerer { return null; const symbol = this.resolveValueSymbol(value.expression); if (!symbol || !this.isStdlibSymbol(symbol)) return null; - const kind = symbol.name === "Map" ? "map" : symbol.name === "Set" ? "set" : null; + const kind = + ts.symbolName(symbol) === "Map" ? "map" : ts.symbolName(symbol) === "Set" ? "set" : null; if (!kind) return null; const arms = expected.kind === "union" ? (this.unions.get(expected.unionId)?.arms ?? []) : [expected]; @@ -10176,7 +10198,7 @@ export class Lowerer { while (ts.isParenthesizedExpression(literal)) literal = literal.expression; return ( expected.kind === "dyn" && - isJsSourceFile(node.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(node)) && ts.isObjectLiteralExpression(literal) ); } @@ -10642,7 +10664,7 @@ export class Lowerer { } } } - if (!isJsSourceFile(decl.getSourceFile())) continue; + if (!isJsSourceFile(ts.getSourceFileOfNode(decl))) continue; for (const member of decl.members.nodes) { if (ts.isConstructorDeclaration(member)) { for (const param of member.parameters.nodes) { @@ -10675,7 +10697,7 @@ export class Lowerer { } // npm-static implicit-any classification resolves body parameter // references while collecting the method's shape. - if (implicitMonoFile(decl.getSourceFile()) && member.body) { + if (implicitMonoFile(ts.getSourceFileOfNode(decl)) && member.body) { symbolRoots.push(member.body); } } @@ -12874,14 +12896,14 @@ export class Lowerer { // node:stream/web's exports are the web stream globals themselves: in // JavaScript sources the same identity token as the global spelling // (`require("stream/web").ReadableStream === globalThis.ReadableStream`). - if (bi.module === "stream/web" && isJsSourceFile(expr.getSourceFile())) { + if (bi.module === "stream/web" && isJsSourceFile(ts.getSourceFileOfNode(expr))) { return { kind: "strLit", value: `[builtin ${bi.member}]`, type: STRING, loc }; } // A bare `fs.constants` read (not one of the baked bits above). if (bi.member === "constants" && bi.module === "fs") return fsConstantsObject(loc); // `stream.Readable` as a JavaScript value: the runtime's untyped // constructor, the base a constructor function inherits. - if (isJsSourceFile(expr.getSourceFile())) { + if (isJsSourceFile(ts.getSourceFileOfNode(expr))) { const classKind = builtinMemberClassKind(bi.module, bi.member); if (classKind !== null) return builtinClassValue(classKind, loc); } @@ -12889,7 +12911,7 @@ export class Lowerer { this.unsupported( "SC1090", expr, - `library functions as values (call '${expr.getText()}' directly)`, + `library functions as values (call '${ts.getTextOfNode(expr)}' directly)`, ); } this.noLowering( diff --git a/packages/compiler/src/frontend/lowering/module-member-this.ts b/packages/compiler/src/frontend/lowering/module-member-this.ts index 592136bec..8d1592b47 100644 --- a/packages/compiler/src/frontend/lowering/module-member-this.ts +++ b/packages/compiler/src/frontend/lowering/module-member-this.ts @@ -70,9 +70,9 @@ function ignoresOwnThis(lowerer: Lowerer, fn: ts.Node, bindings: Bindings, depth for (const arg of node.arguments.nodes.slice(1)) visit(arg); return; } - node.forEachChild(visit); + ts.forEachChild(node, visit); }; - fn.forEachChild(visit); + ts.forEachChild(fn, visit); return ignores; } @@ -176,7 +176,7 @@ function mayRebind(lowerer: Lowerer, fn: ts.Node, param: ts.Identifier): boolean found = true; return; } - node.forEachChild(visit); + ts.forEachChild(node, visit); }; // The function's own parameters declare the name; their defaults can write it. const params = (fn as { parameters?: ts.NodeArray }).parameters; @@ -195,9 +195,9 @@ function returnedExpressions(fn: ts.Node): ts.Expression[] | null { const visit = (node: ts.Node): void => { if (ts.isFunctionLike(node) || ts.isClassLikeDeclaration(node)) return; if (ts.isReturnStatement(node) && node.expression) result.push(node.expression); - node.forEachChild(visit); + ts.forEachChild(node, visit); }; - body.forEachChild(visit); + ts.forEachChild(body, visit); return result; } diff --git a/packages/compiler/src/frontend/lowering/nan-coded-reads.ts b/packages/compiler/src/frontend/lowering/nan-coded-reads.ts index 4270457d5..65efe5b2a 100644 --- a/packages/compiler/src/frontend/lowering/nan-coded-reads.ts +++ b/packages/compiler/src/frontend/lowering/nan-coded-reads.ts @@ -365,20 +365,22 @@ export class NanCodedReads { private removalReason(slot: Slot): string { const badSource = slot.sources.find((s) => s === null || this.sourceInfo(s) === null); if (badSource !== undefined) - return `source ${badSource ? badSource.getText().slice(0, 60) : ""}`; + return `source ${badSource ? ts.getTextOfNode(badSource).slice(0, 60) : ""}`; const nan = this.genuineNaN.has(slot); const badUse = slot.uses.find((u) => !this.useSafe(u) && (nan || !this.reboxConsumer(u))); - if (badUse) - return `${nan ? "nan-use" : "use"} ${transparentParent(badUse).parent?.getText().slice(0, 80) ?? "?"}`; + if (badUse) { + const parent = transparentParent(badUse).parent; + return `${nan ? "nan-use" : "use"} ${parent === undefined ? "?" : ts.getTextOfNode(parent).slice(0, 80)}`; + } return "?"; } private debugReport(reached: Set): void { const name = (slot: Slot): string => { const decl = this.host.declarationOf(slot.symbol); - const sf = decl?.getSourceFile(); - const where = sf && decl ? `${sf.fileName}:${decl.getStart()}` : "?"; - return `${slot.kind} ${slot.symbol.name} @ ${where}`; + const sf = decl === undefined ? undefined : ts.getSourceFileOfNode(decl); + const where = sf && decl ? `${sf.fileName}:${ts.tokenStartOfNode(decl)}` : "?"; + return `${slot.kind} ${ts.symbolName(slot.symbol)} @ ${where}`; }; for (const slot of reached) { const ok = this.eligible.has(slot); @@ -483,7 +485,11 @@ export class NanCodedReads { return true; } const decl = this.host.declarationOf(symbol); - if (!decl || isJsSourceFile(decl.getSourceFile()) || decl.getSourceFile().isDeclarationFile) + if ( + !decl || + isJsSourceFile(ts.getSourceFileOfNode(decl)) || + ts.getSourceFileOfNode(decl).isDeclarationFile + ) return false; let name: ts.Identifier; if (ts.isParameter(decl)) { @@ -565,7 +571,7 @@ export class NanCodedReads { !nonNegativeIntegerLiteral(decl.initializer) || !ts.isVariableDeclarationList(decl.parent) || !(decl.parent.flags & (ts.NodeFlags.Let | ts.NodeFlags.Const)) || - isJsSourceFile(decl.getSourceFile()) + isJsSourceFile(ts.getSourceFileOfNode(decl)) ) return false; const name = decl.name; @@ -685,7 +691,7 @@ export class NanCodedReads { !(decl.parent.flags & ts.NodeFlags.Const) || // A module binding can escape through an export. (ts.isVariableStatement(decl.parent.parent) && ts.isSourceFile(decl.parent.parent.parent)) || - isJsSourceFile(decl.getSourceFile()) || + isJsSourceFile(ts.getSourceFileOfNode(decl)) || this.host.bytesElemOf(decl.name) === null ) return null; @@ -755,7 +761,7 @@ export class NanCodedReads { if (!decl || (!ts.isFunctionDeclaration(decl) && !ts.isMethodDeclaration(decl))) return null; if (!decl.body || decl.asteriskToken || decl.typeParameters?.nodes || !decl.name) return null; if (!ts.isIdentifier(decl.name)) return null; - const sf = decl.getSourceFile(); + const sf = ts.getSourceFileOfNode(decl); if (isJsSourceFile(sf) || sf.isDeclarationFile) return null; const flags = ts.getCombinedModifierFlags(decl); if (flags & (ts.ModifierFlags.Async | ts.ModifierFlags.Abstract | ts.ModifierFlags.Ambient)) @@ -831,7 +837,7 @@ export class NanCodedReads { private scanBindingSlot(symbol: ts.Symbol): BindingSlot | null { const decl = this.host.declarationOf(symbol); if (!decl) return null; - const sf = decl.getSourceFile(); + const sf = ts.getSourceFileOfNode(decl); if (isJsSourceFile(sf) || sf.isDeclarationFile) return null; let nameNode: ts.Identifier; const slot: BindingSlot = { @@ -1362,7 +1368,7 @@ export class NanCodedReads { decl.initializer && ts.isVariableDeclarationList(decl.parent) && decl.parent.flags & ts.NodeFlags.Const && - !isJsSourceFile(decl.getSourceFile()) + !isJsSourceFile(ts.getSourceFileOfNode(decl)) ) return this.plainNumber(decl.initializer, depth + 1); return null; diff --git a/packages/compiler/src/frontend/lowering/narrowing-stability.ts b/packages/compiler/src/frontend/lowering/narrowing-stability.ts index 01801470d..1c991c6ea 100644 --- a/packages/compiler/src/frontend/lowering/narrowing-stability.ts +++ b/packages/compiler/src/frontend/lowering/narrowing-stability.ts @@ -191,7 +191,8 @@ export class NarrowingStability { const checker = this.lowerer.checker; const signature = checker.getResolvedSignature(call); const declaration = signature ? signature.declaration : undefined; - if (!declaration || !this.lowerer.isStdlibFile(declaration.getSourceFile())) return true; + if (!declaration || !this.lowerer.isStdlibFile(ts.getSourceFileOfNode(declaration))) + return true; const callee = skipOuter(call.expression); if ( ts.isPropertyAccessExpression(callee) && @@ -287,11 +288,11 @@ export class NarrowingStability { const last = chain.links[chain.links.length - 1]; const refKey = last ? `.${last.name}` : (chain.root as ts.Identifier).text; const candidates = facts.refs.get(refKey) ?? []; - const useStart = node.getStart(); + const useStart = ts.tokenStartOfNode(node); let start = -1; for (const candidate of candidates) { if (candidate === node) continue; - const candidateStart = candidate.getStart(); + const candidateStart = ts.tokenStartOfNode(candidate); if (candidateStart >= useStart) break; if (referenceKey(candidate) !== chain.key) continue; start = candidateStart; @@ -313,7 +314,7 @@ export class NarrowingStability { // between. let end = useStart; for (const loop of facts.loops) { - const loopStart = loop.getStart(); + const loopStart = ts.tokenStartOfNode(loop); if (loopStart >= start && loopStart <= useStart && loop.end >= node.end) end = Math.max(end, loop.end); } @@ -376,7 +377,9 @@ export class NarrowingStability { if (decl !== undefined && ts.isVariableDeclaration(decl)) { if (decl.initializer !== undefined && this.mayBeStale(decl.initializer)) result = true; else { - for (const write of this.writesIn(decl.getSourceFile()).get(symbol.name) ?? []) { + for (const write of this.writesIn(ts.getSourceFileOfNode(decl)).get( + ts.symbolName(symbol), + ) ?? []) { const assignment = write.parent; if ( ts.isBinaryExpression(assignment) && @@ -430,7 +433,7 @@ export class NarrowingStability { ts.isParameter(decl)); if (decl && mutableBinding) { const home = flowBoundary(decl); - const writes = this.writesIn(decl.getSourceFile()).get(symbol.name) ?? []; + const writes = this.writesIn(ts.getSourceFileOfNode(decl)).get(ts.symbolName(symbol)) ?? []; for (const write of writes) { if (flowBoundary(write) === home) continue; if (this.lowerer.resolveValueSymbol(write) === symbol) { @@ -497,7 +500,7 @@ export class NarrowingStability { if (name === "toString" || name === "valueOf" || name === "toJSON") facts.conversion = true; if (ts.isComputedPropertyName(node.name)) { - const text = node.name.expression.getText(); + const text = ts.getTextOfNode(node.name.expression); if (text.includes("toPrimitive")) facts.conversion = true; if (text.includes("iterator") || text.includes("asyncIterator")) facts.iteration = true; } @@ -711,7 +714,7 @@ export class NarrowingStability { const symbol = type.getSymbol(); return ( symbol !== undefined && - BUILTIN_COLLECTIONS.has(symbol.name) && + BUILTIN_COLLECTIONS.has(ts.symbolName(symbol)) && this.lowerer.isStdlibSymbol(symbol) ); } @@ -1062,7 +1065,7 @@ function memberUse( function flowBoundary(node: ts.Node): ts.Node { let n: ts.Node | undefined = node.parent; while (n && !isFlowBoundary(n)) n = n.parent; - return n ?? node.getSourceFile(); + return n ?? ts.getSourceFileOfNode(node); } function isFlowBoundary(node: ts.Node): boolean { diff --git a/packages/compiler/src/frontend/lowering/native-ffi.ts b/packages/compiler/src/frontend/lowering/native-ffi.ts index c9ff3300d..0f932ba93 100644 --- a/packages/compiler/src/frontend/lowering/native-ffi.ts +++ b/packages/compiler/src/frontend/lowering/native-ffi.ts @@ -393,7 +393,7 @@ export function lowerFfiCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr (decl) => ts.isFunctionDeclaration(decl) && decl.body === undefined && - libraryCallbackOwnsFile(lowerer, decl.getSourceFile()), + libraryCallbackOwnsFile(lowerer, ts.getSourceFileOfNode(decl)), ); if (callbackShaped) { lowerer.pushDiag( @@ -429,13 +429,13 @@ export function lowerFfiCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr if (lowerer.libraryCallbacks) { const declarations = symbol.declarations; const programDeclarations = declarations.filter((decl) => - libraryCallbackOwnsFile(lowerer, decl.getSourceFile()), + libraryCallbackOwnsFile(lowerer, ts.getSourceFileOfNode(decl)), ); const programAmbient = programDeclarations.length > 0 && programDeclarations.every( (decl) => - decl.getSourceFile().isDeclarationFile || + ts.getSourceFileOfNode(decl).isDeclarationFile || (ts.getCombinedModifierFlags(decl as ts.Declaration) & ts.ModifierFlags.Ambient) !== 0, ); diff --git a/packages/compiler/src/frontend/lowering/object-factory-new.ts b/packages/compiler/src/frontend/lowering/object-factory-new.ts index ddd8d6d56..bcb675692 100644 --- a/packages/compiler/src/frontend/lowering/object-factory-new.ts +++ b/packages/compiler/src/frontend/lowering/object-factory-new.ts @@ -41,7 +41,7 @@ function isObjectFactoryNew(lowerer: Lowerer, expr: ts.NewExpression): boolean { !owner || !declaration?.body || !bindingNeverReassigned(lowerer, symbol, owner) || - !isJsSourceFile(declaration.getSourceFile()) || + !isJsSourceFile(ts.getSourceFileOfNode(declaration)) || declaration.asteriskToken || declaration.modifiers?.nodes.some((m) => m.kind === ts.SyntaxKind.AsyncKeyword) ) diff --git a/packages/compiler/src/frontend/lowering/private-brands.ts b/packages/compiler/src/frontend/lowering/private-brands.ts index e7b3edbd8..8507cc81a 100644 --- a/packages/compiler/src/frontend/lowering/private-brands.ts +++ b/packages/compiler/src/frontend/lowering/private-brands.ts @@ -30,7 +30,7 @@ export function perEvaluationPrivateNames(info: ClassInfo): boolean { const declaration = info.localClass ? info.decl : null; return ( !!declaration && - isJsSourceFile(declaration.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(declaration)) && declaration.members.nodes.some( (member) => member.name !== undefined && ts.isPrivateIdentifier(member.name), ) diff --git a/packages/compiler/src/frontend/lowering/require-initialization.ts b/packages/compiler/src/frontend/lowering/require-initialization.ts index 2f84e988a..d979423b0 100644 --- a/packages/compiler/src/frontend/lowering/require-initialization.ts +++ b/packages/compiler/src/frontend/lowering/require-initialization.ts @@ -119,7 +119,7 @@ export function markRequireEarlyBindings(lowerer: Lowerer): void { const tag = rawTag === "" ? "e." : rawTag.replace(/^%/, ""); const bySymbol = new Map(); for (const { decl } of declared) { - const flagId = `%g.${tag}%required.${decl.getStart(sf)}`; + const flagId = `%g.${tag}%required.${ts.tokenStartOfNode(decl)}`; lowerer.globalsList.push({ id: flagId, name: "%initialized", type: BOOL, mutable: true }); const flag: RequireInitFlag = { flagId, names: new Map() }; for (const id of boundNames(decl.name)) { @@ -148,7 +148,7 @@ export function markRequireEarlyBindings(lowerer: Lowerer): void { if (owner === undefined) continue; if (boundNames(owner.decl.name).includes(id)) continue; // Code after the require runs after it, except inside functions. - if (id.getStart(sf) >= owner.decl.getEnd() && !inDeferredPosition(id)) continue; + if (ts.tokenStartOfNode(id) >= owner.decl.end && !inDeferredPosition(id)) continue; const body = enclosingBody(id); lowerer.requireEarlyReferences.push({ flag: owner.flag, diff --git a/packages/compiler/src/frontend/lowering/runtime-optional-bounds.ts b/packages/compiler/src/frontend/lowering/runtime-optional-bounds.ts index d3acaff6b..f332d0f51 100644 --- a/packages/compiler/src/frontend/lowering/runtime-optional-bounds.ts +++ b/packages/compiler/src/frontend/lowering/runtime-optional-bounds.ts @@ -287,7 +287,7 @@ function assertsSlot(node: ts.Node, operands: Operands): boolean { return; } if ((n.flags & ts.NodeFlags.OptionalChain) !== 0) return; - n.forEachChild(visit); + ts.forEachChild(n, visit); }; if (ts.isVariableStatement(node)) { for (const declaration of node.declarationList.declarations.nodes) @@ -323,7 +323,7 @@ function unchanged(nodes: readonly ts.Node[], { array, index }: Operands): boole let hit = false; const walk = (n: ts.Node): void => { if (ts.isIdentifier(n) && roots.includes(n.text)) hit = true; - n.forEachChild(walk); + ts.forEachChild(n, walk); }; walk(e); return hit; @@ -372,7 +372,7 @@ function unchanged(nodes: readonly ts.Node[], { array, index }: Operands): boole ok = false; return; } - n.forEachChild(visit); + ts.forEachChild(n, visit); }; for (const node of nodes) visit(node); return ok; diff --git a/packages/compiler/src/frontend/lowering/runtime-optional-elements.ts b/packages/compiler/src/frontend/lowering/runtime-optional-elements.ts index 2fa8785c9..5ed011ce1 100644 --- a/packages/compiler/src/frontend/lowering/runtime-optional-elements.ts +++ b/packages/compiler/src/frontend/lowering/runtime-optional-elements.ts @@ -611,7 +611,7 @@ export class ArrayElementStates { if (this.host.markIterationBinding(name, known)) changed = true; return; } - name.forEachChild(mark); + ts.forEachChild(name, mark); }; if (ts.isVariableDeclarationList(initializer)) { for (const declaration of initializer.declarations.nodes) mark(declaration.name); diff --git a/packages/compiler/src/frontend/lowering/runtime-optional-guards.ts b/packages/compiler/src/frontend/lowering/runtime-optional-guards.ts index 768932ad3..3e4605f94 100644 --- a/packages/compiler/src/frontend/lowering/runtime-optional-guards.ts +++ b/packages/compiler/src/frontend/lowering/runtime-optional-guards.ts @@ -21,7 +21,7 @@ export function reassignedBindings( else if (ts.isArrayLiteralExpression(e) || ts.isObjectLiteralExpression(e)) { const walk = (n: ts.Node): void => { if (ts.isIdentifier(n)) mark(n); - else n.forEachChild(walk); + else ts.forEachChild(n, walk); }; walk(e); } diff --git a/packages/compiler/src/frontend/lowering/runtime-optional-ownership.ts b/packages/compiler/src/frontend/lowering/runtime-optional-ownership.ts index adecfa5a9..dd1dce04d 100644 --- a/packages/compiler/src/frontend/lowering/runtime-optional-ownership.ts +++ b/packages/compiler/src/frontend/lowering/runtime-optional-ownership.ts @@ -378,9 +378,9 @@ function usesArguments(fn: ts.FunctionExpression): boolean { if (found) return; if (ts.isIdentifier(node) && node.text === "arguments") found = true; else if (!ts.isFunctionDeclaration(node) && !ts.isFunctionExpression(node)) - node.forEachChild(visit); + ts.forEachChild(node, visit); }; - fn.body?.forEachChild(visit); + if (fn.body !== undefined) ts.forEachChild(fn.body, visit); return found; } diff --git a/packages/compiler/src/frontend/lowering/runtime-optional-provenance.ts b/packages/compiler/src/frontend/lowering/runtime-optional-provenance.ts index f150e314b..3363f197d 100644 --- a/packages/compiler/src/frontend/lowering/runtime-optional-provenance.ts +++ b/packages/compiler/src/frontend/lowering/runtime-optional-provenance.ts @@ -33,8 +33,8 @@ export class RuntimeOptionalProvenance { function describe(node: ts.Node | undefined): string { if (!node) return "?"; - const sf = node.getSourceFile(); - const start = node.getStart(); + const sf = ts.getSourceFileOfNode(node); + const start = ts.tokenStartOfNode(node); const pos = ts.getLineAndCharacterOfPosition(sf, start); const text = sf.text.slice(start, node.end).replace(/\s+/g, " "); const short = text.length > 60 ? `${text.slice(0, 57)}...` : text; diff --git a/packages/compiler/src/frontend/lowering/runtime-optional-records.test.ts b/packages/compiler/src/frontend/lowering/runtime-optional-records.test.ts index f95694624..50b508a52 100644 --- a/packages/compiler/src/frontend/lowering/runtime-optional-records.test.ts +++ b/packages/compiler/src/frontend/lowering/runtime-optional-records.test.ts @@ -21,7 +21,7 @@ function classes(source: string): { const symbolFor = (name: string): ts.Symbol => { let symbol = symbols.get(name); if (!symbol) { - symbol = { name, declarations: [] } as unknown as ts.Symbol; + symbol = { escapedName: name, declarations: [] } as unknown as ts.Symbol; symbols.set(name, symbol); } return symbol; diff --git a/packages/compiler/src/frontend/lowering/runtime-optional-records.ts b/packages/compiler/src/frontend/lowering/runtime-optional-records.ts index bad2523c8..18e136d69 100644 --- a/packages/compiler/src/frontend/lowering/runtime-optional-records.ts +++ b/packages/compiler/src/frontend/lowering/runtime-optional-records.ts @@ -351,7 +351,7 @@ export class RecordFlowClasses { private referencesOf(symbol: ts.Symbol): readonly ts.Identifier[] { let list = this.references.get(symbol); if (!list) { - list = (this.names.get(symbol.name) ?? []).filter( + list = (this.names.get(ts.symbolName(symbol)) ?? []).filter( (node) => this.host.symbolOf(node) === symbol, ); this.references.set(symbol, list); @@ -412,7 +412,7 @@ export class RecordFlowClasses { const symbol = ts.isIdentifier(base) ? this.host.symbolOf(base) : null; const target = symbol ? this.host.declarationOf(symbol) : undefined; if (!target || !ts.isClassDeclaration(target)) return true; - const file = target.getSourceFile(); + const file = ts.getSourceFileOfNode(target); if (file.isDeclarationFile || isJsFile(file)) return true; current = target; } @@ -591,7 +591,7 @@ export class RecordFlowClasses { } else { this.markOpen(slot); } - if (this.shorthandNames.has(symbol.name)) this.markOpen(slot); + if (this.shorthandNames.has(ts.symbolName(symbol))) this.markOpen(slot); for (const reference of this.referencesOf(symbol)) { if (declaration && (declaration as { name?: ts.Node }).name === reference) continue; if (!this.use(reference, slot)) this.markOpen(slot); @@ -824,9 +824,9 @@ export function returnExpressions(fn: ts.FunctionLikeDeclaration): ts.Expression } if (ts.isFunctionLike(node) || ts.isClassDeclaration(node) || ts.isClassExpression(node)) return; - node.forEachChild(visit); + ts.forEachChild(node, visit); }; - fn.body?.forEachChild(visit); + if (fn.body !== undefined) ts.forEachChild(fn.body, visit); return out; } @@ -865,9 +865,9 @@ function usesArguments(body: ts.Node): boolean { if (found) return; if (ts.isIdentifier(node) && node.text === "arguments") found = true; else if (!ts.isFunctionDeclaration(node) && !ts.isFunctionExpression(node)) - node.forEachChild(visit); + ts.forEachChild(node, visit); }; - body.forEachChild(visit); + ts.forEachChild(body, visit); return found; } diff --git a/packages/compiler/src/frontend/lowering/split-consumers.ts b/packages/compiler/src/frontend/lowering/split-consumers.ts index 9624ac0aa..8195d2792 100644 --- a/packages/compiler/src/frontend/lowering/split-consumers.ts +++ b/packages/compiler/src/frontend/lowering/split-consumers.ts @@ -45,7 +45,7 @@ export function isPrivateSplitConsumer( ) return false; - for (const use of identifiersNamed(lowerer, decl.getSourceFile(), name.text)) { + for (const use of identifiersNamed(lowerer, ts.getSourceFileOfNode(decl), name.text)) { if (use === decl.name || use === name) continue; if (lowerer.resolveValueSymbol(use) === symbol) return false; } diff --git a/packages/compiler/src/frontend/lowering/static-function-names.ts b/packages/compiler/src/frontend/lowering/static-function-names.ts index d86e55a80..8868d6745 100644 --- a/packages/compiler/src/frontend/lowering/static-function-names.ts +++ b/packages/compiler/src/frontend/lowering/static-function-names.ts @@ -21,7 +21,7 @@ export function nameReadParams(lowerer: Lowerer, info: GenericFnInfo): Map(); // JavaScript generics read function names through the checked-value // box, which carries the runtime name already. - if (isJsSourceFile(info.decl.getSourceFile())) { + if (isJsSourceFile(ts.getSourceFileOfNode(info.decl))) { info.nameReadParams = out; return out; } diff --git a/packages/compiler/src/frontend/lowering/static-this.ts b/packages/compiler/src/frontend/lowering/static-this.ts index 004a72375..5c26ef0c9 100644 --- a/packages/compiler/src/frontend/lowering/static-this.ts +++ b/packages/compiler/src/frontend/lowering/static-this.ts @@ -31,10 +31,10 @@ export function rejectStaticThis( const keyword = node.kind === ts.SyntaxKind.ThisKeyword ? "this" : "super"; lowerer.unsupported("SC1090", node, message(keyword)); } - node.forEachChild(visit); + ts.forEachChild(node, visit); }; if (includeRoot) visit(root); - else root.forEachChild(visit); + else ts.forEachChild(root, visit); } export function hasStaticThis(root: ts.Node): boolean { @@ -47,9 +47,9 @@ export function hasStaticThis(root: ts.Node): boolean { ts.isClassExpression(node) ) return; - node.forEachChild(visit); + ts.forEachChild(node, visit); }; - root.forEachChild(visit); + ts.forEachChild(root, visit); return found; } @@ -60,8 +60,8 @@ export function hasLexicalPrivateReference(root: ts.Node): boolean { const visit = (node: ts.Node): void => { if (ts.isClassDeclaration(node) || ts.isClassExpression(node)) return; if (ts.isPrivateIdentifier(node)) found = true; - node.forEachChild(visit); + ts.forEachChild(node, visit); }; - root.forEachChild(visit); + ts.forEachChild(root, visit); return found; } diff --git a/packages/compiler/src/frontend/lowering/string-index-bounds.ts b/packages/compiler/src/frontend/lowering/string-index-bounds.ts index acb41514f..6e422b231 100644 --- a/packages/compiler/src/frontend/lowering/string-index-bounds.ts +++ b/packages/compiler/src/frontend/lowering/string-index-bounds.ts @@ -73,7 +73,7 @@ function isBoundary(node: ts.Node): boolean { function containerOf(node: ts.Node): ts.Node { let n = node.parent; while (n && !isBoundary(n)) n = n.parent; - return n ?? node.getSourceFile(); + return n ?? ts.getSourceFileOfNode(node); } function isLoop(node: ts.Node): boolean { @@ -130,7 +130,7 @@ function writeOf(id: ts.Identifier): Write | null { const k = p.operatorToken.kind; if (p.left !== n || k < ts.SyntaxKind.FirstAssignment || k > ts.SyntaxKind.LastAssignment) return null; - const at = id.getStart(); + const at = ts.tokenStartOfNode(id); if (destructured) return { at, step: null, value: null }; if (k === ts.SyntaxKind.EqualsToken) return { at, step: null, value: p.right }; const amount = integerLiteral(p.right); @@ -146,15 +146,15 @@ function writeOf(id: ts.Identifier): Write | null { (p.operator !== ts.SyntaxKind.PlusPlusToken && p.operator !== ts.SyntaxKind.MinusMinusToken) ) return null; - if (destructured) return { at: id.getStart(), step: null, value: null }; + if (destructured) return { at: ts.tokenStartOfNode(id), step: null, value: null }; return { - at: id.getStart(), + at: ts.tokenStartOfNode(id), step: p.operator === ts.SyntaxKind.PlusPlusToken ? 1 : -1, value: null, }; } if (ts.isForOfStatement(p) || ts.isForInStatement(p)) - return p.initializer === n ? { at: id.getStart(), step: null, value: null } : null; + return p.initializer === n ? { at: ts.tokenStartOfNode(id), step: null, value: null } : null; if (ts.isParenthesizedExpression(p)) { n = p; continue; @@ -199,7 +199,7 @@ export class StringIndexBounds { private prove(read: ts.ElementAccessExpression): boolean { // JavaScript declarations are not checked against callers. - if (read.questionDotToken || isJsSourceFile(read.getSourceFile())) return false; + if (read.questionDotToken || isJsSourceFile(ts.getSourceFileOfNode(read))) return false; const textNode = read.expression; const indexNode = read.argumentExpression; if (!ts.isIdentifier(textNode) || !ts.isIdentifier(indexNode)) return false; @@ -268,7 +268,7 @@ export class StringIndexBounds { if (!(list.flags & ts.NodeFlags.Const)) return null; } else if (declaration.initializer) { writes.push({ - at: declaration.name.getStart(), + at: ts.tokenStartOfNode(declaration.name), step: null, value: declaration.initializer, }); @@ -280,7 +280,7 @@ export class StringIndexBounds { const container = containerOf(declaration); let escapes = false; let assigned = 0; - for (const id of this.namesIn(container).get(symbol.name) ?? []) { + for (const id of this.namesIn(container).get(ts.symbolName(symbol)) ?? []) { if (id === nameNode) continue; const write = writeOf(id); if (!write) continue; diff --git a/packages/compiler/src/frontend/lowering/surfaces.ts b/packages/compiler/src/frontend/lowering/surfaces.ts index be2207bcd..a884b39e1 100644 --- a/packages/compiler/src/frontend/lowering/surfaces.ts +++ b/packages/compiler/src/frontend/lowering/surfaces.ts @@ -3312,7 +3312,7 @@ export function isChildSurfaceMember( const decl = sym ? sym.declarations[0] : undefined; const iface = decl?.parent; if (!decl || !iface || !ts.isInterfaceDeclaration(iface)) return false; - if (lowerer.isStdlibFile(decl.getSourceFile())) return false; + if (lowerer.isStdlibFile(ts.getSourceFileOfNode(decl))) return false; return lowerer.mapTypeOf(lowerer.checker.getTypeAtLocation(iface.name))?.kind === "child"; } @@ -3322,7 +3322,9 @@ export function isChildSurfaceMember( * with lib interfaces, so a member can carry declarations from both. A * user's own declaration is in neither, so shadowing never matches. */ export function isStdlibSymbol(lowerer: Lowerer, symbol: ts.Symbol | undefined): boolean { - return !!symbol && symbol.declarations.some((d) => lowerer.isStdlibFile(d.getSourceFile())); + return ( + !!symbol && symbol.declarations.some((d) => lowerer.isStdlibFile(ts.getSourceFileOfNode(d))) + ); } /** The canonical stdlib-global name `expr` denotes, or null. Three @@ -3380,20 +3382,20 @@ export function stdlibGlobalNameOf(lowerer: Lowerer, expr: ts.Expression): strin if ( decl !== undefined && (ts.isVariableDeclaration(decl) || ts.isBindingElement(decl)) && - isJsSourceFile(decl.getSourceFile()) + isJsSourceFile(ts.getSourceFileOfNode(decl)) ) { const imported = lowerer.builtinImportOf(expr); if (imported?.module === "buffer" && imported.member === "Buffer") return "Buffer"; } return null; } - return resolved.name === "global" ? "globalThis" : resolved.name; + return ts.symbolName(resolved) === "global" ? "globalThis" : ts.symbolName(resolved); } if (ts.isPropertyAccessExpression(expr) && !expr.questionDotToken) { if (stdlibGlobalNameOf(lowerer, expr.expression) !== "globalThis") return null; const symbol = lowerer.checker.getSymbolAtLocation(expr.name); if (!symbol || !lowerer.isStdlibSymbol(symbol)) return null; - return symbol.name === "global" ? "globalThis" : symbol.name; + return ts.symbolName(symbol) === "global" ? "globalThis" : ts.symbolName(symbol); } return null; } @@ -3527,7 +3529,7 @@ export function nodeTypesOnlySymbol(lowerer: Lowerer, sym: ts.Symbol | null | un if (!decls || decls.length === 0) return false; let viaNode = false; for (const d of decls) { - const sf = d.getSourceFile(); + const sf = ts.getSourceFileOfNode(d); if (sf.isDeclarationFile && isNodeTypesPath(sf.fileName)) viaNode = true; else if (lowerer.isStdlibFile(sf)) return false; } diff --git a/packages/compiler/src/frontend/lowering/symbol-fields.ts b/packages/compiler/src/frontend/lowering/symbol-fields.ts index 0791fc6cd..56718615e 100644 --- a/packages/compiler/src/frontend/lowering/symbol-fields.ts +++ b/packages/compiler/src/frontend/lowering/symbol-fields.ts @@ -118,13 +118,13 @@ export function classSymbolKeyOfSymbol(lowerer: Lowerer, sym: ts.Symbol): ClassS const init = decl.initializer; if (init && (ts.isIdentifier(init) || ts.isPropertyAccessExpression(init))) { const target = classSymbolKeyOf(lowerer, init); - if (!target || bindingWritten(lowerer, sym, decl.getSourceFile())) return null; + if (!target || bindingWritten(lowerer, sym, ts.getSourceFileOfNode(decl))) return null; const preceding = list.declarations.nodes.slice(0, list.declarations.nodes.indexOf(decl)); if ( bindingEarlyUse7( lowerer.program, - decl.getSourceFile(), - decl.getSourceFile().statements.nodes.indexOf(list.parent), + ts.getSourceFileOfNode(decl), + ts.getSourceFileOfNode(decl).statements.nodes.indexOf(list.parent), decl, preceding, ) !== null @@ -139,7 +139,7 @@ export function classSymbolKeyOfSymbol(lowerer: Lowerer, sym: ts.Symbol): ClassS const { registered, description } = call; // Ordinary Symbol() is a fresh identity per evaluation, so only the // module-scope declaration above can name one fixed layout slot. - const sf = decl.getSourceFile(); + const sf = ts.getSourceFileOfNode(decl); if (bindingWritten(lowerer, sym, sf)) return null; const preceding = list.declarations.nodes.slice(0, list.declarations.nodes.indexOf(decl)); // Creating a function value does not execute its body. The general @@ -268,7 +268,7 @@ function cjsTableSymbolKeyOf( ) return null; const stmt = assign.parent; - const sf = stmt.getSourceFile(); + const sf = ts.getSourceFileOfNode(stmt); if (cjsExportDiscardReason(stmt) !== null) return null; const call = literalSymbolCallOf(lowerer, decl.initializer); if (!call) return null; @@ -330,7 +330,7 @@ function tablePropertyWritten( if (node.name.text !== name) return false; const recv = node.expression; if ( - node.getSourceFile() === home && + ts.getSourceFileOfNode(node) === home && ((ts.isIdentifier(recv) && recv.text === "exports") || isModuleExportsAccess(recv)) ) return true; @@ -381,7 +381,7 @@ function bindingWritten(lowerer: Lowerer, sym: ts.Symbol, sf: ts.SourceFile): bo // Assignment to obj[key] changes the property, never the key's // binding. The same applies to these leaves in destructuring. if (ts.isPropertyAccessExpression(node) || ts.isElementAccessExpression(node)) return "skip"; - if (!ts.isIdentifier(node) || node.text !== sym.name) return; + if (!ts.isIdentifier(node) || node.text !== ts.symbolName(sym)) return; const targetSym = ts.isShorthandPropertyAssignment(node.parent) ? lowerer.checker.getShorthandAssignmentValueSymbol(node.parent) : lowerer.checker.getSymbolAtLocation(node); diff --git a/packages/compiler/src/frontend/lowering/typed-arguments.ts b/packages/compiler/src/frontend/lowering/typed-arguments.ts index b52a3e059..2fbd83482 100644 --- a/packages/compiler/src/frontend/lowering/typed-arguments.ts +++ b/packages/compiler/src/frontend/lowering/typed-arguments.ts @@ -71,7 +71,7 @@ export function typedArgumentsCandidate(node: ts.Node): node is ts.FunctionExpre return ( ts.isFunctionExpression(node) && node.asteriskToken === undefined && - !isJsSourceFile(node.getSourceFile()) && + !isJsSourceFile(ts.getSourceFileOfNode(node)) && node.parameters.nodes.every(isThisParameter) && bodyReadsArguments(node) ); @@ -243,7 +243,7 @@ export function onlyForwarded( lowerer.checker.getNonNullableType(lowerer.typeOf(p.parent.expression)), ); const decl = sigs.length === 1 ? sigs[0]!.declaration : undefined; - if (decl && !decl.getSourceFile().isDeclarationFile) return undefined; + if (decl && !ts.getSourceFileOfNode(decl).isDeclarationFile) return undefined; } if ( ts.isVariableDeclaration(p) && @@ -317,7 +317,7 @@ export function forwardedTupleType(lowerer: Lowerer, node: ts.Expression): IrTyp ts.isParameter(decl) && decl.dotDotDotToken !== undefined && ts.isIdentifier(decl.name) && - !isJsSourceFile(decl.getSourceFile()) && + !isJsSourceFile(ts.getSourceFileOfNode(decl)) && // Checker-only probe first: an ordinary array rest never maps here. restTupleElementTypes(lowerer.typeOf(operand), lowerer.typeCtx) !== null ) { diff --git a/packages/compiler/src/frontend/lowering/var-hoisting.ts b/packages/compiler/src/frontend/lowering/var-hoisting.ts index c445f1751..6276ab671 100644 --- a/packages/compiler/src/frontend/lowering/var-hoisting.ts +++ b/packages/compiler/src/frontend/lowering/var-hoisting.ts @@ -97,7 +97,7 @@ function hoistingScopeOf(node: ts.Node): Scope { ) return p; } - return node.getSourceFile(); + return ts.getSourceFileOfNode(node); } function statementsOf(scope: Scope): readonly ts.Statement[] { @@ -221,7 +221,7 @@ class ScopeAnalysis { * the statement at index `settle`. `read`: `ref` reads the binding * itself, so tsc vouches for one in the scope's own code. */ settles(scope: Scope, settle: number, ref: ts.Identifier, read = true): boolean { - if (ref.getSourceFile() !== this.sf) return true; + if (ts.getSourceFileOfNode(ref) !== this.sf) return true; const statements = statementsOf(scope); let deferred: ts.Node | null = null; let top: ts.Node | null = null; diff --git a/packages/compiler/src/frontend/module-source-candidates.test.ts b/packages/compiler/src/frontend/module-source-candidates.test.ts index 4ff20decc..8fccef000 100644 --- a/packages/compiler/src/frontend/module-source-candidates.test.ts +++ b/packages/compiler/src/frontend/module-source-candidates.test.ts @@ -6,6 +6,7 @@ import * as ast from "./ts7/ast.js"; import { Ts7Api } from "./ts7/rpc-api.js"; import { Ts7Host } from "./ts7/program-host.js"; import { moduleSourceCandidates } from "./module-source-candidates.js"; +import { getTextOfNode } from "./ts7/node-functions.js"; test("module candidates preserve nested call order and belong to one program snapshot", () => { const directory = mkdtempSync(join(tmpdir(), "scriptc-module-candidates-")); @@ -24,7 +25,7 @@ test("module candidates preserve nested call order and belong to one program sna const source = first.getSourceFile(entry)!; const candidates = moduleSourceCandidates(first, source); expect(candidates.specifiers).toEqual(["first", "second", "third", "fourth"]); - expect(candidates.calls.map((call) => call.expression.getText())).toEqual([ + expect(candidates.calls.map((call) => getTextOfNode(call.expression))).toEqual([ "outer", "require", "import", diff --git a/packages/compiler/src/frontend/npm-static-bundled-chunks.ts b/packages/compiler/src/frontend/npm-static-bundled-chunks.ts index c61f4a439..b625c456d 100644 --- a/packages/compiler/src/frontend/npm-static-bundled-chunks.ts +++ b/packages/compiler/src/frontend/npm-static-bundled-chunks.ts @@ -24,6 +24,7 @@ import * as ts from "./ts7/syntax.js"; import { helperTokens } from "./helper-tokens.js"; import { canonicalBuiltinModule } from "./builtin-modules.js"; +import { getTextOfNode, tokenStartOfNode } from "./ts7/node-functions.js"; export interface BundledChunkSplit { /** The bundle with chunk declarations blanked and calls respelled. */ @@ -366,16 +367,16 @@ function helperForm( const ret = stmts[0]; if (stmts.length !== 1 || ret === undefined || !ts.isReturnStatement(ret) || !ret.expression) return null; - const actual = helperTokens(ret.expression.getText(sf)); + const actual = helperTokens(getTextOfNode(ret.expression)); if (actual === null) return null; // The own-key reader: esbuild's alias or the 0.11 spelling. - const keysRef = /\[\s*([A-Za-z_$][\w$]*(?:\.keys)?)\s*\(/.exec(ret.expression.getText(sf)); + const keysRef = /\[\s*([A-Za-z_$][\w$]*(?:\.keys)?)\s*\(/.exec(getTextOfNode(ret.expression)); const keys = keysRef?.[1]; if (keys === undefined || (keys !== "Object.keys" && !ownNames(keys))) return null; return actual === template(KEYED_RETURN, cb, mod, keys) ? "keyed" : null; } if (ts.isArrowFunction(body) && body.parameters.nodes.length === 0 && !ts.isBlock(body.body)) { - const actual = helperTokens(body.body.getText(sf)); + const actual = helperTokens(getTextOfNode(body.body)); return actual !== null && actual === template(ARROW_BODY, cb, mod, "") ? "arrow" : null; } return null; @@ -539,7 +540,7 @@ export function splitBundledChunks( const init = t.node.initializer; return ( init !== undefined && - helperTokens(init.getText(sf)) === helperTokens("Object.getOwnPropertyNames") + helperTokens(getTextOfNode(init)) === helperTokens("Object.getOwnPropertyNames") ); }; if (helperNames.length === 0) { @@ -640,8 +641,8 @@ export function splitBundledChunks( }; const forwarded = forwarderSpec(chunk); callEdits.push({ - start: call.getStart(sf), - end: call.getEnd(), + start: tokenStartOfNode(call), + end: call.end, text: `require(${JSON.stringify(forwarded ?? chunk.spec)})`, }); } @@ -656,7 +657,7 @@ export function splitBundledChunks( for (const chunk of chunks.values()) { if (forwarderSpec(chunk) !== null) { // A chunk that only re-exports a builtin module is that module. - bundleEdits.push({ start: chunk.stmt.getStart(sf), end: chunk.stmt.getEnd(), text: "" }); + bundleEdits.push({ start: tokenStartOfNode(chunk.stmt), end: chunk.stmt.end, text: "" }); continue; } const result = chunkSource( @@ -672,7 +673,7 @@ export function splitBundledChunks( ); if (typeof result !== "string") return result; out.push({ path: chunk.path, source: result }); - bundleEdits.push({ start: chunk.stmt.getStart(sf), end: chunk.stmt.getEnd(), text: "" }); + bundleEdits.push({ start: tokenStartOfNode(chunk.stmt), end: chunk.stmt.end, text: "" }); } return { text: applyEdits(source, 0, source.length, bundleEdits), chunks: out }; } @@ -848,8 +849,8 @@ function chunkSource( const p = r.parent; edits.push( ts.isShorthandPropertyAssignment(p) - ? { start: r.getStart(sf), end: r.getEnd(), text: `${name}: ${global}` } - : { start: r.getStart(sf), end: r.getEnd(), text: global }, + ? { start: tokenStartOfNode(r), end: r.end, text: `${name}: ${global}` } + : { start: tokenStartOfNode(r), end: r.end, text: global }, ); } continue; @@ -893,15 +894,17 @@ function chunkSource( } // Declaration order of the bundle: a helper reads only earlier ones. const ordered = [...copied].sort( - (a, b) => top.get(a)!.node.getStart(sf) - top.get(b)!.node.getStart(sf), + (a, b) => tokenStartOfNode(top.get(a)!.node) - tokenStartOfNode(top.get(b)!.node), ); for (const name of ordered) { const node = top.get(name)!.node; - header.push(ts.isFunctionDeclaration(node) ? node.getText(sf) : `var ${node.getText(sf)};`); + header.push( + ts.isFunctionDeclaration(node) ? getTextOfNode(node) : `var ${getTextOfNode(node)};`, + ); } const bodyStrict = strictPrologue(body.statements.nodes); - const text = applyEdits(sf.text, body.getStart(sf) + 1, body.getEnd() - 1, edits); + const text = applyEdits(sf.text, tokenStartOfNode(body) + 1, body.end - 1, edits); const prologue = bundleStrict || bodyStrict ? '"use strict";\n' : ""; return prologue + (header.length > 0 ? header.join("\n") + "\n" : "") + text + "\n"; } diff --git a/packages/compiler/src/frontend/npm-static-bundled-cjs.ts b/packages/compiler/src/frontend/npm-static-bundled-cjs.ts index 89cd0fda1..be43284a6 100644 --- a/packages/compiler/src/frontend/npm-static-bundled-cjs.ts +++ b/packages/compiler/src/frontend/npm-static-bundled-cjs.ts @@ -1,5 +1,6 @@ import * as ts from "./ts7/syntax.js"; import { helperTokens } from "./helper-tokens.js"; +import { getTextOfNode, tokenStartOfNode } from "./ts7/node-functions.js"; // Bun's cached node-mode interop helper. Match the complete token stream, // including its dependencies: helper names alone are not a semantic contract. @@ -117,14 +118,14 @@ export function rewriteBundledFunctionImports(source: string, sf: ts.SourceFile) } for (const [name, shape] of helperShapes) { const actual = declarations.get(name); - if (!actual || helperTokens(actual.stmt.getText(sf)) !== shape) return null; + if (!actual || helperTokens(getTextOfNode(actual.stmt)) !== shape) return null; } // These initializers must execute before any factory or namespace. The // original bundle would throw if a var helper were still undefined. let helperEnd = 0; for (const name of helperShapes.keys()) { const stmt = declarations.get(name)!.stmt; - if (stmt.getStart(sf) < helperEnd) return null; + if (tokenStartOfNode(stmt) < helperEnd) return null; helperEnd = stmt.end; } const edits: Edit[] = []; @@ -140,7 +141,7 @@ export function rewriteBundledFunctionImports(source: string, sf: ts.SourceFile) const call = decl.initializer; if (!ts.isIdentifier(call.expression) || call.expression.text !== "__commonJS") continue; if ( - stmt.getStart(sf) < helperEnd || + tokenStartOfNode(stmt) < helperEnd || (ts.canHaveModifiers(stmt) && ts.getModifiers(stmt)?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword)) ) @@ -193,7 +194,7 @@ export function rewriteBundledFunctionImports(source: string, sf: ts.SourceFile) if (unsafe) return null; // The comma expression prevents inferred naming of an anonymous export. edits.push( - { start: call.getStart(sf), end: value.getStart(sf), text: "(0," }, + { start: tokenStartOfNode(call), end: tokenStartOfNode(value), text: "(0," }, { start: value.end, end: call.end, text: ")" }, ); factories.set(name, stmt); @@ -212,7 +213,7 @@ export function rewriteBundledFunctionImports(source: string, sf: ts.SourceFile) const call = decl.initializer; if (!ts.isIdentifier(call.expression) || call.expression.text !== "__toESM") continue; if ( - stmt.getStart(sf) < helperEnd || + tokenStartOfNode(stmt) < helperEnd || (ts.canHaveModifiers(stmt) && ts.getModifiers(stmt)?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword)) ) @@ -230,9 +231,9 @@ export function rewriteBundledFunctionImports(source: string, sf: ts.SourceFile) ) return null; const factory = factories.get(requireCall.expression.text); - if (!factory || factory.end > stmt.getStart(sf)) return null; + if (!factory || factory.end > tokenStartOfNode(stmt)) return null; edits.push({ - start: call.getStart(sf), + start: tokenStartOfNode(call), end: call.end, text: `{default:${requireCall.expression.text}}`, }); @@ -286,7 +287,7 @@ export function rewriteBundledFunctionImports(source: string, sf: ts.SourceFile) // Remove only helpers that became dead. Other bundle features may share // __defProp, so a recognized declaration alone does not justify erasure. const dead = (n: ts.Node): boolean => - edits.some((e) => n.getStart(sf) >= e.start && n.end <= e.end); + edits.some((e) => tokenStartOfNode(n) >= e.start && n.end <= e.end); const removed = new Set(); for (let changed = true; changed;) { changed = false; @@ -304,7 +305,7 @@ export function rewriteBundledFunctionImports(source: string, sf: ts.SourceFile) live = true; }); if (!live) { - edits.push({ start: stmt.getStart(sf), end: stmt.end, text: "" }); + edits.push({ start: tokenStartOfNode(stmt), end: stmt.end, text: "" }); removed.add(name); changed = true; } diff --git a/packages/compiler/src/frontend/npm-static-bundled-esm.ts b/packages/compiler/src/frontend/npm-static-bundled-esm.ts index 166676b3a..ac572000d 100644 --- a/packages/compiler/src/frontend/npm-static-bundled-esm.ts +++ b/packages/compiler/src/frontend/npm-static-bundled-esm.ts @@ -40,6 +40,7 @@ import { unparen, type Edit, } from "./npm-static-bundled-chunks.js"; +import { getTextOfNode, tokenStartOfNode } from "./ts7/node-functions.js"; export interface EsmInitHost { requireTargetEsModuleStamped(fromFile: string, specifier: string): boolean; @@ -92,13 +93,13 @@ function esmHelperForm( const stmts = body.body.statements.nodes; const ret = stmts[0]; if (stmts.length !== 1 || !ret || !ts.isReturnStatement(ret) || !ret.expression) return null; - const text = ret.expression.getText(sf); + const text = getTextOfNode(ret.expression); const keys = /\[\s*([A-Za-z_$][\w$]*(?:\.keys)?)\s*\(/.exec(text)?.[1]; if (keys === undefined || (keys !== "Object.keys" && !ownNames(keys))) return null; return helperTokens(text) === template(KEYED_RETURN, f, r, keys) ? "keyed" : null; } if (ts.isArrowFunction(body) && body.parameters.nodes.length === 0 && !ts.isBlock(body.body)) { - const actual = helperTokens(body.body.getText(sf)); + const actual = helperTokens(getTextOfNode(body.body)); return actual !== null && actual === template(ARROW_BODY, f, r, "") ? "arrow" : null; } return null; @@ -117,7 +118,7 @@ function removeDeclarators(sf: ts.SourceFile, dead: ReadonlySet dead.has(d)); if (deadAt.every((x) => x)) { - edits.push({ start: stmt.getStart(sf), end: stmt.getEnd(), text: "" }); + edits.push({ start: tokenStartOfNode(stmt), end: stmt.end, text: "" }); continue; } const lastLive = deadAt.lastIndexOf(false); @@ -125,8 +126,8 @@ function removeDeclarators(sf: ts.SourceFile, dead: ReadonlySet(); @@ -423,7 +424,7 @@ export function lowerBundledEsmInits( } const spec = bareRequireSpecOf(rhs); if (spec === null) continue; - const req = rhs.getText(sf); + const req = getTextOfNode(rhs); const defaultIsModule = interop && (isNodeMode || !host.requireTargetEsModuleStamped(filePath, spec)); // Every other reference must be a plain member read. @@ -442,11 +443,11 @@ export function lowerBundledEsmInits( break; } if (defaultIsModule && access.name.text === "default") - uses.push({ start: access.getStart(sf), end: access.getEnd(), text: `(${req})` }); - else uses.push({ start: r.getStart(sf), end: r.getEnd(), text: req }); + uses.push({ start: tokenStartOfNode(access), end: access.end, text: `(${req})` }); + else uses.push({ start: tokenStartOfNode(r), end: r.end, text: req }); } if (!ok) continue; - edits.push({ start: el.getStart(sf), end: el.getEnd(), text: req }, ...uses); + edits.push({ start: tokenStartOfNode(el), end: el.end, text: req }, ...uses); deadDeclarators.add(decl); } } @@ -500,7 +501,7 @@ export function lowerBundledEsmInits( return false; }; const getterText = (value: ts.Expression): string => - applyEdits(source, value.getStart(sf), value.getEnd(), edits); + applyEdits(source, tokenStartOfNode(value), value.end, edits); /** A call through the namespace (`ns.fn()`, `L().fn()`) passes the object * the read came from as `this`; the lowered object differs from Node's * module object (`__esModule`, property attributes), so it must stay @@ -628,23 +629,23 @@ export function lowerBundledEsmInits( // call through it (`L().fn()`) loses its receiver. if (calledWithReceiver(access as ts.Expression) && !receiverHidden(entries, member)) return loaderWhy; - const init = initCall === null ? "" : `${initCall.getText(sf)}, `; + const init = initCall === null ? "" : `${getTextOfNode(initCall)}, `; edits.push({ - start: access.getStart(sf), - end: access.getEnd(), + start: tokenStartOfNode(access), + end: access.end, text: `(${init}${getterText(value)})`, }); } deadDeclarators.add(d); deadDeclarators.add(memoDecl); - loaderSpans.push({ start: d.getStart(sf), end: d.getEnd() }); + loaderSpans.push({ start: tokenStartOfNode(d), end: d.end }); lowered.add(target); } for (const r of allRefs) { const table = tables.get(r.text); if (table === undefined) continue; - if (loaderSpans.some((span) => r.getStart(sf) >= span.start && r.getEnd() <= span.end)) + if (loaderSpans.some((span) => tokenStartOfNode(r) >= span.start && r.end <= span.end)) continue; const p = r.parent; // The table's own declaration and __export call are handled below. @@ -709,7 +710,7 @@ export function lowerBundledEsmInits( const inner = unparen(ns); const init = ts.isBinaryExpression(inner) && inner.operatorToken.kind === ts.SyntaxKind.CommaToken - ? `${unparen(inner.left).getText(sf)}; ` + ? `${getTextOfNode(unparen(inner.left))}; ` : ""; if (shadowed(holder, table.entries)) return { degrade: `its shipped JS ${why}` }; const entries = table.entries @@ -717,8 +718,8 @@ export function lowerBundledEsmInits( .map(([n, value]) => `${JSON.stringify(n)}: () => ${getterText(value)}`) .join(", "); edits.push({ - start: holder.parent.getStart(sf), - end: holder.parent.getEnd(), + start: tokenStartOfNode(holder.parent), + end: holder.parent.end, text: `${init}__export(${mainTarget}, { ${entries} });`, }); lowered.add(r.text); @@ -771,7 +772,7 @@ export function lowerBundledEsmInits( `get ${/^[A-Za-z_$][\w$]*$/.test(n) ? n : JSON.stringify(n)}() { return ${getterText(value)}; }`, ) .join(", "); - edits.push({ start: p.getStart(sf), end: p.getEnd(), text: `{ ${getters} }` }); + edits.push({ start: tokenStartOfNode(p), end: p.end, text: `{ ${getters} }` }); lowered.add(r.text); } // A table nothing reads (its module is never required) goes too: its @@ -781,7 +782,7 @@ export function lowerBundledEsmInits( const live = (refsByName.get(target) ?? []).some((x) => { const p = x.parent; if (p !== undefined && ts.isVariableDeclaration(p) && p.name === x) return false; - return !(x.getStart(sf) >= table.stmt.getStart(sf) && x.getEnd() <= table.stmt.getEnd()); + return !(tokenStartOfNode(x) >= tokenStartOfNode(table.stmt) && x.end <= table.stmt.end); }); if (!live) lowered.add(target); } @@ -797,7 +798,7 @@ export function lowerBundledEsmInits( writes.has(target) ) return { degrade: ESM_SHAPE_DEGRADE }; - edits.push({ start: table.stmt.getStart(sf), end: table.stmt.getEnd(), text: "" }); + edits.push({ start: tokenStartOfNode(table.stmt), end: table.stmt.end, text: "" }); deadDeclarators.add(decl); } // Every removed declarator in one pass: declarators of one statement @@ -810,13 +811,13 @@ export function lowerBundledEsmInits( const { call, body } = init; edits.push( { - start: call.getStart(sf), - end: body.getStart(sf) + 1, + start: tokenStartOfNode(call), + end: tokenStartOfNode(body) + 1, text: "(() => { let __scriptc_done = false; function __scriptc_init() {", }, { - start: body.getEnd() - 1, - end: call.getEnd(), + start: body.end - 1, + end: call.end, text: "} return function () { if (__scriptc_done) return; __scriptc_done = true; __scriptc_init(); }; })()", }, ); diff --git a/packages/compiler/src/frontend/npm-static-cjs-exports-alias.ts b/packages/compiler/src/frontend/npm-static-cjs-exports-alias.ts index dfe45b862..bf753be8c 100644 --- a/packages/compiler/src/frontend/npm-static-cjs-exports-alias.ts +++ b/packages/compiler/src/frontend/npm-static-cjs-exports-alias.ts @@ -29,6 +29,7 @@ import { unparen, type Edit, } from "./npm-static-bundled-chunks.js"; +import { tokenStartOfNode } from "./ts7/node-functions.js"; function isModuleExports(e: ts.Expression): e is ts.PropertyAccessExpression { const x = unparen(e); @@ -132,7 +133,7 @@ export function normalizeCjsFunctionExports(sf: ts.SourceFile): string | null { continue; } // Without the rebinding, `exports` keeps the replaced object. - if (!whole.rebinds || r.getStart(sf) < whole.stmt.getEnd()) { + if (!whole.rebinds || tokenStartOfNode(r) < whole.stmt.end) { unsafe = true; continue; } @@ -140,8 +141,8 @@ export function normalizeCjsFunctionExports(sf: ts.SourceFile): string | null { const shorthand = r.parent !== undefined && ts.isShorthandPropertyAssignment(r.parent) && r.parent.name === r; edits.push({ - start: r.getStart(sf), - end: r.getEnd(), + start: tokenStartOfNode(r), + end: r.end, text: shorthand ? `exports: ${fnName}` : fnName, }); continue; @@ -168,8 +169,8 @@ export function normalizeCjsFunctionExports(sf: ts.SourceFile): string | null { (ts.isPropertyAccessExpression(outer) || ts.isElementAccessExpression(outer)) && outer.expression === access ) { - if (access.getStart(sf) < whole.stmt.getEnd() && !insideFunction(access)) unsafe = true; - edits.push({ start: access.getStart(sf), end: access.getEnd(), text: fnName }); + if (tokenStartOfNode(access) < whole.stmt.end && !insideFunction(access)) unsafe = true; + edits.push({ start: tokenStartOfNode(access), end: access.end, text: fnName }); } } } @@ -177,7 +178,7 @@ export function normalizeCjsFunctionExports(sf: ts.SourceFile): string | null { if (whole.rebinds) { // `exports = module.exports = W` → `module.exports = W`. const e = unparen(whole.stmt.expression) as ts.BinaryExpression; - edits.push({ start: e.getStart(sf), end: unparen(e.right).getStart(sf), text: "" }); + edits.push({ start: tokenStartOfNode(e), end: tokenStartOfNode(unparen(e.right)), text: "" }); } if (edits.length === 0) return null; return applyEdits(source, 0, source.length, edits); diff --git a/packages/compiler/src/frontend/npm-static-cjs-exports-var.ts b/packages/compiler/src/frontend/npm-static-cjs-exports-var.ts index 1af4ea1ed..1e7489595 100644 --- a/packages/compiler/src/frontend/npm-static-cjs-exports-var.ts +++ b/packages/compiler/src/frontend/npm-static-cjs-exports-var.ts @@ -17,6 +17,7 @@ * never reassigned. Other shapes answer null and keep their existing * treatment. */ import * as ts from "./ts7/syntax.js"; +import { tokenStartOfNode } from "./ts7/node-functions.js"; function unparen(e: ts.Expression): ts.Expression { let x = e; @@ -219,14 +220,14 @@ export function normalizeCjsExportsVar(sf: ts.SourceFile): string | null { (ts.isPropertyAccessExpression(p) || ts.isElementAccessExpression(p)) && p.expression === n ) { - edits.push({ start: n.getStart(sf), end: n.getEnd(), text: "exports" }); + edits.push({ start: tokenStartOfNode(n), end: n.end, text: "exports" }); return undefined; } ok = false; /* used as a value, assigned, or passed along */ return undefined; }); if (!ok || edits.length === 0) return null; - edits.push({ start: alias.stmt.getStart(sf), end: alias.stmt.getEnd(), text: "" }); + edits.push({ start: tokenStartOfNode(alias.stmt), end: alias.stmt.end, text: "" }); edits.sort((a, b) => a.start - b.start); let out = ""; let at = 0; diff --git a/packages/compiler/src/frontend/npm-static-declaration-syntax.ts b/packages/compiler/src/frontend/npm-static-declaration-syntax.ts index 4a84dcfeb..0d713dcff 100644 --- a/packages/compiler/src/frontend/npm-static-declaration-syntax.ts +++ b/packages/compiler/src/frontend/npm-static-declaration-syntax.ts @@ -10,6 +10,7 @@ * too; inferred checked values validate at the ordinary return boundary. */ import * as ts from "./ts7/syntax.js"; +import { getTextOfNode, tokenStartOfNode } from "./ts7/node-functions.js"; export interface NpmStaticOverloadParameter { name: string; @@ -56,11 +57,11 @@ export function applyNpmStaticJsDocNamepaths( for (const tag of doc.tags?.nodes ?? []) { const expression = tag.typeExpression; if (expression?.kind !== ts.SyntaxKind.JSDocTypeExpression) continue; - const start = expression.getStart(sourceFile); + const start = tokenStartOfNode(expression); if (source[start] !== "{") continue; let depth = 1; let quote = ""; - for (let i = start + 1; i < tag.getEnd() && depth > 0; i++) { + for (let i = start + 1; i < tag.end && depth > 0; i++) { const char = source[i]!; if (quote !== "") { if (char === "\\") i++; @@ -80,7 +81,7 @@ export function applyNpmStaticJsDocNamepaths( continue; } const name = /^[$A-Z_a-z][$\w]*(?:[.#~][$A-Z_a-z][$\w]*)*/.exec( - source.slice(i, tag.getEnd()), + source.slice(i, tag.end), )?.[0]; if (!name) continue; if (name.includes("~") || name.includes("#")) replacements.set(i, name.length); @@ -171,7 +172,7 @@ export function applyNpmStaticNullableClassFields( hasModifier(member, ts.SyntaxKind.StaticKeyword) ) continue; - const leading = source.slice(member.getFullStart(), member.getStart(sourceFile)); + const leading = source.slice(member.pos, tokenStartOfNode(member)); if (jsDocTypeOf(member) !== undefined || leading.includes("@type")) continue; fields.set(member.name.text, { member, invalid: false }); } @@ -285,7 +286,7 @@ export function applyNpmStaticNullableClassFields( for (const field of fields.values()) { if (field.invalid || field.className === undefined) continue; inserts.push({ - offset: field.member.getStart(sourceFile), + offset: tokenStartOfNode(field.member), text: `/** @type {${field.className} | null} */ `, }); } @@ -358,7 +359,7 @@ export function applyNpmStaticFindReturnWidening( (returnType.typeArguments?.nodes.length ?? 0) !== 0 ) continue; - insertions.push({ offset: returnType.getEnd(), length: " | undefined".length }); + insertions.push({ offset: returnType.end, length: " | undefined".length }); } } if (insertions.length === 0) return null; @@ -428,26 +429,22 @@ function hasModifier(node: ts.Node, kind: ts.SyntaxKind): boolean { ); } -function safeTypeText( - node: ts.TypeNode, - sourceFile: ts.SourceFile, - className: string, -): string | null { - if (SAFE_KEYWORD_TYPES.has(node.kind) || ts.isThisTypeNode(node)) return node.getText(sourceFile); +function safeTypeText(node: ts.TypeNode, className: string): string | null { + if (SAFE_KEYWORD_TYPES.has(node.kind) || ts.isThisTypeNode(node)) return getTextOfNode(node); if (ts.isLiteralTypeNode(node) && node.literal.kind === ts.SyntaxKind.NullKeyword) return "null"; if (ts.isParenthesizedTypeNode(node)) { - const inner = safeTypeText(node.type, sourceFile, className); + const inner = safeTypeText(node.type, className); return inner === null ? null : `(${inner})`; } if (ts.isArrayTypeNode(node)) { - const element = safeTypeText(node.elementType, sourceFile, className); + const element = safeTypeText(node.elementType, className); return element === null ? null : `${element}[]`; } if (ts.isTypeOperatorNode(node) && node.operator === ts.SyntaxKind.ReadonlyKeyword) { - return safeTypeText(node.type, sourceFile, className); + return safeTypeText(node.type, className); } if (ts.isUnionTypeNode(node)) { - const arms = node.types.nodes.map((type) => safeTypeText(type, sourceFile, className)); + const arms = node.types.nodes.map((type) => safeTypeText(type, className)); return arms.some((arm) => arm === null) ? null : arms.join(" | "); } if ( @@ -483,7 +480,7 @@ function overloadSignature( ) { return null; } - const returnType = safeTypeText(method.type, sourceFile, className); + const returnType = safeTypeText(method.type, className); if (returnType === null) return null; const parameters: NpmStaticOverloadParameter[] = []; for (const parameter of method.parameters.nodes) { @@ -496,7 +493,7 @@ function overloadSignature( ) { return null; } - const type = safeTypeText(parameter.type, sourceFile, className); + const type = safeTypeText(parameter.type, className); if (type === null) return null; parameters.push({ name: parameter.name.text, @@ -553,13 +550,9 @@ export function parseNpmStaticDeclarationOverloads( return classes; } -function nullableSelfType( - node: ts.TypeNode, - sourceFile: ts.SourceFile, - className: string, -): string | null { +function nullableSelfType(node: ts.TypeNode, className: string): string | null { if (!ts.isUnionTypeNode(node) || node.types.nodes.length !== 2) return null; - const arms = node.types.nodes.map((type) => safeTypeText(type, sourceFile, className)); + const arms = node.types.nodes.map((type) => safeTypeText(type, className)); return arms.includes(className) && arms.includes("null") ? `${className} | null` : null; } @@ -613,8 +606,7 @@ export function parseNpmStaticDeclarationProperties( continue; } const type = - nullableSelfType(member.type, sourceFile, className) ?? - nullableVoidCallbackType(member.type); + nullableSelfType(member.type, className) ?? nullableVoidCallbackType(member.type); if (type !== null) properties.set(member.name.text, type); } if (properties.size > 0) classes.set(className, properties); @@ -1012,13 +1004,10 @@ export function applyNpmStaticDeclarationOverloads( ) { continue; } - const leading = source.slice( - bodyStatement.getFullStart(), - bodyStatement.getStart(sourceFile), - ); + const leading = source.slice(bodyStatement.pos, tokenStartOfNode(bodyStatement)); if (!leading.includes("@type")) { inserts.push({ - offset: bodyStatement.getStart(sourceFile), + offset: tokenStartOfNode(bodyStatement), text: `/** @type {${backing.type}} */ `, }); } @@ -1036,13 +1025,13 @@ export function applyNpmStaticDeclarationOverloads( (parameter) => parameter.optional && !existing.includes(`[${parameter.name}]`), ); if (missingOptional) - inserts.push({ offset: member.getStart(sourceFile), text: `${implementation} ` }); + inserts.push({ offset: tokenStartOfNode(member), text: `${implementation} ` }); else if ( jsDocs.length === 0 && member.parameters.nodes.length === 0 && SCALAR_METHOD_RETURNS.has(signatures[0]!.returnType) ) { - inserts.push({ offset: member.getStart(sourceFile), text: `${implementation} ` }); + inserts.push({ offset: tokenStartOfNode(member), text: `${implementation} ` }); } } continue; @@ -1050,7 +1039,9 @@ export function applyNpmStaticDeclarationOverloads( const implementation = jsDocs.length === 0 ? implementationComment(statement.name.text, member, signatures) : null; if (jsDocs.length === 0 && implementation === null) continue; - const offset = jsDocs[0]?.getStart(sourceFile) ?? member.getStart(sourceFile); + const offset = + (jsDocs[0] === undefined ? undefined : tokenStartOfNode(jsDocs[0])) ?? + tokenStartOfNode(member); const text = `${signatures.map(overloadComment).join(" ")} ${implementation === null ? "" : implementation + " "}`; inserts.push({ offset, text }); } @@ -1158,9 +1149,9 @@ export function applyNpmStaticDeclarationProperties( !callbackInitializer(member.initializer)) ) continue; - const leading = source.slice(member.getFullStart(), member.getStart(sourceFile)); + const leading = source.slice(member.pos, tokenStartOfNode(member)); if (jsDocTypeOf(member) !== undefined || leading.includes("@type")) continue; - inserts.push({ offset: member.getStart(sourceFile), text: `/** @type {${type}} */ ` }); + inserts.push({ offset: tokenStartOfNode(member), text: `/** @type {${type}} */ ` }); } const constructor = statement.members.nodes.find( (member): member is ts.ConstructorDeclaration => @@ -1184,12 +1175,9 @@ export function applyNpmStaticDeclarationProperties( } const type = properties.get(left.name.text); if (type === undefined) continue; - const leading = source.slice( - bodyStatement.getFullStart(), - bodyStatement.getStart(sourceFile), - ); + const leading = source.slice(bodyStatement.pos, tokenStartOfNode(bodyStatement)); if (leading.includes("@type")) continue; - inserts.push({ offset: bodyStatement.getStart(sourceFile), text: `/** @type {${type}} */ ` }); + inserts.push({ offset: tokenStartOfNode(bodyStatement), text: `/** @type {${type}} */ ` }); } } if (inserts.length === 0) return null; diff --git a/packages/compiler/src/frontend/npm-static-rewrite-syntax.ts b/packages/compiler/src/frontend/npm-static-rewrite-syntax.ts index 348b04817..c48420625 100644 --- a/packages/compiler/src/frontend/npm-static-rewrite-syntax.ts +++ b/packages/compiler/src/frontend/npm-static-rewrite-syntax.ts @@ -6,6 +6,7 @@ import { isModuleExports, } from "./cjs-syntax.js"; import { rewriteBundledFunctionImports } from "./npm-static-bundled-cjs.js"; +import { getTextOfNode, tokenStartOfNode } from "./ts7/node-functions.js"; /** Resolution stays with the compilation host. Syntax never reads disk or * starts a parser implicitly, so native and Node hosts run the same pass. */ @@ -57,7 +58,7 @@ function liveExportTableFirst( }; let sawTable = false; for (const stmt of sf.statements.nodes) { - if (stmt.getStart(sf) >= installAt) return sawTable; + if (tokenStartOfNode(stmt) >= installAt) return sawTable; if (ts.isFunctionDeclaration(stmt) || ts.isEmptyStatement(stmt)) continue; if (ts.isExpressionStatement(stmt) && ts.isStringLiteral(stmt.expression)) continue; if (ts.isVariableStatement(stmt)) { @@ -146,7 +147,7 @@ function chaseValue(e: ts.Expression): { kind: "ident" | "hoist"; text: string } cur.kind === ts.SyntaxKind.FalseKeyword || cur.kind === ts.SyntaxKind.NullKeyword ) { - return { kind: "hoist", text: cur.getText() }; + return { kind: "hoist", text: getTextOfNode(cur) }; } // ident-rooted member chains: this.x / a.b.c / a["k"].d — side-effect // free reads the tail const can evaluate once. @@ -161,7 +162,7 @@ function chaseValue(e: ts.Expression): { kind: "ident" | "hoist"; text: string } ) { walk = walk.expression; } - if (ts.isIdentifier(walk)) return { kind: "hoist", text: cur.getText() }; + if (ts.isIdentifier(walk)) return { kind: "hoist", text: getTextOfNode(cur) }; return null; } @@ -182,7 +183,7 @@ function chaseObjectLiteral(e: ts.Expression): { kind: "hoist"; text: string } | ) return null; } - return { kind: "hoist", text: cur.getText() }; + return { kind: "hoist", text: getTextOfNode(cur) }; } /** The getter body's returned expression for the two descriptor getter @@ -326,7 +327,7 @@ function recognizedToEsmDecl(d: ts.VariableDeclaration): boolean { let to: ts.Expression = expr.arguments.nodes[0]!; while (ts.isParenthesizedExpression(to)) to = to.expression; if (!ts.isConditionalExpression(to)) return false; - const condText = to.condition.getText(); + const condText = getTextOfNode(to.condition); if (!condText.includes(isNodeMode) || !condText.includes("__esModule")) return false; let stamp: ts.Expression = to.whenTrue; while (ts.isParenthesizedExpression(stamp)) stamp = stamp.expression; @@ -443,7 +444,7 @@ function planToEsmInterop(sf: ts.SourceFile, filePath: string, host: CjsRewriteH ) { if (parent.name.text === "default" && defaultIsModule) { if (!readOnlyAccess(parent)) return fail(TO_ESM_ESCAPE_DEGRADE); - pads.push({ start: child.getEnd(), end: parent.getEnd() }); + pads.push({ start: child.end, end: parent.end }); } } else { return fail(TO_ESM_ESCAPE_DEGRADE); @@ -451,8 +452,8 @@ function planToEsmInterop(sf: ts.SourceFile, filePath: string, host: CjsRewriteH // the wrapper itself: callee + '(' down, and everything after the // require argument (`, 1` included) — the bare require survives at its // original offsets. - pads.push({ start: call.getStart(sf), end: arg0!.getStart(sf) }); - pads.push({ start: arg0!.getEnd(), end: call.getEnd() }); + pads.push({ start: tokenStartOfNode(call), end: tokenStartOfNode(arg0!) }); + pads.push({ start: arg0!.end, end: call.end }); } if (bindings.size > 0) { @@ -513,7 +514,7 @@ function planToEsmInterop(sf: ts.SourceFile, filePath: string, host: CjsRewriteH violated = true; return; } - pads.push({ start: n.expression.getEnd(), end: n.getEnd() }); + pads.push({ start: n.expression.end, end: n.end }); } }; ts.walkPreorder(sf, padDefaults); @@ -628,8 +629,8 @@ export function rewriteBundlerCjsSyntax( ts.isIdentifier(rhs.arguments.nodes[0]!) ) { esbuildMainTarget = (rhs.arguments.nodes[0] as ts.Identifier).text; - tablePos = stmt.getStart(sf); - neutralize.push({ start: stmt.getStart(sf), end: stmt.getEnd() }); + tablePos = tokenStartOfNode(stmt); + neutralize.push({ start: tokenStartOfNode(stmt), end: stmt.end }); sawBundleShape = true; continue; } @@ -662,9 +663,9 @@ export function rewriteBundlerCjsSyntax( names, finalRhs: cur, viaBareExports, - pos: stmt.getStart(sf), - start: stmt.getStart(sf), - end: stmt.getEnd(), + pos: tokenStartOfNode(stmt), + start: tokenStartOfNode(stmt), + end: stmt.end, }); continue; // classified later (only when a rewrite actually happens) } @@ -684,7 +685,7 @@ export function rewriteBundlerCjsSyntax( isModuleExports(inner.left) && ts.isObjectLiteralExpression(inner.right) ) { - neutralize.push({ start: stmt.getStart(sf), end: stmt.getEnd() }); + neutralize.push({ start: tokenStartOfNode(stmt), end: stmt.end }); sawBundleShape = true; continue; } @@ -695,7 +696,7 @@ export function rewriteBundlerCjsSyntax( const table = exportCallOf(e); if (table !== null) { exportTables.push(table); - neutralize.push({ start: stmt.getStart(sf), end: stmt.getEnd() }); + neutralize.push({ start: tokenStartOfNode(stmt), end: stmt.end }); sawBundleShape = true; continue; } @@ -705,7 +706,7 @@ export function rewriteBundlerCjsSyntax( const spec = bareRequireSpecOf(e.arguments.nodes[0]!); if (spec !== null) { addStarSpec(spec); - neutralize.push({ start: stmt.getStart(sf), end: stmt.getEnd() }); + neutralize.push({ start: tokenStartOfNode(stmt), end: stmt.end }); sawBundleShape = true; continue; } @@ -716,7 +717,7 @@ export function rewriteBundlerCjsSyntax( const spec = bareRequireSpecOf(e.arguments.nodes[1]!); if (spec !== null) { addStarSpec(spec); - neutralize.push({ start: stmt.getStart(sf), end: stmt.getEnd() }); + neutralize.push({ start: tokenStartOfNode(stmt), end: stmt.end }); sawBundleShape = true; continue; } @@ -725,7 +726,7 @@ export function rewriteBundlerCjsSyntax( const def = definePropertyExportOf(e); if (def !== null) { if (def.esModuleStamp) { - neutralize.push({ start: stmt.getStart(sf), end: stmt.getEnd() }); + neutralize.push({ start: tokenStartOfNode(stmt), end: stmt.end }); // the canonical table re-stamps it (lexer-visibly — merve detects // this defineProperty form, so parity wants a visible entry) continue; @@ -733,7 +734,7 @@ export function rewriteBundlerCjsSyntax( const chased = def.value !== null ? chaseValue(def.value) : null; if (chased !== null) { definePropEntries.push({ name: def.name, value: def.value }); - neutralize.push({ start: stmt.getStart(sf), end: stmt.getEnd() }); + neutralize.push({ start: tokenStartOfNode(stmt), end: stmt.end }); sawBundleShape = true; } // unchaseable descriptor: LEAVE the statement (its runtime fence @@ -792,7 +793,7 @@ export function rewriteBundlerCjsSyntax( // The table feeding module.exports keeps a getter it cannot chase // as a live accessor rather than a snapshot. if (spec.kind === "any" && value !== null && t.target === esbuildMainTarget) - claim(name, { name, kind: "getter", value: value.getText() }); + claim(name, { name, kind: "getter", value: getTextOfNode(value) }); else claim(name, spec); } } @@ -951,8 +952,8 @@ export function rewriteBundlerCjsSyntax( helperDecls.set( d.name.text, decls.length === 1 - ? { start: stmt.getStart(sf), end: stmt.getEnd() } - : { start: d.getStart(sf), end: d.getEnd() }, + ? { start: tokenStartOfNode(stmt), end: stmt.end } + : { start: tokenStartOfNode(d), end: d.end }, ); } if (anyHelper && decls.length > 1) helperStmts.push(stmt); @@ -966,8 +967,8 @@ export function rewriteBundlerCjsSyntax( if (parent !== undefined && ts.isVariableDeclaration(parent) && parent.name === node) return; const list = refs.get(node.text); - if (list === undefined) refs.set(node.text, [node.getStart(sf)]); - else list.push(node.getStart(sf)); + if (list === undefined) refs.set(node.text, [tokenStartOfNode(node)]); + else list.push(tokenStartOfNode(node)); } }; ts.walkPreorder(sf, record); @@ -999,12 +1000,12 @@ export function rewriteBundlerCjsSyntax( const deadAt = decls.map((d) => { if (!ts.isIdentifier(d.name) || !removed.has(d.name.text)) return false; const span = helperDecls.get(d.name.text)!; - if (span.start !== d.getStart(sf)) return false; + if (span.start !== tokenStartOfNode(d)) return false; declaratorSpans.add(span); return true; }); if (deadAt.every((x) => x)) { - declaratorPads.push({ start: stmt.getStart(sf), end: stmt.getEnd() }); + declaratorPads.push({ start: tokenStartOfNode(stmt), end: stmt.end }); continue; } // Dead declarators before the last live one take their trailing @@ -1015,8 +1016,8 @@ export function rewriteBundlerCjsSyntax( const d = decls[i]!; declaratorPads.push( i < lastLive - ? { start: d.getStart(sf), end: decls[i + 1]!.getStart(sf) } - : { start: decls[lastLive]!.getEnd(), end: d.getEnd() }, + ? { start: tokenStartOfNode(d), end: tokenStartOfNode(decls[i + 1]!) } + : { start: decls[lastLive]!.end, end: d.end }, ); } } diff --git a/packages/compiler/src/frontend/npm-static-subtyping.ts b/packages/compiler/src/frontend/npm-static-subtyping.ts index 04c66a559..18d69d4c4 100644 --- a/packages/compiler/src/frontend/npm-static-subtyping.ts +++ b/packages/compiler/src/frontend/npm-static-subtyping.ts @@ -19,8 +19,8 @@ export function isNpmStaticSubclassArgument( symbol.declarations.some( (declaration) => (ts.isClassDeclaration(declaration) || ts.isClassExpression(declaration)) && - /\.[cm]?js$/.test(declaration.getSourceFile().fileName) && - npmStaticPackageOfPath(declaration.getSourceFile().fileName) !== null, + /\.[cm]?js$/.test(ts.getSourceFileOfNode(declaration).fileName) && + npmStaticPackageOfPath(ts.getSourceFileOfNode(declaration).fileName) !== null, ) ); }; @@ -50,12 +50,12 @@ export function isNpmStaticSubclassArgument( }; let accepted = false; ts.walkPreorder(source, (node) => { - if (node.end < diagnostic.pos || node.getStart(source) > diagnostic.end) return "skip"; + if (node.end < diagnostic.pos || ts.tokenStartOfNode(node) > diagnostic.end) return "skip"; if (!ts.isCallExpression(node) && !ts.isNewExpression(node)) return undefined; const index = node.arguments?.nodes.findIndex( (argument) => - argument.getStart(source) === diagnostic.pos && argument.end === diagnostic.end, + ts.tokenStartOfNode(argument) === diagnostic.pos && argument.end === diagnostic.end, ) ?? -1; if (index < 0) return undefined; const parameter = checker.getResolvedSignature(node)?.getParameters()[index]; diff --git a/packages/compiler/src/frontend/program.ts b/packages/compiler/src/frontend/program.ts index c29f31c8a..2e75542d7 100644 --- a/packages/compiler/src/frontend/program.ts +++ b/packages/compiler/src/frontend/program.ts @@ -1236,7 +1236,7 @@ export function isEsModuleStamp(expr: ts.Expression): boolean { function pureCjsExport7(program: ts.Program, stmt: ts.Statement): boolean { const cjs = cjsExportAssignmentOf(stmt); if (cjs === null) return false; - const sf = stmt.getSourceFile(); + const sf = ts.getSourceFileOfNode(stmt); const plainMember = (left: ts.Expression): boolean => { if (!ts.isPropertyAccessExpression(left) || left.questionDotToken) return false; // A replacement export can have setters or non-writable function @@ -1291,7 +1291,7 @@ function sourceBinding7(program: ts.Program, ident: ts.Identifier): boolean { return ( symbol !== undefined && symbol.declarations.some( - (decl) => !decl.getSourceFile().isDeclarationFile && !ts.isSourceFile(decl), + (decl) => !ts.getSourceFileOfNode(decl).isDeclarationFile && !ts.isSourceFile(decl), ) ); } @@ -1318,7 +1318,7 @@ function purePrefixStmt7(program: ts.Program, s: ts.Statement): boolean { if (ts.isEmptyStatement(s) || ts.isFunctionDeclaration(s)) return true; if (ts.isExpressionStatement(s) && ts.isStringLiteral(s.expression)) return true; // directive if (isRequireStatement7(s)) return true; - if (isCjsJsFile7(s.getSourceFile(), program)) { + if (isCjsJsFile7(ts.getSourceFileOfNode(s), program)) { if (ts.isExpressionStatement(s) && isEsModuleStamp(s.expression)) { const call = s.expression as ts.CallExpression; const object = (call.expression as ts.PropertyAccessExpression).expression as ts.Identifier; @@ -1445,7 +1445,7 @@ function requireEarlyReference7( if (cached !== undefined) return cached; let found: ts.Identifier | null = null; const stmt = decl.parent?.parent; - const sf = decl.getSourceFile(); + const sf = ts.getSourceFileOfNode(decl); if (stmt !== undefined && ts.isVariableStatement(stmt) && ts.isSourceFile(stmt.parent)) { const stmts = sf.statements.nodes; const k = stmts.indexOf(stmt); @@ -2021,7 +2021,7 @@ function isCreateRequireImport7(program: ts.Program, ident: ts.Identifier): bool if (symbol !== undefined && (symbol.flags & ts.SymbolFlags.Alias) !== 0) { const target = checker.getAliasedSymbol(symbol); const targetDecl = target.declarations[0]; - if (targetDecl !== undefined && targetDecl.getSourceFile().isDeclarationFile) { + if (targetDecl !== undefined && ts.getSourceFileOfNode(targetDecl).isDeclarationFile) { for ( let p: ts.Node | undefined = targetDecl.parent; p !== undefined && !ts.isSourceFile(p); @@ -2029,7 +2029,8 @@ function isCreateRequireImport7(program: ts.Program, ident: ts.Identifier): bool ) { if (ts.isModuleDeclaration(p) && ts.isStringLiteral(p.name)) { return ( - canonicalBuiltinModule(p.name.text) === "module" && target.name === "createRequire" + canonicalBuiltinModule(p.name.text) === "module" && + ts.symbolName(target) === "createRequire" ); } } @@ -2113,7 +2114,7 @@ function stableCreateRequireBindingReason7( return "its createRequire binding is redeclared"; if ((ts.getCombinedModifierFlags(decl) & ts.ModifierFlags.Export) !== 0) return "its createRequire result escapes through an exported binding"; - const sf = decl.getSourceFile(); + const sf = ts.getSourceFileOfNode(decl); const refs = identifierOccurrences7(sf, decl.name.text); checker.prefetchSymbolNodesExact(refs); for (const ref of refs) { @@ -2180,7 +2181,7 @@ function npmStaticModuleImportReason7( } if (!bindings || !ts.isNamedImports(bindings)) return null; const checker = program.getTypeChecker(); - const sf = stmt.getSourceFile(); + const sf = ts.getSourceFileOfNode(stmt); for (const specifier of bindings.elements.nodes) { if ((specifier.propertyName?.text ?? specifier.name.text) !== "createRequire") continue; const imported = checker.getSymbolAtLocation(specifier.name); @@ -2260,7 +2261,7 @@ function requireCallBindingKind7(program: ts.Program, node: ts.Node): RequireCal const symbol = checker.getSymbolAtLocation(call.expression); if (symbol === undefined) return "ambient"; const decls = symbol.declarations; - if (decls.every((d) => d.getSourceFile().isDeclarationFile)) return "ambient"; + if (decls.every((d) => ts.getSourceFileOfNode(d).isDeclarationFile)) return "ambient"; return isCreateRequireBinding7(program, call.expression) ? "createRequire" : "source"; } @@ -2415,15 +2416,15 @@ function selfImportTdzFences7( // are initialized. Hoisted functions have none. const tdzEndOf = (aliased: ts.Symbol): number | null => { const d = aliased.valueDeclaration ?? aliased.declarations[0]; - if (d === undefined || d.getSourceFile() !== sf) return null; + if (d === undefined || ts.getSourceFileOfNode(d) !== sf) return null; if (ts.isFunctionDeclaration(d)) return null; - return d.getEnd(); + return d.end; }; const fence = (ref: ts.Identifier, name: string): void => { diags.push( unsupportedDiag( "SC1030", - { file: sf.fileName, start: ref.getStart(sf), end: ref.getEnd() }, + { file: sf.fileName, start: ts.tokenStartOfNode(ref), end: ref.end }, `the self-import binding '${name}' read above its declaration (the import aliases this module's OWN binding — Node initializes it when the declaration executes, so this read throws ReferenceError; move the read below the declaration)`, ), ); @@ -2439,7 +2440,7 @@ function selfImportTdzFences7( ts.isIdentifier(node) && node !== bindingName && node.text === bindingName.text && - node.getStart(sf) < end && + ts.tokenStartOfNode(node) < end && !insideFunction(node) && checker.getSymbolAtLocation(node) === sym ) { @@ -2472,7 +2473,7 @@ function selfImportTdzFences7( const memberSym = checker.getSymbolAtLocation(node.name); if (memberSym !== undefined) { const end = tdzEndOf(memberSym); - if (end !== null && node.getStart(sf) < end) + if (end !== null && ts.tokenStartOfNode(node) < end) fence(node.expression, `${nsName.text}.${node.name.text}`); } } @@ -2672,7 +2673,7 @@ function topLevelInertness7(program: ts.Program): (stmt: ts.Statement) => ts.Nod if (sym === undefined) return false; if (sym.flags & ts.SymbolFlags.Alias) sym = checker.getAliasedSymbol(sym); const decls = sym.declarations; - return decls.length > 0 && decls.every((d) => d.getSourceFile().isDeclarationFile); + return decls.length > 0 && decls.every((d) => ts.getSourceFileOfNode(d).isDeclarationFile); }; const hasDecorator = (n: ts.Node): boolean => ((n as { modifiers?: ts.NodeArray }).modifiers?.nodes ?? []).some( @@ -2946,8 +2947,8 @@ export function makeCycleAdmission( } }; const lineOf = (node: ts.Node): string => { - const nsf = node.getSourceFile(); - return `${nsf.fileName}:${ts.getLineAndCharacterOfPosition(nsf, node.getStart(nsf)).line + 1}`; + const nsf = ts.getSourceFileOfNode(node); + return `${nsf.fileName}:${ts.getLineAndCharacterOfPosition(nsf, ts.tokenStartOfNode(node)).line + 1}`; }; // Cluster verdict memo (keyed by the component array identity): true // when every member is an ES module passing the inert-top-level bar. @@ -3454,7 +3455,7 @@ function exactCycleRefusal7( const targetOf = (stmt: ts.ImportDeclaration | ts.ExportDeclaration): ts.SourceFile | null => { const spec = stmt.moduleSpecifier; if (spec === undefined || !ts.isStringLiteral(spec)) return null; - const sf = stmt.getSourceFile(); + const sf = ts.getSourceFileOfNode(stmt); return resolveImport7(program, sf, spec.text) ?? npmStaticDepSf7(program, sf, spec.text); }; for (const m of comp) { @@ -3688,8 +3689,8 @@ function computeCycleEarlyBindings7( module: m, exposed, rank, - start: stmt.getStart(m), - end: stmt.getEnd(), + start: ts.tokenStartOfNode(stmt), + end: stmt.end, reference: null, folded: null, }); @@ -3734,13 +3735,13 @@ function computeCycleEarlyBindings7( if ( c.kind === "class" && sf === c.module && - ref.getStart(sf) >= c.start && - ref.getEnd() <= c.end + ts.tokenStartOfNode(ref) >= c.start && + ref.end <= c.end ) continue; let early: boolean; if (inDeferredPosition7(ref)) early = c.exposed; - else if (sf === c.module) early = ref.getStart(sf) < c.start; + else if (sf === c.module) early = ts.tokenStartOfNode(ref) < c.start; else if (!memberSet.has(sf) || !rankOf.has(sf)) early = false; else early = !known || rankOf.get(sf)! < c.rank; if (early) c.reference = ref; @@ -3879,7 +3880,7 @@ function moduleEarlyBindingsOf7( store(stmt.name, [stmt]); } else if (ts.isClassDeclaration(stmt) && stmt.name !== undefined) { store(stmt.name, [stmt]); - candidates.push({ name: stmt.name, index, start: stmt.getStart(sf), cls: stmt }); + candidates.push({ name: stmt.name, index, start: ts.tokenStartOfNode(stmt), cls: stmt }); candidateNames.add(stmt.name.text); } else if (ts.isVariableStatement(stmt)) { const lexical = (stmt.declarationList.flags & (ts.NodeFlags.Let | ts.NodeFlags.Const)) !== 0; @@ -3891,7 +3892,7 @@ function moduleEarlyBindingsOf7( } if (!lexical) continue; for (const name of boundNames7(d.name)) { - candidates.push({ name, index, start: stmt.getStart(sf) }); + candidates.push({ name, index, start: ts.tokenStartOfNode(stmt) }); candidateNames.add(name.text); } } @@ -3983,9 +3984,10 @@ function moduleEarlyBindingsOf7( c !== undefined && node !== c.name && !reachedRefs.has(symbol) && - !(c.cls !== undefined && node.getStart(sf) >= c.cls.pos && node.end <= c.cls.end) + !(c.cls !== undefined && ts.tokenStartOfNode(node) >= c.cls.pos && node.end <= c.cls.end) ) { - if (inDeferredPosition7(node) || node.getStart(sf) < c.start) reachedRefs.set(symbol, node); + if (inDeferredPosition7(node) || ts.tokenStartOfNode(node) < c.start) + reachedRefs.set(symbol, node); } for (const unit of unitsBySymbol.get(symbol) ?? []) { if (reachedUnits.has(unit)) continue; @@ -4004,8 +4006,8 @@ function moduleEarlyBindingsOf7( const report = (declaration: ts.Node): void => { while ( next < candidates.length && - candidates[next]!.name.getStart(sf) >= declaration.getStart(sf) && - candidates[next]!.name.getEnd() <= declaration.getEnd() + ts.tokenStartOfNode(candidates[next]!.name) >= ts.tokenStartOfNode(declaration) && + candidates[next]!.name.end <= declaration.end ) { const c = candidates[next++]!; const symbol = checker.getSymbolAtLocation(c.name); @@ -4065,7 +4067,7 @@ function returnsOnlyClosuresOver7( const decls = symbol === undefined ? [] : symbol.declarations; if (decls.length !== 1) return false; const decl = decls[0]!; - if (decl.getSourceFile().isDeclarationFile) return false; + if (ts.getSourceFileOfNode(decl).isDeclarationFile) return false; let fn: ts.Node | undefined; if (ts.isFunctionDeclaration(decl)) fn = decl; else if (ts.isVariableDeclaration(decl) && decl.initializer !== undefined) { @@ -4186,12 +4188,13 @@ function laterJobCallee7(checker: ts.TypeChecker, callee: ts.Expression): boolea } if (symbol === undefined) return false; const decls = symbol.declarations; - if (decls.length === 0 || !decls.every((d) => d.getSourceFile().isDeclarationFile)) return false; + if (decls.length === 0 || !decls.every((d) => ts.getSourceFileOfNode(d).isDeclarationFile)) + return false; if (LATER_JOB_BUILTINS7.has(name.text)) return true; // The JavaScript standard library's callbacks mostly run synchronously; // package declarations describe user code. return decls.every((d) => { - const file = d.getSourceFile().fileName; + const file = ts.getSourceFileOfNode(d).fileName; return isNodeTypesPath(file) || file === fallbackDtsPath(); }); } @@ -4334,8 +4337,8 @@ function resolveNpmImport7( } function locOf7(node: ts.Node): { file: string; start: number; end: number } { - const sf = node.getSourceFile(); - return { file: sf.fileName, start: node.getStart(sf), end: node.getEnd() }; + const sf = ts.getSourceFileOfNode(node); + return { file: sf.fileName, start: ts.tokenStartOfNode(node), end: node.end }; } /** The program file holding an opted-in package's resolved runtime JS: @@ -4867,7 +4870,7 @@ function preflight7(load: LoadResult): { } if (reDep && !reDep.fileName.endsWith(".json")) { deps.push({ dep: reDep, stmt }); - depPositions.get(sf)!.push({ dep: reDep, pos: stmt.getStart(sf) }); + depPositions.get(sf)!.push({ dep: reDep, pos: ts.tokenStartOfNode(stmt) }); } continue; } @@ -4915,7 +4918,7 @@ function preflight7(load: LoadResult): { refusals.get(sf)!.push({ crash: { message, className, loc: locOf7(stmt) }, fallback: unsupportedDiag("SC1010", locOf7(stmt), fallbackFeature), - pos: stmt.getStart(sf), + pos: ts.tokenStartOfNode(stmt), }); }; // Project modules win before npm fallback: this is the source graph @@ -5128,7 +5131,7 @@ function preflight7(load: LoadResult): { } if (dep && !isJson) { deps.push({ dep, stmt }); - depPositions.get(sf)!.push({ dep, pos: stmt.getStart(sf) }); + depPositions.get(sf)!.push({ dep, pos: ts.tokenStartOfNode(stmt) }); } } { @@ -5168,7 +5171,11 @@ function preflight7(load: LoadResult): { for (let k = 0; k < stmts.length; k++) { const stmt = stmts[k]!; for (const req of requiresOf7(stmt)) { - const loc = { file: sf.fileName, start: req.node.getStart(sf), end: req.node.getEnd() }; + const loc = { + file: sf.fileName, + start: ts.tokenStartOfNode(req.node), + end: req.node.end, + }; const bindingKind = requireCallBindingKind7(program, req.node); // TypeScript source files join this scan only through the // canonical createRequire bridge. Their ordinary require-shaped @@ -5314,7 +5321,7 @@ function preflight7(load: LoadResult): { ); for (const call of nestedBareRequires) { const spec = requireSpecOf7(call)!; - const loc = { file: sf.fileName, start: call.getStart(sf), end: call.getEnd() }; + const loc = { file: sf.fileName, start: ts.tokenStartOfNode(call), end: call.end }; const bindingKind = requireCallBindingKind7(program, call); if (!jsRequireFile && bindingKind !== "createRequire") continue; if (isNodeEsmFile7(sf, program)) { @@ -5730,7 +5737,7 @@ function cjsNamedImportLinkCheck( // swaps ` as ` for `: ` — reproduced exactly, quirks included // (multi-line import statements simply lose the hint). const text = bad.sf.text; - const pos = bad.nameNode.getStart(bad.sf); + const pos = ts.tokenStartOfNode(bad.nameNode); const lineStart = text.lastIndexOf("\n", pos - 1) + 1; const lineEnd = text.indexOf("\n", pos); let line = text.slice(lineStart, lineEnd < 0 ? text.length : lineEnd); @@ -6044,7 +6051,7 @@ function earlierLinkCrash( if (!element.isTypeOnly) { entries.push({ local: element.name.text, - start: (element.propertyName ?? element.name).getStart(sf), + start: ts.tokenStartOfNode(element.propertyName ?? element.name), kind: 0, }); } @@ -6068,7 +6075,7 @@ function earlierLinkCrash( for (const element of exportClause.elements.nodes) { if ( !element.isTypeOnly && - (element.propertyName ?? element.name).getStart(sf) === crash.loc.start + ts.tokenStartOfNode(element.propertyName ?? element.name) === crash.loc.start ) { return [1, reexport]; } @@ -6134,7 +6141,11 @@ export function isJsSourceFile(sf: ts.SourceFile): boolean { export function isCheckedJsSourceFile(sf: ts.SourceFile, projectCheckJs: boolean): boolean { if (!isJsSourceFile(sf) || isNodeModulesPath(sf.fileName)) return false; let checked = projectCheckJs; - const head = sf.text.slice(0, sf.statements.nodes[0]?.getStart(sf) ?? sf.text.length); + const head = sf.text.slice( + 0, + (sf.statements.nodes.length === 0 ? undefined : ts.tokenStartOfNode(sf.statements.nodes[0]!)) ?? + sf.text.length, + ); for (const line of head.split("\n")) { const directive = /^\s*\/\/\s*@ts-(no)?check\b/.exec(line); if (directive) checked = directive[1] === undefined; @@ -6333,8 +6344,8 @@ export function cjsVoidInitPreambleReals( tail = tail.right; } if (!isVoid(tail)) return null; - const sf = cjs.expr.getSourceFile(); - const myPos = cjs.expr.getStart(sf); + const sf = ts.getSourceFileOfNode(cjs.expr); + const myPos = ts.tokenStartOfNode(cjs.expr); const reals = new Map< string, Extract, { kind: "member" }> @@ -6347,7 +6358,7 @@ export function cjsVoidInitPreambleReals( other?.kind === "member" && ts.isIdentifier(other.name) && other.name.text === name && - other.expr.getStart(sf) > myPos && + ts.tokenStartOfNode(other.expr) > myPos && !isVoid(other.value), ); if (real?.kind !== "member") return null; @@ -6505,7 +6516,7 @@ export function isCjsExportTableLiteral(obj: ts.ObjectLiteralExpression): boolea } const decl = obj.parent; if (!ts.isVariableDeclaration(decl) || decl.initializer !== obj) return false; - const sf = obj.getSourceFile(); + const sf = ts.getSourceFileOfNode(obj); if (!isJsSourceFile(sf)) return false; for (const stmt of sf.statements.nodes) { const cjs = cjsExportAssignmentOf(stmt); diff --git a/packages/compiler/src/frontend/services.test.ts b/packages/compiler/src/frontend/services.test.ts index bd172bbc5..e491b0ac5 100644 --- a/packages/compiler/src/frontend/services.test.ts +++ b/packages/compiler/src/frontend/services.test.ts @@ -7,6 +7,7 @@ import { Ts7Api } from "./ts7/rpc-api.js"; import { checkPreflight, isNodeEsmFile, loadProgram } from "./program.js"; import { clearResolveCaches } from "./resolve.js"; import type { Ts7ApiOptions } from "./ts7/program-host.js"; +import { getTextOfNode } from "./ts7/node-functions.js"; const directories: string[] = []; afterEach(() => { @@ -80,7 +81,7 @@ test("syntax sessions reuse one connection and returned ASTs survive edits and c services.close(); services.close(); expect(connections[0]!.close).toHaveBeenCalledTimes(1); - expect(before.statements.nodes[0]!.getText()).toBe(before.text); + expect(getTextOfNode(before.statements.nodes[0]!)).toBe(before.text); }); test("program hosts remain usable after their creating services close", () => { diff --git a/packages/compiler/src/frontend/tier-analysis.ts b/packages/compiler/src/frontend/tier-analysis.ts index 2ed5c8c9b..b75e491a8 100644 --- a/packages/compiler/src/frontend/tier-analysis.ts +++ b/packages/compiler/src/frontend/tier-analysis.ts @@ -107,8 +107,8 @@ function nodeAt(sf: ts.SourceFile, start: number, end: number): ts.Node { let node: ts.Node = sf; for (;;) { let next: ts.Node | undefined; - node.forEachChild((child) => { - if (child.getStart(sf) <= start && end <= child.getEnd()) { + ts.forEachChild(node, (child) => { + if (ts.tokenStartOfNode(child) <= start && end <= child.end) { next = child; return true; } @@ -263,23 +263,23 @@ function functionName(node: ts.Node): string { if (ts.isBinaryExpression(parent) && parent.right === node) { const left = parent.left; if (ts.isIdentifier(left)) return left.text; - if (ts.isPropertyAccessExpression(left)) return left.getText(); + if (ts.isPropertyAccessExpression(left)) return ts.getTextOfNode(left); } } - const sf = node.getSourceFile(); - const pos = ts.getLineAndCharacterOfPosition(sf, node.getStart(sf)); + const sf = ts.getSourceFileOfNode(node); + const pos = ts.getLineAndCharacterOfPosition(sf, ts.tokenStartOfNode(node)); return ``; } function locOfNode(node: ts.Node): SrcLoc { - const sf = node.getSourceFile(); - return { file: sf.fileName, start: node.getStart(sf), end: node.getEnd() }; + const sf = ts.getSourceFileOfNode(node); + return { file: sf.fileName, start: ts.tokenStartOfNode(node), end: node.end }; } function enclosingFunction(node: ts.Node): ts.Node { let current: ts.Node | undefined = node.parent; while (current !== undefined && !isFunctionLikeOrFile(current)) current = current.parent; - return current ?? node.getSourceFile(); + return current ?? ts.getSourceFileOfNode(node); } function functionUnit(node: ts.Node, instance: string | null): DemotionUnit { @@ -423,7 +423,10 @@ class UnitResolver { return this.declarationUnit(binding); } if (ts.isVariableDeclaration(decl) || ts.isParameter(decl) || ts.isBindingElement(decl)) { - const name = nameText(decl.name) ?? decl.name?.getText() ?? ""; + const name = + nameText(decl.name) ?? + (decl.name === undefined ? undefined : ts.getTextOfNode(decl.name)) ?? + ""; const scope = enclosingFunction(decl); const unit: DemotionUnit = { kind: "binding", name, loc: locOfNode(decl) }; if (ts.isParameter(decl) && ts.isFunctionLikeDeclaration(decl.parent)) @@ -437,7 +440,7 @@ class UnitResolver { /** The unit an operand expression denotes. */ valueUnit(expr: ts.Node): { unit: DemotionUnit; reflection: boolean } | null { const { root, viaPrototype } = rootOf(expr); - const sf = root.getSourceFile(); + const sf = ts.getSourceFileOfNode(root); if (root.kind === ts.SyntaxKind.ThisKeyword) { const owner = enclosingFunction(root); if (ts.isSourceFile(owner)) @@ -475,7 +478,7 @@ class UnitResolver { if (symbol !== undefined && (symbol.flags & ts.SymbolFlags.Alias) !== 0) symbol = this.checker.getAliasedSymbol(symbol); const decls = symbol === undefined ? [] : symbol.declarations; - const decl = decls.find((d) => !d.getSourceFile().isDeclarationFile); + const decl = decls.find((d) => !ts.getSourceFileOfNode(d).isDeclarationFile); if (decl === undefined) { // CommonJS `exports`/`module` as values: the module object. if (isModuleObjectName(root)) diff --git a/packages/compiler/src/frontend/ts7/api-conformance.ts b/packages/compiler/src/frontend/ts7/api-conformance.ts new file mode 100644 index 000000000..304b4f32f --- /dev/null +++ b/packages/compiler/src/frontend/ts7/api-conformance.ts @@ -0,0 +1,297 @@ +/* Compile-time checks that scriptc's AST and semantic objects have the shapes + * of tsc-ts/api's interfaces. The frontend keeps its own types, whose nodes + * extend the AstNode class, because native code reads class fields directly + * and reads through a structural interface dynamically. This module only + * declares types and emits nothing. */ + +import type * as api from "tsc-ts/api"; +import type { AstNode, AstNodeList } from "./ast-node.js"; +import type * as ast from "./ast-types.js"; + +type Conforms = [T, U]; + +/* Members where tsgo's wire, which the RPC model reads, differs from tsc-ts: + * - SourceFile.externalModuleIndicator is `true` where moduleDetection makes a + * file a module without an import or export. + * - JSDocTypeLiteral.jsdocPropertyTags is one tag. + * - SyntaxList.children is a node list. + * - SyntheticExpression's isSpread and type are factory fields that a parsed + * tree never has. */ +type Without = Omit; + +export interface AstConformance { + Node: Conforms; + NodeList: Conforms; + SourceFile: Conforms< + Without, + Without + >; + ArrayBindingPattern: Conforms; + ArrayLiteralExpression: Conforms; + ArrayTypeNode: Conforms; + ArrowFunction: Conforms; + AsExpression: Conforms; + AwaitExpression: Conforms; + BigIntLiteral: Conforms; + BinaryExpression: Conforms; + BindingElement: Conforms; + Block: Conforms; + BodyBase: Conforms; + BreakStatement: Conforms; + CallExpression: Conforms; + CallSignatureDeclaration: Conforms; + CaseBlock: Conforms; + CaseClause: Conforms; + CatchClause: Conforms; + ClassDeclaration: Conforms; + ClassElementBase: Conforms; + ClassExpression: Conforms; + ClassLikeBase: Conforms; + ClassStaticBlockDeclaration: Conforms< + ast.ClassStaticBlockDeclaration, + api.ClassStaticBlockDeclaration + >; + ComputedPropertyName: Conforms; + ConditionalExpression: Conforms; + ConditionalTypeNode: Conforms; + ConstructSignatureDeclaration: Conforms< + ast.ConstructSignatureDeclaration, + api.ConstructSignatureDeclaration + >; + ConstructorDeclaration: Conforms; + ConstructorTypeNode: Conforms; + ContinueStatement: Conforms; + DebuggerStatement: Conforms; + DeclarationBase: Conforms; + Decorator: Conforms; + DefaultClause: Conforms; + DeleteExpression: Conforms; + DoStatement: Conforms; + ElementAccessExpression: Conforms; + EmptyStatement: Conforms; + EnumDeclaration: Conforms; + EnumMember: Conforms; + ExportAssignment: Conforms; + ExportDeclaration: Conforms; + ExportSpecifier: Conforms; + ExpressionBase: Conforms; + ExpressionStatement: Conforms; + ExpressionWithTypeArguments: Conforms< + ast.ExpressionWithTypeArguments, + api.ExpressionWithTypeArguments + >; + ExternalModuleReference: Conforms; + ForInStatement: Conforms; + ForOfStatement: Conforms; + ForStatement: Conforms; + FunctionDeclaration: Conforms; + FunctionExpression: Conforms; + FunctionLikeBase: Conforms; + FunctionLikeWithBodyBase: Conforms; + FunctionTypeNode: Conforms; + GetAccessorDeclaration: Conforms; + HeritageClause: Conforms; + Identifier: Conforms; + IfStatement: Conforms; + ImportAttribute: Conforms; + ImportAttributes: Conforms; + ImportClause: Conforms; + ImportDeclaration: Conforms; + ImportEqualsDeclaration: Conforms; + ImportSpecifier: Conforms; + ImportTypeNode: Conforms; + IndexSignatureDeclaration: Conforms; + IndexedAccessTypeNode: Conforms; + InferTypeNode: Conforms; + InterfaceDeclaration: Conforms; + IntersectionTypeNode: Conforms; + IterationStatementBase: Conforms; + JSDoc: Conforms; + JSDocAllType: Conforms; + JSDocAugmentsTag: Conforms; + JSDocCallbackTag: Conforms; + JSDocCommentBase: Conforms; + JSDocDeprecatedTag: Conforms; + JSDocImplementsTag: Conforms; + JSDocImportTag: Conforms; + JSDocLink: Conforms; + JSDocLinkCode: Conforms; + JSDocLinkPlain: Conforms; + JSDocNameReference: Conforms; + JSDocNonNullableType: Conforms; + JSDocNullableType: Conforms; + JSDocOptionalType: Conforms; + JSDocOverloadTag: Conforms; + JSDocOverrideTag: Conforms; + JSDocParameterTag: Conforms; + JSDocPrivateTag: Conforms; + JSDocPropertyTag: Conforms; + JSDocProtectedTag: Conforms; + JSDocPublicTag: Conforms; + JSDocReadonlyTag: Conforms; + JSDocReturnTag: Conforms; + JSDocSatisfiesTag: Conforms; + JSDocSeeTag: Conforms; + JSDocSignature: Conforms; + JSDocTagBase: Conforms; + JSDocTemplateTag: Conforms; + JSDocText: Conforms; + JSDocThisTag: Conforms; + JSDocThrowsTag: Conforms; + JSDocTypeBase: Conforms; + JSDocTypeExpression: Conforms; + JSDocTypeLiteral: Conforms< + Without, + Without + >; + JSDocTypeTag: Conforms; + JSDocTypedefTag: Conforms; + JSDocUnknownTag: Conforms; + JSDocVariadicType: Conforms; + JsxAttribute: Conforms; + JsxAttributes: Conforms; + JsxClosingElement: Conforms; + JsxClosingFragment: Conforms; + JsxElement: Conforms; + JsxExpression: Conforms; + JsxFragment: Conforms; + JsxNamespacedName: Conforms; + JsxOpeningElement: Conforms; + JsxOpeningFragment: Conforms; + JsxSelfClosingElement: Conforms; + JsxSpreadAttribute: Conforms; + JsxText: Conforms; + KeywordExpression: Conforms; + KeywordTypeNode: Conforms; + LabeledStatement: Conforms; + LeftHandSideExpressionBase: Conforms< + ast.LeftHandSideExpressionBase, + api.LeftHandSideExpressionBase + >; + LiteralExpressionBase: Conforms; + LiteralLikeNodeBase: Conforms; + LiteralTypeNode: Conforms; + MappedTypeNode: Conforms; + MemberExpressionBase: Conforms; + MetaProperty: Conforms; + MethodDeclaration: Conforms; + MethodSignatureDeclaration: Conforms< + ast.MethodSignatureDeclaration, + api.MethodSignatureDeclaration + >; + MissingDeclaration: Conforms; + ModifiersBase: Conforms; + ModuleBlock: Conforms; + ModuleDeclaration: Conforms; + NamedExports: Conforms; + NamedImports: Conforms; + NamedMemberBase: Conforms; + NamedTupleMember: Conforms; + NamespaceExport: Conforms; + NamespaceExportDeclaration: Conforms< + ast.NamespaceExportDeclaration, + api.NamespaceExportDeclaration + >; + NamespaceImport: Conforms; + NewExpression: Conforms; + NoSubstitutionTemplateLiteral: Conforms< + ast.NoSubstitutionTemplateLiteral, + api.NoSubstitutionTemplateLiteral + >; + NodeBase: Conforms; + NodeWithTypeArgumentsBase: Conforms; + NonNullExpression: Conforms; + NotEmittedStatement: Conforms; + NotEmittedTypeElement: Conforms; + NumericLiteral: Conforms; + ObjectBindingPattern: Conforms; + ObjectLiteralElementBase: Conforms; + ObjectLiteralExpression: Conforms; + OmittedExpression: Conforms; + OptionalTypeNode: Conforms; + ParameterDeclaration: Conforms; + ParenthesizedExpression: Conforms; + ParenthesizedTypeNode: Conforms; + PartiallyEmittedExpression: Conforms< + ast.PartiallyEmittedExpression, + api.PartiallyEmittedExpression + >; + PostfixUnaryExpression: Conforms; + PrefixUnaryExpression: Conforms; + PrimaryExpressionBase: Conforms; + PrivateIdentifier: Conforms; + PropertyAccessExpression: Conforms; + PropertyAssignment: Conforms; + PropertyDeclaration: Conforms; + PropertySignatureDeclaration: Conforms< + ast.PropertySignatureDeclaration, + api.PropertySignatureDeclaration + >; + QualifiedName: Conforms; + RegularExpressionLiteral: Conforms; + RestTypeNode: Conforms; + ReturnStatement: Conforms; + SatisfiesExpression: Conforms; + SemicolonClassElement: Conforms; + SetAccessorDeclaration: Conforms; + ShorthandPropertyAssignment: Conforms< + ast.ShorthandPropertyAssignment, + api.ShorthandPropertyAssignment + >; + SpreadAssignment: Conforms; + SpreadElement: Conforms; + StatementBase: Conforms; + StringLiteral: Conforms; + SwitchStatement: Conforms; + SyntaxList: Conforms, Without>; + SyntheticExpression: Conforms< + Without, + Without + >; + SyntheticReferenceExpression: Conforms< + ast.SyntheticReferenceExpression, + api.SyntheticReferenceExpression + >; + TaggedTemplateExpression: Conforms; + TemplateExpression: Conforms; + TemplateHead: Conforms; + TemplateLiteralLikeNodeBase: Conforms< + ast.TemplateLiteralLikeNodeBase, + api.TemplateLiteralLikeNodeBase + >; + TemplateLiteralTypeNode: Conforms; + TemplateLiteralTypeSpan: Conforms; + TemplateMiddle: Conforms; + TemplateSpan: Conforms; + TemplateTail: Conforms; + ThisTypeNode: Conforms; + ThrowStatement: Conforms; + Token: Conforms; + TryStatement: Conforms; + TupleTypeNode: Conforms; + TypeAliasDeclaration: Conforms; + TypeAssertion: Conforms; + TypeElementBase: Conforms; + TypeLiteralNode: Conforms; + TypeNodeBase: Conforms; + TypeOfExpression: Conforms; + TypeOperatorNode: Conforms; + TypeParameterDeclaration: Conforms; + TypePredicateNode: Conforms; + TypeQueryNode: Conforms; + TypeReferenceNode: Conforms; + UnaryExpressionBase: Conforms; + UnionOrIntersectionTypeNodeBase: Conforms< + ast.UnionOrIntersectionTypeNodeBase, + api.UnionOrIntersectionTypeNodeBase + >; + UnionTypeNode: Conforms; + UpdateExpressionBase: Conforms; + VariableDeclaration: Conforms; + VariableDeclarationList: Conforms; + VariableStatement: Conforms; + VoidExpression: Conforms; + WhileStatement: Conforms; + WithStatement: Conforms; + YieldExpression: Conforms; +} diff --git a/packages/compiler/src/frontend/ts7/ast-guards.generated.ts b/packages/compiler/src/frontend/ts7/ast-guards.generated.ts index aeaf56e9a..d163ac18d 100644 --- a/packages/compiler/src/frontend/ts7/ast-guards.generated.ts +++ b/packages/compiler/src/frontend/ts7/ast-guards.generated.ts @@ -4,6 +4,7 @@ import type { AbstractKeyword, AccessExpression, AccessorDeclaration, AccessorKeyword, AdditiveOperator, AdditiveOperatorOrHigher, AnyImportSyntax, ArrayBindingElement, ArrayBindingPattern, ArrayDestructuringAssignment, ArrayLiteralExpression, ArrayTypeNode, ArrowFunction, AsExpression, AssertKeyword, AssertionExpression, AssertsKeyword, AssignmentOperator, AssignmentOperatorOrHigher, AssignmentOperatorToken, AsteriskToken, AsyncKeyword, AwaitExpression, AwaitKeyword, BigIntLiteral, BinaryExpression, BinaryOperator, BinaryOperatorToken, BindingElement, BindingName, BindingPattern, BitwiseOperator, BitwiseOperatorOrHigher, Block, BlockOrExpression, BooleanLiteral, BreakOrContinueStatement, BreakStatement, CallExpression, CallLikeExpression, CallOrNewExpression, CallSignatureDeclaration, CaseBlock, CaseClause, CaseKeyword, CatchClause, ClassDeclaration, ClassExpression, ClassLikeDeclaration, ClassStaticBlockDeclaration, ColonToken, CompoundAssignmentOperator, ComputedPropertyName, ConciseBody, ConditionalExpression, ConditionalTypeNode, ConstKeyword, ConstructSignatureDeclaration, ConstructorDeclaration, ConstructorTypeNode, ContinueStatement, DebuggerStatement, DeclarationName, DeclareKeyword, Decorator, DefaultClause, DefaultKeyword, DeleteExpression, DestructuringAssignment, DoStatement, DotDotDotToken, DotToken, ElementAccessExpression, EmptyStatement, EndOfFile, EntityName, EnumDeclaration, EnumMember, EqualityOperator, EqualityOperatorOrHigher, EqualsGreaterThanToken, EqualsToken, ExclamationToken, ExponentiationOperator, ExportAssignment, ExportDeclaration, ExportKeyword, ExportSpecifier, Expression, ExpressionStatement, ExpressionWithTypeArguments, ExternalModuleReference, FalseLiteral, ForInStatement, ForInitializer, ForOfStatement, ForStatement, FunctionBody, FunctionDeclaration, FunctionExpression, FunctionLikeDeclaration, FunctionTypeNode, GetAccessorDeclaration, HeritageClause, Identifier, IfStatement, ImportAttribute, ImportAttributeName, ImportAttributes, ImportClause, ImportClauseOrBindingPattern, ImportDeclaration, ImportEqualsDeclaration, ImportExpression, ImportPhaseModifierSyntaxKind, ImportSpecifier, ImportTypeNode, InKeyword, IndexSignatureDeclaration, IndexedAccessTypeNode, InferTypeNode, InterfaceDeclaration, IntersectionTypeNode, JSDoc, JSDocAllType, JSDocAugmentsTag, JSDocCallbackTag, JSDocComment, JSDocDeprecatedTag, JSDocFullName, JSDocImplementsTag, JSDocImportTag, JSDocLink, JSDocLinkCode, JSDocLinkPlain, JSDocNameReference, JSDocNonNullableType, JSDocNullableType, JSDocOptionalType, JSDocOverloadTag, JSDocOverrideTag, JSDocParameterTag, JSDocPrivateTag, JSDocPropertyTag, JSDocProtectedTag, JSDocPublicTag, JSDocReadonlyTag, JSDocReturnTag, JSDocSatisfiesTag, JSDocSeeTag, JSDocSignature, JSDocTemplateTag, JSDocText, JSDocThisTag, JSDocThrowsTag, JSDocTypeExpression, JSDocTypeLiteral, JSDocTypeTag, JSDocTypedefTag, JSDocUnknownTag, JSDocVariadicType, JsxAttribute, JsxAttributeLike, JsxAttributeName, JsxAttributeValue, JsxAttributes, JsxChild, JsxClosingElement, JsxClosingFragment, JsxElement, JsxExpression, JsxFragment, JsxNamespacedName, JsxOpeningElement, JsxOpeningFragment, JsxOpeningLikeElement, JsxSelfClosingElement, JsxSpreadAttribute, JsxTagNameExpression, JsxText, JsxTokenSyntaxKind, KeywordExpression, KeywordExpressionSyntaxKind, KeywordTypeNode, KeywordTypeSyntaxKind, LabeledStatement, LeftHandSideExpression, LiteralExpression, LiteralLikeNode, LiteralToken, LiteralTypeNode, LogicalOperator, LogicalOperatorOrHigher, LogicalOrCoalescingAssignmentOperator, MappedTypeNode, MemberName, MetaProperty, MethodDeclaration, MethodSignatureDeclaration, MinusToken, MissingDeclaration, Modifier, ModifierLike, ModifierSyntaxKind, ModuleBlock, ModuleBody, ModuleDeclaration, ModuleExportName, ModuleName, ModuleReference, MultiplicativeOperator, MultiplicativeOperatorOrHigher, NamedExportBindings, NamedExports, NamedImportBindings, NamedImports, NamedImportsOrExports, NamedTupleMember, NamespaceExport, NamespaceExportDeclaration, NamespaceImport, NewExpression, NoSubstitutionTemplateLiteral, Node, NonNullExpression, NotEmittedStatement, NotEmittedTypeElement, NullLiteral, NumericLiteral, NumericOrStringLikeLiteral, ObjectBindingPattern, ObjectDestructuringAssignment, ObjectLiteralElementLike, ObjectLiteralExpression, ObjectLiteralLikeNode, ObjectTypeDeclaration, OmittedExpression, OptionalTypeNode, OutKeyword, OverrideKeyword, ParameterDeclaration, ParenthesizedExpression, ParenthesizedTypeNode, PartiallyEmittedExpression, PlusToken, PostfixUnaryExpression, PostfixUnaryOperator, PrefixUnaryExpression, PrefixUnaryOperator, PrivateIdentifier, PrivateKeyword, PropertyAccessExpression, PropertyAssignment, PropertyDeclaration, PropertyName, PropertyNameLiteral, PropertySignatureDeclaration, ProtectedKeyword, PseudoLiteralSyntaxKind, PseudoLiteralToken, PublicKeyword, QualifiedName, QuestionDotToken, QuestionToken, ReadonlyKeyword, RegularExpressionLiteral, RelationalOperator, RelationalOperatorOrHigher, RestTypeNode, ReturnStatement, SatisfiesExpression, SemicolonClassElement, SetAccessorDeclaration, ShiftOperator, ShiftOperatorOrHigher, ShorthandPropertyAssignment, SignatureDeclaration, SourceFile, SpreadAssignment, SpreadElement, Statement, StaticKeyword, StringLiteral, StringLiteralLikeNode, SuperExpression, SwitchStatement, SyntaxList, SyntheticExpression, SyntheticReferenceExpression, TaggedTemplateExpression, TemplateExpression, TemplateHead, TemplateLiteral, TemplateLiteralLikeNode, TemplateLiteralToken, TemplateLiteralTypeNode, TemplateLiteralTypeSpan, TemplateMiddle, TemplateMiddleOrTail, TemplateSpan, TemplateTail, ThisExpression, ThisTypeNode, ThrowStatement, Token, TriviaSyntaxKind, TrueLiteral, TryStatement, TupleTypeNode, TypeAliasDeclaration, TypeAssertion, TypeLiteralNode, TypeNode, TypeOfExpression, TypeOperatorNode, TypeParameterDeclaration, TypePredicateNode, TypePredicateParameterName, TypeQueryNode, TypeReferenceNode, UnaryExpressionBase, UnionOrIntersectionTypeNode, UnionTypeNode, VariableDeclaration, VariableDeclarationList, VariableOrParameterDeclaration, VariableOrPropertyDeclaration, VariableStatement, VoidExpression, WhileStatement, WithStatement, YieldExpression } from "./ast-types.js"; import { SyntaxKind, NodeFlags, ScriptKind, OuterExpressionKinds } from "./enums.js"; +import { getSourceFileOfNode } from "./node-functions.js"; export function isToken(node: Node | undefined): node is Token { if (node === undefined) return false; @@ -1913,7 +1914,7 @@ export function skipOuterExpressions(node: Node, kinds: OuterExpressionKinds = O function isJSDocTypeAssertion(node: Node | undefined): boolean { if (node === undefined) return false; - const sourceFile = node.getSourceFile(); + const sourceFile = getSourceFileOfNode(node); if (sourceFile.scriptKind !== ScriptKind.JS && sourceFile.scriptKind !== ScriptKind.JSX) { return false; } diff --git a/packages/compiler/src/frontend/ts7/ast-node.test.ts b/packages/compiler/src/frontend/ts7/ast-node.test.ts index e2440b787..b9b8f0e7b 100644 --- a/packages/compiler/src/frontend/ts7/ast-node.test.ts +++ b/packages/compiler/src/frontend/ts7/ast-node.test.ts @@ -20,7 +20,13 @@ import { walkPreorder } from "./ast.js"; import { ts7Executable } from "./rpc-api.js"; import { Ts7RpcClient } from "./rpc-client.js"; import { spawnTs7Wire } from "./rpc-process.js"; -import { isJSDocTypeLiteral, isJSDocPropertyTag } from "./ast-guards.generated.js"; +import { + isArrayLiteralExpression, + isCallExpression, + isJSDocTypeLiteral, + isJSDocPropertyTag, +} from "./ast-guards.generated.js"; +import { ModifierFlags, SyntaxKind } from "./enums.js"; const require = createRequire(import.meta.url); const sdkRoot = dirname(require.resolve("typescript/package.json")); @@ -95,6 +101,8 @@ const cases: Record = { '/** @typedef {Object} Options\n * @property {string} name\n * @property {number} count\n */\n/** @type {Options} */ const options = { name: "x", count: 2 };', "missing.ts": "const incomplete = ;\nfunction f( {\n", "empty.ts": "", + "lists.ts": + "const trailing = [1, 2 /* last */, ];\nconst plain = [1, 2];\nf(1, 2,);\nexport abstract class Flags { protected static readonly value = 1; }\n", }; const decoded = new Map(); let directory: string; @@ -159,6 +167,26 @@ function ids( return nodes?.map((n) => (n as OracleNode).index); } +test("node lists report a trailing comma and modifier lists their flags", () => { + const { file } = decoded.get("lists.ts")!; + const lists: [SyntaxKind, boolean][] = []; + const modifiers: number[] = []; + walkPreorder(file.root, (node) => { + if (isArrayLiteralExpression(node)) lists.push([node.kind, node.elements.hasTrailingComma()]); + if (isCallExpression(node)) lists.push([node.kind, node.arguments.hasTrailingComma()]); + if (node.modifiers !== undefined) modifiers.push(node.modifiers.modifierFlags); + }); + expect(lists).toEqual([ + [SyntaxKind.ArrayLiteralExpression, true], + [SyntaxKind.ArrayLiteralExpression, false], + [SyntaxKind.CallExpression, true], + ]); + expect(modifiers).toEqual([ + ModifierFlags.Export | ModifierFlags.Abstract, + ModifierFlags.Protected | ModifierFlags.Static | ModifierFlags.Readonly, + ]); +}); + test("JSDoc property refinements describe the pinned native wire", () => { const { file, oracle } = decoded.get("properties.js")!; let checked = 0; diff --git a/packages/compiler/src/frontend/ts7/ast-node.ts b/packages/compiler/src/frontend/ts7/ast-node.ts index 3760fff8d..8eaa87c72 100644 --- a/packages/compiler/src/frontend/ts7/ast-node.ts +++ b/packages/compiler/src/frontend/ts7/ast-node.ts @@ -35,6 +35,25 @@ export class AstNodeList { get end(): number { return this.wire.end(this.index) >>> 0; } + + /** Whether a comma follows the last node before the list's end. */ + hasTrailingComma(): boolean { + const last = this.nodes[this.nodes.length - 1]; + if (last === undefined) return false; + const text = last.getSourceFile().text; + const next = astSkipTrivia(text, last.end, false, false); + return next < this.end && text.charCodeAt(next) === 44; + } +} + +/** A declaration's modifiers, with the flags of all of them: tsc-ts's + * ModifierList. */ +export class AstModifierList extends AstNodeList { + get modifierFlags(): number { + let result = 0; + for (const modifier of this.nodes) result |= astModifierFlag(modifier.kind); + return result; + } } /** One immutable AST response owns one object per node and one array per @@ -85,6 +104,12 @@ export class AstFile { } list(index: number): AstNodeList { + return new AstNodeList(this.wire, index, this.listNodes(index)); + } + modifierList(index: number): AstModifierList { + return new AstModifierList(this.wire, index, this.listNodes(index)); + } + private listNodes(index: number): AstNode[] { let nodes = this.lists[index]; if (nodes === undefined) { nodes = []; @@ -92,7 +117,7 @@ export class AstFile { this.lists[index] = nodes; this.materialized?.(); } - return new AstNodeList(this.wire, index, nodes); + return nodes; } namedChild(node: AstNode, name: string): number { @@ -422,6 +447,11 @@ export class AstNode { return result; } + get modifiers(): AstModifierList | undefined { + const index = this.file.namedChild(this, "modifiers"); + return index === 0 ? undefined : this.file.modifierList(index); + } + get body(): AstNode | undefined { const body = this.childNode("body"); // TS7 can serialize a reparsed @type callable before a method's @@ -599,9 +629,6 @@ export class AstNode { get members(): AstNodeList | undefined { return this.childList("members"); } - get modifiers(): AstNodeList | undefined { - return this.childList("modifiers"); - } get moduleReference(): AstNode | undefined { return this.childNode("moduleReference"); } diff --git a/packages/compiler/src/frontend/ts7/ast-types.generated.ts b/packages/compiler/src/frontend/ts7/ast-types.generated.ts index de6ff892f..59f01e9ea 100644 --- a/packages/compiler/src/frontend/ts7/ast-types.generated.ts +++ b/packages/compiler/src/frontend/ts7/ast-types.generated.ts @@ -3,7 +3,7 @@ // Regenerate when changing the TypeScript pin; do not edit by hand. import type { AstNode } from "./ast-node.js"; -import type { JsxTagNamePropertyAccess, NodeArray } from "./ast-types.js"; +import type { JsxTagNamePropertyAccess, ModifierArray, NodeArray } from "./ast-types.js"; import { SyntaxKind, NodeFlags, ModifierFlags, TokenFlags } from "./enums.js"; type Node = AstNode; @@ -142,7 +142,7 @@ export interface DeclarationBase extends Node { } export interface ModifiersBase extends Node { - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly kind: SyntaxKind.VariableStatement | SyntaxKind.Parameter | SyntaxKind.MissingDeclaration | SyntaxKind.FunctionDeclaration | SyntaxKind.ClassDeclaration | SyntaxKind.ClassExpression | SyntaxKind.InterfaceDeclaration | SyntaxKind.TypeAliasDeclaration | SyntaxKind.EnumMember | SyntaxKind.EnumDeclaration | SyntaxKind.ImportDeclaration | SyntaxKind.ExportAssignment | SyntaxKind.NamespaceExportDeclaration | SyntaxKind.Constructor | SyntaxKind.GetAccessor | SyntaxKind.SetAccessor | SyntaxKind.IndexSignature | SyntaxKind.MethodSignature | SyntaxKind.MethodDeclaration | SyntaxKind.PropertySignature | SyntaxKind.PropertyDeclaration | SyntaxKind.ClassStaticBlockDeclaration | SyntaxKind.BinaryExpression | SyntaxKind.ArrowFunction | SyntaxKind.FunctionExpression | SyntaxKind.PropertyAssignment | SyntaxKind.ShorthandPropertyAssignment | SyntaxKind.FunctionType | SyntaxKind.ConstructorType | SyntaxKind.ModuleDeclaration | SyntaxKind.ImportEqualsDeclaration | SyntaxKind.ExportDeclaration | SyntaxKind.TypeParameter; } @@ -170,7 +170,7 @@ export interface FunctionLikeWithBodyBase extends Node { } export interface ClassLikeBase extends Node { - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly name: Identifier | undefined; readonly typeParameters: NodeArray | undefined; @@ -209,7 +209,7 @@ export interface ClassElementBase extends Node { } export interface NamedMemberBase extends Node { - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly name: PropertyName; readonly postfixToken: QuestionToken | ExclamationToken | undefined; @@ -397,7 +397,7 @@ export interface Block extends Node { export interface VariableStatement extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly kind: SyntaxKind.VariableStatement; readonly declarationList: VariableDeclarationList; @@ -420,7 +420,7 @@ export interface VariableDeclarationList extends Node { export interface ParameterDeclaration extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly kind: SyntaxKind.Parameter; readonly dotDotDotToken: DotDotDotToken | undefined; @@ -441,14 +441,14 @@ export interface BindingElement extends Node { export interface MissingDeclaration extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly kind: SyntaxKind.MissingDeclaration; } export interface FunctionDeclaration extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly typeParameters: NodeArray | undefined; readonly parameters: NodeArray; @@ -461,7 +461,7 @@ export interface FunctionDeclaration extends Node { export interface ClassDeclaration extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly name: Identifier | undefined; readonly typeParameters: NodeArray | undefined; @@ -472,7 +472,7 @@ export interface ClassDeclaration extends Node { export interface ClassExpression extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly name: Identifier | undefined; readonly typeParameters: NodeArray | undefined; @@ -490,7 +490,7 @@ export interface HeritageClause extends Node { export interface InterfaceDeclaration extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly kind: SyntaxKind.InterfaceDeclaration; readonly name: Identifier; @@ -501,7 +501,7 @@ export interface InterfaceDeclaration extends Node { export interface TypeAliasDeclaration extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly kind: SyntaxKind.TypeAliasDeclaration; readonly name: Identifier; @@ -511,7 +511,7 @@ export interface TypeAliasDeclaration extends Node { export interface EnumMember extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly name: PropertyName; readonly postfixToken: QuestionToken | ExclamationToken | undefined; @@ -521,7 +521,7 @@ export interface EnumMember extends Node { export interface EnumDeclaration extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly kind: SyntaxKind.EnumDeclaration; readonly name: Identifier; @@ -546,7 +546,7 @@ export interface NotEmittedTypeElement extends Node { export interface ImportDeclaration extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly kind: SyntaxKind.ImportDeclaration; readonly importClause: ImportClause | undefined; @@ -574,7 +574,7 @@ export interface NamedImports extends Node { export interface ExportAssignment extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly kind: SyntaxKind.ExportAssignment; readonly isExportEquals: boolean; @@ -584,7 +584,7 @@ export interface ExportAssignment extends Node { export interface NamespaceExportDeclaration extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly kind: SyntaxKind.NamespaceExportDeclaration; readonly name: Identifier; @@ -628,7 +628,7 @@ export interface ConstructSignatureDeclaration extends Node { export interface ConstructorDeclaration extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly typeParameters: NodeArray | undefined; readonly parameters: NodeArray; @@ -639,7 +639,7 @@ export interface ConstructorDeclaration extends Node { } export interface GetAccessorDeclaration extends Node { - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly name: PropertyName; readonly postfixToken: QuestionToken | ExclamationToken | undefined; @@ -653,7 +653,7 @@ export interface GetAccessorDeclaration extends Node { } export interface SetAccessorDeclaration extends Node { - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly name: PropertyName; readonly postfixToken: QuestionToken | ExclamationToken | undefined; @@ -668,7 +668,7 @@ export interface SetAccessorDeclaration extends Node { export interface IndexSignatureDeclaration extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly typeParameters: NodeArray | undefined; readonly parameters: NodeArray; @@ -678,7 +678,7 @@ export interface IndexSignatureDeclaration extends Node { export interface MethodSignatureDeclaration extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly name: PropertyName; readonly postfixToken: QuestionToken | ExclamationToken | undefined; @@ -690,7 +690,7 @@ export interface MethodSignatureDeclaration extends Node { export interface MethodDeclaration extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly name: PropertyName; readonly postfixToken: QuestionToken | ExclamationToken | undefined; @@ -704,7 +704,7 @@ export interface MethodDeclaration extends Node { export interface PropertySignatureDeclaration extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly name: PropertyName; readonly postfixToken: QuestionToken | ExclamationToken | undefined; @@ -715,7 +715,7 @@ export interface PropertySignatureDeclaration extends Node { export interface PropertyDeclaration extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly name: PropertyName; readonly postfixToken: QuestionToken | ExclamationToken | undefined; @@ -731,7 +731,7 @@ export interface SemicolonClassElement extends Node { export interface ClassStaticBlockDeclaration extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly kind: SyntaxKind.ClassStaticBlockDeclaration; readonly body: Block; @@ -786,7 +786,7 @@ export interface NoSubstitutionTemplateLiteral extends Node { export interface BinaryExpression extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly kind: SyntaxKind.BinaryExpression; readonly left: Expression; @@ -818,7 +818,7 @@ export interface YieldExpression extends Node { export interface ArrowFunction extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly typeParameters: NodeArray | undefined; readonly parameters: NodeArray; @@ -831,7 +831,7 @@ export interface ArrowFunction extends Node { export interface FunctionExpression extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly typeParameters: NodeArray | undefined; readonly parameters: NodeArray; @@ -969,7 +969,7 @@ export interface SpreadAssignment extends Node { export interface PropertyAssignment extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly name: PropertyName; readonly postfixToken: QuestionToken | ExclamationToken | undefined; @@ -980,7 +980,7 @@ export interface PropertyAssignment extends Node { export interface ShorthandPropertyAssignment extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly name: PropertyName; readonly postfixToken: QuestionToken | ExclamationToken | undefined; @@ -1180,7 +1180,7 @@ export interface ParenthesizedTypeNode extends Node { export interface FunctionTypeNode extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly typeParameters: NodeArray | undefined; readonly parameters: NodeArray; @@ -1190,7 +1190,7 @@ export interface FunctionTypeNode extends Node { export interface ConstructorTypeNode extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly typeParameters: NodeArray | undefined; readonly parameters: NodeArray; @@ -1562,7 +1562,7 @@ export interface JSDocNameReference extends Node { export interface ModuleDeclaration extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly asteriskToken: AsteriskToken | undefined; readonly body: ModuleBody | undefined; @@ -1573,7 +1573,7 @@ export interface ModuleDeclaration extends Node { export interface ImportEqualsDeclaration extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly kind: SyntaxKind.ImportEqualsDeclaration; readonly isTypeOnly: boolean; @@ -1583,7 +1583,7 @@ export interface ImportEqualsDeclaration extends Node { export interface ExportDeclaration extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly kind: SyntaxKind.ExportDeclaration; readonly isTypeOnly: boolean; @@ -1647,7 +1647,7 @@ export interface JSDocLinkCode extends Node { export interface TypeParameterDeclaration extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly kind: SyntaxKind.TypeParameter; readonly name: Identifier; @@ -1842,7 +1842,7 @@ export interface CaseClause extends Node { export interface DefaultClause extends Node { readonly flags: NodeFlags; readonly kind: SyntaxKind.DefaultClause; - readonly expression: Expression; + readonly expression: undefined; readonly statements: NodeArray; } diff --git a/packages/compiler/src/frontend/ts7/ast-types.ts b/packages/compiler/src/frontend/ts7/ast-types.ts index 6d421e58d..e40e40e30 100644 --- a/packages/compiler/src/frontend/ts7/ast-types.ts +++ b/packages/compiler/src/frontend/ts7/ast-types.ts @@ -1,4 +1,4 @@ -import type { AstNode, AstNodeList } from "./ast-node.js"; +import type { AstModifierList, AstNode, AstNodeList } from "./ast-node.js"; import { SyntaxKind } from "./enums.js"; import type { EndOfFile, @@ -14,8 +14,11 @@ export type Node = AstNode; /** A node list. The type is the list class itself, not a structural shape: * native code represents a class and a record type differently. */ export type NodeArray = AstNodeList; +/** A declaration's modifiers: a node list with the flags of all of them. */ +export type ModifierArray = AstModifierList; export type Path = string; -export type __String = string; +/** An escaped symbol name, the key of member and export tables (TypeScript 7's `__String`). */ +export type __String = string & { readonly __escapedIdentifier: void }; export interface TextRange { pos: number; end: number; diff --git a/packages/compiler/src/frontend/ts7/ast.ts b/packages/compiler/src/frontend/ts7/ast.ts index ec26aa0b0..a92cfafd8 100644 --- a/packages/compiler/src/frontend/ts7/ast.ts +++ b/packages/compiler/src/frontend/ts7/ast.ts @@ -53,6 +53,7 @@ import { ModifierFlags, SyntaxKind } from "./enums.js"; // The frontend's node types, guards and token spellings share the concrete // AstNode layout. Parser and transform factories remain outside this client. export * from "./ast-types.js"; +export * from "./node-functions.js"; export * from "./ast-guards.generated.js"; export { tokenToString } from "./ast-tokens.generated.js"; diff --git a/packages/compiler/src/frontend/ts7/checker.test.ts b/packages/compiler/src/frontend/ts7/checker.test.ts index 7b3647b2d..ac062b283 100644 --- a/packages/compiler/src/frontend/ts7/checker.test.ts +++ b/packages/compiler/src/frontend/ts7/checker.test.ts @@ -10,10 +10,10 @@ import { ts7Executable } from "./rpc-api.js"; import { Ts7Session } from "./session.js"; import { SemanticChecker } from "./semantic-checker.js"; import { SemanticSnapshot } from "./semantic-model.js"; -import { TypeFlags } from "./enums.js"; +import { SignatureKind, SymbolFlags, TypeFlags } from "./enums.js"; import { createProgram, Ts7Host } from "./program-adapter.js"; import type { AstNode } from "./ast-node.js"; -import type { SourceFile } from "./ast-types.js"; +import type { Identifier, SourceFile } from "./ast-types.js"; const tempRoot = process.platform === "win32" ? tmpdir() : "/tmp"; const protocolPath = (path: string) => path.replaceAll("\\", "/"); @@ -167,6 +167,12 @@ test("all public query and prefetch entry points reject a disposed facade before const calls: Record unknown> = { getCallSignatures: () => facade.getCallSignatures(type), getConstructSignatures: () => facade.getConstructSignatures(type), + getSignaturesOfType: () => facade.getSignaturesOfType(type, SignatureKind.Call), + resolveName: () => facade.resolveName("value", SymbolFlags.Value, name), + getResolvedSymbol: () => facade.getResolvedSymbol(name as Identifier), + getAnyType: () => facade.getAnyType(), + getNeverType: () => facade.getNeverType(), + getBigIntType: () => facade.getBigIntType(), getPropertyOfType: () => facade.getPropertyOfType(type, "missing"), prefetchSourceFile: () => facade.prefetchSourceFile(root), prefetchSourceFileStructures: () => facade.prefetchSourceFileStructures([]), diff --git a/packages/compiler/src/frontend/ts7/checker.ts b/packages/compiler/src/frontend/ts7/checker.ts index 41eb0d0b9..4f2d60294 100644 --- a/packages/compiler/src/frontend/ts7/checker.ts +++ b/packages/compiler/src/frontend/ts7/checker.ts @@ -35,7 +35,7 @@ import { CheckerCache } from "./checker-cache.js"; * repeats an IPC request. All paths are verified against the raw checker * and against 5.9.3 by the adapter's suites. */ -import type { Node, SourceFile } from "./ast-types.js"; +import type { Identifier, Node, SourceFile } from "./ast-types.js"; import type { IndexInfo, InterfaceType, @@ -46,9 +46,10 @@ import type { TypeReference, } from "./semantic-types.js"; import type { SemanticChecker as Checker } from "./semantic-checker.js"; -import type { SemanticProject as Project } from "./semantic-model.js"; +import { symbolNameOf, type SemanticProject as Project } from "./semantic-model.js"; import { isTypeNode, walkPreorder } from "./ast.js"; -import { SignatureKind, SyntaxKind, TypeFlags } from "./enums.js"; +import { SignatureKind, SyntaxKind, TypeFlags, type SymbolFlags } from "./enums.js"; +import { getSourceFileOfNode } from "./node-functions.js"; /** Array-overload chunk size: large enough that per-request overhead * vanishes, small enough to keep any single JSON-RPC payload modest. */ @@ -259,6 +260,14 @@ export class CheckerFacade { return sigs; } + /** The call or construct signatures of `type`, as getCallSignatures and getConstructSignatures + * answer them. */ + getSignaturesOfType(type: Type, kind: SignatureKind): readonly Signature[] { + return kind === SignatureKind.Call + ? this.getCallSignatures(type) + : this.getConstructSignatures(type); + } + /** 5.9.3's type.getProperty(name). */ getPropertyOfType(type: Type, name: string): Ts7Symbol | undefined { this.ensureActive(); @@ -370,7 +379,7 @@ export class CheckerFacade { private markManaged(roots: readonly Node[]): void { for (const root of roots) { - const sf = root.getSourceFile(); + const sf = getSourceFileOfNode(root); this.cache.managedTypes.add(sf); this.cache.managedSymbols.add(sf); } @@ -447,7 +456,7 @@ export class CheckerFacade { private autoPrefetch(node: Node, kind: "types" | "symbols"): void { if (this.options.autoPrefetch === false) return; - const sf = node.getSourceFile(); + const sf = getSourceFileOfNode(node); // A referenced declaration rarely needs the rest of its library's // metadata. Keep those queries direct; explicit whole-file prefetch // remains available to consumers that do need the complete surface. @@ -786,6 +795,38 @@ export class CheckerFacade { return this.raw.getTypeOfSymbolAtLocation(symbol, location); } + resolveName( + name: string, + meaning: SymbolFlags, + location?: Node, + excludeGlobals?: boolean, + ): Ts7Symbol | undefined { + this.ensureActive(); + return this.raw.resolveName(name, meaning, location, excludeGlobals); + } + + /** The value `node` names where it is: resolveName of its text with SymbolFlags.Value and + * SymbolFlags.ExportValue. */ + getResolvedSymbol(node: Identifier): Ts7Symbol | undefined { + this.ensureActive(); + return this.raw.getResolvedSymbol(node); + } + + getAnyType(): Type { + this.ensureActive(); + return this.anyType(); + } + + getNeverType(): Type { + this.ensureActive(); + return this.intrinsic("never", () => this.raw.getNeverType()); + } + + getBigIntType(): Type { + this.ensureActive(); + return this.intrinsic("bigint", () => this.raw.getBigIntType()); + } + getUnknownType(): Type { this.ensureActive(); if (this.cache.unknownType === null) this.cache.unknownType = this.raw.getUnknownType(); @@ -851,7 +892,7 @@ export class CheckerFacade { * programs see the es2025 lib's Promise (the ambient world forces it). */ private promiseArgumentOf(type: Type): Type | null { if (!type.isTypeReference()) return null; - const name = type.getTarget()?.getSymbol()?.name; + const name = symbolNameOf(type.getTarget()?.getSymbol()); if (name !== "Promise" && name !== "PromiseLike") return null; const args = this.getTypeArguments(type); return args[0] ?? null; diff --git a/packages/compiler/src/frontend/ts7/diagnostic.ts b/packages/compiler/src/frontend/ts7/diagnostic.ts new file mode 100644 index 000000000..db3ff0e63 --- /dev/null +++ b/packages/compiler/src/frontend/ts7/diagnostic.ts @@ -0,0 +1,38 @@ +import type { DiagnosticCategory } from "./enums.generated.js"; +import type { Ts7DiagnosticData } from "./session-schema.generated.js"; + +/** A diagnostic with every member set, as tsc-ts/api's Diagnostic has them. The server leaves out + * the members a diagnostic doesn't have. `fileName` is undefined for global and program + * diagnostics. */ +export interface Ts7Diagnostic { + readonly fileName: string | undefined; + readonly pos: number; + readonly end: number; + readonly code: number; + readonly category: DiagnosticCategory; + readonly text: string; + readonly reportsUnnecessary: boolean; + readonly reportsDeprecated: boolean; + readonly messageChain: readonly Ts7Diagnostic[]; + readonly relatedInformation: readonly Ts7Diagnostic[]; +} + +/** `data` with its absent members set: undefined, false, or no chained or related diagnostics. */ +export function ts7Diagnostic(data: Ts7DiagnosticData): Ts7Diagnostic { + const chain: Ts7Diagnostic[] = []; + for (const item of data.messageChain ?? []) chain.push(ts7Diagnostic(item)); + const related: Ts7Diagnostic[] = []; + for (const item of data.relatedInformation ?? []) related.push(ts7Diagnostic(item)); + return { + fileName: data.fileName, + pos: data.pos, + end: data.end, + code: data.code, + category: data.category, + text: data.text, + reportsUnnecessary: data.reportsUnnecessary ?? false, + reportsDeprecated: data.reportsDeprecated ?? false, + messageChain: chain, + relatedInformation: related, + }; +} diff --git a/packages/compiler/src/frontend/ts7/node-functions.ts b/packages/compiler/src/frontend/ts7/node-functions.ts new file mode 100644 index 000000000..3b37779e8 --- /dev/null +++ b/packages/compiler/src/frontend/ts7/node-functions.ts @@ -0,0 +1,23 @@ +/* Position and text functions over nodes, with tsc-ts/api's names. The + * frontend calls these rather than node methods, so that its node types can + * be tsc-ts/api's interfaces, which have no methods. Today every node is an + * AstNode, and each function forwards to the AstNode method. */ + +import type { AstNode } from "./ast-node.js"; +import type { Node, SourceFile } from "./ast-types.js"; + +/** The start of the node's first token, after its leading trivia, and before + * its JSDoc comments when `includeJSDoc` is true. */ +export function tokenStartOfNode(node: Node, includeJSDoc = false): number { + return (node as AstNode).getStart(undefined, includeJSDoc); +} + +/** The node's text from its first token to its end. */ +export function getTextOfNode(node: Node): string { + return (node as AstNode).getText(); +} + +/** The source file that contains the node. */ +export function getSourceFileOfNode(node: Node): SourceFile { + return (node as AstNode).getSourceFile(); +} diff --git a/packages/compiler/src/frontend/ts7/program-host.ts b/packages/compiler/src/frontend/ts7/program-host.ts index 2f2fdf76b..dbc0804d5 100644 --- a/packages/compiler/src/frontend/ts7/program-host.ts +++ b/packages/compiler/src/frontend/ts7/program-host.ts @@ -21,7 +21,7 @@ import { dirname, join, resolve } from "node:path"; import { Ts7Api } from "./api.js"; import type { Ts7TimingInfo } from "./session-timing.js"; import type { Ts7FileSystem } from "./rpc-filesystem.js"; -import type { CompilerOptions, Diagnostic } from "./semantic-types.js"; +import type { CompilerOptions, Diagnostic, SourceFileMetadata } from "./semantic-types.js"; import type { SourceFile } from "./ast-types.js"; import type { Ts7SessionProject as Project, Ts7SessionSnapshot as Snapshot } from "./session.js"; import { CheckerFacade } from "./checker.js"; @@ -343,10 +343,22 @@ export class Ts7Program { return this.project.program.getSyntacticDiagnostics(sourceFile?.fileName); } + getBindDiagnostics(sourceFile?: SourceFile): readonly Diagnostic[] { + return this.project.program.getBindDiagnostics(sourceFile?.fileName); + } + getSemanticDiagnostics(sourceFile?: SourceFile): readonly Diagnostic[] { return this.project.program.getSemanticDiagnostics(sourceFile?.fileName); } + getSuggestionDiagnostics(sourceFile?: SourceFile): readonly Diagnostic[] { + return this.project.program.getSuggestionDiagnostics(sourceFile?.fileName); + } + + getDeclarationDiagnostics(sourceFile?: SourceFile): readonly Diagnostic[] { + return this.project.program.getDeclarationDiagnostics(sourceFile?.fileName); + } + getGlobalDiagnostics(): readonly Diagnostic[] { return this.project.program.getGlobalDiagnostics(); } @@ -359,6 +371,10 @@ export class Ts7Program { return this.project.program.getConfigFileParsingDiagnostics(); } + getSourceFileMetadata(fileName: string): SourceFileMetadata | undefined { + return this.project.program.getSourceFileMetadata(resolve(fileName)); + } + isSourceFileDefaultLibrary(file: SourceFile): boolean { return this.project.program.isSourceFileDefaultLibrary(file); } diff --git a/packages/compiler/src/frontend/ts7/semantic-checker.test.ts b/packages/compiler/src/frontend/ts7/semantic-checker.test.ts index 5c5f2e2fc..90a7a25cf 100644 --- a/packages/compiler/src/frontend/ts7/semantic-checker.test.ts +++ b/packages/compiler/src/frontend/ts7/semantic-checker.test.ts @@ -10,6 +10,8 @@ import { tsgoExecutable } from "./rpc-api.js"; import { spawnTs7Wire } from "./rpc-process.js"; import { Ts7Session } from "./session.js"; import { SyntaxKind } from "./enums.js"; +import { getSourceFileOfNode } from "./node-functions.js"; +import { forEachChild } from "./ast.js"; const source = [ "export class Box { value = 1; }", @@ -98,15 +100,15 @@ test("synthetic type roots and descendants match the pinned SDK without claiming expect(actual.file.node(1)).toBe(actual); expect(actual.parent).toBeUndefined(); expect(() => actual.file.sourceFile).toThrow("expected a source file root"); - expect(() => actual.getSourceFile()).toThrow("expected a source file root"); - actual.forEachChild((child) => { + expect(() => getSourceFileOfNode(actual)).toThrow("expected a source file root"); + forEachChild(actual, (child) => { expect(child.parent).toBe(actual); }); checked++; } expect(checked).toBe(6); expect(root.file.sourceFile).toBe(root); - expect(root.getSourceFile()).toBe(root); + expect(getSourceFileOfNode(root)).toBe(root); } finally { snapshot.dispose(); sdkSnapshot.dispose(); diff --git a/packages/compiler/src/frontend/ts7/semantic-checker.ts b/packages/compiler/src/frontend/ts7/semantic-checker.ts index 17ca43bf8..92aa77333 100644 --- a/packages/compiler/src/frontend/ts7/semantic-checker.ts +++ b/packages/compiler/src/frontend/ts7/semantic-checker.ts @@ -16,16 +16,32 @@ import { type SemanticQuery, } from "./semantic-model.js"; import { SemanticSymbolFlags } from "./semantic-schema.generated.js"; +import type { TypePredicateKind } from "./enums.generated.js"; -export interface SemanticIndexInfo { - keyType: SemanticType; - valueType: SemanticType; - isReadonly: boolean; - declaration: SemanticNodeHandle | undefined; +/** An index signature of a type. Its declaration resolves on first read: most queries only count a + * type's index infos. */ +export class SemanticIndexInfo { + private declarationNode: AstNode | undefined; + private declarationResolved = false; + + constructor( + readonly keyType: SemanticType, + readonly type: SemanticType, + readonly isReadonly: boolean, + private readonly declarationHandle: SemanticNodeHandle | undefined, + ) {} + + get declaration(): AstNode | undefined { + if (!this.declarationResolved) { + this.declarationNode = this.declarationHandle?.resolve(); + this.declarationResolved = true; + } + return this.declarationNode; + } } export interface SemanticTypePredicate { - kind: number; + kind: TypePredicateKind; parameterIndex: number; parameterName: string | undefined; type: SemanticType | undefined; @@ -382,15 +398,17 @@ export class SemanticChecker { const data = this.project.request("getIndexInfosOfType", { type: type.id, }); - return (data ?? []).map((item) => ({ - keyType: this.project.type(item.keyType), - valueType: this.project.type(item.valueType), - isReadonly: item.isReadonly ?? false, - declaration: - item.declaration === undefined - ? undefined - : new SemanticNodeHandle(item.declaration, this.project), - })); + return (data ?? []).map( + (item) => + new SemanticIndexInfo( + this.project.type(item.keyType), + this.project.type(item.valueType), + item.isReadonly ?? false, + item.declaration === undefined + ? undefined + : new SemanticNodeHandle(item.declaration, this.project), + ), + ); } private getWellKnownSymbols(): WellKnownSymbols { this.project.ensureActive(); diff --git a/packages/compiler/src/frontend/ts7/semantic-model.test.ts b/packages/compiler/src/frontend/ts7/semantic-model.test.ts index e0230e8fe..36a445bc8 100644 --- a/packages/compiler/src/frontend/ts7/semantic-model.test.ts +++ b/packages/compiler/src/frontend/ts7/semantic-model.test.ts @@ -23,7 +23,13 @@ import { isUnionType, type Type, } from "typescript/unstable/sync"; -import { SemanticNodeHandle, SemanticSnapshot, SemanticType } from "./semantic-model.js"; +import { + SemanticNodeHandle, + SemanticSnapshot, + SemanticType, + symbolNameOf, +} from "./semantic-model.js"; +import type { __String } from "./ast-types.js"; import { SemanticObjectFlags as ObjectFlags, SemanticSignatureFlags as SignatureFlags, @@ -97,7 +103,7 @@ test("absent handles and empty known lists do not issue requests", () => { expect(type.getExtendsType()).toBeUndefined(); expect(type.getBaseType()).toBeUndefined(); expect(type.getConstraint()).toBeUndefined(); - expect(type.getTypes()).toBeUndefined(); + expect(type.getTypes()).toEqual([]); expect(type.getBaseTypes()).toBeUndefined(); expect(type.getTypeParameters()).toEqual([]); expect(type.getOuterTypeParameters()).toEqual([]); @@ -304,8 +310,8 @@ test("symbol tables keep escaped keys, display names, canonical context and cach answers.set("getExportSymbolOfSymbol", symbol(3)); const members = parent.getMembers(); const exports = parent.getExports(); - expect(members.get("___x")?.name).toBe("__x"); - expect(exports.get("__x")?.name).toBe("__x"); + expect(symbolNameOf(members.get("___x" as __String))).toBe("__x"); + expect(symbolNameOf(exports.get("__x" as __String))).toBe("__x"); expect(parent.getMembers()).toBe(members); expect(parent.getExports()).toBe(exports); expect(parent.getParent()).toBe(first.symbol(symbol(2))); diff --git a/packages/compiler/src/frontend/ts7/semantic-model.ts b/packages/compiler/src/frontend/ts7/semantic-model.ts index 4920c96ae..5d1bab78e 100644 --- a/packages/compiler/src/frontend/ts7/semantic-model.ts +++ b/packages/compiler/src/frontend/ts7/semantic-model.ts @@ -4,6 +4,8 @@ import type { SemanticTypeData as TypeResponse, } from "./semantic-schema.generated.js"; import { AstNode } from "./ast-node.js"; +import type { __String } from "./ast-types.js"; +import type * as enums from "./enums.generated.js"; import { parseAstNodeHandle } from "./ast-wire.js"; import type { SemanticChecker, SemanticJsDocTag } from "./semantic-checker.js"; import { parseSemanticJson } from "./semantic-json.js"; @@ -290,9 +292,9 @@ export class SemanticNodeHandle { export class SemanticSymbol { readonly id: number; - readonly escapedName: string; - readonly name: string; - readonly flags: number; + /** The key of the member and export tables the symbol is in; `symbolName` gives its name. */ + readonly escapedName: __String; + readonly flags: enums.SymbolFlags; readonly checkFlags: number; /** The declarations' node handles, as the server sends them. */ readonly declarationHandles: SemanticNodeHandle[]; @@ -302,16 +304,15 @@ export class SemanticSymbol { private valueDeclarationResolved = false; private readonly parent: number | undefined; private readonly exportSymbol: number | undefined; - private membersCache: Map | undefined; - private exportsCache: Map | undefined; + private membersCache: Map<__String, SemanticSymbol> | undefined; + private exportsCache: Map<__String, SemanticSymbol> | undefined; constructor( data: SymbolResponse, readonly canonicalProject: SemanticProject, ) { this.id = data.id; - this.escapedName = data.name; - this.name = data.name.startsWith("___") ? data.name.slice(1) : data.name; + this.escapedName = data.name as __String; this.flags = data.flags; this.checkFlags = data.checkFlags; this.declarationHandles = (data.declarations ?? []).map( @@ -362,18 +363,18 @@ export class SemanticSymbol { if (result === undefined) throw new Error(`Missing export symbol for ${this.id}`); return result; } - private table(method: string): Map { - const table = new Map(); + private table(method: string): Map<__String, SemanticSymbol> { + const table = new Map<__String, SemanticSymbol>(); for (const symbol of this.canonicalProject.fetchSymbols(this.id, method)) table.set(symbol.escapedName, symbol); return table; } - getMembers(): Map { + getMembers(): Map<__String, SemanticSymbol> { this.canonicalProject.ensureActive(); if (this.membersCache === undefined) this.membersCache = this.table("getMembersOfSymbol"); return this.membersCache; } - getExports(): Map { + getExports(): Map<__String, SemanticSymbol> { this.canonicalProject.ensureActive(); if (this.exportsCache === undefined) this.exportsCache = this.table("getExportsOfSymbol"); return this.exportsCache; @@ -392,10 +393,26 @@ export class SemanticSymbol { } } +/** The name of `symbol`, as TypeScript 7's API gives it: its escaped name, with the extra `_` of a + * name that starts with `__` removed. */ +export function symbolName(symbol: SemanticSymbol): string { + const name: string = symbol.escapedName; + return name.startsWith("___") ? name.slice(1) : name; +} + +/** `symbolName(symbol)`, or undefined without a symbol. */ +export function symbolNameOf(symbol: SemanticSymbol | null | undefined): string | undefined { + return symbol === undefined || symbol === null ? undefined : symbolName(symbol); +} + /** Type refinements keep the same native object, including its project and * lazy handle caches. Narrowed fields are read from their original storage. */ -export interface SemanticObjectType extends SemanticType { - readonly objectFlags: number; +export type SemanticObjectType = SemanticType; +export interface SemanticTypeParameter extends SemanticType { + readonly isThisType: boolean; +} +export interface SemanticTemplateLiteralType extends SemanticType { + readonly texts: string[]; } export interface SemanticIntrinsicType extends SemanticType { readonly intrinsicName: string; @@ -416,7 +433,7 @@ export interface SemanticBooleanLiteralType extends SemanticLiteralType { readonly value: boolean; } export interface SemanticTupleType extends SemanticObjectType { - readonly elementFlags: number[]; + readonly elementFlags: enums.ElementFlags[]; readonly fixedLength: number; readonly readonly: boolean; } @@ -424,12 +441,14 @@ export interface SemanticTupleType extends SemanticObjectType { /** Kind-specific accessors share one nominal layout. */ export class SemanticType { readonly id: number; - readonly flags: number; - readonly objectFlags: number | undefined; + readonly flags: enums.TypeFlags; + /** The object flags of an object type; 0 for other types, as TypeScript 7's API gives them. */ + readonly objectFlags: enums.ObjectFlags; readonly value: string | number | boolean | bigint | undefined; readonly intrinsicName: string | undefined; + /** Whether a type parameter is the `this` type; undefined for other types. */ readonly isThisType: boolean | undefined; - readonly elementFlags: number[] | undefined; + readonly elementFlags: enums.ElementFlags[] | undefined; readonly fixedLength: number | undefined; readonly readonly: boolean | undefined; readonly texts: string[] | undefined; @@ -444,7 +463,7 @@ export class SemanticType { ) { this.id = data.id; this.flags = data.flags; - this.objectFlags = data.objectFlags; + this.objectFlags = data.objectFlags ?? 0; const value = data.value; if ((data.flags & TypeFlags.BigIntLiteral) !== 0 && value != null) { if (typeof value !== "string") @@ -454,7 +473,8 @@ export class SemanticType { this.value = value ?? undefined; } this.intrinsicName = data.intrinsicName; - this.isThisType = data.isThisType; + this.isThisType = + (data.flags & TypeFlags.TypeParameter) !== 0 ? (data.isThisType ?? false) : undefined; this.elementFlags = data.elementFlags; this.fixedLength = data.fixedLength; this.readonly = data.readonly; @@ -494,26 +514,26 @@ export class SemanticType { getConstraint(): SemanticType | undefined { return this.project.fetchType(this.id, "getConstraintOfType", this.data.substConstraint); } - getTypeParameters(): SemanticType[] { + getTypeParameters(): SemanticTypeParameter[] { return this.project.fetchTypes( this.id, "getTypeParametersOfType", this.data.typeParameters ?? [], - ); + ) as SemanticTypeParameter[]; } - getOuterTypeParameters(): SemanticType[] { + getOuterTypeParameters(): SemanticTypeParameter[] { return this.project.fetchTypes( this.id, "getOuterTypeParametersOfType", this.data.outerTypeParameters ?? [], - ); + ) as SemanticTypeParameter[]; } - getLocalTypeParameters(): SemanticType[] { + getLocalTypeParameters(): SemanticTypeParameter[] { return this.project.fetchTypes( this.id, "getLocalTypeParametersOfType", this.data.localTypeParameters ?? [], - ); + ) as SemanticTypeParameter[]; } getAliasTypeArguments(): SemanticType[] { return this.project.fetchTypes( @@ -522,10 +542,11 @@ export class SemanticType { this.data.aliasTypeArguments ?? [], ); } - getTypes(): SemanticType[] | undefined { + /** The members of a union or intersection, or the types of a template literal type; empty for + * other types. */ + getTypes(): SemanticType[] { this.project.ensureActive(); - if ((this.flags & (TypeFlags.UnionOrIntersection | TypeFlags.TemplateLiteral)) === 0) - return undefined; + if ((this.flags & (TypeFlags.UnionOrIntersection | TypeFlags.TemplateLiteral)) === 0) return []; if (this.constituentsPanic !== undefined) throw this.constituentsPanic; if (this.constituents === undefined) { try { @@ -567,7 +588,7 @@ export class SemanticType { return result; } isClassOrInterface(): boolean { - return this.isObjectType() && ((this.objectFlags ?? 0) & ObjectFlags.ClassOrInterface) !== 0; + return this.isObjectType() && (this.objectFlags & ObjectFlags.ClassOrInterface) !== 0; } isUnionType(): boolean { return (this.flags & TypeFlags.Union) !== 0; @@ -575,7 +596,7 @@ export class SemanticType { isIntersectionType(): boolean { return (this.flags & TypeFlags.Intersection) !== 0; } - isObjectType(): this is SemanticObjectType { + isObjectType(): boolean { return (this.flags & TypeFlags.Object) !== 0; } isIntrinsicType(): this is SemanticIntrinsicType { @@ -600,10 +621,10 @@ export class SemanticType { return (this.flags & TypeFlags.BooleanLiteral) !== 0; } isTypeReference(): boolean { - return this.isObjectType() && ((this.objectFlags ?? 0) & ObjectFlags.Reference) !== 0; + return this.isObjectType() && (this.objectFlags & ObjectFlags.Reference) !== 0; } - isTupleType(): boolean { - return this.isObjectType() && ((this.objectFlags ?? 0) & ObjectFlags.Tuple) !== 0; + isTupleType(): this is SemanticTupleType { + return this.isObjectType() && (this.objectFlags & ObjectFlags.Tuple) !== 0; } isIndexType(): boolean { return (this.flags & TypeFlags.Index) !== 0; @@ -617,20 +638,20 @@ export class SemanticType { isSubstitutionType(): boolean { return (this.flags & TypeFlags.Substitution) !== 0; } - isTemplateLiteralType(): boolean { + isTemplateLiteralType(): this is SemanticTemplateLiteralType { return (this.flags & TypeFlags.TemplateLiteral) !== 0; } isStringMappingType(): boolean { return (this.flags & TypeFlags.StringMapping) !== 0; } - isTypeParameter(): boolean { + isTypeParameter(): this is SemanticTypeParameter { return (this.flags & TypeFlags.TypeParameter) !== 0; } } export class SemanticSignature { readonly id: number; - readonly flags: number; + readonly flags: enums.SignatureFlags; /** The declaration's node handle, as the server sends it. */ readonly declarationHandle: SemanticNodeHandle | undefined; private declarationNode: AstNode | undefined; @@ -658,12 +679,12 @@ export class SemanticSignature { return this.declarationNode; } - getTypeParameters(): SemanticType[] { + getTypeParameters(): SemanticTypeParameter[] { return this.project.fetchTypes( this.id, "getTypeParametersOfSignature", this.data.typeParameters ?? [], - ); + ) as SemanticTypeParameter[]; } getParameters(): SemanticSymbol[] { return this.project.fetchSymbols( diff --git a/packages/compiler/src/frontend/ts7/semantic-types.ts b/packages/compiler/src/frontend/ts7/semantic-types.ts index b4f3d5f2e..ea43e3885 100644 --- a/packages/compiler/src/frontend/ts7/semantic-types.ts +++ b/packages/compiler/src/frontend/ts7/semantic-types.ts @@ -1,27 +1,40 @@ /** Frontend names over the concrete native client. No SDK object or - * structural cast participates in the production checker boundary. */ + * structural cast participates in the production checker boundary. The + * names follow tsc-ts/api's. */ export type { SemanticType as Type, - SemanticType as InterfaceType, - SemanticType as TypeReference, SemanticType as UnionOrIntersectionType, SemanticType as UnionType, + SemanticType as IntersectionType, + SemanticType as FreshableType, + SemanticType as InterfaceType, + SemanticType as TypeReference, SemanticTupleType as TupleType, // A reference carries its element flags on the target, not necessarily on // itself. Claiming a tuple shape here makes a native presence test constant. - SemanticObjectType as TupleTypeReference, + SemanticType as TupleTypeReference, + SemanticLiteralType as LiteralType, SemanticStringLiteralType as StringLiteralType, SemanticNumberLiteralType as NumberLiteralType, + SemanticBigIntLiteralType as BigIntLiteralType, SemanticBooleanLiteralType as BooleanLiteralType, + SemanticIntrinsicType as IntrinsicType, SemanticObjectType as ObjectType, + SemanticTypeParameter as TypeParameter, + SemanticType as IndexType, + SemanticType as IndexedAccessType, + SemanticType as ConditionalType, + SemanticType as SubstitutionType, + SemanticTemplateLiteralType as TemplateLiteralType, + SemanticType as StringMappingType, SemanticSignature as Signature, SemanticSymbol as Symbol, } from "./semantic-model.js"; +export { symbolName, symbolNameOf } from "./semantic-model.js"; export type { SemanticIndexInfo as IndexInfo, SemanticTypePredicate as TypePredicate, } from "./semantic-checker.js"; -export type { - Ts7CompilerOptionsData as CompilerOptions, - Ts7DiagnosticData as Diagnostic, -} from "./session-schema.generated.js"; +export type { Ts7CompilerOptionsData as CompilerOptions } from "./session-schema.generated.js"; +export type { Ts7Diagnostic as Diagnostic } from "./diagnostic.js"; +export type { Ts7SourceMetadata as SourceFileMetadata } from "./session-schema.generated.js"; diff --git a/packages/compiler/src/frontend/ts7/session.test.ts b/packages/compiler/src/frontend/ts7/session.test.ts index 522b72fe6..d957fe0dd 100644 --- a/packages/compiler/src/frontend/ts7/session.test.ts +++ b/packages/compiler/src/frontend/ts7/session.test.ts @@ -14,6 +14,8 @@ import { spawnTs7Wire } from "./rpc-process.js"; import { tsgoExecutable } from "./rpc-api.js"; import { Ts7Session, Ts7SessionProject, Ts7SessionSnapshot } from "./session.js"; import { AstNode } from "./ast-node.js"; +import { ts7Diagnostic } from "./diagnostic.js"; +import { getTextOfNode } from "./node-functions.js"; const tempRoot = process.platform === "win32" ? tmpdir() : "/tmp"; const protocolPath = (path: string) => path.replaceAll("\\", "/"); @@ -104,8 +106,11 @@ test("owned snapshots preserve SDK options, diagnostics, metadata and default-pr "getSuggestionDiagnostics", "getDeclarationDiagnostics", ] as const) { - expect(project.program[name](file), name).toEqual(expected.program[name](file)); - expect(project.program[name](), name).toEqual(expected.program[name]()); + // scriptc's diagnostics have every member set (ts7Diagnostic); the SDK's leave absent ones out. + expect(project.program[name](file), name).toEqual( + expected.program[name](file).map(ts7Diagnostic), + ); + expect(project.program[name](), name).toEqual(expected.program[name]().map(ts7Diagnostic)); } for (const name of [ "getProgramDiagnostics", @@ -289,7 +294,7 @@ test("a released latest snapshot cannot lend stale ASTs to the same project", () source = "export const third = true;"; const third = session.updateSnapshot({ fileChanges: { created: [file] } }); expect(third.getProject(config)!.program.getSourceFile(file)!.text).toBe(source); - expect(old.statements.nodes[0]!.getText()).toBe("export const value = 1;"); + expect(getTextOfNode(old.statements.nodes[0]!)).toBe("export const value = 1;"); } finally { session.close(); rmSync(dir, { recursive: true, force: true }); diff --git a/packages/compiler/src/frontend/ts7/session.ts b/packages/compiler/src/frontend/ts7/session.ts index 4933da9d6..87b97d25e 100644 --- a/packages/compiler/src/frontend/ts7/session.ts +++ b/packages/compiler/src/frontend/ts7/session.ts @@ -1,3 +1,4 @@ +import { ts7Diagnostic, type Ts7Diagnostic } from "./diagnostic.js"; import type { SourceFile } from "./ast-types.js"; import { AstFile, AstNode } from "./ast-node.js"; import { Ts7RpcClient } from "./rpc-client.js"; @@ -314,31 +315,35 @@ export class Ts7SessionProgram { isSourceFileDefaultLibrary(file: AstNode): boolean { return this.getSourceFileMetadataByPath(file.path)?.isDefaultLibrary ?? false; } - private diagnostics(method: string, file?: SemanticDocument): Ts7DiagnosticData[] { - return this.session.request(method, this.query(file)) ?? []; + private diagnostics(method: string, file?: SemanticDocument): Ts7Diagnostic[] { + const result: Ts7Diagnostic[] = []; + for (const data of this.session.request(method, this.query(file)) ?? + []) + result.push(ts7Diagnostic(data)); + return result; } - getSyntacticDiagnostics(file?: SemanticDocument): Ts7DiagnosticData[] { + getSyntacticDiagnostics(file?: SemanticDocument): Ts7Diagnostic[] { return this.diagnostics("getSyntacticDiagnostics", file); } - getBindDiagnostics(file?: SemanticDocument): Ts7DiagnosticData[] { + getBindDiagnostics(file?: SemanticDocument): Ts7Diagnostic[] { return this.diagnostics("getBindDiagnostics", file); } - getSemanticDiagnostics(file?: SemanticDocument): Ts7DiagnosticData[] { + getSemanticDiagnostics(file?: SemanticDocument): Ts7Diagnostic[] { return this.diagnostics("getSemanticDiagnostics", file); } - getSuggestionDiagnostics(file?: SemanticDocument): Ts7DiagnosticData[] { + getSuggestionDiagnostics(file?: SemanticDocument): Ts7Diagnostic[] { return this.diagnostics("getSuggestionDiagnostics", file); } - getDeclarationDiagnostics(file?: SemanticDocument): Ts7DiagnosticData[] { + getDeclarationDiagnostics(file?: SemanticDocument): Ts7Diagnostic[] { return this.diagnostics("getDeclarationDiagnostics", file); } - getProgramDiagnostics(): Ts7DiagnosticData[] { + getProgramDiagnostics(): Ts7Diagnostic[] { return this.diagnostics("getProgramDiagnostics"); } - getGlobalDiagnostics(): Ts7DiagnosticData[] { + getGlobalDiagnostics(): Ts7Diagnostic[] { return this.diagnostics("getGlobalDiagnostics"); } - getConfigFileParsingDiagnostics(): Ts7DiagnosticData[] { + getConfigFileParsingDiagnostics(): Ts7Diagnostic[] { return this.diagnostics("getConfigFileParsingDiagnostics"); } dispose(): void { diff --git a/packages/compiler/src/frontend/ts7/source-parser.test.ts b/packages/compiler/src/frontend/ts7/source-parser.test.ts index d5b54f080..b6950b3d5 100644 --- a/packages/compiler/src/frontend/ts7/source-parser.test.ts +++ b/packages/compiler/src/frontend/ts7/source-parser.test.ts @@ -8,6 +8,7 @@ import type { Ts7FileSystem } from "./rpc-filesystem.js"; import { Ts7SourceParser, type Ts7SourceKind } from "./source-parser.js"; import { closeSourceParser, parseSourceFile } from "./source-parser-node.js"; import * as ts from "./syntax.js"; +import { getTextOfNode, tokenStartOfNode } from "./node-functions.js"; const parsers: Ts7SourceParser[] = []; const directories: string[] = []; @@ -46,9 +47,9 @@ test("each parse owns its text and nodes across same-path edits, deletion and cl expect(restored.text).toBe(first.text); parser.close(); parser.close(); - expect(first.statements.nodes[0]!.getText()).toBe("export const first = 1;"); + expect(getTextOfNode(first.statements.nodes[0]!)).toBe("export const first = 1;"); expect(second.statements.nodes[0]!.parent).toBe(second); - expect(other.statements.nodes[0]!.getText()).toBe("exports.third = 3;"); + expect(getTextOfNode(other.statements.nodes[0]!)).toBe("exports.third = 3;"); expect(() => parser.parse("late.ts", "", "ts")).toThrow("closed"); }); @@ -114,9 +115,9 @@ for (const { name, kind, source } of syntaxCases) { expect(file.isDeclarationFile).toBe(oracle.isDeclarationFile); expect( file.statements.nodes.map((statement) => ({ - start: statement.getStart(), + start: tokenStartOfNode(statement), end: statement.end, - text: statement.getText(), + text: getTextOfNode(statement), })), ).toEqual( oracle.statements.map((statement) => ({ @@ -198,7 +199,11 @@ test("JSDoc callable types cannot replace method body views", () => { const expected = oracle.statements[0]!; if (!ts.isClassDeclaration(native) || !ts5.isClassDeclaration(expected)) throw new Error("expected class"); - expect(native.members.nodes.map((member) => member.body?.getText())).toEqual( + expect( + native.members.nodes.map((member) => + member.body === undefined ? undefined : getTextOfNode(member.body), + ), + ).toEqual( expected.members.map((member) => ts5.isMethodDeclaration(member) ? member.body?.getText() : undefined, ), @@ -231,7 +236,7 @@ test("a parser starts a new server once it has parsed its source budget, and ear expect(servers).toHaveLength(2); const third = parser.parse("b.ts", "export const b = 3;", "ts"); expect(servers).toHaveLength(2); - expect(first.statements.nodes[0]!.getText()).toBe(`export const a = ${"1 + ".repeat(20)}1;`); - expect(second.statements.nodes[0]!.getText()).toBe("export const b = 2;"); - expect(third.statements.nodes[0]!.getText()).toBe("export const b = 3;"); + expect(getTextOfNode(first.statements.nodes[0]!)).toBe(`export const a = ${"1 + ".repeat(20)}1;`); + expect(getTextOfNode(second.statements.nodes[0]!)).toBe("export const b = 2;"); + expect(getTextOfNode(third.statements.nodes[0]!)).toBe("export const b = 3;"); }); diff --git a/packages/compiler/src/frontend/type-mapper.ts b/packages/compiler/src/frontend/type-mapper.ts index c2fa0a06d..3129e9e3c 100644 --- a/packages/compiler/src/frontend/type-mapper.ts +++ b/packages/compiler/src/frontend/type-mapper.ts @@ -69,7 +69,7 @@ export function jsOpenObjectType( shapes: ShapeRegistry, unions: UnionRegistry, ): IrType { - if (!decl || !isJsSourceFile(decl.getSourceFile())) return type; + if (!decl || !isJsSourceFile(ts.getSourceFileOfNode(decl))) return type; const arms = type.kind === "union" ? unions.get(type.unionId)?.arms : [type]; const present = arms?.filter((arm) => arm.kind !== "undefinedT"); const parameter = ts.isParameter(decl) @@ -1004,13 +1004,13 @@ export function isGeneralNodeStreamInterface( if (psym === undefined) return false; return psym.declarations.some((d) => { if (!(ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d))) return false; - if (!ctx.isStdlibFile(d.getSourceFile())) return false; - if (psym.name === "ReadableStream" || psym.name === "WritableStream") + if (!ctx.isStdlibFile(ts.getSourceFileOfNode(d))) return false; + if (ts.symbolName(psym) === "ReadableStream" || ts.symbolName(psym) === "WritableStream") return ts.isInterfaceDeclaration(d) && isDeclaredInAmbientNamespace(d, "NodeJS"); - if (psym.name === "ChildStdin") return isDeclaredInAmbientModule(d, "child_process"); + if (ts.symbolName(psym) === "ChildStdin") return isDeclaredInAmbientModule(d, "child_process"); return ( - (psym.name === "Readable" || psym.name === "Writable") && - isNodeTypesPath(d.getSourceFile().fileName) && + (ts.symbolName(psym) === "Readable" || ts.symbolName(psym) === "Writable") && + isNodeTypesPath(ts.getSourceFileOfNode(d).fileName) && isDeclaredInAmbientModule(d, "stream") ); }); @@ -1027,7 +1027,7 @@ export function isWebAssemblyType( if (!symbol) return false; return symbol.declarations.some( (d) => - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientNamespace(d as ts.Declaration, "WebAssembly"), ); } @@ -1315,7 +1315,7 @@ export function isLegacyUrlSymbol( ): boolean { if (!symbol) return false; for (const d of symbol.declarations) { - if (!ctx.isStdlibFile(d.getSourceFile())) continue; + if (!ctx.isStdlibFile(ts.getSourceFileOfNode(d))) continue; let family = false; for (let n: ts.Node | undefined = d; n; n = n.parent) { if (ts.isInterfaceDeclaration(n) && LEGACY_URL_TYPES.has(n.name.text)) family = true; @@ -1355,7 +1355,10 @@ export function isOsConstantsSymbol( const alias = types && ts.isTypeAliasDeclaration(d) && d.name.text === "SignalConstants"; const signals = !types && ts.isVariableDeclaration(d) && ts.isIdentifier(d.name) && d.name.text === "signals"; - if (!(alias || signals || ts.isModuleDeclaration(d)) || !ctx.isStdlibFile(d.getSourceFile())) + if ( + !(alias || signals || ts.isModuleDeclaration(d)) || + !ctx.isStdlibFile(ts.getSourceFileOfNode(d)) + ) continue; let inConstants = ts.isModuleDeclaration(d) && d.name.text === "constants"; for (let n: ts.Node | undefined = d.parent; n; n = n.parent) { @@ -1378,14 +1381,14 @@ function isWorkerHandleSymbol( ctx: Pick, ): boolean { return ( - (symbol?.name === "Worker" || - symbol?.name === "MessagePort" || - symbol?.name === "MessageChannel" || - symbol?.name === "BroadcastChannel") && + ((symbol !== undefined && ts.symbolName(symbol) === "Worker") || + (symbol !== undefined && ts.symbolName(symbol) === "MessagePort") || + (symbol !== undefined && ts.symbolName(symbol) === "MessageChannel") || + (symbol !== undefined && ts.symbolName(symbol) === "BroadcastChannel")) && symbol.declarations.some( (d) => (ts.isClassDeclaration(d) || ts.isInterfaceDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "worker_threads"), ) ); @@ -1404,11 +1407,11 @@ function isLookupOptionsUnion(type: ts.Type, ctx: Pick ctx.isStdlibFile(d.getSourceFile())) + !/^Lookup(One|All)?Options$/.test(ts.symbolName(symbol)) || + !symbol.declarations.some((d) => ctx.isStdlibFile(ts.getSourceFileOfNode(d))) ) return false; - if (symbol.name === "LookupAllOptions") all = true; + if (ts.symbolName(symbol) === "LookupAllOptions") all = true; } return all; } @@ -1455,12 +1458,12 @@ function isFetchBodyUnion(type: ts.Type, ctx: Pick ctx.isStdlibFile(d.getSourceFile())) + !FETCH_BODY_MEMBERS.has(ts.symbolName(symbol)) || + !symbol.declarations.some((d) => ctx.isStdlibFile(ts.getSourceFileOfNode(d))) ) return false; - if (symbol.name === "FormData") formData = true; - if (symbol.name === "URLSearchParams") searchParams = true; + if (ts.symbolName(symbol) === "FormData") formData = true; + if (ts.symbolName(symbol) === "URLSearchParams") searchParams = true; } return formData && searchParams; } @@ -1480,7 +1483,7 @@ function isDynInterfaceSubset(type: ts.Type, ctx: TypeMapperCtx): boolean { if ( parent === undefined || !ts.isInterfaceDeclaration(parent) || - !ctx.isStdlibFile(parent.getSourceFile()) || + !ctx.isStdlibFile(ts.getSourceFileOfNode(parent)) || (owner !== undefined && owner !== parent) ) return false; @@ -1502,16 +1505,17 @@ function isFetchHeadersUnion(type: ts.Type, ctx: Pick ctx.isStdlibFile(d.getSourceFile())) + symbol !== undefined && + ts.symbolName(symbol) === "Headers" && + symbol.declarations.some((d) => ctx.isStdlibFile(ts.getSourceFileOfNode(d))) ) { headers = true; continue; } - const alias = member.getAliasSymbol()?.name; + const alias = ts.symbolNameOf(member.getAliasSymbol()); if ( - symbol?.name === "Array" || - symbol?.name === "ReadonlyArray" || + (symbol !== undefined && ts.symbolName(symbol) === "Array") || + (symbol !== undefined && ts.symbolName(symbol) === "ReadonlyArray") || alias === "Record" || alias === "HeaderRecord" ) { @@ -1540,7 +1544,7 @@ function isReceiveMessageResult( !fn || !ts.isFunctionDeclaration(fn) || fn.name?.text !== "receiveMessageOnPort" || - !ctx.isStdlibFile(decl.getSourceFile()) || + !ctx.isStdlibFile(ts.getSourceFileOfNode(decl)) || !isDeclaredInAmbientModule(fn, "worker_threads") ) { return false; @@ -1584,9 +1588,9 @@ export function mapAsyncGeneratorAnnotation(type: ts.Type, ctx: TypeMapperCtx): const symbol = type.getSymbol(); if ( !symbol || - !["AsyncIterator", "AsyncIterableIterator", "AsyncIterable"].includes(symbol.name) || + !["AsyncIterator", "AsyncIterableIterator", "AsyncIterable"].includes(ts.symbolName(symbol)) || !symbol.declarations.some( - (d) => ts.isInterfaceDeclaration(d) && ctx.isStdlibFile(d.getSourceFile()), + (d) => ts.isInterfaceDeclaration(d) && ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) return null; @@ -1615,11 +1619,13 @@ export function isFetchHandleType( const symbol = member.getSymbol(); if ( !symbol || - (symbol.name !== "Headers" && symbol.name !== "Request" && symbol.name !== "Response") || + (ts.symbolName(symbol) !== "Headers" && + ts.symbolName(symbol) !== "Request" && + ts.symbolName(symbol) !== "Response") || !symbol.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()), + ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) return false; @@ -1700,7 +1706,7 @@ function spreadsCheckedDynamic(symbol: ts.Symbol | undefined, ctx: TypeMapperCtx if (symbol === undefined || (symbol.flags & ts.SymbolFlags.ObjectLiteral) === 0) return false; const decl = symbol.declarations[0]; if (decl === undefined || !ts.isObjectLiteralExpression(decl)) return false; - if (isJsSourceFile(decl.getSourceFile())) return false; + if (isJsSourceFile(ts.getSourceFileOfNode(decl))) return false; if (ctx.checker.getContextualType(decl)) return false; return decl.properties.nodes.some( (prop) => @@ -1714,7 +1720,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // TypeScript's intrinsic globalThis symbol has no declaration. A stored // reference needs native object identity, including var's undefined state. const intrinsic = type.getSymbol(); - if (intrinsic?.name === "globalThis" && intrinsic.declarations.length === 0) return DYN; + if ( + intrinsic !== undefined && + ts.symbolName(intrinsic) === "globalThis" && + intrinsic.declarations.length === 0 + ) + return DYN; if (ctx.jsDynamicDeclaration !== undefined && intrinsic !== undefined) { const objectLiteral = (intrinsic.flags & ts.SymbolFlags.ObjectLiteral) !== 0; const classInstance = @@ -1753,8 +1764,9 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // intersection mapping decomposes it into unsupported utility types. const directAlias = type.getAliasSymbol(); if ( - directAlias?.name === "ExecFileException" && - directAlias.declarations.some((decl) => ctx.isStdlibFile(decl.getSourceFile())) + directAlias !== undefined && + ts.symbolName(directAlias) === "ExecFileException" && + directAlias.declarations.some((decl) => ctx.isStdlibFile(ts.getSourceFileOfNode(decl))) ) { return { kind: "object", className: "%Error" }; } @@ -1763,11 +1775,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // ordinary Error as well. const execException = type.getSymbol(); if ( - execException?.name === "ExecException" && + execException !== undefined && + ts.symbolName(execException) === "ExecException" && execException.declarations.some( (decl) => ts.isInterfaceDeclaration(decl) && - ctx.isStdlibFile(decl.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(decl)) && isDeclaredInAmbientModule(decl, "child_process"), ) ) { @@ -1788,7 +1801,7 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { if ( type.isIntersectionType() && ts.constituentTypes(type).every((t) => { - if (t.getSymbol()?.name !== "Promise") return false; + if (ts.symbolNameOf(t.getSymbol()) !== "Promise") return false; const arg = ctx.checker.getTypeArguments(t as ts.TypeReference)[0]; return arg !== undefined && isWebAssemblyType(arg, ctx); }) @@ -1807,7 +1820,7 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { (ctx.erasingSignature || ctx.isErasing?.()) && !(type.flags & ts.TypeFlags.TypeParameter) && !checker.getCallSignatures(type).some((sig) => sig === ctx.erasingSignature) && - !ERASURE_KEPT_CONTAINERS.has(type.getSymbol()?.name ?? "") && + !ERASURE_KEPT_CONTAINERS.has(ts.symbolNameOf(type.getSymbol()) ?? "") && mentionsErasedTypeParam(type, ctx, 0) ) { contextResolutions++; @@ -1890,8 +1903,9 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // constructors. Only the standard library's interface gets this ABI. const errorOptionsSymbol = widened.getSymbol(); if ( - errorOptionsSymbol?.name === "ErrorOptions" && - errorOptionsSymbol.declarations.some((d) => ctx.isStdlibFile(d.getSourceFile())) + errorOptionsSymbol !== undefined && + ts.symbolName(errorOptionsSymbol) === "ErrorOptions" && + errorOptionsSymbol.declarations.some((d) => ctx.isStdlibFile(ts.getSourceFileOfNode(d))) ) { return DYN; } @@ -1906,7 +1920,7 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { { const parseArgsSym = widened.getAliasSymbol() ?? widened.getSymbol(); const belongs = (declaration: ts.Node): boolean => { - if (!ctx.isStdlibFile(declaration.getSourceFile())) return false; + if (!ctx.isStdlibFile(ts.getSourceFileOfNode(declaration))) return false; let family = false; for (let node: ts.Node | undefined = declaration; node; node = node.parent) { if ( @@ -1922,7 +1936,7 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { }; if ( parseArgsSym && - PARSE_ARGS_DYN_TYPES.has(parseArgsSym.name) && + PARSE_ARGS_DYN_TYPES.has(ts.symbolName(parseArgsSym)) && parseArgsSym.declarations.some(belongs) ) return DYN; @@ -1942,10 +1956,16 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { const boxSym = widened.getSymbol(); if ( boxSym && - (boxSym.name === "Number" || boxSym.name === "String" || boxSym.name === "Boolean") && - boxSym.declarations.some((d) => ctx.isStdlibFile(d.getSourceFile())) + (ts.symbolName(boxSym) === "Number" || + ts.symbolName(boxSym) === "String" || + ts.symbolName(boxSym) === "Boolean") && + boxSym.declarations.some((d) => ctx.isStdlibFile(ts.getSourceFileOfNode(d))) ) { - return boxSym.name === "Number" ? F64 : boxSym.name === "String" ? STRING : BOOL; + return ts.symbolName(boxSym) === "Number" + ? F64 + : ts.symbolName(boxSym) === "String" + ? STRING + : BOOL; } } // The lib's PRIMITIVE-CONSTRUCTOR interfaces as TYPES (`typeof String`, @@ -1963,16 +1983,16 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { const ctorSym = widened.getSymbol(); if ( ctorSym && - (ctorSym.name === "StringConstructor" || - ctorSym.name === "NumberConstructor" || - ctorSym.name === "BooleanConstructor") && - ctorSym.declarations.some((d) => ctx.isStdlibFile(d.getSourceFile())) + (ts.symbolName(ctorSym) === "StringConstructor" || + ts.symbolName(ctorSym) === "NumberConstructor" || + ts.symbolName(ctorSym) === "BooleanConstructor") && + ctorSym.declarations.some((d) => ctx.isStdlibFile(ts.getSourceFileOfNode(d))) ) { return funcOf( [STRING], - ctorSym.name === "StringConstructor" + ts.symbolName(ctorSym) === "StringConstructor" ? STRING - : ctorSym.name === "NumberConstructor" + : ts.symbolName(ctorSym) === "NumberConstructor" ? F64 : BOOL, ); @@ -2076,7 +2096,7 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // declaration file. { const classSym = widened.getSymbol(); - const name = classSym?.name; + const name = ts.symbolNameOf(classSym); if (name === "LifetimeRegion" || name === "InternTable" || name === "ThreadSlots") { for (const d of classSym!.declarations) { if (!ts.isClassDeclaration(d)) continue; @@ -2104,7 +2124,7 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { return decls && decls.length > 0 && decls.every((d) => { - const sf = d.getSourceFile(); + const sf = ts.getSourceFileOfNode(d); return sf.isDeclarationFile && !ctx.isStdlibFile(sf) && !ctx.isExternalTypeFile(sf); }) ? decls @@ -2114,7 +2134,7 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { decls === null || decls.every( (d) => - packageTypeFile(d.getSourceFile(), ctx) && + packageTypeFile(ts.getSourceFileOfNode(d), ctx) && (ts.isInterfaceDeclaration(d) || ts.isTypeAliasDeclaration(d) || ts.isTypeLiteralNode(d) || @@ -2321,18 +2341,19 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // Nameless class DECLARATIONS are legal only as `export default class // {}` — classNamer spells them "%anon" (unique per file), the same // name their collection registered. - !classDecl.getSourceFile().isDeclarationFile && + !ts.getSourceFileOfNode(classDecl).isDeclarationFile && checker.getConstructSignatures(widened).length === 0 && // A TypeScript class declared OUTSIDE the lowered program (reached // through `import type` alone) never registers, and the program can // hold no instance of it: its instance type maps below as the // structural type it describes, like an interface. - (ctx.isProgramFile(classDecl.getSourceFile()) || isJsSourceFile(classDecl.getSourceFile())) + (ctx.isProgramFile(ts.getSourceFileOfNode(classDecl)) || + isJsSourceFile(ts.getSourceFileOfNode(classDecl))) ) { // A JavaScript class outside the lowered program (jsdoc `typeof // import()` over a never-required module) stays unmappable: JSDoc // types are not checked, so its slots fall back to the dynamic value. - if (!ctx.isProgramFile(classDecl.getSourceFile())) return null; + if (!ctx.isProgramFile(ts.getSourceFileOfNode(classDecl))) return null; // The class inside a MIXIN function: per-call-site instantiations // share this one node — the CURRENT instantiation (collecting or // lowering) is the answer; outside any mixin context the node's type @@ -2376,10 +2397,10 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { if ( classDecl && (ts.isClassDeclaration(classDecl) || ts.isClassExpression(classDecl)) && - !classDecl.getSourceFile().isDeclarationFile && + !ts.getSourceFileOfNode(classDecl).isDeclarationFile && checker.getConstructSignatures(widened).length > 0 ) { - if (!ctx.isProgramFile(classDecl.getSourceFile())) return null; + if (!ctx.isProgramFile(ts.getSourceFileOfNode(classDecl))) return null; // The MIXIN class node's static side (`typeof C` inside the mixin): // the current instantiation's classval, like the instance type above. { @@ -2445,9 +2466,10 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // naming the offending half, as does the `new Map` lowering per site. const psym = widened.getSymbol(); const isStdlibInterface = (name: string): boolean => - psym?.name === name && + psym !== undefined && + ts.symbolName(psym) === name && psym.declarations.some( - (d) => ts.isInterfaceDeclaration(d) && ctx.isStdlibFile(d.getSourceFile()), + (d) => ts.isInterfaceDeclaration(d) && ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ); // The builtin Error classes: references to the lib's Error interfaces map to the runtime-provided class // hierarchy (provenance, not the name — a user's own `class Error` @@ -2461,8 +2483,11 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // node:assert's AssertionError is declared as a class (in the shipped // fallback and in @types/node), not as a lib interface. if ( - psym?.name === "AssertionError" && - psym.declarations.some((d) => ts.isClassDeclaration(d) && ctx.isStdlibFile(d.getSourceFile())) + psym !== undefined && + ts.symbolName(psym) === "AssertionError" && + psym.declarations.some( + (d) => ts.isClassDeclaration(d) && ctx.isStdlibFile(ts.getSourceFileOfNode(d)), + ) ) return { kind: "object", className: "%AssertionError" }; // The lib's `Object` interface is a TOP type: tsc admits every @@ -2479,12 +2504,13 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // representation. The node:module class's merged instance interface is // included, while unrelated user interfaces named Module stay structural. if ( - (psym?.name === "ScriptcModule" || psym?.name === "Module") && + ((psym !== undefined && ts.symbolName(psym) === "ScriptcModule") || + (psym !== undefined && ts.symbolName(psym) === "Module")) && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && - (psym.name === "ScriptcModule" || + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && + (ts.symbolName(psym) === "ScriptcModule" || isDeclaredInAmbientNamespace(d, "NodeJS") || isDeclaredInAmbientModule(d, "module")), ) @@ -2499,11 +2525,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // NodeJS.EventEmitter interface (the structural surface net.Socket // et al. extend) is the same runtime value. Provenance-checked. if ( - psym?.name === "EventEmitter" && + psym !== undefined && + ts.symbolName(psym) === "EventEmitter" && psym.declarations.some( (d) => (ts.isClassDeclaration(d) || ts.isInterfaceDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && (isDeclaredInAmbientModule(d, "events") || isDeclaredInAmbientNamespace(d, "NodeJS")), ) ) { @@ -2514,11 +2541,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // precedent). The NAME is generic, so the provenance check adds the // enclosing ambient module, like net.Server. if ( - psym?.name === "Interface" && + psym !== undefined && + ts.symbolName(psym) === "Interface" && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && (isDeclaredInAmbientModule(d, "readline") || isDeclaredInAmbientModule(d, "readline/promises")), ) @@ -2529,11 +2557,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // it keeps Node's own shape (an undefined name, the escape sequences' // code member). if ( - psym?.name === "Key" && + psym !== undefined && + ts.symbolName(psym) === "Key" && psym.declarations.some( (d) => ts.isInterfaceDeclaration(d) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && (isDeclaredInAmbientModule(d, "readline") || isDeclaredInAmbientModule(d, "readline/promises")), ) @@ -2542,11 +2571,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { } // readline/promises.Readline: an f64 handle the same way. if ( - psym?.name === "Readline" && + psym !== undefined && + ts.symbolName(psym) === "Readline" && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "readline/promises"), ) ) { @@ -2559,11 +2589,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // plus the ambient module suffices for both the fallback declarations // and @types/node's class Channel. if ( - psym?.name === "Channel" && + psym !== undefined && + ts.symbolName(psym) === "Channel" && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "diagnostics_channel"), ) ) { @@ -2573,11 +2604,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // the runtime's store id space (the Channel story — stores are // process-lived; contexts ride the fiber machinery). if ( - psym?.name === "AsyncLocalStorage" && + psym !== undefined && + ts.symbolName(psym) === "AsyncLocalStorage" && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "async_hooks"), ) ) { @@ -2586,11 +2618,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // diagnostics_channel.TracingChannel: the same f64-handle story over the // runtime's tracing registry (five event channels per entry). if ( - psym?.name === "TracingChannel" && + psym !== undefined && + ts.symbolName(psym) === "TracingChannel" && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "diagnostics_channel"), ) ) { @@ -2600,7 +2633,7 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { const encodeResult = psym && psym.declarations.some((decl) => { - if (!ctx.isStdlibFile(decl.getSourceFile())) return false; + if (!ctx.isStdlibFile(ts.getSourceFileOfNode(decl))) return false; if (ts.isInterfaceDeclaration(decl) && decl.name.text === "EncodeIntoResult") return true; const parent = decl.parent; return ( @@ -2625,19 +2658,20 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // WHATWG codec instances have ordinary reference identity and ownership. // Their private encoding slot is omitted from enumeration and JSON. if ( - (psym?.name === "TextEncoder" || psym?.name === "TextDecoder") && + ((psym !== undefined && ts.symbolName(psym) === "TextEncoder") || + (psym !== undefined && ts.symbolName(psym) === "TextDecoder")) && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()), + ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { return { kind: "record", shapeId: ctx.shapes.intern( [ - { name: `%${psym.name}`, type: F64 }, - ...(psym.name === "TextDecoder" + { name: `%${ts.symbolName(psym)}`, type: F64 }, + ...(ts.symbolName(psym) === "TextDecoder" ? [ { name: "%fatal", type: BOOL }, { name: "%ignoreBOM", type: BOOL }, @@ -2659,11 +2693,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // special-cased in lowerNew / lowerStringDecoderMethodCall, so the // record shape never surfaces. if ( - psym?.name === "StringDecoder" && + psym !== undefined && + ts.symbolName(psym) === "StringDecoder" && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()), + ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { return { @@ -2688,14 +2723,14 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { if ( flags & ts.TypeFlags.Object && promiseDeclarations.length > 0 && - promiseDeclarations.every((d) => d.getSourceFile().isDeclarationFile) + promiseDeclarations.every((d) => ts.getSourceFileOfNode(d).isDeclarationFile) ) { const props = checker.getPropertiesOfType(widened); if ( props.length === 3 && - ["promise", "resolve", "reject"].every((n) => props.some((p) => p.name === n)) + ["promise", "resolve", "reject"].every((n) => props.some((p) => ts.symbolName(p) === n)) ) { - const promProp = props.find((p) => p.name === "promise")!; + const promProp = props.find((p) => ts.symbolName(p) === "promise")!; const promT = mapType(checker.getTypeOfSymbol(promProp), ctx); if (promT?.kind === "promise") { const inner = promT.inner; @@ -2744,9 +2779,11 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // option record whose members include functions, agents and signals. // Values ride checked-dynamic storage, like SpawnOptions. if ( - psym?.name === "RequestOptions" && + psym !== undefined && + ts.symbolName(psym) === "RequestOptions" && psym.declarations.some((d) => { - if (!ts.isInterfaceDeclaration(d) || !ctx.isStdlibFile(d.getSourceFile())) return false; + if (!ts.isInterfaceDeclaration(d) || !ctx.isStdlibFile(ts.getSourceFileOfNode(d))) + return false; for (let p: ts.Node | undefined = d.parent; p !== undefined; p = p.parent) { if (ts.isModuleDeclaration(p) && ts.isStringLiteral(p.name)) return HTTP_REQUEST_OPTION_MODULES.has(p.name.text); @@ -2769,9 +2806,10 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { { const alias = widened.getAliasSymbol(); if ( - alias?.name === "ReadableStreamReadResult" && + alias !== undefined && + ts.symbolName(alias) === "ReadableStreamReadResult" && alias.declarations.some( - (d) => ts.isTypeAliasDeclaration(d) && ctx.isStdlibFile(d.getSourceFile()), + (d) => ts.isTypeAliasDeclaration(d) && ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { const elemTs = widened.getAliasTypeArguments()?.[0]; @@ -2783,10 +2821,10 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { } if ( psym && - (psym.name === "ReadableStreamReadValueResult" || - psym.name === "ReadableStreamReadDoneResult") && + (ts.symbolName(psym) === "ReadableStreamReadValueResult" || + ts.symbolName(psym) === "ReadableStreamReadDoneResult") && psym.declarations.some( - (d) => ts.isInterfaceDeclaration(d) && ctx.isStdlibFile(d.getSourceFile()), + (d) => ts.isInterfaceDeclaration(d) && ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { const elemTs = checker.getTypeArguments(widened as ts.TypeReference)[0]; @@ -2798,11 +2836,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // node:net's SocketAddress and BlockList: native handles (scr_net.c) // whose getters and methods dispatch through the handle. if ( - (psym?.name === "SocketAddress" || psym?.name === "BlockList") && + ((psym !== undefined && ts.symbolName(psym) === "SocketAddress") || + (psym !== undefined && ts.symbolName(psym) === "BlockList")) && psym.declarations.some( (d) => (ts.isClassDeclaration(d) || ts.isInterfaceDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()), + ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { return DYN; @@ -2813,11 +2852,11 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // checked-dynamic objects. if ( psym && - WEBCRYPTO_DYN_TYPES.has(psym.name) && + WEBCRYPTO_DYN_TYPES.has(ts.symbolName(psym)) && psym.declarations.some( (d) => (ts.isClassDeclaration(d) || ts.isInterfaceDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()), + ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { return DYN; @@ -2826,20 +2865,21 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // whose getters and methods dispatch through the handle. The JsonWebKey // that export() returns is a plain checked-dynamic object. if ( - (psym?.name === "KeyObject" || psym?.name === "JsonWebKey") && + ((psym !== undefined && ts.symbolName(psym) === "KeyObject") || + (psym !== undefined && ts.symbolName(psym) === "JsonWebKey")) && psym.declarations.some( (d) => (ts.isClassDeclaration(d) || ts.isInterfaceDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()), + ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { return DYN; } if ( psym && - (FETCH_AMBIENT_TYPES as readonly string[]).includes(psym.name) && + (FETCH_AMBIENT_TYPES as readonly string[]).includes(ts.symbolName(psym)) && !( - psym.name === "ReadableStream" && + ts.symbolName(psym) === "ReadableStream" && psym.declarations.some( (d) => ts.isInterfaceDeclaration(d) && isDeclaredInAmbientNamespace(d, "NodeJS"), ) @@ -2847,7 +2887,7 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()), + ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { return DYN; @@ -2860,12 +2900,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // Symbol.asyncDispose method. A DisposableStack is a native handle whose // methods are function values of its checked-dynamic view. if ( - (psym?.name === "Console" || - psym?.name === "Disposable" || - psym?.name === "AsyncDisposable" || - psym?.name === "DisposableStack") && + ((psym !== undefined && ts.symbolName(psym) === "Console") || + (psym !== undefined && ts.symbolName(psym) === "Disposable") || + (psym !== undefined && ts.symbolName(psym) === "AsyncDisposable") || + (psym !== undefined && ts.symbolName(psym) === "DisposableStack")) && psym.declarations.some( - (d) => ts.isInterfaceDeclaration(d) && ctx.isStdlibFile(d.getSourceFile()), + (d) => ts.isInterfaceDeclaration(d) && ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { return DYN; @@ -2875,8 +2915,8 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // representation; lowerCipherMethodCall dispatches their methods. if ( psym !== undefined && - CIPHER_TYPE_NAMES.has(psym.name) && - psym.declarations.some((d) => ctx.isStdlibFile(d.getSourceFile())) + CIPHER_TYPE_NAMES.has(ts.symbolName(psym)) && + psym.declarations.some((d) => ctx.isStdlibFile(ts.getSourceFileOfNode(d))) ) { return DYN; } @@ -3003,11 +3043,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // fallback and by @types/node (whose NonSharedBuffer alias resolves to // the same symbol); provenance-checked like URL. if ( - psym?.name === "Buffer" && + psym !== undefined && + ts.symbolName(psym) === "Buffer" && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()), + ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { return bytesOf("u8"); @@ -3018,11 +3059,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // instance type). Provenance, not the name: a user's own URL maps as a // record/class like any other. if ( - psym?.name === "URL" && + psym !== undefined && + ts.symbolName(psym) === "URL" && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()), + ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { return { kind: "url" }; @@ -3031,11 +3073,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // interface by the shipped fallback and by @types/node (plus `class // URLSearchParams` in the "url" module). Provenance-checked like URL. if ( - psym?.name === "URLSearchParams" && + psym !== undefined && + ts.symbolName(psym) === "URLSearchParams" && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()), + ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { return { kind: "searchParams" }; @@ -3043,11 +3086,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // fs.Stats: @types/node's `class Stats` (module "fs") or the fallback // declarations' interface. Provenance-checked like URL. if ( - psym?.name === "Stats" && + psym !== undefined && + ts.symbolName(psym) === "Stats" && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()), + ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { return { kind: "stats" }; @@ -3055,13 +3099,13 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // Capacity snapshots and options keep their live object representation // across numeric/BigInt overloads, runtime options, and callbacks. if ( - (psym?.name === "StatsFs" || - psym?.name === "BigIntStatsFs" || - psym?.name === "StatFsOptions") && + ((psym !== undefined && ts.symbolName(psym) === "StatsFs") || + (psym !== undefined && ts.symbolName(psym) === "BigIntStatsFs") || + (psym !== undefined && ts.symbolName(psym) === "StatFsOptions")) && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "fs"), ) ) @@ -3071,11 +3115,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // distinguishes it from user interfaces with the same name; both the // fallback and @types/node declare it in "fs/promises". if ( - psym?.name === "FileHandle" && + psym !== undefined && + ts.symbolName(psym) === "FileHandle" && psym.declarations.some( (d) => ts.isInterfaceDeclaration(d) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "fs/promises"), ) ) { @@ -3084,11 +3129,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // fs.Dir: an open directory stream, declared in "fs" by both the // fallback and @types/node. if ( - psym?.name === "Dir" && + psym !== undefined && + ts.symbolName(psym) === "Dir" && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "fs"), ) ) { @@ -3099,11 +3145,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // reads work either way) or the fallback declarations' plain interface. // Provenance-checked like Stats. if ( - psym?.name === "SpawnSyncReturns" && + psym !== undefined && + ts.symbolName(psym) === "SpawnSyncReturns" && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()), + ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { return { kind: "spawnRes" }; @@ -3113,13 +3160,13 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // refinements only tell the checker which stdio properties are null. if ( psym !== undefined && - (psym.name === "ChildProcess" || - psym.name === "ChildProcessWithoutNullStreams" || - psym.name === "ChildProcessByStdio") && + (ts.symbolName(psym) === "ChildProcess" || + ts.symbolName(psym) === "ChildProcessWithoutNullStreams" || + ts.symbolName(psym) === "ChildProcessByStdio") && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "child_process"), ) ) { @@ -3133,25 +3180,27 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // that module (http's Server) stays unmapped and fences at its use // site. if ( - (psym?.name === "Server" || psym?.name === "Socket" || psym?.name === "TLSSocket") && + ((psym !== undefined && ts.symbolName(psym) === "Server") || + (psym !== undefined && ts.symbolName(psym) === "Socket") || + (psym !== undefined && ts.symbolName(psym) === "TLSSocket")) && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && (isDeclaredInAmbientModule(d, "net") || // http.Server extends net.Server in @types/node — and tls.Server // / https.Server extend it transitively: same lowered surface // (listen/close/address/'error'), same handle kind. tls.TLSSocket // extends net.Socket the same way: post-handshake it IS a net // socket to every lowered member. - (psym.name === "Server" && + (ts.symbolName(psym) === "Server" && (isDeclaredInAmbientModule(d, "http") || isDeclaredInAmbientModule(d, "tls") || isDeclaredInAmbientModule(d, "https"))) || - (psym.name === "TLSSocket" && isDeclaredInAmbientModule(d, "tls"))), + (ts.symbolName(psym) === "TLSSocket" && isDeclaredInAmbientModule(d, "tls"))), ) ) { - return psym.name === "Server" ? { kind: "netServer" } : { kind: "netSocket" }; + return ts.symbolName(psym) === "Server" ? { kind: "netServer" } : { kind: "netSocket" }; } // tls.SecureContext: the opaque parsed-cert/key handle SNI callbacks // answer with (tls.createSecureContext({ cert, key }) mints it; the TLS @@ -3159,11 +3208,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // @types/node declares `interface SecureContext` inside `declare module // "tls"`, and the shipped fallback mirrors it. if ( - psym?.name === "SecureContext" && + psym !== undefined && + ts.symbolName(psym) === "SecureContext" && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "tls"), ) ) { @@ -3172,11 +3222,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // Scrypt's runtime options retain property accessors and aliases. Use // checked values so presence probes and repeated getter reads match Node. if ( - psym?.name === "ScryptOptions" && + psym !== undefined && + ts.symbolName(psym) === "ScryptOptions" && psym.declarations.some( (d) => ts.isInterfaceDeclaration(d) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "crypto"), ) ) @@ -3185,26 +3236,28 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // provenance module-qualified so user classes with these common names // retain their ordinary structural/class representation. if ( - (psym?.name === "Hash" || psym?.name === "Hmac") && + ((psym !== undefined && ts.symbolName(psym) === "Hash") || + (psym !== undefined && ts.symbolName(psym) === "Hmac")) && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "crypto"), ) ) { - return psym.name === "Hash" ? CRYPTOHASH_T : CRYPTOHMAC_T; + return ts.symbolName(psym) === "Hash" ? CRYPTOHASH_T : CRYPTOHMAC_T; } // fs.FSWatcher: the fs.watch handle (scr_watch.c). Module-checked like // net.Server — @types/node declares `interface FSWatcher extends // EventEmitter` inside `declare module "fs"`, and the shipped fallback // mirrors it. if ( - psym?.name === "FSWatcher" && + psym !== undefined && + ts.symbolName(psym) === "FSWatcher" && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "fs"), ) ) { @@ -3239,11 +3292,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { ts.constituentTypes(type).some((part) => { const sym = part.getSymbol(); return ( - (sym?.name === "ReadableStream" || sym?.name === "WritableStream") && + ((sym !== undefined && ts.symbolName(sym) === "ReadableStream") || + (sym !== undefined && ts.symbolName(sym) === "WritableStream")) && sym.declarations.some( (d) => ts.isInterfaceDeclaration(d) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientNamespace(d, "NodeJS"), ) ); @@ -3252,22 +3306,24 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { return DYN; } const nodeWritableInterface = - psym?.name === "WritableStream" && + psym !== undefined && + ts.symbolName(psym) === "WritableStream" && psym.declarations.some( (d) => ts.isInterfaceDeclaration(d) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientNamespace(d, "NodeJS"), ); if (nodeWritableInterface && ctx.generalNodeStreams === true) { return ctx.dynNodeStreams === true ? DYN : { kind: "object", className: "%Writable" }; } if ( - (psym?.name === "WriteStream" && + (psym !== undefined && + ts.symbolName(psym) === "WriteStream" && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && (isDeclaredInAmbientModule(d, "tty") || (isDeclaredInAmbientModule(d, "process") && isDeclaredInAmbientNamespace(d, "NodeJS"))), )) || @@ -3280,21 +3336,23 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // stream.Writable. As with stream.Readable below, unsupported producers // of the broad @types class fence where they are constructed. if ( - (psym?.name === "ChildStdin" && + (psym !== undefined && + ts.symbolName(psym) === "ChildStdin" && ctx.generalNodeStreams !== true && psym.declarations.some( (d) => ts.isInterfaceDeclaration(d) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "child_process"), )) || - (psym?.name === "Writable" && + (psym !== undefined && + ts.symbolName(psym) === "Writable" && ctx.generalNodeStreams !== true && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && - isNodeTypesPath(d.getSourceFile().fileName) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && + isNodeTypesPath(ts.getSourceFileOfNode(d).fileName) && isDeclaredInAmbientModule(d, "stream"), )) ) { @@ -3304,11 +3362,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // ChildStdin is the runtime Writable too (child.stdioStream bridges it). if ( ctx.generalNodeStreams === true && - psym?.name === "ChildStdin" && + psym !== undefined && + ts.symbolName(psym) === "ChildStdin" && psym.declarations.some( (d) => ts.isInterfaceDeclaration(d) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "child_process"), ) ) { @@ -3326,12 +3385,13 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // Writable join them under the general representation. for (const [irName, rec] of RUNTIME_STREAM_CLASSES) { if ( - psym?.name === rec.lib && + psym !== undefined && + ts.symbolName(psym) === rec.lib && psym.declarations.some( (d) => ts.isClassDeclaration(d) && - ctx.isStdlibFile(d.getSourceFile()) && - (!isNodeTypesPath(d.getSourceFile().fileName) || + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && + (!isNodeTypesPath(ts.getSourceFileOfNode(d).fileName) || ctx.generalNodeStreams === true || (rec.lib !== "Readable" && rec.lib !== "Writable")) && isDeclaredInAmbientModule(d, "stream"), @@ -3344,12 +3404,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // @types/node's): their instances are runtime Transform streams. if ( psym !== undefined && - ZLIB_STREAM_MODES.has(psym.name) && + ZLIB_STREAM_MODES.has(ts.symbolName(psym)) && psym.declarations.some( (d) => (ts.isClassDeclaration(d) || - (ts.isInterfaceDeclaration(d) && isNodeTypesPath(d.getSourceFile().fileName))) && - ctx.isStdlibFile(d.getSourceFile()) && + (ts.isInterfaceDeclaration(d) && isNodeTypesPath(ts.getSourceFileOfNode(d).fileName))) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "zlib"), ) ) { @@ -3359,29 +3419,35 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // @types/node's): their instances are runtime Readable / Writable // streams (scr_fs_stream.c). if ( - (psym?.name === "ReadStream" || psym?.name === "WriteStream") && + ((psym !== undefined && ts.symbolName(psym) === "ReadStream") || + (psym !== undefined && ts.symbolName(psym) === "WriteStream")) && psym.declarations.some( (d) => ts.isClassDeclaration(d) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "fs"), ) ) { - return { kind: "object", className: psym.name === "ReadStream" ? "%Readable" : "%Writable" }; + return { + kind: "object", + className: ts.symbolName(psym) === "ReadStream" ? "%Readable" : "%Writable", + }; } if ( - (psym?.name === "Readable" && + (psym !== undefined && + ts.symbolName(psym) === "Readable" && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "stream"), )) || - (psym?.name === "ReadableStream" && + (psym !== undefined && + ts.symbolName(psym) === "ReadableStream" && psym.declarations.some( (d) => ts.isInterfaceDeclaration(d) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientNamespace(d, "NodeJS"), )) ) { @@ -3390,7 +3456,7 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // checked-dynamic stream slots, NodeJS.ReadableStream holds any // stream value (dyn-streams.ts); stream.Readable keeps the class. if (ctx.generalNodeStreams === true) { - if (ctx.dynNodeStreams === true && psym.name === "ReadableStream") return DYN; + if (ctx.dynNodeStreams === true && ts.symbolName(psym) === "ReadableStream") return DYN; return { kind: "object", className: "%Readable" }; } return { kind: "childStream" }; @@ -3400,11 +3466,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // EventEmitter` and the fallback declarations' interface both live // inside `declare module "dgram"`. if ( - psym?.name === "Socket" && + psym !== undefined && + ts.symbolName(psym) === "Socket" && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "dgram"), ) ) { @@ -3417,11 +3484,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // `declare module "node:test"` (isDeclaredInAmbientModule answers for // both spellings). if ( - (psym?.name === "TestContext" || psym?.name === "SuiteContext") && + ((psym !== undefined && ts.symbolName(psym) === "TestContext") || + (psym !== undefined && ts.symbolName(psym) === "SuiteContext")) && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "test"), ) ) { @@ -3431,18 +3499,19 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // records built from F (mock-shapes.ts). Mock then maps as the hybrid // { %call: F, mock } record through mapHybridCallableIntersection. if ( - (psym?.name === "MockFunctionContext" || psym?.name === "MockFunctionCall") && + ((psym !== undefined && ts.symbolName(psym) === "MockFunctionContext") || + (psym !== undefined && ts.symbolName(psym) === "MockFunctionCall")) && psym.declarations.some( (d) => ts.isInterfaceDeclaration(d) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "test"), ) ) { const fnTs = checker.getTypeArguments(widened as ts.TypeReference)[0]; const fn = fnTs ? mapType(fnTs, ctx) : null; if (!fn || fn.kind !== "func") return null; - return psym.name === "MockFunctionContext" + return ts.symbolName(psym) === "MockFunctionContext" ? mockContextType(fn, ctx) : mockCallRecordType(fn, ctx); } @@ -3452,9 +3521,10 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // above; optional metadata uses the same checked property view. // Provenance-checked like the rest. if ( - psym?.name === "ErrnoException" && + psym !== undefined && + ts.symbolName(psym) === "ErrnoException" && psym.declarations.some( - (d) => ts.isInterfaceDeclaration(d) && ctx.isStdlibFile(d.getSourceFile()), + (d) => ts.isInterfaceDeclaration(d) && ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { return { kind: "object", className: "%Error" }; @@ -3466,11 +3536,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // of fencing at the declaration. Module-checked like the rest (https' // Agent extends http's; both live in their ambient modules). if ( - psym?.name === "Agent" && + psym !== undefined && + ts.symbolName(psym) === "Agent" && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && (isDeclaredInAmbientModule(d, "http") || isDeclaredInAmbientModule(d, "https")), ) ) { @@ -3480,15 +3551,16 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // net.Server (the names repeat nowhere today, but provenance stays the // rule). if ( - (psym?.name === "IncomingMessage" || psym?.name === "ServerResponse") && + ((psym !== undefined && ts.symbolName(psym) === "IncomingMessage") || + (psym !== undefined && ts.symbolName(psym) === "ServerResponse")) && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "http"), ) ) { - return psym.name === "IncomingMessage" ? { kind: "httpReq" } : { kind: "httpRes" }; + return ts.symbolName(psym) === "IncomingMessage" ? { kind: "httpReq" } : { kind: "httpRes" }; } // http2's compatibility surface (the allowHTTP1 lowering): the secure // server IS a net server (listen/close/address/emit ride the same @@ -3499,19 +3571,19 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // member at their use sites (lower-server.ts). Module-checked like the // rest. if ( - (psym?.name === "Http2SecureServer" || - psym?.name === "Http2ServerRequest" || - psym?.name === "Http2ServerResponse") && + ((psym !== undefined && ts.symbolName(psym) === "Http2SecureServer") || + (psym !== undefined && ts.symbolName(psym) === "Http2ServerRequest") || + (psym !== undefined && ts.symbolName(psym) === "Http2ServerResponse")) && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "http2"), ) ) { - return psym.name === "Http2SecureServer" + return ts.symbolName(psym) === "Http2SecureServer" ? { kind: "netServer" } - : psym.name === "Http2ServerRequest" + : ts.symbolName(psym) === "Http2ServerRequest" ? { kind: "httpReq" } : { kind: "httpRes" }; } @@ -3520,33 +3592,34 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // kinds (client and server flavors share a kind — the runtime handle // carries the role). Module-checked like the rest. if ( - (psym?.name === "Http2Server" || - psym?.name === "Http2Session" || - psym?.name === "ClientHttp2Session" || - psym?.name === "ServerHttp2Session" || - psym?.name === "ClientHttp2Stream" || - psym?.name === "ServerHttp2Stream") && + ((psym !== undefined && ts.symbolName(psym) === "Http2Server") || + (psym !== undefined && ts.symbolName(psym) === "Http2Session") || + (psym !== undefined && ts.symbolName(psym) === "ClientHttp2Session") || + (psym !== undefined && ts.symbolName(psym) === "ServerHttp2Session") || + (psym !== undefined && ts.symbolName(psym) === "ClientHttp2Stream") || + (psym !== undefined && ts.symbolName(psym) === "ServerHttp2Stream")) && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "http2"), ) ) { - return psym.name === "Http2Server" + return ts.symbolName(psym) === "Http2Server" ? { kind: "netServer" } - : psym.name === "ClientHttp2Stream" || psym.name === "ServerHttp2Stream" + : ts.symbolName(psym) === "ClientHttp2Stream" || ts.symbolName(psym) === "ServerHttp2Stream" ? { kind: "http2Stream" } : { kind: "http2Session" }; } // http.ClientRequest (http.request/http.get results) — module-checked // like IncomingMessage. if ( - psym?.name === "ClientRequest" && + psym !== undefined && + ts.symbolName(psym) === "ClientRequest" && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "http"), ) ) { @@ -3561,9 +3634,10 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // declarations' anonymous env type takes the pureIndexShape path below // and interns the SAME shape. if ( - psym?.name === "ProcessEnv" && + psym !== undefined && + ts.symbolName(psym) === "ProcessEnv" && psym.declarations.some( - (d) => ts.isInterfaceDeclaration(d) && ctx.isStdlibFile(d.getSourceFile()), + (d) => ts.isInterfaceDeclaration(d) && ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { const value = withUndefinedArm(STRING, ctx.unions); @@ -3653,9 +3727,10 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { { const alias = widened.getAliasSymbol(); if ( - alias?.name === "IteratorResult" && + alias !== undefined && + ts.symbolName(alias) === "IteratorResult" && alias.declarations.some( - (d) => ts.isTypeAliasDeclaration(d) && ctx.isStdlibFile(d.getSourceFile()), + (d) => ts.isTypeAliasDeclaration(d) && ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { const targs = widened.getAliasTypeArguments() ?? []; @@ -3775,10 +3850,10 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { ]; const fields: { name: string; type: IrType }[] = []; for (const property of checker.getPropertiesOfType(widened)) { - if (!known.includes(property.name)) return null; + if (!known.includes(ts.symbolName(property))) return null; const type = mapType(checker.getTypeOfSymbol(property), ctx); if (!type) return null; - fields.push({ name: property.name, type }); + fields.push({ name: ts.symbolName(property), type }); } const order = fields.map((field) => field.name); fields.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0)); @@ -3787,23 +3862,24 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // Filesystem options are data records. Keep their declared optional fields // instead of allowing arbitrary standard-library interfaces into records. if ( - (psym?.name === "FileReadOptions" || - psym?.name === "ReadOptions" || - psym?.name === "ReadOptionsWithBuffer" || - psym?.name === "WriteOptions") && + ((psym !== undefined && ts.symbolName(psym) === "FileReadOptions") || + (psym !== undefined && ts.symbolName(psym) === "ReadOptions") || + (psym !== undefined && ts.symbolName(psym) === "ReadOptionsWithBuffer") || + (psym !== undefined && ts.symbolName(psym) === "WriteOptions")) && psym.declarations.some( (d) => ts.isInterfaceDeclaration(d) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && (isDeclaredInAmbientModule(d, "fs/promises") || isDeclaredInAmbientModule(d, "fs")), ) ) { const fields: { name: string; type: IrType }[] = []; for (const property of checker.getPropertiesOfType(widened)) { - if (!["buffer", "offset", "length", "position"].includes(property.name)) return null; + if (!["buffer", "offset", "length", "position"].includes(ts.symbolName(property))) + return null; const type = mapType(checker.getTypeOfSymbol(property), ctx); if (!type) return null; - fields.push({ name: property.name, type }); + fields.push({ name: ts.symbolName(property), type }); } const order = fields.map((field) => field.name); fields.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0)); @@ -3814,28 +3890,28 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // record does not model the prototype (unobservable through the lowered // surface); it preserves the caller buffer/string by reference. if ( - (psym?.name === "FileReadResult" || - psym?.name === "FileWriteResult" || - psym?.name === "FileReadvResult" || - psym?.name === "FileWritevResult" || - psym?.name === "ReadVResult" || - psym?.name === "WriteVResult") && + ((psym !== undefined && ts.symbolName(psym) === "FileReadResult") || + (psym !== undefined && ts.symbolName(psym) === "FileWriteResult") || + (psym !== undefined && ts.symbolName(psym) === "FileReadvResult") || + (psym !== undefined && ts.symbolName(psym) === "FileWritevResult") || + (psym !== undefined && ts.symbolName(psym) === "ReadVResult") || + (psym !== undefined && ts.symbolName(psym) === "WriteVResult")) && psym.declarations.some( (d) => ts.isInterfaceDeclaration(d) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && (isDeclaredInAmbientModule(d, "fs/promises") || - ((psym.name === "ReadVResult" || psym.name === "WriteVResult") && + ((ts.symbolName(psym) === "ReadVResult" || ts.symbolName(psym) === "WriteVResult") && isDeclaredInAmbientModule(d, "fs"))), ) ) { const args = checker.getTypeArguments(widened as ts.TypeReference); if (args.length !== 1) return null; const vector = - psym.name === "FileReadvResult" || - psym.name === "FileWritevResult" || - psym.name === "ReadVResult" || - psym.name === "WriteVResult"; + ts.symbolName(psym) === "FileReadvResult" || + ts.symbolName(psym) === "FileWritevResult" || + ts.symbolName(psym) === "ReadVResult" || + ts.symbolName(psym) === "WriteVResult"; const emptyVector = vector && checker.isArrayType(args[0]!) && @@ -3850,9 +3926,9 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { return null; } const count = - psym.name === "FileReadResult" || - psym.name === "FileReadvResult" || - psym.name === "ReadVResult" + ts.symbolName(psym) === "FileReadResult" || + ts.symbolName(psym) === "FileReadvResult" || + ts.symbolName(psym) === "ReadVResult" ? "bytesRead" : "bytesWritten"; const payloadName = vector ? "buffers" : "buffer"; @@ -3884,11 +3960,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // declares it concrete (an interface; @types/node uses a class — both // provenance-checked, the Buffer/URL pattern). if ( - psym?.name === "Dirent" && + psym !== undefined && + ts.symbolName(psym) === "Dirent" && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()), + ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { const args = checker.getTypeArguments(widened as ts.TypeReference); @@ -3945,7 +4022,7 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { d.parent.name.text === "times" && ts.isInterfaceDeclaration(d.parent.parent) && d.parent.parent.name.text === "CpuInfo" && - ctx.isStdlibFile(d.getSourceFile()), + ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) return cpuTimes(); @@ -3966,7 +4043,8 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { }; } if (isStdlibInterface("ParsedPath") || isStdlibInterface("FormatInputPathObject")) { - const field = psym!.name === "ParsedPath" ? STRING : withUndefinedArm(STRING, ctx.unions); + const field = + ts.symbolName(psym!) === "ParsedPath" ? STRING : withUndefinedArm(STRING, ctx.unions); if (!field) return null; return { kind: "record", @@ -4017,7 +4095,7 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { psym.declarations.some( (d) => ts.isTypeLiteralNode(d) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && (isDeclaredInAmbientModule(d, "child_process") || isDeclaredInAmbientModule(d, "util") || isDeclaredInAmbientModule(d, "dns")), @@ -4032,7 +4110,7 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { (checker.getTypeOfSymbol(p).flags & ts.TypeFlags.String) !== 0, ) ? props - .map((p) => p.name) + .map((p) => ts.symbolName(p)) .sort() .join(",") : ""; @@ -4060,11 +4138,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // JSON.stringify of one shows the three fields where Node's // internal-slot object shows {} — the Dirent %dtype edge's cousin. if ( - psym?.name === "X509Certificate" && + psym !== undefined && + ts.symbolName(psym) === "X509Certificate" && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "crypto"), ) ) { @@ -4091,13 +4170,13 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // @types/node declares the classes in perf_hooks and global interfaces // over them; the shipped fallback declares the classes. if ( - (psym?.name === "PerformanceEntry" || - psym?.name === "PerformanceMark" || - psym?.name === "PerformanceMeasure") && + ((psym !== undefined && ts.symbolName(psym) === "PerformanceEntry") || + (psym !== undefined && ts.symbolName(psym) === "PerformanceMark") || + (psym !== undefined && ts.symbolName(psym) === "PerformanceMeasure")) && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()), + ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { return { kind: "record", shapeId: perfEntryShape(ctx.shapes, ctx.unions) }; @@ -4254,7 +4333,7 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { isStdlibInterface("NetworkInterfaceInfoIPv4") || isStdlibInterface("NetworkInterfaceInfoIPv6") ) { - const v6 = psym!.name === "NetworkInterfaceInfoIPv6"; + const v6 = ts.symbolName(psym!) === "NetworkInterfaceInfoIPv6"; const cidrArms = [NULL_T, STRING].sort((a, b) => (typeKey(a) < typeKey(b) ? -1 : 1)); const cidr: IrType = { kind: "union", unionId: ctx.unions.intern(cidrArms) }; const scopeid = v6 ? F64 : withUndefinedArm(F64, ctx.unions); @@ -4402,7 +4481,7 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { psym !== undefined && psym.declarations.length > 0 && psym.declarations.every( - (d) => ts.isInterfaceDeclaration(d) && !ctx.isStdlibFile(d.getSourceFile()), + (d) => ts.isInterfaceDeclaration(d) && !ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { const CHILD_SURFACE = new Set([ @@ -4437,24 +4516,26 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { const props = checker.getPropertiesOfType(widened); let core = 0; const childMemberType = (p: ts.Symbol): boolean => { - if (["kill", "on", "once", "off", "removeListener", "unref", "ref"].includes(p.name)) { + if ( + ["kill", "on", "once", "off", "removeListener", "unref", "ref"].includes(ts.symbolName(p)) + ) { return checker.getCallSignatures(checker.getTypeOfSymbol(p)).length > 0; } const expected = - p.name === "stdout" || p.name === "stderr" + ts.symbolName(p) === "stdout" || ts.symbolName(p) === "stderr" ? "childStream" - : p.name === "stdin" + : ts.symbolName(p) === "stdin" ? "childWriter" - : p.name === "killed" + : ts.symbolName(p) === "killed" ? "bool" : "f64"; const unit = - p.name === "pid" + ts.symbolName(p) === "pid" ? "undefinedT" - : p.name === "exitCode" || - p.name === "stdin" || - p.name === "stdout" || - p.name === "stderr" + : ts.symbolName(p) === "exitCode" || + ts.symbolName(p) === "stdin" || + ts.symbolName(p) === "stdout" || + ts.symbolName(p) === "stderr" ? "nullT" : null; const mapped = mapType(checker.getTypeOfSymbol(p), ctx); @@ -4482,8 +4563,8 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { const childShaped = props.length > 0 && props.every((p) => { - if (!CHILD_SURFACE.has(p.name) || !childMemberType(p)) return false; - if (CHILD_CORE.has(p.name)) core++; + if (!CHILD_SURFACE.has(ts.symbolName(p)) || !childMemberType(p)) return false; + if (CHILD_CORE.has(ts.symbolName(p))) core++; return true; }); if (childShaped && core >= 2) return CHILD_T; @@ -4688,7 +4769,7 @@ function mapCallSignature(sig: ts.Signature, ctx: TypeMapperCtx): IrType | null // arity ABI selected by lambdaSignature. Treating an inferred any[] // declaration as a typed packed-array closure would bypass its boxed // spread/error semantics and change coverage classification. - if (rest && decl !== undefined && isJsSourceFile(decl.getSourceFile())) return null; + if (rest && decl !== undefined && isJsSourceFile(ts.getSourceFileOfNode(decl))) return null; // A fixed-tuple rest (`...args: [A, B]`, `...args: Parameters` over // a bound F) flattens to its positional parameters. Pattern rests keep // their packed-record ABI and stay unmapped as values. @@ -4722,8 +4803,8 @@ function mapCallSignature(sig: ts.Signature, ctx: TypeMapperCtx): IrType | null ((ts.isArrowFunction(decl.parent) || ts.isFunctionExpression(decl.parent)) && decl.initializer !== undefined && decl.type === undefined && - !isJsSourceFile(decl.getSourceFile()))) && - !decl.getSourceFile().isDeclarationFile && + !isJsSourceFile(ts.getSourceFileOfNode(decl)))) && + !ts.getSourceFileOfNode(decl).isDeclarationFile && (checker.getTypeOfSymbol(p).flags & ts.TypeFlags.Any) !== 0 ) pt = DYN; @@ -4731,7 +4812,7 @@ function mapCallSignature(sig: ts.Signature, ctx: TypeMapperCtx): IrType | null decl && ts.isParameter(decl) && ts.isSetAccessorDeclaration(decl.parent) && - isJsSourceFile(decl.getSourceFile()) + isJsSourceFile(ts.getSourceFileOfNode(decl)) ) pt = DYN; // An inferred JavaScript binding pattern consumes an iterable; it @@ -4751,17 +4832,19 @@ function mapCallSignature(sig: ts.Signature, ctx: TypeMapperCtx): IrType | null ts.isIdentifier(construction.expression) && construction.expression.text === "Promise" && constructorSymbol && - constructorSymbol.declarations.some((site) => ctx.isStdlibFile(site.getSourceFile())); + constructorSymbol.declarations.some((site) => ctx.isStdlibFile(ts.getSourceFileOfNode(site))); if ( !nativeExecutor && decl && ts.isParameter(decl) && !decl.dotDotDotToken && !ts.isSetAccessorDeclaration(decl.parent) && - isJsSourceFile(decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(decl)) && !decl.type && !/@(?:param|type)\b/.test( - decl.getSourceFile().text.slice(decl.parent?.pos ?? decl.pos, decl.getStart()), + ts + .getSourceFileOfNode(decl) + .text.slice(decl.parent?.pos ?? decl.pos, ts.tokenStartOfNode(decl)), ) ) pt = DYN; @@ -4782,7 +4865,7 @@ function mapCallSignature(sig: ts.Signature, ctx: TypeMapperCtx): IrType | null (ts.isArrowFunction(decl.parent) || ts.isFunctionExpression(decl.parent) || ts.isFunctionDeclaration(decl.parent)) && - !decl.getSourceFile().isDeclarationFile && + !ts.getSourceFileOfNode(decl).isDeclarationFile && (checker.getTypeOfSymbol(p).flags & ts.TypeFlags.Any) !== 0 ) pt = DYN; @@ -4807,7 +4890,8 @@ function mapCallSignature(sig: ts.Signature, ctx: TypeMapperCtx): IrType | null if ( rest && decl !== undefined && - (!decl.getSourceFile().isDeclarationFile || isNodeTestDefaultFunctionParam(decl, ctx)) && + (!ts.getSourceFileOfNode(decl).isDeclarationFile || + isNodeTestDefaultFunctionParam(decl, ctx)) && (pt?.kind === "dyn" || (!pt && isAnyArrayType(checker.getTypeOfSymbol(p), checker))) ) { dynRest = true; @@ -4832,7 +4916,7 @@ function mapCallSignature(sig: ts.Signature, ctx: TypeMapperCtx): IrType | null const sigDecl = sig.declaration; const jsUnitReturn = sigDecl !== undefined && - isJsSourceFile(sigDecl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(sigDecl)) && (retT.flags & (ts.TypeFlags.Null | ts.TypeFlags.Undefined)) !== 0; let ret = jsUnitReturn || (retT.flags & ts.TypeFlags.Any) !== 0 @@ -4840,24 +4924,33 @@ function mapCallSignature(sig: ts.Signature, ctx: TypeMapperCtx): IrType | null : retT.flags & ts.TypeFlags.Never ? VOID : mapType(retT, ctx); - if (sigDecl && ts.isGetAccessorDeclaration(sigDecl) && isJsSourceFile(sigDecl.getSourceFile())) + if ( + sigDecl && + ts.isGetAccessorDeclaration(sigDecl) && + isJsSourceFile(ts.getSourceFileOfNode(sigDecl)) + ) + ret = DYN; + if (sigDecl && isJsSourceFile(ts.getSourceFileOfNode(sigDecl)) && ret?.kind === "array") ret = DYN; - if (sigDecl && isJsSourceFile(sigDecl.getSourceFile()) && ret?.kind === "array") ret = DYN; if ( sigDecl && ts.isFunctionLike(sigDecl) && - isJsSourceFile(sigDecl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(sigDecl)) && !sigDecl.type && - !/@returns?\b/.test(sigDecl.getSourceFile().text.slice(sigDecl.pos, sigDecl.getStart())) && + !/@returns?\b/.test( + ts.getSourceFileOfNode(sigDecl).text.slice(sigDecl.pos, ts.tokenStartOfNode(sigDecl)), + ) && ret?.kind === "record" ) ret = DYN; if ( sigDecl && ts.isFunctionLike(sigDecl) && - isJsSourceFile(sigDecl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(sigDecl)) && !sigDecl.type && - !/@returns?\b/.test(sigDecl.getSourceFile().text.slice(sigDecl.pos, sigDecl.getStart())) && + !/@returns?\b/.test( + ts.getSourceFileOfNode(sigDecl).text.slice(sigDecl.pos, ts.tokenStartOfNode(sigDecl)), + ) && (ret?.kind === "date" || (ret?.kind === "union" && ctx.unions.get(ret.unionId)?.arms.some((arm) => arm.kind === "func"))) @@ -4866,7 +4959,7 @@ function mapCallSignature(sig: ts.Signature, ctx: TypeMapperCtx): IrType | null if ( ret && sigDecl && - isJsSourceFile(sigDecl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(sigDecl)) && functionCanReturnUndefined(sigDecl) ) ret = withUndefinedArm(ret, ctx.unions) ?? ret; @@ -4899,12 +4992,12 @@ function unionDiscriminant( // no slot to compare (discriminantField refuses tuple shapes too). if (parts.some((part) => ctx.shapes.get(part.mapped.shapeId)?.tuple)) return undefined; for (const candidate of checker.getPropertiesOfType(parts[0]!.source)) { - if (candidate.name.startsWith("__@")) continue; + if (ts.symbolName(candidate).startsWith("__@")) continue; const byTag = new Map(); const owner = new Map(); let complete = true; for (const part of parts) { - const prop = checker.getPropertyOfType(part.source, candidate.name); + const prop = checker.getPropertyOfType(part.source, ts.symbolName(candidate)); if ( !prop || prop.flags & @@ -4935,7 +5028,7 @@ function unionDiscriminant( } if (complete) { return { - field: candidate.name, + field: ts.symbolName(candidate), cases: [...byTag] .sort(([a], [b]) => a - b) .map(([tag, values]) => ({ @@ -4977,7 +5070,8 @@ function mapClassView( if ( declarations.length === 0 || !declarations.every( - (decl) => ts.isInterfaceDeclaration(decl) && !ctx.isStdlibFile(decl.getSourceFile()), + (decl) => + ts.isInterfaceDeclaration(decl) && !ctx.isStdlibFile(ts.getSourceFileOfNode(decl)), ) ) return undefined; @@ -5015,7 +5109,7 @@ function mapClassView( const classBase = declaration && (ts.isClassDeclaration(declaration) || ts.isClassExpression(declaration)) && - !declaration.getSourceFile().isDeclarationFile; + !ts.getSourceFileOfNode(declaration).isDeclarationFile; const mapped = classBase ? mapType(base, viewCtx) : mapClassView(base, viewCtx, seen); if (mapped === null) return null; if (mapped === undefined) continue; @@ -5032,7 +5126,7 @@ function mapClassView( ) return null; for (const property of checker.getPropertiesOfType(type)) { - const original = checker.getPropertyOfType(baseType, property.name); + const original = checker.getPropertyOfType(baseType, ts.symbolName(property)); if (!original) return null; if (original === property) continue; const originalDeclarations = original.declarations; @@ -5078,7 +5172,7 @@ function mapCollectionView( if ( declarations.length === 0 || !declarations.every( - (decl) => ts.isInterfaceDeclaration(decl) && !ctx.isStdlibFile(decl.getSourceFile()), + (decl) => ts.isInterfaceDeclaration(decl) && !ctx.isStdlibFile(ts.getSourceFileOfNode(decl)), ) ) return null; @@ -5126,18 +5220,20 @@ function mapCollectionView( const baseSymbol = base.getSymbol(); const native = baseSymbol && - ["Map", "ReadonlyMap", "Set", "ReadonlySet"].includes(baseSymbol.name) && + ["Map", "ReadonlyMap", "Set", "ReadonlySet"].includes(ts.symbolName(baseSymbol)) && baseSymbol.declarations.some( - (decl) => ts.isInterfaceDeclaration(decl) && ctx.isStdlibFile(decl.getSourceFile()), + (decl) => ts.isInterfaceDeclaration(decl) && ctx.isStdlibFile(ts.getSourceFileOfNode(decl)), ); const mapped = native ? mapType(base, baseCtx) : mapCollectionView(base, baseCtx, seen); if (mapped?.kind !== "map" && mapped?.kind !== "set") return null; const hasType = funcOf([mapped.kind === "map" ? mapped.key : mapped.elem], BOOL); for (const property of checker.getPropertiesOfType(type)) { - const own = property.declarations.filter((decl) => !ctx.isStdlibFile(decl.getSourceFile())); + const own = property.declarations.filter( + (decl) => !ctx.isStdlibFile(ts.getSourceFileOfNode(decl)), + ); if (own.length === 0) continue; if ( - property.name !== "has" || + ts.symbolName(property) !== "has" || property.flags & ts.SymbolFlags.Optional || !own.every((decl) => decl.kind === ts.SyntaxKind.MethodSignature) ) @@ -5247,12 +5343,13 @@ function boundSignatureAlias( const { checker, resolveTypeParamTs } = ctx; if (!resolveTypeParamTs) return null; const alias = type.getAliasSymbol(); - if (!alias || (alias.name !== "Parameters" && alias.name !== "ReturnType")) return null; + const aliasName = alias === undefined ? undefined : ts.symbolName(alias); + if (!alias || (aliasName !== "Parameters" && aliasName !== "ReturnType")) return null; const aliasArgs = type.getAliasTypeArguments(); const arg = aliasArgs[0]; if (aliasArgs.length !== 1 || !arg || !(arg.flags & ts.TypeFlags.TypeParameter)) return null; const isLibAlias = alias.declarations.some( - (d) => ts.isTypeAliasDeclaration(d) && ctx.isStdlibFile(d.getSourceFile()), + (d) => ts.isTypeAliasDeclaration(d) && ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ); if (!isLibAlias) return null; const bound = resolveTypeParamTs(arg); @@ -5260,7 +5357,7 @@ function boundSignatureAlias( const sigs = checker.getCallSignatures(bound); const sig = sigs[0]; if (sigs.length !== 1 || !sig || sig.getTypeParameters().length > 0) return null; - return { name: alias.name, sig }; + return { name: aliasName, sig }; } /** One position of a fixed-arity parameter tuple. An optional position @@ -5463,7 +5560,7 @@ function mapGenericAwaitedAlias(type: ts.Type, ctx: TypeMapperCtx): IrType | nul const { resolveTypeParam } = ctx; if (!resolveTypeParam) return null; const alias = type.getAliasSymbol(); - if (!alias || alias.name !== "Awaited") return null; + if (!alias || ts.symbolName(alias) !== "Awaited") return null; const aliasArgs = type.getAliasTypeArguments(); const arg = aliasArgs[0]; if (aliasArgs.length !== 1 || !arg) return null; @@ -5474,7 +5571,7 @@ function mapGenericAwaitedAlias(type: ts.Type, ctx: TypeMapperCtx): IrType | nul if (!(arg.flags & ts.TypeFlags.TypeParameter) && viaSignature?.name !== "ReturnType") return null; // Provenance: the STANDARD LIBRARY's alias, not a user's shadowing one. const isLibAlias = alias.declarations.some( - (d) => ts.isTypeAliasDeclaration(d) && ctx.isStdlibFile(d.getSourceFile()), + (d) => ts.isTypeAliasDeclaration(d) && ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ); if (!isLibAlias) return null; const bound = viaSignature ? mapBoundSignatureAlias(arg, ctx) : resolveTypeParam(arg); @@ -5618,7 +5715,7 @@ function mapBoundMappedType(widened: ts.Type, ctx: TypeMapperCtx): IrType | null const fields: { name: string; type: IrType }[] = []; const order: string[] = []; for (const property of checker.getPropertiesOfType(bound)) { - const name = property.name; + const name = ts.symbolName(property); const evaluated = evalMappedTemplate(template, new Map([[key, name]]), new Map(), ctx); let type = evaluated ? mapType(evaluated, ctx) : null; if (!type) return null; @@ -5886,8 +5983,8 @@ function matchInferThenable( const symbol = t.getSymbol(); return ( symbol !== undefined && - names.includes(symbol.name) && - symbol.declarations.some((d) => ctx.isStdlibFile(d.getSourceFile())) + names.includes(ts.symbolName(symbol)) && + symbol.declarations.some((d) => ctx.isStdlibFile(ts.getSourceFileOfNode(d))) ); }; if (!stdlibNamed(extendsType, ["PromiseLike", "Promise"])) return undefined; @@ -5919,11 +6016,11 @@ function mentionsMappedParams( function readonlyClassType(type: ts.Type, ctx: TypeMapperCtx): IrType | null { const alias = type.getAliasSymbol(); - if (alias?.name !== "Readonly") return null; + if (alias === undefined || ts.symbolName(alias) !== "Readonly") return null; const args = type.getAliasTypeArguments(); if (args.length !== 1 || !args[0]) return null; const isLibAlias = alias.declarations.some( - (d) => ts.isTypeAliasDeclaration(d) && ctx.isStdlibFile(d.getSourceFile()), + (d) => ts.isTypeAliasDeclaration(d) && ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ); if (!isLibAlias) return null; const mapped = mapType(args[0], ctx); @@ -5934,19 +6031,20 @@ function mapGenericUtilityAlias(widened: ts.Type, ctx: TypeMapperCtx): IrType | const { resolveTypeParam, shapes, unions } = ctx; if (!resolveTypeParam) return null; const alias = widened.getAliasSymbol(); - if (!alias || (alias.name !== "Partial" && alias.name !== "Readonly")) return null; + if (!alias || (ts.symbolName(alias) !== "Partial" && ts.symbolName(alias) !== "Readonly")) + return null; const aliasArgs = widened.getAliasTypeArguments(); const arg = aliasArgs[0]; if (aliasArgs.length !== 1 || !arg) return null; if (!(arg.flags & ts.TypeFlags.TypeParameter)) return null; // Provenance: the STANDARD LIBRARY's alias, not a user's shadowing one. const isLibAlias = alias.declarations.some( - (d) => ts.isTypeAliasDeclaration(d) && ctx.isStdlibFile(d.getSourceFile()), + (d) => ts.isTypeAliasDeclaration(d) && ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ); if (!isLibAlias) return null; const bound = resolveTypeParam(arg); if (!bound || bound.kind !== "record") return null; - if (alias.name === "Readonly") return bound; + if (ts.symbolName(alias) === "Readonly") return bound; const shape = shapes.get(bound.shapeId); if (!shape) return null; // Partial over an index-signature shape would need `V | undefined` @@ -6072,7 +6170,7 @@ export function isUnitOnlyTsType(t: ts.Type, resolveTypeParam?: TypeParamResolve /** `ReturnType` over a type parameter bound to a void-returning * function (a variadic wrapper's result slot): a unit-only value. */ function boundVoidReturnType(p: ts.Type, resolveTypeParam?: TypeParamResolver): boolean { - if (!resolveTypeParam || p.getAliasSymbol()?.name !== "ReturnType") return false; + if (!resolveTypeParam || ts.symbolNameOf(p.getAliasSymbol()) !== "ReturnType") return false; const args = p.getAliasTypeArguments(); const arg = args[0]; if (args.length !== 1 || !arg || !(arg.flags & ts.TypeFlags.TypeParameter)) return false; @@ -6163,8 +6261,8 @@ function mapHybridCallableIntersection(widened: ts.Type, ctx: TypeMapperCtx): Ir const pt = mapType(checker.getTypeOfSymbol(p), ctx); if (!pt || pt.kind === "void") return null; - fields.push({ name: p.name, type: pt }); - declaredOrder.push(p.name); + fields.push({ name: ts.symbolName(p), type: pt }); + declaredOrder.push(ts.symbolName(p)); } if (fields.length === 1) return null; // no properties — it is just F fields.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0)); @@ -6206,8 +6304,8 @@ function mapErrorMetadataIntersection(type: ts.Type, ctx: TypeMapperCtx): IrType "URIError", "AggregateError", "SuppressedError", - ].includes(symbol.name) && - symbol.declarations.some((decl) => ctx.isStdlibFile(decl.getSourceFile())) + ].includes(ts.symbolName(symbol)) && + symbol.declarations.some((decl) => ctx.isStdlibFile(ts.getSourceFileOfNode(decl))) ); }); if (!hasError) return null; @@ -6228,9 +6326,9 @@ function mapErrorMetadataIntersection(type: ts.Type, ctx: TypeMapperCtx): IrType return null; for (const property of ctx.checker.getPropertiesOfType(part)) { const expected = - property.name === "errno" + ts.symbolName(property) === "errno" ? "f64" - : ["code", "syscall", "path", "dest"].includes(property.name) + : ["code", "syscall", "path", "dest"].includes(ts.symbolName(property)) ? "string" : null; if (!expected) { @@ -6258,11 +6356,11 @@ function mapErrorMetadataIntersection(type: ts.Type, ctx: TypeMapperCtx): IrType for (const property of extra) { const field = mapType(ctx.checker.getTypeOfSymbol(property), ctx); if (!field || field.kind === "void") return null; - if (property.name === "message" || property.name === "name") { + if (ts.symbolName(property) === "message" || ts.symbolName(property) === "name") { if (field.kind !== "string") return null; continue; } - if (property.name === "stack" || property.name === "cause") return null; + if (ts.symbolName(property) === "stack" || ts.symbolName(property) === "cause") return null; if ( !canDynCheckTo( field, @@ -6382,7 +6480,8 @@ function immutableSourceType(type: ts.Type, ctx: TypeMapperCtx): ts.Type | null const { checker } = ctx; const symbol = type.getSymbol(); const decl = symbol ? symbol.declarations[0] : undefined; - if (decl === undefined || !CONTRACT_TYPES_FILE.test(decl.getSourceFile().fileName)) return null; + if (decl === undefined || !CONTRACT_TYPES_FILE.test(ts.getSourceFileOfNode(decl).fileName)) + return null; const props = checker.getPropertiesOfType(type); const seen = new Set(); for (const prop of props) { @@ -6462,14 +6561,14 @@ function recordProvenanceOk(t: ts.Type, ctx: TypeMapperCtx): boolean { .every((property) => property.declarations.every( (decl) => - !decl.getSourceFile().isDeclarationFile || - ctx.isExternalTypeFile(decl.getSourceFile()) || - packageTypeFile(decl.getSourceFile(), ctx), + !ts.getSourceFileOfNode(decl).isDeclarationFile || + ctx.isExternalTypeFile(ts.getSourceFileOfNode(decl)) || + packageTypeFile(ts.getSourceFileOfNode(decl), ctx), ), ); } return !decls.some((d) => { - const sf = d.getSourceFile(); + const sf = ts.getSourceFileOfNode(d); return sf.isDeclarationFile && !ctx.isExternalTypeFile(sf) && !packageTypeFile(sf, ctx); }); } @@ -6546,7 +6645,7 @@ export function erasedGenericMemberType(t: ts.Type, ctx: TypeMapperCtx): IrType const sigs = ctx.checker.getCallSignatures(t); if (sigs.length !== 1) return undefined; const sig = sigs[0]!; - const own = new Set(sig.getTypeParameters()); + const own = new Set(sig.getTypeParameters()); const outer = ctx.resolveTypeParam; const erased = mapType(t, { ...ctx, @@ -6578,7 +6677,7 @@ export function isOverloadedCallableMemberType(t: ts.Type, checker: ts.TypeCheck if (decl === undefined) return false; if (!ts.isMethodSignatureDeclaration(decl) && !ts.isCallSignatureDeclaration(decl)) return false; - const file = decl.getSourceFile(); + const file = ts.getSourceFileOfNode(decl); return !file.isDeclarationFile && !isJsSourceFile(file); }); } @@ -6640,7 +6739,7 @@ function joinOverloadTypes(types: readonly IrType[], ctx: TypeMapperCtx): IrType export function erasedOverloadMemberType(t: ts.Type, ctx: TypeMapperCtx): IrType | undefined { const funcs: (IrType & { kind: "func" })[] = []; for (const sig of ctx.checker.getCallSignatures(t)) { - const own = new Set(sig.getTypeParameters()); + const own = new Set(sig.getTypeParameters()); const outer = ctx.resolveTypeParam; const mapped = mapCallSignature( sig, @@ -6686,7 +6785,7 @@ export function erasedOverloadMemberType(t: ts.Type, ctx: TypeMapperCtx): IrType function describeOverloadSetBlocker(t: ts.Type, ctx: TypeMapperCtx): string { const { checker } = ctx; for (const sig of checker.getCallSignatures(t)) { - const own = new Set(sig.getTypeParameters()); + const own = new Set(sig.getTypeParameters()); const outer = ctx.resolveTypeParam; const sigCtx: TypeMapperCtx = own.size === 0 @@ -6699,14 +6798,14 @@ function describeOverloadSetBlocker(t: ts.Type, ctx: TypeMapperCtx): string { const mapped = mapCallSignature(sig, sigCtx); if (mapped?.kind === "func" && mapped.rest !== true && mapped.argumentsAll !== true) continue; const decl = sig.declaration; - const spelled = decl ? decl.getText().replace(/\s+/g, " ").replace(/;$/, "") : ""; + const spelled = decl ? ts.getTextOfNode(decl).replace(/\s+/g, " ").replace(/;$/, "") : ""; const which = spelled ? `overload '${spelled}'` : "one overload"; if (mapped?.kind === "func") return `is an overload set, and its ${which} has a rest parameter (an overload set stores one fixed-arity function)`; for (const param of sig.getParameters()) { const paramTs = checker.getTypeOfSymbol(param); if (!mapType(paramTs, sigCtx)) - return `is an overload set, and its ${which} has parameter '${param.name}' of type '${checker.typeToString(paramTs)}', which does not compile`; + return `is an overload set, and its ${which} has parameter '${ts.symbolName(param)}' of type '${checker.typeToString(paramTs)}', which does not compile`; } const retTs = checker.getReturnTypeOfSignature(sig); return `is an overload set, and its ${which} returns '${checker.typeToString(retTs)}', which does not compile`; @@ -6892,9 +6991,7 @@ function dictSpreadLiteralShape( const infos = checker.getIndexInfosOfType(contextual); const unknownValued = infos.length > 0 && - infos.every( - (info) => (info.valueType.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) !== 0, - ); + infos.every((info) => (info.type.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) !== 0); if (!unknownValued) return null; } const kinds = props.map((p) => @@ -6906,7 +7003,7 @@ function dictSpreadLiteralShape( if (checker.getPropertiesOfType(t).length === 0) { const infos = checker.getIndexInfosOfType(t); if (infos.length !== 1 || !(infos[0]!.keyType.flags & ts.TypeFlags.String)) return null; - return mapType(infos[0]!.valueType, ctx); + return mapType(infos[0]!.type, ctx); } // Another such literal's type (`{ ...tags, repo }` over a binding // initialized by `{ ...base, plan }`) is stored as the same kind of @@ -7033,7 +7130,7 @@ function spreadMemberNamesTs(checker: ts.TypeChecker, e: ts.Expression): string[ const names: string[] = []; for (const l of literals) { const t = checker.getNonNullableType(checker.getTypeAtLocation(l)); - for (const property of checker.getPropertiesOfType(t)) names.push(property.name); + for (const property of checker.getPropertiesOfType(t)) names.push(ts.symbolName(property)); } return names; } @@ -7085,11 +7182,11 @@ function refinedConstituentShape( ): string | null { if (!widened.isIntersectionType()) return null; const { checker, shapes } = ctx; - const names = checker.getPropertiesOfType(widened).map((prop) => prop.name); + const names = checker.getPropertiesOfType(widened).map((prop) => ts.symbolName(prop)); let found: string | null = null; for (const part of ts.constituentTypes(widened)) { if ((part.flags & ts.TypeFlags.Object) === 0 || shapes.inProgress.has(part)) continue; - const own = new Set(checker.getPropertiesOfType(part).map((prop) => prop.name)); + const own = new Set(checker.getPropertiesOfType(part).map((prop) => ts.symbolName(prop))); if (own.size !== names.length || !names.every((name) => own.has(name))) continue; const mapped = mapType(part, ctx); if (mapped?.kind !== "record") continue; @@ -7166,7 +7263,7 @@ function mapRecordTypeInner( let v: IrType | null = null; for (const info of indexInfos) { if (!stringKey(info.keyType) && !(info.keyType.flags & ts.TypeFlags.Number)) return null; - const iv = mapType(info.valueType, ctx); + const iv = mapType(info.type, ctx); // JSONValue>` (a package's own JSON alias) and `Record` @@ -7262,7 +7359,7 @@ function mapRecordTypeInner( if (ctx.classImplementedProtocol?.(widened)) return DYN; // Symbol-keyed records use the checked object's descriptor table. A // checker-internal __@ name is not a string field in a native struct. - if (props.some((property) => property.name.startsWith("__@"))) return DYN; + if (props.some((property) => ts.symbolName(property).startsWith("__@"))) return DYN; // A computed shape with NO members is not a real empty record: inside a // generic body `Partial` resolves to no members at all (keyof T is // still unknown there) — interning `{}` would give a wrong type. The @@ -7321,13 +7418,13 @@ function mapRecordTypeInner( (ts.isObjectLiteralExpression(d.parent) || ts.isInterfaceDeclaration(d.parent) || d.parent.kind === ts.SyntaxKind.TypeLiteral) && - !d.getSourceFile().isDeclarationFile && - !isJsSourceFile(d.getSourceFile()), + !ts.getSourceFileOfNode(d).isDeclarationFile && + !isJsSourceFile(ts.getSourceFileOfNode(d)), ); if (!accessorOwned || (!getDecl && !setDecl)) return null; // Symbol-keyed accessors have no foldable literal name to fill at // the literal — no slot to make. - if (p.name.startsWith("__@")) return null; + if (ts.symbolName(p).startsWith("__@")) return null; // An index-signature shape stores accessor NAMES nowhere the keyed // read/walk machinery can answer (the overflow would miss where // Node dispatches the getter) — those shapes stay unmapped. @@ -7349,8 +7446,9 @@ function mapRecordTypeInner( if (readT !== null && typeKey(readT) !== typeKey(writeT)) return null; } if (readT !== null || getDecl) - fields.push({ name: `%get:${p.name}`, type: funcOf([], readT ?? VOID) }); - if (writeT !== null) fields.push({ name: `%set:${p.name}`, type: funcOf([writeT], VOID) }); + fields.push({ name: `%get:${ts.symbolName(p)}`, type: funcOf([], readT ?? VOID) }); + if (writeT !== null) + fields.push({ name: `%set:${ts.symbolName(p)}`, type: funcOf([writeT], VOID) }); continue; } // Computed shapes carry provenance per MEMBER: a utility type over a @@ -7360,7 +7458,7 @@ function mapRecordTypeInner( if ( computed && p.declarations.some((d) => { - const sf = d.getSourceFile(); + const sf = ts.getSourceFileOfNode(d); return sf.isDeclarationFile && !ctx.isExternalTypeFile(sf) && !packageTypeFile(sf, ctx); }) ) { @@ -7410,11 +7508,11 @@ function mapRecordTypeInner( // A DATA property spelled like a reserved accessor slot (`{ "%get:x": // v }` — a string-literal key): mapping it would collide with the // accessor dispatch, so the shape stays unmapped. - if (accessorSlotProp(p.name) !== null) return null; + if (accessorSlotProp(ts.symbolName(p)) !== null) return null; // an options record with `signal?: AbortSignal` must not turn its - fields.push({ name: p.name, type: pt }); + fields.push({ name: ts.symbolName(p), type: pt }); } // getPropertiesOfType yields DECLARATION order (interface/alias/literal // source order) — captured before the canonical sort so Object.keys/ @@ -7509,7 +7607,7 @@ function isNodeTestDefaultFunctionParam(decl: ts.Declaration, ctx: TypeMapperCtx fnType.parent !== undefined && ts.isTypeParameterDeclaration(fnType.parent) && fnType.parent.defaultType === fnType && - ctx.isStdlibFile(decl.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(decl)) && isDeclaredInAmbientModule(decl, "test") ); } @@ -7585,11 +7683,11 @@ export function describeComponentBlocker(widened: ts.Type, ctx: TypeMapperCtx): const psym = widened.getSymbol(); const container = psym !== undefined && - Object.prototype.hasOwnProperty.call(STDLIB_CONTAINERS, psym.name) && + Object.prototype.hasOwnProperty.call(STDLIB_CONTAINERS, ts.symbolName(psym)) && psym.declarations.some( - (d) => ts.isInterfaceDeclaration(d) && ctx.isStdlibFile(d.getSourceFile()), + (d) => ts.isInterfaceDeclaration(d) && ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ) - ? psym.name + ? ts.symbolName(psym) : undefined; if (container !== undefined) { const spec = STDLIB_CONTAINERS[container]!; @@ -7731,7 +7829,7 @@ export function describeComponentBlocker(widened: ts.Type, ctx: TypeMapperCtx): for (const p of sig.getParameters()) { const pTs = checker.getTypeOfSymbol(p); if (!mapType(pTs, ctx)) { - return `the function shape is supported, but its parameter '${p.name}' has type '${text(pTs)}', which does not compile`; + return `the function shape is supported, but its parameter '${ts.symbolName(p)}' has type '${text(pTs)}', which does not compile`; } } const retTs = checker.getReturnTypeOfSignature(sig); @@ -7763,14 +7861,14 @@ export function describeRecordMemberBlocker(widened: ts.Type, ctx: TypeMapperCtx // Accessor members and symbol-keyed members have their own multi-shaped // rules (mapRecordTypeInner) — no single-member claim is honest there. if ((p.flags & (ts.SymbolFlags.GetAccessor | ts.SymbolFlags.SetAccessor)) !== 0) continue; - if (p.name.startsWith("__@")) continue; + if (ts.symbolName(p).startsWith("__@")) continue; // Computed shapes over LIBRARY members (`Readonly`): the record // fence there is per-member PROVENANCE (mapRecordTypeInner's rule), not // any one member's type — that story stays with the residual fence. if ( computed && p.declarations.some((d) => { - const sf = d.getSourceFile(); + const sf = ts.getSourceFileOfNode(d); return sf.isDeclarationFile && !ctx.isExternalTypeFile(sf) && !packageTypeFile(sf, ctx); }) ) { @@ -7779,7 +7877,7 @@ export function describeRecordMemberBlocker(widened: ts.Type, ctx: TypeMapperCtx const fieldTs = checker.getTypeOfSymbol(p); if (isOverloadedCallableMemberType(fieldTs, checker)) { if (erasedOverloadMemberType(fieldTs, ctx) !== undefined) continue; - return `the record shape is supported, but its member '${p.name}' ${describeOverloadSetBlocker(fieldTs, ctx)}`; + return `the record shape is supported, but its member '${ts.symbolName(p)}' ${describeOverloadSetBlocker(fieldTs, ctx)}`; } let pt = isGenericCallableMemberType(fieldTs, checker) ? erasedGenericMemberType(fieldTs, ctx) @@ -7788,7 +7886,7 @@ export function describeRecordMemberBlocker(widened: ts.Type, ctx: TypeMapperCtx if (pt?.kind === "void" && isUnitOnlyTsType(fieldTs, ctx.resolveTypeParam)) pt = unitOnlyUnion(ctx.unions); if (!pt || pt.kind === "void") { - return `the record shape is supported, but its member '${p.name}' has type '${checker.typeToString(fieldTs)}', which does not compile`; + return `the record shape is supported, but its member '${ts.symbolName(p)}' has type '${checker.typeToString(fieldTs)}', which does not compile`; } } return null; diff --git a/packages/compiler/src/frontend/worker-target.ts b/packages/compiler/src/frontend/worker-target.ts index 99c97dded..5193f0539 100644 --- a/packages/compiler/src/frontend/worker-target.ts +++ b/packages/compiler/src/frontend/worker-target.ts @@ -84,7 +84,8 @@ export function isWorkerConstructor(program: ts.Program, expression: ts.Expressi export function workerModulePath(program: ts.Program, expression: ts.Expression): string | null { if (ts.isIdentifier(expression) && expression.text === "__filename") { - if (isNodeGlobal(program, expression)) return resolve(expression.getSourceFile().fileName); + if (isNodeGlobal(program, expression)) + return resolve(ts.getSourceFileOfNode(expression).fileName); } const value = staticForkString(program, expression); if (value === null) return null; diff --git a/packages/compiler/src/ir/traverse.test.ts b/packages/compiler/src/ir/traverse.test.ts index 874e7846e..fbe84644a 100644 --- a/packages/compiler/src/ir/traverse.test.ts +++ b/packages/compiler/src/ir/traverse.test.ts @@ -28,7 +28,8 @@ const program = createProgram([path], { noLib: true, noResolve: true, types: [] const source = program.getSourceFile(path)!; afterAll(() => program.dispose()); function childType(type: ts.TypeNode): boolean { - if (ts.isTypeReferenceNode(type)) return ["IrExpr", "IrStmt"].includes(type.typeName.getText()); + if (ts.isTypeReferenceNode(type)) + return ["IrExpr", "IrStmt"].includes(ts.getTextOfNode(type.typeName)); if (ts.isArrayTypeNode(type)) return childType(type.elementType); if (ts.isUnionTypeNode(type)) return type.types.nodes.some(childType); if (ts.isTypeLiteralNode(type)) @@ -47,15 +48,15 @@ for (const name of ["IrExpr", "IrStmt"]) { for (const variant of alias.type.types.nodes) { if (!ts.isTypeLiteralNode(variant)) throw new Error("IR variants must be records"); const members = variant.members.nodes.filter(ts.isPropertySignature); - const tag = members.find((member) => member.name.getText() === "kind")!.type!; - const kind = JSON.parse(tag.getText()) as string; + const tag = members.find((member) => ts.getTextOfNode(member.name) === "kind")!.type!; + const kind = JSON.parse(ts.getTextOfNode(tag)) as string; test(`${name}.${kind}: every declared child is visited and rewritten, metadata is preserved`, () => { for (const presence of [true, false]) { const expected: (IrExpr | IrStmt)[] = []; function value(type: ts.TypeNode): unknown { if (ts.isTypeReferenceNode(type)) { const node = - type.typeName.getText() === "IrExpr" + ts.getTextOfNode(type.typeName) === "IrExpr" ? num(expected.length) : statement(expected.length); expected.push(node); @@ -72,7 +73,7 @@ for (const name of ["IrExpr", "IrStmt"]) { }; for (const member of type.members.nodes) { if (ts.isPropertySignature(member) && member.type && childType(member.type)) - result[member.name.getText()] = value(member.type); + result[ts.getTextOfNode(member.name)] = value(member.type); } return result; } @@ -86,7 +87,7 @@ for (const name of ["IrExpr", "IrStmt"]) { }; for (const member of members) { if (member.type && childType(member.type) && (presence || !member.postfixToken)) - input[member.name.getText()] = value(member.type); + input[ts.getTextOfNode(member.name)] = value(member.type); } const before = structuredClone(input); const seen: (IrExpr | IrStmt)[] = []; @@ -135,7 +136,7 @@ for (const name of ["IrExpr", "IrStmt"]) { expect(output.loc).toBe(loc); expect((output as unknown as Record)["metadata"]).toBe(input["metadata"]); for (const member of members) { - const key = member.name.getText(); + const key = ts.getTextOfNode(member.name); if (member.postfixToken && !presence) expect(Object.hasOwn(output, key)).toBe(false); } } @@ -217,7 +218,7 @@ test("module traversal covers every declared type slot", () => { const active = new Set(); function slots(node: ts.TypeNode): unknown { if (ts.isTypeReferenceNode(node)) { - const name = node.typeName.getText(); + const name = ts.getTextOfNode(node.typeName); if (name === "IrType") { const value: IrType = { kind: "regex" }; expected.push(value); @@ -268,7 +269,7 @@ test("module traversal covers every declared type slot", () => { for (const member of members) { if (!ts.isPropertySignature(member) || !member.type) continue; const value = slots(member.type); - if (value !== undefined) result[member.name.getText()] = value; + if (value !== undefined) result[ts.getTextOfNode(member.name)] = value; } return result; } @@ -316,20 +317,20 @@ test("type traversal visits all structural children and leaves named shapes as r if (!ts.isTypeLiteralNode(variant)) throw new Error("IrType variants must be records"); const members = variant.members.nodes.filter(ts.isPropertySignature); const kind = JSON.parse( - members.find((member) => member.name.getText() === "kind")!.type!.getText(), + ts.getTextOfNode(members.find((member) => ts.getTextOfNode(member.name) === "kind")!.type!), ) as string; const node: Record = { kind }; const expected: IrType[] = []; for (const member of members) { if (!member.type) continue; const child = - ts.isTypeReferenceNode(member.type) && member.type.typeName.getText() === "IrType"; + ts.isTypeReferenceNode(member.type) && ts.getTextOfNode(member.type.typeName) === "IrType"; const array = - ts.isArrayTypeNode(member.type) && member.type.elementType.getText() === "IrType"; + ts.isArrayTypeNode(member.type) && ts.getTextOfNode(member.type.elementType) === "IrType"; if (!child && !array) continue; const value: IrType = { kind: "fileHandle" }; expected.push(value); - node[member.name.getText()] = array ? [value] : value; + node[ts.getTextOfNode(member.name)] = array ? [value] : value; } const seen: IrType[] = []; expect( diff --git a/packages/compiler/src/wasm/discover.ts b/packages/compiler/src/wasm/discover.ts index 931bc1dac..de38bf64f 100644 --- a/packages/compiler/src/wasm/discover.ts +++ b/packages/compiler/src/wasm/discover.ts @@ -159,7 +159,7 @@ class Discovery { if (!paths && !base64 && !bytes) return; const file = sf.fileName; const lineOf = (node: ts.Node): number => - ts.getLineAndCharacterOfPosition(sf, node.getStart()).line + 1; + ts.getLineAndCharacterOfPosition(sf, ts.tokenStartOfNode(node)).line + 1; ts.walkPreorder( sf, (node) => { diff --git a/packages/compiler/test/ts7/parity.test.ts b/packages/compiler/test/ts7/parity.test.ts index 4904f2720..978de5cc2 100644 --- a/packages/compiler/test/ts7/parity.test.ts +++ b/packages/compiler/test/ts7/parity.test.ts @@ -1,4 +1,5 @@ import { Ts7Host } from "../../src/frontend/ts7/program-adapter.js"; +import { symbolName, symbolNameOf } from "../../src/frontend/ts7/semantic-model.js"; /* The two-world agreement battery: every fixture program is parsed and * checked through BOTH worlds — typescript@5.9.3 in-process and the TS7 * adapter over tsgo — and the worlds must agree on structure (walk order, @@ -331,7 +332,7 @@ for (const battery of ALL_BATTERIES) { if (s5[a] !== undefined && isFixtureSymbol(w(), s5[a]!) && s5[a]!.name !== "export=") { // finding 5: the module.exports receiver's symbol NAME differs in // JS files (5.9.3 "export=", tsgo "module") — pinned separately. - expect(s7[a]!.name, `${file}#${ids[a]} symbol name`).toBe(s5[a]!.name); + expect(symbolName(s7[a]!), `${file}#${ids[a]} symbol name`).toBe(s5[a]!.name); } for (let b = a + 1; b < ids.length; b++) { if (s5[a] === undefined || s5[b] === undefined) continue; @@ -432,7 +433,7 @@ test("finding pinned: tsgo does not synthesize CJS expando members; module resol const moduleIdx = n5.findIndex((n) => ts5.isIdentifier(n) && n.getText() === "module"); expect(moduleIdx).toBeGreaterThanOrEqual(0); expect(w.c5.getSymbolAtLocation(n5[moduleIdx]!)?.name).toBe("export="); - expect(w.c7.getSymbolAtLocation(n7[moduleIdx]!)?.name).toBe("module"); + expect(symbolNameOf(w.c7.getSymbolAtLocation(n7[moduleIdx]!))).toBe("module"); }); test("getAwaitedType shim agrees with 5.9.3 across the async battery (pinned limitation aside)", () => { @@ -487,7 +488,7 @@ test("symbol declarations resolve back into the same client AST", () => { const sym7 = w.c7.getSymbolAtLocation(n7[i]!); expect(sym7).toBeDefined(); const aliased = w.c7.getAliasedSymbol(sym7!); - expect(aliased.name).toBe("answer"); + expect(symbolName(aliased)).toBe("answer"); const declHandle = aliased.declarationHandles[0]; expect(declHandle).toBeDefined(); const decl = declHandle!.resolve(w.p7.project); diff --git a/packages/compiler/test/ts7/rpc-api.test.ts b/packages/compiler/test/ts7/rpc-api.test.ts index a8b4aebce..a39c71321 100644 --- a/packages/compiler/test/ts7/rpc-api.test.ts +++ b/packages/compiler/test/ts7/rpc-api.test.ts @@ -14,7 +14,21 @@ import { SemanticSignature, SemanticSymbol, SemanticType, + symbolName, } from "../../src/frontend/ts7/semantic-model.js"; +import { ts7Diagnostic, type Ts7Diagnostic } from "../../src/frontend/ts7/diagnostic.js"; +import type { Ts7DiagnosticData } from "../../src/frontend/ts7/session-schema.generated.js"; + +// The SDK's symbols have `name`; scriptc's give it with symbolName, which reads `escapedName`, as the +// SDK's symbols also have it. +function nameOf(symbol: SdkSymbol): string { + return symbolName(symbol as unknown as SemanticSymbol); +} + +// scriptc's diagnostics have every member set; the SDK's leave absent ones out. +function diagnostics(list: readonly unknown[]): Ts7Diagnostic[] { + return list.map((item) => ts7Diagnostic(item as Ts7DiagnosticData)); +} // scriptc's symbols keep their declarations' node handles in // `declarationHandles`, and the SDK's in `declarations`. @@ -51,12 +65,12 @@ function programFacts(snapshot: Snapshot, config: string, path: string) { options: project.program.getCompilerOptions(), text: file.text, type: checker.typeToString(type), - symbol: symbol.name, + symbol: nameOf(symbol), declaration: declarationHandlesOf(symbol)[0]!.resolve()!.getText(file), - syntactic: project.program.getSyntacticDiagnostics(path), - semantic: project.program.getSemanticDiagnostics(path), - globals: project.program.getGlobalDiagnostics(), - config: project.program.getConfigFileParsingDiagnostics(), + syntactic: diagnostics(project.program.getSyntacticDiagnostics(path)), + semantic: diagnostics(project.program.getSemanticDiagnostics(path)), + globals: diagnostics(project.program.getGlobalDiagnostics()), + config: diagnostics(project.program.getConfigFileParsingDiagnostics()), defaultLibrary: project.program.isSourceFileDefaultLibrary(file), externalLibrary: project.program.isSourceFileFromExternalLibrary(file), }; @@ -233,7 +247,7 @@ test("native semantic registries preserve project scope and snapshot lifetimes", .declarationList.declarations, )[0], texts: [a.checker.typeToString(typeA), b.checker.typeToString(typeB)], - parameters: signatureA.getParameters().map((symbol) => symbol.name), + parameters: signatureA.getParameters().map(nameOf), }, symbolA, typeA, @@ -258,7 +272,7 @@ test("native semantic registries preserve project scope and snapshot lifetimes", first.dispose(); expect(() => actual.typeA.getSymbol()).toThrow("disposed"); expect(() => declarationHandlesOf(actual.symbolA)[0]!.resolve()).toThrow("disposed"); - expect(next.signatureA.getParameters()[0]!.name).toBe("value"); + expect(nameOf(next.signatureA.getParameters()[0]!)).toBe("value"); expect(second.getProject(firstConfig)!.checker.typeToString(next.typeA)).toBe("42"); second.dispose(); expect(() => next.signatureA.getParameters()).toThrow("disposed"); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cbe529577..9c42ad00c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -107,8 +107,8 @@ importers: specifier: workspace:* version: link:../runtime tsc-ts: - specifier: 0.0.3 - version: 0.0.3 + specifier: 0.0.5 + version: 0.0.5 typescript: specifier: 7.0.2 version: 7.0.2 @@ -1357,6 +1357,10 @@ packages: cpu: [x64] os: [win32] + '@scriptc/threads@0.3.6': + resolution: {integrity: sha512-XZb4JhXM/ivhx4MMLxqLLF2mCI28nUK+VVKlCnELO83tacUuGlq7YCe/8LwvzO7fZwy/rCH4+genAYYEc6BF6Q==} + engines: {node: '>=24.0.0'} + '@sinclair/typebox@0.25.24': resolution: {integrity: sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==} @@ -1370,42 +1374,37 @@ packages: '@ts-morph/common@0.11.1': resolution: {integrity: sha512-7hWZS0NRpEsNV8vWJzg7FEz6V8MaLNeJOmwmghqUXTpzk16V1LLZhdo+4QvE/+zv4cVci0OviuJFnqhEfoV3+g==} - '@tsc-ts/darwin-arm64@0.0.3': - resolution: {integrity: sha512-NSP4IhLx658tY21ub3ruraZwUwAC3GCbIf1eM0eOKUHfOBBXqq7LU/Loj3zvlvn/tJUyv2VdIDmzrWI/mxp5sA==} - cpu: [arm64] - os: [darwin] - - '@tsc-ts/darwin-x64@0.0.3': - resolution: {integrity: sha512-+BNvqyygkMx0/oHkjK8AmsPrE2+OcE/hGzT0wa2dndiH8lCSDBs+77C7Kmy/FSTWi9ItJShp1+AuWKgQPEOztg==} + '@tsc-ts/darwin-x64@0.0.5': + resolution: {integrity: sha512-CEeHOFfpx5H9V3WSq+V7XGU8tfc06nRN818auiRVwuwPHvpti9gHq0HYkvf/43PH1pIGcgf68sQYdIxJhrQDKQ==} cpu: [x64] os: [darwin] - '@tsc-ts/linux-arm64-gnu@0.0.3': - resolution: {integrity: sha512-I3v0J0L387TEkau0BTGiVx2GdstYfOrwVYwb0pJ/LbFURhrcGcsUODP5bdSOIYCtsp3aAX6e2afdqSIlpsxtbw==} + '@tsc-ts/linux-arm64-gnu@0.0.5': + resolution: {integrity: sha512-qwybOqGDEa35eV9x/3WO81ExAUbUMY8288ATC83kUfYGmOAVStaO+JM2IdrsiI8hLWVC7/f0RFgZ+7dyWh9/nA==} cpu: [arm64] os: [linux] libc: [glibc] - '@tsc-ts/linux-arm64-musl@0.0.3': - resolution: {integrity: sha512-f7wD9D6fC1xL7+ifvMAbJ0TqxOuZP04R2/7c8lb7Gor5SFKe85udJoxf0gocK7mbzWzmEiOJ53J1PujJLBVIxA==} + '@tsc-ts/linux-arm64-musl@0.0.5': + resolution: {integrity: sha512-6oSw3NbQIrnxxCRuEe190MbMFoOrM+P+u1usascQMgSjsMcVRnJ/ztvTnRrCDtfPEHJ5EC8drgWIpcdrhGsB0w==} cpu: [arm64] os: [linux] libc: [musl] - '@tsc-ts/linux-x64-gnu@0.0.3': - resolution: {integrity: sha512-BL9zownW4tHv/qqQkF8OlurHNDXi7xtGtJ6PV4qMeRYPMKsivTIGFRoZLW023KcLOr1Zptaof6X3zpv1nJEIpA==} + '@tsc-ts/linux-x64-gnu@0.0.5': + resolution: {integrity: sha512-KrlBLUcG7M6p7lN+ke+kHVLqy7IiY/H0FxFZzq5slXanhrps/BfjdkUahsyREg7KVprr08xFuXqJTBtLEI4eow==} cpu: [x64] os: [linux] libc: [glibc] - '@tsc-ts/linux-x64-musl@0.0.3': - resolution: {integrity: sha512-g4/lE5sULNGUVLM0Px/jrsjLsQQAYSiL+Rl+JZ+l5JE2k1dXroCejT2+IRB/NucVcz6N8EOUiCRZ13zl+Wtpcg==} + '@tsc-ts/linux-x64-musl@0.0.5': + resolution: {integrity: sha512-fr8yGzkocCcTYoE+NwC+/WGPNteQ2GmOPJTy/z9NcsLchnpSIZ1fzFll15UYPKQlwQdNRJXxKK3npVBtLRdHTg==} cpu: [x64] os: [linux] libc: [musl] - '@tsc-ts/win32-x64-msvc@0.0.3': - resolution: {integrity: sha512-oGtboSHgpuQP1dHAHqWyGjAWCpvHDpciYCi/2ywcp6MT+oOHxC99X48H/PZr29fzYVxCVeaEqWZ/VFU9qLZaKg==} + '@tsc-ts/win32-x64-msvc@0.0.5': + resolution: {integrity: sha512-SQwufJafzsiOBYncHOxYkyELWjLlBijcKB+xOs6hr6Lxxp5MMLoPtvDFNG88c7G5xv2TDb1XbvyxnjQltr6SHw==} cpu: [x64] os: [win32] @@ -2566,8 +2565,8 @@ packages: ts-toolbelt@6.15.5: resolution: {integrity: sha512-FZIXf1ksVyLcfr7M317jbB67XFJhOO1YqdTcuGaq9q5jLUoTikukZ+98TPjKiP2jC5CgmYdWWYs0s2nLSU0/1A==} - tsc-ts@0.0.3: - resolution: {integrity: sha512-Qr0i/BxP3ULIPDfr38vRb02x7sj6Q0r70Gubgu4ty2J4CEDCUKzT8RT2KMNqLXI3ZAhl7cncJq7a4LjKYZ1+0g==} + tsc-ts@0.0.5: + resolution: {integrity: sha512-Eea+zNv7y696pqNZtXBd0AX7/6PXIhR7aaEb0EhgpHadbk7VEiURD94/NngeLpYWts2tPjetkpwFLJY0+6yOnQ==} engines: {node: '>=20'} hasBin: true @@ -3428,6 +3427,8 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.62.2': optional: true + '@scriptc/threads@0.3.6': {} + '@sinclair/typebox@0.25.24': {} '@standard-schema/spec@1.1.0': {} @@ -3441,25 +3442,22 @@ snapshots: mkdirp: 1.0.4 path-browserify: 1.0.1 - '@tsc-ts/darwin-arm64@0.0.3': + '@tsc-ts/darwin-x64@0.0.5': optional: true - '@tsc-ts/darwin-x64@0.0.3': + '@tsc-ts/linux-arm64-gnu@0.0.5': optional: true - '@tsc-ts/linux-arm64-gnu@0.0.3': + '@tsc-ts/linux-arm64-musl@0.0.5': optional: true - '@tsc-ts/linux-arm64-musl@0.0.3': + '@tsc-ts/linux-x64-gnu@0.0.5': optional: true - '@tsc-ts/linux-x64-gnu@0.0.3': + '@tsc-ts/linux-x64-musl@0.0.5': optional: true - '@tsc-ts/linux-x64-musl@0.0.3': - optional: true - - '@tsc-ts/win32-x64-msvc@0.0.3': + '@tsc-ts/win32-x64-msvc@0.0.5': optional: true '@tybys/wasm-util@0.10.3': @@ -4798,15 +4796,16 @@ snapshots: ts-toolbelt@6.15.5: {} - tsc-ts@0.0.3: + tsc-ts@0.0.5: + dependencies: + '@scriptc/threads': 0.3.6 optionalDependencies: - '@tsc-ts/darwin-arm64': 0.0.3 - '@tsc-ts/darwin-x64': 0.0.3 - '@tsc-ts/linux-arm64-gnu': 0.0.3 - '@tsc-ts/linux-arm64-musl': 0.0.3 - '@tsc-ts/linux-x64-gnu': 0.0.3 - '@tsc-ts/linux-x64-musl': 0.0.3 - '@tsc-ts/win32-x64-msvc': 0.0.3 + '@tsc-ts/darwin-x64': 0.0.5 + '@tsc-ts/linux-arm64-gnu': 0.0.5 + '@tsc-ts/linux-arm64-musl': 0.0.5 + '@tsc-ts/linux-x64-gnu': 0.0.5 + '@tsc-ts/linux-x64-musl': 0.0.5 + '@tsc-ts/win32-x64-msvc': 0.0.5 tslib@2.8.1: optional: true diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index d4d370721..a3e992b0a 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -7,14 +7,14 @@ minimumReleaseAgeExclude: - "effect@4.0.0" - "@effect/platform-node@4.0.0" - "@effect/platform-node-shared@4.0.0" - - "@tsc-ts/darwin-arm64@0.0.3" - - "@tsc-ts/darwin-x64@0.0.3" - - "@tsc-ts/linux-arm64-gnu@0.0.3" - - "@tsc-ts/linux-arm64-musl@0.0.3" - - "@tsc-ts/linux-x64-gnu@0.0.3" - - "@tsc-ts/linux-x64-musl@0.0.3" - - "@tsc-ts/win32-x64-msvc@0.0.3" - - "tsc-ts@0.0.3" + - "@tsc-ts/darwin-arm64@0.0.5" + - "@tsc-ts/darwin-x64@0.0.5" + - "@tsc-ts/linux-arm64-gnu@0.0.5" + - "@tsc-ts/linux-arm64-musl@0.0.5" + - "@tsc-ts/linux-x64-gnu@0.0.5" + - "@tsc-ts/linux-x64-musl@0.0.5" + - "@tsc-ts/win32-x64-msvc@0.0.5" + - "tsc-ts@0.0.5" allowBuilds: esbuild: true overrides: diff --git a/scripts/generate-ts7-ast-schema.mjs b/scripts/generate-ts7-ast-schema.mjs index 45a01c213..30c506869 100644 --- a/scripts/generate-ts7-ast-schema.mjs +++ b/scripts/generate-ts7-ast-schema.mjs @@ -268,9 +268,10 @@ if (process.argv.includes("--check")) { // declarations, and require every wire property to have a declared getter. const declarations = readFileSync(join(packageRoot, "dist/api/node/node.generated.d.ts"), "utf8"); const names = [...new Set(Object.values(protocol.childProperties).flat())].sort(); -// These views adapt TS7's shared postfix slot and reparsed JSDoc bodies. -// Their implementations live outside the generated region in AstNode. -const adaptedGetters = new Set(["body", "questionToken"]); +// These views adapt TS7's shared postfix slot and reparsed JSDoc bodies, and +// return modifiers as a modifier list. Their implementations live outside the +// generated region in AstNode. +const adaptedGetters = new Set(["body", "modifiers", "questionToken"]); const getters = names .filter((name) => !adaptedGetters.has(name)) .map((name) => { diff --git a/scripts/generate-ts7-ast-views.mjs b/scripts/generate-ts7-ast-views.mjs index 1525f6246..91e7ac2f7 100644 --- a/scripts/generate-ts7-ast-views.mjs +++ b/scripts/generate-ts7-ast-views.mjs @@ -65,6 +65,8 @@ export function generateAstViews(root, packageRoot, version, check) { if (key === "jsdocPropertyTags") type = "JSDocPropertyTag"; // A list's children are a node list, as every other list field. if (name === "SyntaxList" && key === "children") type = "NodeArray"; + // A declaration's modifiers are a modifier list, with their flags. + if (key === "modifiers") type = type.replace("NodeArray<", "ModifierArray<"); if (member.questionToken) type += " | undefined"; fields.set(key, ` readonly ${key}: ${type};`); } @@ -74,7 +76,7 @@ export function generateAstViews(root, packageRoot, version, check) { ...header, "", 'import type { AstNode } from "./ast-node.js";', - 'import type { JsxTagNamePropertyAccess, NodeArray } from "./ast-types.js";', + 'import type { JsxTagNamePropertyAccess, ModifierArray, NodeArray } from "./ast-types.js";', 'import { SyntaxKind, NodeFlags, ModifierFlags, TokenFlags } from "./enums.js";', "type Node = AstNode;", ]; @@ -99,6 +101,9 @@ export function generateAstViews(root, packageRoot, version, check) { ? `<${node.typeParameters.map((param) => param.getText(generated)).join(", ")}>` : ""; const fields = members(node.name.text); + // A default clause has no expression; the declaration shares the case clause's. + if (node.name.text === "DefaultClause" && fields.has("expression")) + fields.set("expression", " readonly expression: undefined;"); if (!fields.has("kind")) { const kinds = new Set(); for (const [name, child] of interfaces) { @@ -131,6 +136,7 @@ export function generateAstViews(root, packageRoot, version, check) { ...header, "", 'import { SyntaxKind, NodeFlags, ScriptKind, OuterExpressionKinds } from "./enums.js";', + 'import { getSourceFileOfNode } from "./node-functions.js";', ]; for (const stem of ["is.generated", "is"]) { const declarations = parse(`${stem}.d.ts`); @@ -173,10 +179,9 @@ export function generateAstViews(root, packageRoot, version, check) { } let body = fn.body.getText(source); if (name === "isJSDocTypeAssertion") - body = body.replace( - "expression.kind !==", - "expression === undefined || expression.kind !==", - ); + body = body + .replace("expression.kind !==", "expression === undefined || expression.kind !==") + .replace("node.getSourceFile()", "getSourceFileOfNode(node)"); if (head.includes("node: Node") && name.startsWith("is")) { head = head.replace("node: Node", "node: Node | undefined"); body = body.replace("{", "{\n if (node === undefined) return false;"); diff --git a/tests/fixtures/self-hosting/lowering-context-cases.ts b/tests/fixtures/self-hosting/lowering-context-cases.ts index efa27183d..a39063796 100644 --- a/tests/fixtures/self-hosting/lowering-context-cases.ts +++ b/tests/fixtures/self-hosting/lowering-context-cases.ts @@ -35,7 +35,7 @@ function runContextCase(services: FrontendServices, entry: string): string[] { const shadowSymbol = symbols[1]!; const unusedSymbol = symbols[2]!; requireValue(outerSymbol !== shadowSymbol, "shadowed bindings share identity"); - requireValue(outerSymbol.name === shadowSymbol.name, "fixture did not shadow a name"); + requireValue(ts.symbolName(outerSymbol) === ts.symbolName(shadowSymbol), "fixture did not shadow a name"); const source = bindingSource(declarations[0]!); const root = newFnCtx(false, null, null, VOID); const outer = declareContextLocal(root, "value", F64, true, outerSymbol, source); diff --git a/tests/fixtures/self-hosting/ts7-checker-cases.ts b/tests/fixtures/self-hosting/ts7-checker-cases.ts index da0a3dc52..664dccd50 100644 --- a/tests/fixtures/self-hosting/ts7-checker-cases.ts +++ b/tests/fixtures/self-hosting/ts7-checker-cases.ts @@ -2,7 +2,7 @@ import { AstNode } from "../../../packages/compiler/src/frontend/ts7/ast-node.js import type { SourceFile } from "../../../packages/compiler/src/frontend/ts7/ast-types.js"; import { CheckerFacade, constituentTypes } from "../../../packages/compiler/src/frontend/ts7/checker.js"; import { SemanticChecker } from "../../../packages/compiler/src/frontend/ts7/semantic-checker.js"; -import { SemanticType } from "../../../packages/compiler/src/frontend/ts7/semantic-model.js"; +import { SemanticType, symbolName, symbolNameOf } from "../../../packages/compiler/src/frontend/ts7/semantic-model.js"; import { SyntaxKind, TypeFlags } from "../../../packages/compiler/src/frontend/ts7/enums.js"; export function checkerSource(): string { @@ -67,8 +67,8 @@ export function checkCheckerFacade(raw: SemanticChecker, root: SourceFile, reque const box = declaration(root, "Box"); const boxSymbol = facade.getSymbolAtLocation(box.name!)!; const boxType = facade.getDeclaredTypeOfSymbol(boxSymbol); - check(facade.getPropertyOfType(boxType, "value")!.name === "value", "property lookup"); - check(facade.getPropertiesOfType(boxType).some((property) => property.name === "method"), "property list"); + check(symbolName(facade.getPropertyOfType(boxType, "value")!) === "value", "property lookup"); + check(facade.getPropertiesOfType(boxType).some((property) => symbolName(property) === "method"), "property list"); check(facade.getBaseTypes(boxType).length === 0, "empty class bases"); const child = facade.getDeclaredTypeOfSymbol(facade.getSymbolAtLocation(declaration(root, "Child").name!)!); check(facade.getBaseTypes(child)[0] === boxType, "class base identity"); @@ -123,7 +123,7 @@ export function checkCheckerFacade(raw: SemanticChecker, root: SourceFile, reque check(facade.getTypeArguments(number).length === 0, "nonreference arguments"); const dict = facade.getDeclaredTypeOfSymbol(facade.getSymbolAtLocation(declaration(root, "Dict").name!)!); const index = facade.getIndexInfosOfType(dict)[0]!; - check(index.keyType === string && index.valueType === number && index.isReadonly, "index signature"); + check(index.keyType === string && index.type === number && index.isReadonly, "index signature"); check(facade.getAwaitedType(aliasType(facade, root, "Promised")) === number, "await promise"); check(facade.getAwaitedType(aliasType(facade, root, "MaybePromised")) === number, "await collapsed union"); check(facade.getAwaitedType(aliasType(facade, root, "DifferentPromises")) === undefined, "await distinct union refusal"); diff --git a/tests/fixtures/self-hosting/ts7-client-cases.ts b/tests/fixtures/self-hosting/ts7-client-cases.ts index d97f90892..0feaee6c9 100644 --- a/tests/fixtures/self-hosting/ts7-client-cases.ts +++ b/tests/fixtures/self-hosting/ts7-client-cases.ts @@ -12,6 +12,7 @@ import { SyntaxKind, ScriptTarget, ModuleKind, ModuleResolutionKind, ModuleDetec import { Ts7Session } from "../../../packages/compiler/src/frontend/ts7/session.js"; import { Ts7Paths, ts7DocumentFile } from "../../../packages/compiler/src/frontend/ts7/session-path.js"; import { parseSemanticJson } from "../../../packages/compiler/src/frontend/ts7/semantic-json.js"; +import { symbolName, symbolNameOf } from "../../../packages/compiler/src/frontend/ts7/semantic-model.js"; import { checkSemanticModel, semanticSource } from "./ts7-semantic-cases.js"; import { checkAstViews } from "./ts7-ast-views.js"; @@ -313,7 +314,7 @@ export function runTs7Client(client: Ts7RpcClient, directory: string, report: st const timing = client.timing(); check(timing.requests > 20 && timing.callbacks > 0 && reads > 0, "requests and filesystem callbacks executed"); writeFileSync(report, JSON.stringify({ - typeText, symbol: ownedSymbol.name, diagnostics: diagnostics.map((diagnostic) => diagnostic.code), + typeText, symbol: symbolName(ownedSymbol), diagnostics: diagnostics.map((diagnostic) => diagnostic.code), surrogateBoundary: checkSurrogateBoundary(), semanticSurrogateBoundary: checkSemanticSurrogateBoundary(), semanticModel: true, sessionLifecycle: true, echo: true, binaryAst: true, astIdentity: true, astViews: true, checkerFacade: true, checkerSnapshots: true, virtualFiles: true, retainedSnapshot: true, serverErrorRecovery: true, protocolFailures: true, diff --git a/tests/fixtures/self-hosting/ts7-semantic-cases.ts b/tests/fixtures/self-hosting/ts7-semantic-cases.ts index 41b61b824..07f2d7e14 100644 --- a/tests/fixtures/self-hosting/ts7-semantic-cases.ts +++ b/tests/fixtures/self-hosting/ts7-semantic-cases.ts @@ -1,7 +1,8 @@ import { AstFile, AstNode } from "../../../packages/compiler/src/frontend/ts7/ast-node.js"; import { AstKind } from "../../../packages/compiler/src/frontend/ts7/ast-schema.generated.js"; import { SemanticChecker } from "../../../packages/compiler/src/frontend/ts7/semantic-checker.js"; -import { SemanticSignature, SemanticSnapshot, SemanticSymbol, SemanticType } from "../../../packages/compiler/src/frontend/ts7/semantic-model.js"; +import { SemanticSignature, SemanticSnapshot, SemanticSymbol, SemanticType, symbolName, symbolNameOf } from "../../../packages/compiler/src/frontend/ts7/semantic-model.js"; +import type { __String } from "../../../packages/compiler/src/frontend/ts7/ast-types.js"; import { SemanticSignatureKind, SemanticSymbolFlags, SemanticTypeFlags } from "../../../packages/compiler/src/frontend/ts7/semantic-schema.generated.js"; export function semanticSource(): string { @@ -113,7 +114,7 @@ export function checkSemanticModel(snapshot: SemanticSnapshot, checker: Semantic const pairTarget = pair.getTarget()!; check(pairTarget.isTupleType() && pairTarget.fixedLength === 2 && pairTarget.readonly === true && pairTarget.elementFlags!.length === 2, "tuple target metadata"); check(checker.getTypeArguments(pair)[0] === intrinsicNumber && checker.getTypeArguments(pair)[1] === intrinsicString, "tuple argument identities"); - check(pair.getAliasSymbol()!.name === "Pair" && pair.getAliasTypeArguments()[0] === intrinsicNumber, "alias metadata"); + check(symbolName(pair.getAliasSymbol()!) === "Pair" && pair.getAliasTypeArguments()[0] === intrinsicNumber, "alias metadata"); const box = declaration(root, "Box"); const boxSymbol = symbolAt(checker, box.name!); @@ -121,21 +122,21 @@ export function checkSemanticModel(snapshot: SemanticSnapshot, checker: Semantic check(boxType.isClassOrInterface() && boxType.getSymbol() === boxSymbol, "declared class type"); // The checker instantiates a transient property symbol for a class type; // the binder's member symbol is distinct but refers to the same AST node. - const boundMember = boxSymbol.getMembers().get("value")!; + const boundMember = boxSymbol.getMembers().get("value" as __String)!; const checkedMember = checker.getPropertyOfType(boxType, "value")!; check(boundMember !== checkedMember && boundMember.declarationHandles[0]!.resolve() === checkedMember.declarationHandles[0]!.resolve(), "bound and instantiated member declarations"); check(boxSymbol.getMembers() === boxSymbol.getMembers() && boxSymbol.getExports() === boxSymbol.getExports(), "symbol table cache"); check(boxType.getBaseTypes()!.length === 0 && checker.getBaseTypes(boxType).length === 0, "empty base types"); const child = checker.getDeclaredTypeOfSymbol(symbolAt(checker, declaration(root, "Child").name!)); check(child.getBaseTypes()![0] === boxType && checker.getBaseTypes(child)[0] === boxType, "base type request identity"); - check(checker.getPropertiesOfType(boxType).some((symbol) => symbol.name === "method"), "property query"); + check(checker.getPropertiesOfType(boxType).some((symbol) => symbolName(symbol) === "method"), "property query"); check(checker.getPropertyOfType(boxType, "missing") === undefined, "absent property"); const nullable = typeAt(checker, declaration(root, "Nullable").type!); check(checker.getNonNullableType(nullable) === boxType, "non-nullable type"); const dict = checker.getDeclaredTypeOfSymbol(symbolAt(checker, declaration(root, "Dict").name!)); const index = checker.getIndexInfosOfType(dict)[0]!; - check(index.keyType === intrinsicString && index.valueType === intrinsicNumber && index.isReadonly, "index signature types"); - check(index.declaration!.resolve() === declaration(root, "Dict").members?.nodes[0], "index signature declaration"); + check(index.keyType === intrinsicString && index.type === intrinsicNumber && index.isReadonly, "index signature types"); + check(index.declaration === declaration(root, "Dict").members?.nodes[0], "index signature declaration"); const generic = checker.getDeclaredTypeOfSymbol(symbolAt(checker, declaration(root, "Generic").name!)); const genericParameter = generic.getTypeParameters()[0]!; check(genericParameter.isTypeParameter() && generic.getLocalTypeParameters()[0] === genericParameter && generic.getOuterTypeParameters().length === 0, "interface type parameters"); @@ -147,7 +148,7 @@ export function checkSemanticModel(snapshot: SemanticSnapshot, checker: Semantic const signature = signatureAt(checker, identity); const signatureTypes = signature.getTypeParameters(); check(signatureTypes.length === 1 && signatureTypes[0]!.isTypeParameter(), "signature type parameters"); - check(signature.getParameters()[0]!.name === "value" && signature.getParameters()[0]!.declarationHandles[0]!.resolve() === identity.parameters?.nodes[0], "signature parameters"); + check(symbolName(signature.getParameters()[0]!) === "value" && signature.getParameters()[0]!.declarationHandles[0]!.resolve() === identity.parameters?.nodes[0], "signature parameters"); check(signature.declarationHandle!.resolve() === identity, "signature declaration"); check(checker.getReturnTypeOfSignature(signature) === signatureTypes[0], "generic return identity"); check(checker.getParameterType(signature, 0) === signatureTypes[0], "parameter type query"); @@ -159,7 +160,7 @@ export function checkSemanticModel(snapshot: SemanticSnapshot, checker: Semantic const ctor = checker.getSignaturesOfType(checker.getTypeOfSymbol(boxSymbol)!, SemanticSignatureKind.Construct)[0]!; check(ctor.isConstruct && !ctor.isAbstract && checker.getReturnTypeOfSignature(ctor) === boxType, "construct signature"); const thisSignature = signatureAt(checker, declaration(root, "withThis")); - check(thisSignature.hasRestParameter && !thisSignature.isConstruct && thisSignature.getThisParameter()!.name === "this", "signature this/rest flags"); + check(thisSignature.hasRestParameter && !thisSignature.isConstruct && symbolName(thisSignature.getThisParameter()!) === "this", "signature this/rest flags"); check(checker.getRestTypeOfSignature(thisSignature) === intrinsicNumber && checker.isArrayLikeType(pair), "array predicates and rest element type"); const predicate = checker.getTypePredicateOfSignature(signatureAt(checker, declaration(root, "isBox")))!; check(predicate.type === boxType && predicate.parameterIndex === 0 && predicate.parameterName === "value", "type predicate"); @@ -169,7 +170,7 @@ export function checkSemanticModel(snapshot: SemanticSnapshot, checker: Semantic const moduleSymbol = symbolAt(checker, root); const exports = moduleSymbol.getExports(); - const renamed = exports.get("renamed")!; + const renamed = exports.get("renamed" as __String)!; check(checker.getAliasedSymbol(renamed) === answerSymbol && checker.getImmediateAliasedSymbol(renamed) === answerSymbol, "symbol alias targets"); check(checker.getMemberInModuleExports(moduleSymbol, "answer") === answerSymbol, "module export lookup"); check(checker.getExportsOfModule(moduleSymbol).some((symbol) => symbol === answerSymbol), "module exports identity"); From f3c4ba0c200172ee672494912fc5711489b51471 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Tue, 20 Oct 2026 13:43:22 -0500 Subject: [PATCH 121/122] Run macOS CI jobs nightly and for pull requests that change darwin code Pushes to main now run every lane except macos, so a merged commit no longer queues the macOS native build, the darwin test slices and the macOS LLVM artifact jobs. The nightly sweep and manual runs still run every lane. Pull requests keep running the macOS lane when scripts/ci-lanes.mjs selects it. - The iOS runtime pack builds move from mobile_runtime_packs (linux lane) to mobile_runtime_packs_ios (macos lane), since they run on macOS runners. The Android pack stays in the linux lane. - The macos_full dispatch input is replaced by macos_suite (full or darwin, default full). Manual runs run the complete macOS suite unless macos_suite is darwin. - ci-lanes.mjs also selects the macos lane for paths naming kqueue, the runtime's platform header, and every file of the darwin suite in scripts/ci-test-plan.mjs (adding tests/harness/differential.test.ts and the darwin runtime pack's archive test). - The required test check counts a skipped macOS job as passing when the macos lane is off, as it already did for pull requests. --- .github/workflows/ci.yml | 74 +++++++++++++++---------- scripts/ci-lanes.mjs | 23 +++++--- scripts/ci-test-plan.mjs | 15 ++--- tests/harness/ci-lanes.test.ts | 89 +++++++++++++++++++++++++++--- tests/harness/ci-test-plan.test.ts | 19 +++---- 5 files changed, 158 insertions(+), 62 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fbc921b74..bbc9ddb10 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,21 +4,23 @@ on: push: branches: [main] pull_request: - # Nightly full macOS sweep, including on days without a push to main. + # Nightly full macOS sweep. Pushes to main run no macOS jobs, and pull requests run them only when they change + # darwin code (scripts/ci-lanes.mjs). schedule: - cron: "17 7 * * *" - # A manual run; macos_full runs the complete suite on macOS as the nightly sweep does (for example on a - # release candidate commit). + # A manual run, for example on a release candidate commit. It runs every lane; macos_suite selects the complete + # suite on macOS, as the nightly sweep runs it, or the darwin suite that pull requests run. workflow_dispatch: inputs: - macos_full: - description: Run the complete test suite on macOS - type: boolean - default: false + macos_suite: + description: The test suite to run on macOS + type: choice + options: [full, darwin] + default: full concurrency: # A new push to a pull request cancels its previous run. Every push to main runs to completion in a group of - # its own commit, so each merged commit gets a full result (releases require a green run on their commit). The + # its own commit, so each merged commit gets a result (releases require a green run on their commit). The # nightly sweep and manual runs neither cancel nor are cancelled by main pushes. group: ci-${{ github.event_name == 'schedule' && 'schedule' || github.event_name == 'workflow_dispatch' && format('dispatch-{0}', github.run_id) || github.event.pull_request.number || github.sha }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} @@ -59,7 +61,8 @@ jobs: NODE # Which expensive lanes a pull request needs (scripts/ci-lanes.mjs, run from the base commit against the pull - # request's diff, so a pull request cannot change its own selection). Pushes and the nightly sweep run every lane. + # request's diff, so a pull request cannot change its own selection). Pushes to main run every lane except macos, + # whose jobs the nightly sweep runs on main. The nightly sweep and manual runs run every lane. lanes: needs: validation_scope if: ${{ needs.validation_scope.outputs.full == 'true' }} @@ -86,7 +89,9 @@ jobs: HEAD: ${{ github.event.pull_request.head.sha }} run: | every() { printf 'linux=true\nmacos=true\neffects=true\nbootstrap=true\n' | tee -a "$GITHUB_OUTPUT"; } - if [ "$EVENT" != pull_request ]; then + if [ "$EVENT" = push ]; then + printf 'linux=true\nmacos=false\neffects=true\nbootstrap=true\n' | tee -a "$GITHUB_OUTPUT" + elif [ "$EVENT" != pull_request ]; then every elif git show "$BASE:scripts/ci-lanes.mjs" > "$RUNNER_TEMP/ci-lanes.mjs" 2> /dev/null; then node "$RUNNER_TEMP/ci-lanes.mjs" --base "$BASE" --head "$HEAD" --github-output @@ -219,25 +224,25 @@ jobs: tests_macos: needs: [validation_scope, lanes, native_macos] if: ${{ needs.validation_scope.outputs.full == 'true' && needs.lanes.outputs.macos == 'true' }} - name: test (macOS, ${{ matrix.flavor }}, ${{ (github.event_name != 'schedule' && !inputs.macos_full) && 'darwin ' || '' }}${{ matrix.shard }}/${{ (github.event_name != 'schedule' && !inputs.macos_full) && '3' || '10' }}) + name: test (macOS, ${{ matrix.flavor }}, ${{ (github.event_name != 'schedule' && inputs.macos_suite != 'full') && 'darwin ' || '' }}${{ matrix.shard }}/${{ (github.event_name != 'schedule' && inputs.macos_suite != 'full') && '3' || '10' }}) # arm64 macOS: same clang/libc the differential + number-formatting - # oracles were verified against. Pull requests and pushes to main run the - # darwin suite (scripts/ci-test-plan.mjs) in three slices; the nightly - # schedule and manual runs with macos_full run the complete suite in ten. - # The complete suite on every push needed more macOS runners than the - # repository has. + # oracles were verified against. Pull requests that change darwin code and + # manual runs with macos_suite set to darwin run the darwin suite + # (scripts/ci-test-plan.mjs) in three slices; the nightly schedule and + # other manual runs run the complete suite in ten. The complete suite on + # every push needed more macOS runners than the repository has. runs-on: macos-15 strategy: fail-fast: false matrix: flavor: [plain, san] - shard: ${{ fromJSON((github.event_name != 'schedule' && !inputs.macos_full) && '[1, 2, 3]' || '[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]') }} + shard: ${{ fromJSON((github.event_name != 'schedule' && inputs.macos_suite != 'full') && '[1, 2, 3]' || '[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]') }} env: # See the Linux tests job for the worker cap and case partitioning. SCRIPTC_TEST_WORKERS: "4" SCRIPTC_SAN: ${{ matrix.flavor == 'san' && '1' || '' }} - SCRIPTC_TEST_SHARD: ${{ matrix.shard }}/${{ (github.event_name != 'schedule' && !inputs.macos_full) && '3' || '10' }} - SCRIPTC_CI_SUITE: ${{ (github.event_name != 'schedule' && !inputs.macos_full) && 'darwin' || 'full' }} + SCRIPTC_TEST_SHARD: ${{ matrix.shard }}/${{ (github.event_name != 'schedule' && inputs.macos_suite != 'full') && '3' || '10' }} + SCRIPTC_CI_SUITE: ${{ (github.event_name != 'schedule' && inputs.macos_suite != 'full') && 'darwin' || 'full' }} steps: - uses: actions/checkout@v4 - uses: pnpm/action-setup@v4 @@ -254,7 +259,7 @@ jobs: path: ${{ runner.temp }} - run: tar -xzf "$RUNNER_TEMP/native-darwin-arm64.tar.gz" - run: pnpm build - - name: Tests (${{ (github.event_name != 'schedule' && !inputs.macos_full) && 'darwin suite' || 'corpus and integration suites' }}) + - name: Tests (${{ (github.event_name != 'schedule' && inputs.macos_suite != 'full') && 'darwin suite' || 'corpus and integration suites' }}) run: node scripts/ci-test.mjs native_linux: @@ -868,14 +873,25 @@ jobs: strategy: fail-fast: false matrix: - include: - - runtime: runtime-ios-arm64 - runner: macos-15 - - runtime: runtime-ios-simulator-arm64 - runner: macos-15 - - runtime: runtime-android-arm64 - runner: ubuntu-24.04 - runs-on: ${{ matrix.runner }} + runtime: [runtime-android-arm64] + runs-on: ubuntu-24.04 + timeout-minutes: 20 + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/mobile-runtime-pack + with: + runtime: ${{ matrix.runtime }} + + # The iOS runtime packs build on macOS runners, so they run in the macos lane. + mobile_runtime_packs_ios: + needs: [validation_scope, lanes] + if: ${{ needs.validation_scope.outputs.full == 'true' && needs.lanes.outputs.macos == 'true' }} + name: test (${{ matrix.runtime }}) + strategy: + fail-fast: false + matrix: + runtime: [runtime-ios-arm64, runtime-ios-simulator-arm64] + runs-on: macos-15 timeout-minutes: 20 steps: - uses: actions/checkout@v4 @@ -908,6 +924,7 @@ jobs: windows_cli, llvm_artifacts_windows_x64, mobile_runtime_packs, + mobile_runtime_packs_ios, ] # Run even after failed or cancelled lanes: skipped required jobs allow merging. if: ${{ always() }} @@ -939,6 +956,7 @@ jobs: lane windows_cli "${{ needs.windows_cli.result }}" "${{ needs.lanes.outputs.linux }}" lane llvm_artifacts_windows_x64 "${{ needs.llvm_artifacts_windows_x64.result }}" "${{ needs.lanes.outputs.linux }}" lane mobile_runtime_packs "${{ needs.mobile_runtime_packs.result }}" "${{ needs.lanes.outputs.linux }}" + lane mobile_runtime_packs_ios "${{ needs.mobile_runtime_packs_ios.result }}" "${{ needs.lanes.outputs.macos }}" ;; false) echo "::notice::Full CI is deferred until this PR becomes the lowest unmerged layer. Merge the stack from the bottom up." diff --git a/scripts/ci-lanes.mjs b/scripts/ci-lanes.mjs index 9edc48a1c..b020f9de0 100644 --- a/scripts/ci-lanes.mjs +++ b/scripts/ci-lanes.mjs @@ -7,18 +7,20 @@ // own exemptions; a change to this script runs every lane until it is on the base branch. It uses only Node and git. // // Lanes (each an output, true or false): -// - linux: the Linux build and test shards, the TypeScript 7 parity sweep and the smaller Linux, Windows and mobile +// - linux: the Linux build and test shards, the TypeScript 7 parity sweep and the smaller Linux, Windows and Android // jobs. Off only when every change is documentation. -// - macos: the macOS native build, the darwin test shards and the macOS LLVM artifact jobs. On when the change touches -// a darwin package, a path naming darwin, macOS, iOS or Mach-O, the darwin test files of scripts/ci-test-plan.mjs, -// the LLVM helper (native/, packages/llvm-*) or the shared runtime pack code, or changed lines in packages/{runtime,compiler,cli} that mention darwin-specific APIs (__APPLE__, kqueue, Mach-O -// and similar). +// - macos: the macOS native build, the darwin test shards, the macOS LLVM artifact jobs and the iOS runtime packs. On +// when the change touches a darwin package, a path naming darwin, macOS, iOS, Mach-O or kqueue, the runtime's +// platform header, the darwin suite's test files (scripts/ci-test-plan.mjs), the LLVM helper (native/, +// packages/llvm-*) or the shared runtime pack code, or changed lines in packages/{runtime,compiler,cli} that mention +// darwin-specific APIs (__APPLE__, kqueue, Mach-O and similar). // - effects: the Effect suites. On when the change touches their harness or fixtures. // - bootstrap: the native bootstrap seed and self-rebuild contracts. On when the change touches the bootstrap scripts, // the native CLI build, the cli-* packages or the self-hosting tests. // Every lane is on when the change touches CI configuration, the workspace manifests or lockfile, vitest -// configuration, the shared test harness modules or this script, or when the diff cannot be computed. Pushes to main -// and the nightly sweep do not use this script: they run every lane. +// configuration, the shared test harness modules or this script, or when the diff cannot be computed. Pushes to main, +// the nightly sweep and manual runs do not use this script: pushes to main run every lane except macos, and the others +// run every lane. import { execFileSync } from "node:child_process"; import { appendFileSync } from "node:fs"; @@ -38,9 +40,11 @@ const everything = [ /^packages\/[^/]+\/package\.json$/, ]; const documentation = [/^docs\//, /\.mdx?$/, /^LICENSE$/, /^CHANGELOG\.md$/]; -const darwinPath = /darwin|macos|ios|apple|mach-?o/i; +const darwinPath = /darwin|macos|ios|apple|mach-?o|kqueue/i; // Native code generation and runtime packs every platform shares. const sharedNative = /^native\/|^packages\/(llvm-[^/]+|runtime-pack-common)\//; +// The darwin suite's files (darwinCorpusFiles and darwinFiles in scripts/ci-test-plan.mjs), the LLVM differential +// the macOS artifact jobs run, and the runtime's platform header. const darwinFiles = [ "packages/cli/test/bootstrap.test.ts", "packages/cli/test/native-link-info.test.ts", @@ -49,7 +53,10 @@ const darwinFiles = [ "packages/compiler/test/debug-variables.test.ts", "packages/compiler/test/native-codegen-integration.test.ts", "packages/compiler/test/source-debugging.test.ts", + "packages/runtime-darwin-arm64/test/archive.test.ts", + "packages/runtime/src/scr_platform.h", "packages/runtime/test/string.test.ts", + "tests/harness/differential.test.ts", "tests/harness/ffi-callback-pool.test.ts", "tests/harness/ffi-catalog.test.ts", "tests/harness/ffi-frameworks.test.ts", diff --git a/scripts/ci-test-plan.mjs b/scripts/ci-test-plan.mjs index 55eb65e72..2b76b52d6 100644 --- a/scripts/ci-test-plan.mjs +++ b/scripts/ci-test-plan.mjs @@ -69,13 +69,14 @@ export function sideOwners(flavor, count) { return owners; } -// Pull requests and pushes to main run the full suite on Linux. Their macOS -// lane runs the darwin suite, which keeps the darwin clang/libc/kqueue -// oracles: the Node differential corpus, plus every file whose assertions run -// only on darwin or take darwin-specific branches (including the Mach-O -// library localization arm and the arm64 runtime string oracle). -// The nightly schedule and manual runs with macos_full run the full suite on -// macOS. +// Pull requests and pushes to main run the full suite on Linux. Pull requests +// that change darwin code (scripts/ci-lanes.mjs) also run the darwin suite on +// macOS, which keeps the darwin clang/libc/kqueue oracles: the Node +// differential corpus, plus every file whose assertions run only on darwin or +// take darwin-specific branches (including the Mach-O library localization arm +// and the arm64 runtime string oracle). Pushes to main run no macOS jobs. The +// nightly schedule and manual runs run the full suite on macOS, or the darwin +// suite with macos_suite set to darwin. export const darwinCorpusFiles = ["tests/harness/differential.test.ts"]; export const darwinFiles = [ "packages/cli/test/bootstrap.test.ts", diff --git a/tests/harness/ci-lanes.test.ts b/tests/harness/ci-lanes.test.ts index c0e74a934..faca8a7d0 100644 --- a/tests/harness/ci-lanes.test.ts +++ b/tests/harness/ci-lanes.test.ts @@ -1,7 +1,22 @@ -import { readFileSync } from "node:fs"; +import { spawnSync } from "node:child_process"; +import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; import { load } from "js-yaml"; import { expect, test } from "vitest"; import { select } from "../../scripts/ci-lanes.mjs"; +import { darwinCorpusFiles, darwinFiles } from "../../scripts/ci-test-plan.mjs"; + +type Job = { + if?: string; + "runs-on"?: string; + strategy?: { matrix?: { include?: { runner?: string }[] } }; + steps: { id?: string; run?: string }[]; +}; +const workflow = load( + readFileSync(new URL("../../.github/workflows/ci.yml", import.meta.url), "utf8"), +) as { on: { workflow_dispatch: { inputs: Record } }; jobs: Record }; +const lanesScript = workflow.jobs["lanes"]!.steps.find((step) => step.id === "lanes")!.run!; type Selection = { on: Record; reasons: string[] }; const lanesOf = (files: string[], lines: { file: string; text: string }[] = []) => @@ -43,6 +58,16 @@ test.each([ ["tests/harness/ffi-frameworks.test.ts"], { linux: true, macos: true, effects: false, bootstrap: false }, ], + [ + "the kqueue event loop", + ["packages/runtime/src/scr_loop_kqueue.c"], + { linux: true, macos: true, effects: false, bootstrap: false }, + ], + [ + "the runtime's platform header", + ["packages/runtime/src/scr_platform.h"], + { linux: true, macos: true, effects: false, bootstrap: false }, + ], [ "the Effect harness", ["tests/harness/effect.test.ts"], @@ -91,15 +116,61 @@ test("a changed line that mentions a darwin-specific API runs the macOS lane", ( expect(lanesOf([file], [{ file, text: " struct kevent change;" }]).macos).toBe(true); }); +test("every file of the darwin suite runs the macOS lane", () => { + for (const file of [...darwinCorpusFiles, ...darwinFiles]) + expect(lanesOf([file]).macos, file).toBe(true); +}); + test("CI runs the base commit's lane selection against the pull request's diff", () => { - const workflow = load( - readFileSync(new URL("../../.github/workflows/ci.yml", import.meta.url), "utf8"), - ) as { jobs: Record }; - const script = workflow.jobs["lanes"]!.steps.find((step) => step.id === "lanes")!.run!; - expect(script).toContain('git show "$BASE:scripts/ci-lanes.mjs"'); - expect(script).toContain( + expect(lanesScript).toContain('git show "$BASE:scripts/ci-lanes.mjs"'); + expect(lanesScript).toContain( 'node "$RUNNER_TEMP/ci-lanes.mjs" --base "$BASE" --head "$HEAD" --github-output', ); - // Pushes and the nightly sweep, and a base without the script, run every lane. - expect(script.match(/every$/gm)).toHaveLength(2); + // The nightly sweep and manual runs, and a base without the script, run every lane. + expect(lanesScript.match(/every$/gm)).toHaveLength(2); +}); + +function runLanes(event: string) { + const directory = mkdtempSync(join(tmpdir(), "ci-lanes-")); + try { + const output = join(directory, "output"); + writeFileSync(output, ""); + const result = spawnSync("bash", ["-e", "-o", "pipefail", "-c", lanesScript], { + encoding: "utf8", + env: { ...process.env, EVENT: event, GITHUB_OUTPUT: output }, + }); + expect(result.status, result.stderr).toBe(0); + return readFileSync(output, "utf8"); + } finally { + rmSync(directory, { recursive: true, force: true }); + } +} + +test("pushes to main run every lane except macos; the nightly sweep and manual runs run every lane", () => { + expect(runLanes("push")).toBe("linux=true\nmacos=false\neffects=true\nbootstrap=true\n"); + for (const event of ["schedule", "workflow_dispatch"]) + expect(runLanes(event), event).toBe("linux=true\nmacos=true\neffects=true\nbootstrap=true\n"); + expect(workflow.on.workflow_dispatch.inputs["macos_suite"]).toEqual({ + description: "The test suite to run on macOS", + type: "choice", + options: ["full", "darwin"], + default: "full", + }); +}); + +test("every job on a macOS runner is in the macos lane", () => { + const macosJobs = Object.entries(workflow.jobs).filter(([, job]) => + [job["runs-on"], ...(job.strategy?.matrix?.include ?? []).map((entry) => entry.runner)].some( + (runner) => runner?.startsWith("macos"), + ), + ); + expect(macosJobs.map(([name]) => name).sort()).toEqual([ + "llvm_artifacts_macos_arm64", + "llvm_artifacts_macos_x64", + "mobile_runtime_packs_ios", + "native_macos", + "tests_macos", + ]); + for (const [name, job] of macosJobs) + expect(job.if, name).toContain("needs.lanes.outputs.macos == 'true'"); }); diff --git a/tests/harness/ci-test-plan.test.ts b/tests/harness/ci-test-plan.test.ts index 2343407df..34d61346f 100644 --- a/tests/harness/ci-test-plan.test.ts +++ b/tests/harness/ci-test-plan.test.ts @@ -201,12 +201,12 @@ test.for([1, 2, 3])("the darwin suite retains every darwin file with %i shards", /** Expands the ${{ }} expressions of a workflow value for one event: string * literals, ==, !=, !, &&, || and fromJSON, with matrix.shard left as * "{shard}". A value that is a single expression returns its result. */ -function expand(value: string, context: { event: string; macosFull: boolean | null }): unknown { +function expand(value: string, context: { event: string; macosSuite: string | null }): unknown { const evaluate = (expression: string): unknown => new Function( `return (${expression .replaceAll("github.event_name", JSON.stringify(context.event)) - .replaceAll("inputs.macos_full", JSON.stringify(context.macosFull)) + .replaceAll("inputs.macos_suite", JSON.stringify(context.macosSuite)) .replaceAll("fromJSON", "JSON.parse")});`, )(); const single = /^\$\{\{(.*)\}\}$/s.exec(value); @@ -240,18 +240,17 @@ test("pull requests run the full suite on Linux and the darwin suite on macOS", ).toBe("native-linux-x64-gnu"); expect(macos["runs-on"]).toBe("macos-15"); expect(macos.needs).toContain("native_macos"); - // Pull requests, pushes to main and plain manual runs run the darwin suite in - // three slices; the nightly sweep and manual runs with macos_full run the + // Pull requests and manual runs with macos_suite set to darwin run the darwin + // suite in three slices; the nightly sweep and other manual runs run the // complete suite in ten. - for (const [event, macosFull, full] of [ + for (const [event, macosSuite, full] of [ ["pull_request", null, false], - ["push", null, false], - ["workflow_dispatch", false, false], - ["workflow_dispatch", true, true], + ["workflow_dispatch", "darwin", false], + ["workflow_dispatch", "full", true], ["schedule", null, true], ] as const) { - const context = { event, macosFull }; - const label = `${event} ${macosFull}`; + const context = { event, macosSuite }; + const label = `${event} ${macosSuite}`; expect(expand(String(macos.strategy.matrix.shard), context), label).toEqual( full ? [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] : [1, 2, 3], ); From eba133dc374f3afd177b05f12170b9d6ad012872 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Tue, 20 Oct 2026 13:47:38 -0500 Subject: [PATCH 122/122] Order the undefined members widening adds after an object literal's own Widening an array or union of object literals gives each literal an optional `undefined` member for every property only its siblings declare. A record shape took its key order from the checker's member list, where those members sit wherever the checker put them: TypeScript 7.0.2 by the literal its checker widened first, TypeScript 7.1 and tsc-ts by the sibling that declares them. tsgo and tsc-ts then gave webassembly-wpt-jsapi.mjs different LLVM IR. The members are never own keys of the literal's value, so they now follow the declared members, by name. --- packages/compiler/src/frontend/type-mapper.ts | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/packages/compiler/src/frontend/type-mapper.ts b/packages/compiler/src/frontend/type-mapper.ts index c2fa0a06d..35e162ced 100644 --- a/packages/compiler/src/frontend/type-mapper.ts +++ b/packages/compiler/src/frontend/type-mapper.ts @@ -7112,6 +7112,30 @@ function refinedConstituentShape( return found; } +/** The optional `undefined` members widening adds to an object literal's + * type for properties only its sibling literals declare (`[{ a: 1 }, { b: 2 + * }]` gives the first `b?: undefined`). They are never own keys of the + * literal's value, so they go after the declared members, by name. Where + * the checker lists them is not observable: TypeScript 7.0.2 orders them + * by the literal its checker widened first, TypeScript 7.1 by the sibling + * that declares them. */ +function wideningAddedProperties( + type: ts.Type, + props: readonly ts.Symbol[], + ctx: TypeMapperCtx, +): Set { + const added = new Set(); + const literal = type.getSymbol()?.declarations.find(ts.isObjectLiteralExpression); + if (literal === undefined || literal.properties.nodes.some(ts.isSpreadAssignment)) return added; + for (const prop of props) { + if ((prop.flags & ts.SymbolFlags.Optional) === 0 || prop.declarations.length === 0) continue; + if (prop.declarations.some((d) => d.parent === literal)) continue; + if ((ctx.checker.getTypeOfSymbol(prop).flags & ts.TypeFlags.Undefined) === 0) continue; + added.add(prop.name); + } + return added; +} + function mapRecordTypeInner( widened: ts.Type, ctx: TypeMapperCtx, @@ -7425,8 +7449,12 @@ function mapRecordTypeInner( // indices, from folded numeric computed keys and quoted numeric keys) // enumerate FIRST ascending, then string keys in insertion order // (OrdinaryOwnPropertyKeys) — Object.keys/JSON/inspect all read it. + const widenedAbsent = wideningAddedProperties(widened, props, ctx); const declaredOrder = esOwnKeyOrder( - fields.filter((f) => accessorSlotProp(f.name) === null).map((f) => f.name), + fields + .filter((f) => accessorSlotProp(f.name) === null && !widenedAbsent.has(f.name)) + .map((f) => f.name) + .concat([...widenedAbsent].sort()), ); // With accessors: every own key in that order, an accessor property // named once for its get/set pair (the checked-dynamic copy lists it).